Panda3D
 All Classes Functions Variables Enumerations
httpDigestAuthorization.cxx
00001 // Filename: httpDigestAuthorization.cxx
00002 // Created by:  drose (25Oct02)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "httpDigestAuthorization.h"
00016 
00017 #ifdef HAVE_OPENSSL
00018 
00019 #include "httpChannel.h"
00020 #include "openSSLWrapper.h"  // must be included before any other openssl.
00021 #include "openssl/ssl.h"
00022 #include "openssl/md5.h"
00023 #include <time.h>
00024 
00025 const string HTTPDigestAuthorization::_mechanism = "digest";
00026 
00027 ////////////////////////////////////////////////////////////////////
00028 //     Function: HTTPDigestAuthorization::Constructor
00029 //       Access: Protected
00030 //  Description: 
00031 ////////////////////////////////////////////////////////////////////
00032 HTTPDigestAuthorization::
00033 HTTPDigestAuthorization(const HTTPAuthorization::Tokens &tokens, 
00034                         const URLSpec &url, bool is_proxy) : 
00035   HTTPAuthorization(tokens, url, is_proxy)
00036 {
00037   Tokens::const_iterator ti;
00038   ti = tokens.find("nonce");
00039   if (ti != tokens.end()) {
00040     _nonce = (*ti).second;
00041   }
00042   _nonce_count = 0;
00043 
00044   ti = tokens.find("opaque");
00045   if (ti != tokens.end()) {
00046     _opaque = (*ti).second;
00047   }
00048 
00049   _algorithm = A_md5;
00050   ti = tokens.find("algorithm");
00051   if (ti != tokens.end()) {
00052     string algo_str = HTTPChannel::downcase((*ti).second);
00053     if (algo_str == "md5") {
00054       _algorithm = A_md5;
00055     } else if (algo_str == "md5-sess") {
00056       _algorithm = A_md5_sess;
00057     } else {
00058       _algorithm = A_unknown;
00059     }
00060   }
00061 
00062   _qop = 0;
00063   ti = tokens.find("qop");
00064   if (ti != tokens.end()) {
00065     string qop_str = HTTPChannel::downcase((*ti).second);
00066     // A comma-delimited list of tokens.
00067 
00068     size_t p = 0;
00069     while (p < qop_str.length()) {
00070       while (p < qop_str.length() && isspace(qop_str[p])) {
00071         ++p;
00072       }
00073       size_t q = p;
00074       size_t last_char = p;
00075       while (q < qop_str.length() && qop_str[q] != ',') {
00076         if (!isspace(qop_str[q])) {
00077           qop_str[q] = tolower(qop_str[q]);
00078           last_char = q;
00079         }
00080         ++q;
00081       }
00082 
00083       if (last_char > p) {
00084         _qop |= match_qop_token(qop_str.substr(p, last_char - p + 1));
00085       }
00086       p = q + 1;
00087     }
00088   }
00089 
00090   // Compute an arbitrary client nonce.
00091   ostringstream strm;
00092   strm << time(NULL) << ":" << clock() << ":" 
00093        << url.get_url() << ":Panda";
00094 
00095   _cnonce = calc_md5(strm.str());
00096 }
00097 
00098 ////////////////////////////////////////////////////////////////////
00099 //     Function: HTTPDigestAuthorization::Destructor
00100 //       Access: Public, Virtual
00101 //  Description: 
00102 ////////////////////////////////////////////////////////////////////
00103 HTTPDigestAuthorization::
00104 ~HTTPDigestAuthorization() {
00105 }
00106 
00107 ////////////////////////////////////////////////////////////////////
00108 //     Function: HTTPDigestAuthorization::is_valid
00109 //       Access: Public, Virtual
00110 //  Description: Returns true if the authorization challenge was
00111 //               correctly parsed and is usable, or false if there was
00112 //               some unsupported algorithm or some such requested by
00113 //               the server, rendering the challenge unmeetable.
00114 ////////////////////////////////////////////////////////////////////
00115 bool HTTPDigestAuthorization::
00116 is_valid() {
00117   return (_algorithm != A_unknown);
00118 }
00119 
00120 ////////////////////////////////////////////////////////////////////
00121 //     Function: HTTPDigestAuthorization::get_mechanism
00122 //       Access: Public, Virtual
00123 //  Description: Returns the type of authorization mechanism,
00124 //               represented as a string, e.g. "digest".
00125 ////////////////////////////////////////////////////////////////////
00126 const string &HTTPDigestAuthorization::
00127 get_mechanism() const {
00128   return _mechanism;
00129 }
00130 
00131 ////////////////////////////////////////////////////////////////////
00132 //     Function: HTTPDigestAuthorization::generate
00133 //       Access: Public, Virtual
00134 //  Description: Generates a suitable authorization string to send
00135 //               to the server, based on the data stored within this
00136 //               object, for retrieving the indicated URL with the
00137 //               given username:password.
00138 ////////////////////////////////////////////////////////////////////
00139 string HTTPDigestAuthorization::
00140 generate(HTTPEnum::Method method, const string &request_path,
00141          const string &username, const string &body) {
00142   _nonce_count++;
00143 
00144   size_t colon = username.find(':');
00145   string username_only = username.substr(0, colon);
00146   string password_only = username.substr(colon + 1);
00147 
00148   string digest = calc_request_digest(username_only, password_only,
00149                                       method, request_path, body);
00150 
00151   ostringstream strm;
00152   strm << "Digest username=\"" << username.substr(0, colon) << "\""
00153        << ", realm=\"" << get_realm() << "\""
00154        << ", nonce=\"" << _nonce << "\""
00155        << ", uri=" << request_path
00156        << ", response=\"" << digest << "\""
00157        << ", algorithm=" << _algorithm;
00158 
00159   if (!_opaque.empty()) {
00160     strm << ", opaque=\"" << _opaque << "\"";
00161   }
00162 
00163   if (_chosen_qop != Q_unused) {
00164     strm << ", qop=" << _chosen_qop
00165          << ", cnonce=\"" << _cnonce << "\""
00166          << ", nc=" << get_hex_nonce_count();
00167   }
00168 
00169   return strm.str();
00170 }
00171 
00172 ////////////////////////////////////////////////////////////////////
00173 //     Function: HTTPDigestAuthorization::match_qop_token
00174 //       Access: Private, Static
00175 //  Description: Returns the bitfield corresponding to the indicated
00176 //               qop token string, or 0 if the token string is
00177 //               unrecognized.
00178 ////////////////////////////////////////////////////////////////////
00179 int HTTPDigestAuthorization::
00180 match_qop_token(const string &token) {
00181   if (token == "auth") {
00182     return Q_auth;
00183   } else if (token == "auth-int") {
00184     return Q_auth_int;
00185   }
00186   return 0;
00187 }
00188 
00189 ////////////////////////////////////////////////////////////////////
00190 //     Function: HTTPDigestAuthorization::calc_request_digest
00191 //       Access: Private
00192 //  Description: Calculates the appropriate digest response, according
00193 //               to RFC 2617.
00194 ////////////////////////////////////////////////////////////////////
00195 string HTTPDigestAuthorization::
00196 calc_request_digest(const string &username, const string &password,
00197                     HTTPEnum::Method method, const string &request_path, 
00198                     const string &body) {
00199   _chosen_qop = Q_unused;
00200   string h_a1 = calc_h(get_a1(username, password));
00201   string h_a2 = calc_h(get_a2(method, request_path, body));
00202 
00203   ostringstream strm;
00204 
00205   if (_qop == 0) {
00206     _chosen_qop = Q_unused;
00207     strm << _nonce << ":" << h_a2;
00208 
00209   } else {
00210     strm << _nonce << ":" << get_hex_nonce_count() << ":"
00211          << _cnonce << ":" << _chosen_qop << ":"
00212          << h_a2;
00213   }
00214 
00215   return calc_kd(h_a1, strm.str());
00216 }
00217 
00218 ////////////////////////////////////////////////////////////////////
00219 //     Function: HTTPDigestAuthorization::calc_h
00220 //       Access: Private
00221 //  Description: Applies the specified checksum algorithm to the data,
00222 //               according to RFC 2617.
00223 ////////////////////////////////////////////////////////////////////
00224 string HTTPDigestAuthorization::
00225 calc_h(const string &data) const {
00226   switch (_algorithm) {
00227   case A_unknown:
00228   case A_md5:
00229   case A_md5_sess:
00230     return calc_md5(data);
00231   }
00232 
00233   return string();
00234 }
00235 
00236 ////////////////////////////////////////////////////////////////////
00237 //     Function: HTTPDigestAuthorization::calc_kd
00238 //       Access: Private
00239 //  Description: Applies the specified digest algorithm to the
00240 //               indicated data with the indicated secret, according
00241 //               to RFC 2617.
00242 ////////////////////////////////////////////////////////////////////
00243 string HTTPDigestAuthorization::
00244 calc_kd(const string &secret, const string &data) const {
00245   switch (_algorithm) {
00246   case A_unknown:
00247   case A_md5:
00248   case A_md5_sess:
00249     return calc_h(secret + ":" + data);
00250   }
00251 
00252   return string();
00253 }
00254 
00255 ////////////////////////////////////////////////////////////////////
00256 //     Function: HTTPDigestAuthorization::get_a1
00257 //       Access: Private
00258 //  Description: Returns the A1 value, as defined by RFC 2617.
00259 ////////////////////////////////////////////////////////////////////
00260 string HTTPDigestAuthorization::
00261 get_a1(const string &username, const string &password) {
00262   switch (_algorithm) {
00263   case A_unknown:
00264   case A_md5:
00265     return username + ":" + get_realm() + ":" + password;
00266 
00267   case A_md5_sess:
00268     if (_a1.empty()) {
00269       _a1 = calc_h(username + ":" + get_realm() + ":" + password) +
00270         ":" + _nonce + ":" + _cnonce;
00271     }
00272     return _a1;
00273   }
00274 
00275   return string();
00276 }
00277 
00278 ////////////////////////////////////////////////////////////////////
00279 //     Function: HTTPDigestAuthorization::get_a2
00280 //       Access: Private
00281 //  Description: Returns the A2 value, as defined by RFC 2617.
00282 ////////////////////////////////////////////////////////////////////
00283 string HTTPDigestAuthorization::
00284 get_a2(HTTPEnum::Method method, const string &request_path,
00285        const string &body) {
00286   ostringstream strm;
00287 
00288   if ((_qop & Q_auth_int) != 0 && !body.empty()) {
00289     _chosen_qop = Q_auth_int;
00290     strm << method << ":" << request_path << ":" << calc_h(body);
00291 
00292   } else {
00293     _chosen_qop = Q_auth;
00294     strm << method << ":" << request_path;
00295   }
00296 
00297   return strm.str();
00298 }
00299 
00300 ////////////////////////////////////////////////////////////////////
00301 //     Function: HTTPDigestAuthorization::get_hex_nonce_count
00302 //       Access: Private
00303 //  Description: Returns the current nonce count (the number of times
00304 //               we have used the server's nonce value, including this
00305 //               time) as an eight-digit hexadecimal value.
00306 ////////////////////////////////////////////////////////////////////
00307 string HTTPDigestAuthorization::
00308 get_hex_nonce_count() const {
00309   ostringstream strm;
00310   strm << hex << setfill('0') << setw(8) << _nonce_count;
00311   return strm.str();
00312 }
00313 
00314 ////////////////////////////////////////////////////////////////////
00315 //     Function: HTTPDigestAuthorization::calc_md5
00316 //       Access: Private, Static
00317 //  Description: Computes the MD5 of the indicated source string and
00318 //               returns it as a hexadecimal string of 32 ASCII
00319 //               characters.
00320 ////////////////////////////////////////////////////////////////////
00321 string HTTPDigestAuthorization::
00322 calc_md5(const string &source) {
00323   unsigned char binary[MD5_DIGEST_LENGTH];
00324 
00325   MD5((const unsigned char *)source.data(), source.length(), binary);
00326 
00327   string result;
00328   result.reserve(MD5_DIGEST_LENGTH * 2);
00329 
00330   for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
00331     result += hexdigit((binary[i] >> 4) & 0xf);
00332     result += hexdigit(binary[i] & 0xf);
00333   }
00334 
00335   return result;
00336 }
00337 
00338 ostream &
00339 operator << (ostream &out, HTTPDigestAuthorization::Algorithm algorithm) {
00340   switch (algorithm) {
00341   case HTTPDigestAuthorization::A_md5:
00342     out << "MD5";
00343     break;
00344   case HTTPDigestAuthorization::A_md5_sess:
00345     out << "MD5-sess";
00346     break;
00347   case HTTPDigestAuthorization::A_unknown:
00348     out << "unknown";
00349     break;
00350   }
00351 
00352   return out;
00353 }
00354 
00355 ostream &
00356 operator << (ostream &out, HTTPDigestAuthorization::Qop qop) {
00357   switch (qop) {
00358   case HTTPDigestAuthorization::Q_auth:
00359     out << "auth";
00360     break;
00361   case HTTPDigestAuthorization::Q_auth_int:
00362     out << "auth-int";
00363     break;
00364   case HTTPDigestAuthorization::Q_unused:
00365     out << "unused";
00366     break;
00367   }
00368 
00369   return out;
00370 }
00371 
00372 #endif  // HAVE_OPENSSL
 All Classes Functions Variables Enumerations