Panda3D
 All Classes Functions Variables Enumerations
httpDigestAuthorization.cxx
1 // Filename: httpDigestAuthorization.cxx
2 // Created by: drose (25Oct02)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "httpDigestAuthorization.h"
16 
17 #ifdef HAVE_OPENSSL
18 
19 #include "httpChannel.h"
20 #include "openSSLWrapper.h" // must be included before any other openssl.
21 #include "openssl/ssl.h"
22 #include "openssl/md5.h"
23 #include <time.h>
24 
25 const string HTTPDigestAuthorization::_mechanism = "digest";
26 
27 ////////////////////////////////////////////////////////////////////
28 // Function: HTTPDigestAuthorization::Constructor
29 // Access: Protected
30 // Description:
31 ////////////////////////////////////////////////////////////////////
32 HTTPDigestAuthorization::
33 HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens,
34  const URLSpec &url, bool is_proxy) :
35  HTTPAuthorization(tokens, url, is_proxy)
36 {
37  Tokens::const_iterator ti;
38  ti = tokens.find("nonce");
39  if (ti != tokens.end()) {
40  _nonce = (*ti).second;
41  }
42  _nonce_count = 0;
43 
44  ti = tokens.find("opaque");
45  if (ti != tokens.end()) {
46  _opaque = (*ti).second;
47  }
48 
49  _algorithm = A_md5;
50  ti = tokens.find("algorithm");
51  if (ti != tokens.end()) {
52  string algo_str = HTTPChannel::downcase((*ti).second);
53  if (algo_str == "md5") {
54  _algorithm = A_md5;
55  } else if (algo_str == "md5-sess") {
56  _algorithm = A_md5_sess;
57  } else {
58  _algorithm = A_unknown;
59  }
60  }
61 
62  _qop = 0;
63  ti = tokens.find("qop");
64  if (ti != tokens.end()) {
65  string qop_str = HTTPChannel::downcase((*ti).second);
66  // A comma-delimited list of tokens.
67 
68  size_t p = 0;
69  while (p < qop_str.length()) {
70  while (p < qop_str.length() && isspace(qop_str[p])) {
71  ++p;
72  }
73  size_t q = p;
74  size_t last_char = p;
75  while (q < qop_str.length() && qop_str[q] != ',') {
76  if (!isspace(qop_str[q])) {
77  qop_str[q] = tolower(qop_str[q]);
78  last_char = q;
79  }
80  ++q;
81  }
82 
83  if (last_char > p) {
84  _qop |= match_qop_token(qop_str.substr(p, last_char - p + 1));
85  }
86  p = q + 1;
87  }
88  }
89 
90  // Compute an arbitrary client nonce.
91  ostringstream strm;
92  strm << time(NULL) << ":" << clock() << ":"
93  << url.get_url() << ":Panda";
94 
95  _cnonce = calc_md5(strm.str());
96 }
97 
98 ////////////////////////////////////////////////////////////////////
99 // Function: HTTPDigestAuthorization::Destructor
100 // Access: Public, Virtual
101 // Description:
102 ////////////////////////////////////////////////////////////////////
103 HTTPDigestAuthorization::
104 ~HTTPDigestAuthorization() {
105 }
106 
107 ////////////////////////////////////////////////////////////////////
108 // Function: HTTPDigestAuthorization::is_valid
109 // Access: Public, Virtual
110 // Description: Returns true if the authorization challenge was
111 // correctly parsed and is usable, or false if there was
112 // some unsupported algorithm or some such requested by
113 // the server, rendering the challenge unmeetable.
114 ////////////////////////////////////////////////////////////////////
115 bool HTTPDigestAuthorization::
116 is_valid() {
117  return (_algorithm != A_unknown);
118 }
119 
120 ////////////////////////////////////////////////////////////////////
121 // Function: HTTPDigestAuthorization::get_mechanism
122 // Access: Public, Virtual
123 // Description: Returns the type of authorization mechanism,
124 // represented as a string, e.g. "digest".
125 ////////////////////////////////////////////////////////////////////
126 const string &HTTPDigestAuthorization::
127 get_mechanism() const {
128  return _mechanism;
129 }
130 
131 ////////////////////////////////////////////////////////////////////
132 // Function: HTTPDigestAuthorization::generate
133 // Access: Public, Virtual
134 // Description: Generates a suitable authorization string to send
135 // to the server, based on the data stored within this
136 // object, for retrieving the indicated URL with the
137 // given username:password.
138 ////////////////////////////////////////////////////////////////////
139 string HTTPDigestAuthorization::
140 generate(HTTPEnum::Method method, const string &request_path,
141  const string &username, const string &body) {
142  _nonce_count++;
143 
144  size_t colon = username.find(':');
145  string username_only = username.substr(0, colon);
146  string password_only = username.substr(colon + 1);
147 
148  string digest = calc_request_digest(username_only, password_only,
149  method, request_path, body);
150 
151  ostringstream strm;
152  strm << "Digest username=\"" << username.substr(0, colon) << "\""
153  << ", realm=\"" << get_realm() << "\""
154  << ", nonce=\"" << _nonce << "\""
155  << ", uri=" << request_path
156  << ", response=\"" << digest << "\""
157  << ", algorithm=" << _algorithm;
158 
159  if (!_opaque.empty()) {
160  strm << ", opaque=\"" << _opaque << "\"";
161  }
162 
163  if (_chosen_qop != Q_unused) {
164  strm << ", qop=" << _chosen_qop
165  << ", cnonce=\"" << _cnonce << "\""
166  << ", nc=" << get_hex_nonce_count();
167  }
168 
169  return strm.str();
170 }
171 
172 ////////////////////////////////////////////////////////////////////
173 // Function: HTTPDigestAuthorization::match_qop_token
174 // Access: Private, Static
175 // Description: Returns the bitfield corresponding to the indicated
176 // qop token string, or 0 if the token string is
177 // unrecognized.
178 ////////////////////////////////////////////////////////////////////
179 int HTTPDigestAuthorization::
180 match_qop_token(const string &token) {
181  if (token == "auth") {
182  return Q_auth;
183  } else if (token == "auth-int") {
184  return Q_auth_int;
185  }
186  return 0;
187 }
188 
189 ////////////////////////////////////////////////////////////////////
190 // Function: HTTPDigestAuthorization::calc_request_digest
191 // Access: Private
192 // Description: Calculates the appropriate digest response, according
193 // to RFC 2617.
194 ////////////////////////////////////////////////////////////////////
195 string HTTPDigestAuthorization::
196 calc_request_digest(const string &username, const string &password,
197  HTTPEnum::Method method, const string &request_path,
198  const string &body) {
199  _chosen_qop = Q_unused;
200  string h_a1 = calc_h(get_a1(username, password));
201  string h_a2 = calc_h(get_a2(method, request_path, body));
202 
203  ostringstream strm;
204 
205  if (_qop == 0) {
206  _chosen_qop = Q_unused;
207  strm << _nonce << ":" << h_a2;
208 
209  } else {
210  strm << _nonce << ":" << get_hex_nonce_count() << ":"
211  << _cnonce << ":" << _chosen_qop << ":"
212  << h_a2;
213  }
214 
215  return calc_kd(h_a1, strm.str());
216 }
217 
218 ////////////////////////////////////////////////////////////////////
219 // Function: HTTPDigestAuthorization::calc_h
220 // Access: Private
221 // Description: Applies the specified checksum algorithm to the data,
222 // according to RFC 2617.
223 ////////////////////////////////////////////////////////////////////
224 string HTTPDigestAuthorization::
225 calc_h(const string &data) const {
226  switch (_algorithm) {
227  case A_unknown:
228  case A_md5:
229  case A_md5_sess:
230  return calc_md5(data);
231  }
232 
233  return string();
234 }
235 
236 ////////////////////////////////////////////////////////////////////
237 // Function: HTTPDigestAuthorization::calc_kd
238 // Access: Private
239 // Description: Applies the specified digest algorithm to the
240 // indicated data with the indicated secret, according
241 // to RFC 2617.
242 ////////////////////////////////////////////////////////////////////
243 string HTTPDigestAuthorization::
244 calc_kd(const string &secret, const string &data) const {
245  switch (_algorithm) {
246  case A_unknown:
247  case A_md5:
248  case A_md5_sess:
249  return calc_h(secret + ":" + data);
250  }
251 
252  return string();
253 }
254 
255 ////////////////////////////////////////////////////////////////////
256 // Function: HTTPDigestAuthorization::get_a1
257 // Access: Private
258 // Description: Returns the A1 value, as defined by RFC 2617.
259 ////////////////////////////////////////////////////////////////////
260 string HTTPDigestAuthorization::
261 get_a1(const string &username, const string &password) {
262  switch (_algorithm) {
263  case A_unknown:
264  case A_md5:
265  return username + ":" + get_realm() + ":" + password;
266 
267  case A_md5_sess:
268  if (_a1.empty()) {
269  _a1 = calc_h(username + ":" + get_realm() + ":" + password) +
270  ":" + _nonce + ":" + _cnonce;
271  }
272  return _a1;
273  }
274 
275  return string();
276 }
277 
278 ////////////////////////////////////////////////////////////////////
279 // Function: HTTPDigestAuthorization::get_a2
280 // Access: Private
281 // Description: Returns the A2 value, as defined by RFC 2617.
282 ////////////////////////////////////////////////////////////////////
283 string HTTPDigestAuthorization::
284 get_a2(HTTPEnum::Method method, const string &request_path,
285  const string &body) {
286  ostringstream strm;
287 
288  if ((_qop & Q_auth_int) != 0 && !body.empty()) {
289  _chosen_qop = Q_auth_int;
290  strm << method << ":" << request_path << ":" << calc_h(body);
291 
292  } else {
293  _chosen_qop = Q_auth;
294  strm << method << ":" << request_path;
295  }
296 
297  return strm.str();
298 }
299 
300 ////////////////////////////////////////////////////////////////////
301 // Function: HTTPDigestAuthorization::get_hex_nonce_count
302 // Access: Private
303 // Description: Returns the current nonce count (the number of times
304 // we have used the server's nonce value, including this
305 // time) as an eight-digit hexadecimal value.
306 ////////////////////////////////////////////////////////////////////
307 string HTTPDigestAuthorization::
308 get_hex_nonce_count() const {
309  ostringstream strm;
310  strm << hex << setfill('0') << setw(8) << _nonce_count;
311  return strm.str();
312 }
313 
314 ////////////////////////////////////////////////////////////////////
315 // Function: HTTPDigestAuthorization::calc_md5
316 // Access: Private, Static
317 // Description: Computes the MD5 of the indicated source string and
318 // returns it as a hexadecimal string of 32 ASCII
319 // characters.
320 ////////////////////////////////////////////////////////////////////
321 string HTTPDigestAuthorization::
322 calc_md5(const string &source) {
323  unsigned char binary[MD5_DIGEST_LENGTH];
324 
325  MD5((const unsigned char *)source.data(), source.length(), binary);
326 
327  string result;
328  result.reserve(MD5_DIGEST_LENGTH * 2);
329 
330  for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
331  result += hexdigit((binary[i] >> 4) & 0xf);
332  result += hexdigit(binary[i] & 0xf);
333  }
334 
335  return result;
336 }
337 
338 ostream &
339 operator << (ostream &out, HTTPDigestAuthorization::Algorithm algorithm) {
340  switch (algorithm) {
341  case HTTPDigestAuthorization::A_md5:
342  out << "MD5";
343  break;
344  case HTTPDigestAuthorization::A_md5_sess:
345  out << "MD5-sess";
346  break;
347  case HTTPDigestAuthorization::A_unknown:
348  out << "unknown";
349  break;
350  }
351 
352  return out;
353 }
354 
355 ostream &
356 operator << (ostream &out, HTTPDigestAuthorization::Qop qop) {
357  switch (qop) {
358  case HTTPDigestAuthorization::Q_auth:
359  out << "auth";
360  break;
361  case HTTPDigestAuthorization::Q_auth_int:
362  out << "auth-int";
363  break;
364  case HTTPDigestAuthorization::Q_unused:
365  out << "unused";
366  break;
367  }
368 
369  return out;
370 }
371 
372 #endif // HAVE_OPENSSL
A container for a URL, e.g.
Definition: urlSpec.h:29
const string & get_url() const
Returns the complete URL specification.
Definition: urlSpec.I:254