Panda3D

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