Panda3D
|
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