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