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
const std::string & get_url() const
Returns the complete URL specification.
Definition: urlSpec.I:184
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.