| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/http/http_auth_handler_digest.h" | 5 #include "net/http/http_auth_handler_digest.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/md5.h" | 10 #include "base/md5.h" |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 67 | 67 |
| 68 HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator( | 68 HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator( |
| 69 const std::string& nonce) | 69 const std::string& nonce) |
| 70 : nonce_(nonce) { | 70 : nonce_(nonce) { |
| 71 } | 71 } |
| 72 | 72 |
| 73 std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const { | 73 std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const { |
| 74 return nonce_; | 74 return nonce_; |
| 75 } | 75 } |
| 76 | 76 |
| 77 // static | 77 HttpAuthHandlerDigest::Factory::Factory() |
| 78 std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) { | 78 : nonce_generator_(new DynamicNonceGenerator()) { |
| 79 switch (qop) { | |
| 80 case QOP_UNSPECIFIED: | |
| 81 return ""; | |
| 82 case QOP_AUTH: | |
| 83 return "auth"; | |
| 84 default: | |
| 85 NOTREACHED(); | |
| 86 return ""; | |
| 87 } | |
| 88 } | 79 } |
| 89 | 80 |
| 90 // static | 81 HttpAuthHandlerDigest::Factory::~Factory() { |
| 91 std::string HttpAuthHandlerDigest::AlgorithmToString( | |
| 92 DigestAlgorithm algorithm) { | |
| 93 switch (algorithm) { | |
| 94 case ALGORITHM_UNSPECIFIED: | |
| 95 return ""; | |
| 96 case ALGORITHM_MD5: | |
| 97 return "MD5"; | |
| 98 case ALGORITHM_MD5_SESS: | |
| 99 return "MD5-sess"; | |
| 100 default: | |
| 101 NOTREACHED(); | |
| 102 return ""; | |
| 103 } | |
| 104 } | 82 } |
| 105 | 83 |
| 106 HttpAuthHandlerDigest::HttpAuthHandlerDigest( | 84 void HttpAuthHandlerDigest::Factory::set_nonce_generator( |
| 107 int nonce_count, const NonceGenerator* nonce_generator) | 85 const NonceGenerator* nonce_generator) { |
| 108 : stale_(false), | 86 nonce_generator_.reset(nonce_generator); |
| 109 algorithm_(ALGORITHM_UNSPECIFIED), | |
| 110 qop_(QOP_UNSPECIFIED), | |
| 111 nonce_count_(nonce_count), | |
| 112 nonce_generator_(nonce_generator) { | |
| 113 DCHECK(nonce_generator_); | |
| 114 } | 87 } |
| 115 | 88 |
| 116 HttpAuthHandlerDigest::~HttpAuthHandlerDigest() { | 89 int HttpAuthHandlerDigest::Factory::CreateAuthHandler( |
| 90 HttpAuth::ChallengeTokenizer* challenge, |
| 91 HttpAuth::Target target, |
| 92 const GURL& origin, |
| 93 CreateReason reason, |
| 94 int digest_nonce_count, |
| 95 const BoundNetLog& net_log, |
| 96 scoped_ptr<HttpAuthHandler>* handler) { |
| 97 // TODO(cbentzel): Move towards model of parsing in the factory |
| 98 // method and only constructing when valid. |
| 99 scoped_ptr<HttpAuthHandler> tmp_handler( |
| 100 new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get())); |
| 101 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) |
| 102 return ERR_INVALID_RESPONSE; |
| 103 handler->swap(tmp_handler); |
| 104 return OK; |
| 105 } |
| 106 |
| 107 HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge( |
| 108 HttpAuth::ChallengeTokenizer* challenge) { |
| 109 // Even though Digest is not connection based, a "second round" is parsed |
| 110 // to differentiate between stale and rejected responses. |
| 111 // Note that the state of the current handler is not mutated - this way if |
| 112 // there is a rejection the realm hasn't changed. |
| 113 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest")) |
| 114 return HttpAuth::AUTHORIZATION_RESULT_INVALID; |
| 115 |
| 116 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs(); |
| 117 |
| 118 // Try to find the "stale" value. |
| 119 while (parameters.GetNext()) { |
| 120 if (!LowerCaseEqualsASCII(parameters.name(), "stale")) |
| 121 continue; |
| 122 if (LowerCaseEqualsASCII(parameters.value(), "true")) |
| 123 return HttpAuth::AUTHORIZATION_RESULT_STALE; |
| 124 } |
| 125 |
| 126 return HttpAuth::AUTHORIZATION_RESULT_REJECT; |
| 127 } |
| 128 |
| 129 bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) { |
| 130 return ParseChallenge(challenge); |
| 117 } | 131 } |
| 118 | 132 |
| 119 int HttpAuthHandlerDigest::GenerateAuthTokenImpl( | 133 int HttpAuthHandlerDigest::GenerateAuthTokenImpl( |
| 120 const string16* username, | 134 const string16* username, |
| 121 const string16* password, | 135 const string16* password, |
| 122 const HttpRequestInfo* request, | 136 const HttpRequestInfo* request, |
| 123 CompletionCallback* callback, | 137 CompletionCallback* callback, |
| 124 std::string* auth_token) { | 138 std::string* auth_token) { |
| 125 // Generate a random client nonce. | 139 // Generate a random client nonce. |
| 126 std::string cnonce = nonce_generator_->GenerateNonce(); | 140 std::string cnonce = nonce_generator_->GenerateNonce(); |
| 127 | 141 |
| 128 // Extract the request method and path -- the meaning of 'path' is overloaded | 142 // Extract the request method and path -- the meaning of 'path' is overloaded |
| 129 // in certain cases, to be a hostname. | 143 // in certain cases, to be a hostname. |
| 130 std::string method; | 144 std::string method; |
| 131 std::string path; | 145 std::string path; |
| 132 GetRequestMethodAndPath(request, &method, &path); | 146 GetRequestMethodAndPath(request, &method, &path); |
| 133 | 147 |
| 134 *auth_token = AssembleCredentials(method, path, | 148 *auth_token = AssembleCredentials(method, path, |
| 135 *username, | 149 *username, |
| 136 *password, | 150 *password, |
| 137 cnonce, nonce_count_); | 151 cnonce, nonce_count_); |
| 138 return OK; | 152 return OK; |
| 139 } | 153 } |
| 140 | 154 |
| 141 void HttpAuthHandlerDigest::GetRequestMethodAndPath( | 155 HttpAuthHandlerDigest::HttpAuthHandlerDigest( |
| 142 const HttpRequestInfo* request, | 156 int nonce_count, const NonceGenerator* nonce_generator) |
| 143 std::string* method, | 157 : stale_(false), |
| 144 std::string* path) const { | 158 algorithm_(ALGORITHM_UNSPECIFIED), |
| 145 DCHECK(request); | 159 qop_(QOP_UNSPECIFIED), |
| 146 | 160 nonce_count_(nonce_count), |
| 147 const GURL& url = request->url; | 161 nonce_generator_(nonce_generator) { |
| 148 | 162 DCHECK(nonce_generator_); |
| 149 if (target_ == HttpAuth::AUTH_PROXY && url.SchemeIs("https")) { | |
| 150 *method = "CONNECT"; | |
| 151 *path = GetHostAndPort(url); | |
| 152 } else { | |
| 153 *method = request->method; | |
| 154 *path = HttpUtil::PathForRequest(url); | |
| 155 } | |
| 156 } | 163 } |
| 157 | 164 |
| 158 std::string HttpAuthHandlerDigest::AssembleResponseDigest( | 165 HttpAuthHandlerDigest::~HttpAuthHandlerDigest() { |
| 159 const std::string& method, | |
| 160 const std::string& path, | |
| 161 const string16& username, | |
| 162 const string16& password, | |
| 163 const std::string& cnonce, | |
| 164 const std::string& nc) const { | |
| 165 // ha1 = MD5(A1) | |
| 166 // TODO(eroman): is this the right encoding? | |
| 167 std::string ha1 = MD5String(UTF16ToUTF8(username) + ":" + realm_ + ":" + | |
| 168 UTF16ToUTF8(password)); | |
| 169 if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS) | |
| 170 ha1 = MD5String(ha1 + ":" + nonce_ + ":" + cnonce); | |
| 171 | |
| 172 // ha2 = MD5(A2) | |
| 173 // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int. | |
| 174 std::string ha2 = MD5String(method + ":" + path); | |
| 175 | |
| 176 std::string nc_part; | |
| 177 if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) { | |
| 178 nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":"; | |
| 179 } | |
| 180 | |
| 181 return MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2); | |
| 182 } | |
| 183 | |
| 184 std::string HttpAuthHandlerDigest::AssembleCredentials( | |
| 185 const std::string& method, | |
| 186 const std::string& path, | |
| 187 const string16& username, | |
| 188 const string16& password, | |
| 189 const std::string& cnonce, | |
| 190 int nonce_count) const { | |
| 191 // the nonce-count is an 8 digit hex string. | |
| 192 std::string nc = base::StringPrintf("%08x", nonce_count); | |
| 193 | |
| 194 // TODO(eroman): is this the right encoding? | |
| 195 std::string authorization = (std::string("Digest username=") + | |
| 196 HttpUtil::Quote(UTF16ToUTF8(username))); | |
| 197 authorization += ", realm=" + HttpUtil::Quote(realm_); | |
| 198 authorization += ", nonce=" + HttpUtil::Quote(nonce_); | |
| 199 authorization += ", uri=" + HttpUtil::Quote(path); | |
| 200 | |
| 201 if (algorithm_ != ALGORITHM_UNSPECIFIED) { | |
| 202 authorization += ", algorithm=" + AlgorithmToString(algorithm_); | |
| 203 } | |
| 204 std::string response = AssembleResponseDigest(method, path, username, | |
| 205 password, cnonce, nc); | |
| 206 // No need to call HttpUtil::Quote() as the response digest cannot contain | |
| 207 // any characters needing to be escaped. | |
| 208 authorization += ", response=\"" + response + "\""; | |
| 209 | |
| 210 if (!opaque_.empty()) { | |
| 211 authorization += ", opaque=" + HttpUtil::Quote(opaque_); | |
| 212 } | |
| 213 if (qop_ != QOP_UNSPECIFIED) { | |
| 214 // TODO(eroman): Supposedly IIS server requires quotes surrounding qop. | |
| 215 authorization += ", qop=" + QopToString(qop_); | |
| 216 authorization += ", nc=" + nc; | |
| 217 authorization += ", cnonce=" + HttpUtil::Quote(cnonce); | |
| 218 } | |
| 219 | |
| 220 return authorization; | |
| 221 } | |
| 222 | |
| 223 bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) { | |
| 224 return ParseChallenge(challenge); | |
| 225 } | |
| 226 | |
| 227 HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge( | |
| 228 HttpAuth::ChallengeTokenizer* challenge) { | |
| 229 // Even though Digest is not connection based, a "second round" is parsed | |
| 230 // to differentiate between stale and rejected responses. | |
| 231 // Note that the state of the current handler is not mutated - this way if | |
| 232 // there is a rejection the realm hasn't changed. | |
| 233 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest")) | |
| 234 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
| 235 | |
| 236 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs(); | |
| 237 | |
| 238 // Try to find the "stale" value. | |
| 239 while (parameters.GetNext()) { | |
| 240 if (!LowerCaseEqualsASCII(parameters.name(), "stale")) | |
| 241 continue; | |
| 242 if (LowerCaseEqualsASCII(parameters.value(), "true")) | |
| 243 return HttpAuth::AUTHORIZATION_RESULT_STALE; | |
| 244 } | |
| 245 | |
| 246 return HttpAuth::AUTHORIZATION_RESULT_REJECT; | |
| 247 } | 166 } |
| 248 | 167 |
| 249 // The digest challenge header looks like: | 168 // The digest challenge header looks like: |
| 250 // WWW-Authenticate: Digest | 169 // WWW-Authenticate: Digest |
| 251 // [realm="<realm-value>"] | 170 // [realm="<realm-value>"] |
| 252 // nonce="<nonce-value>" | 171 // nonce="<nonce-value>" |
| 253 // [domain="<list-of-URIs>"] | 172 // [domain="<list-of-URIs>"] |
| 254 // [opaque="<opaque-token-value>"] | 173 // [opaque="<opaque-token-value>"] |
| 255 // [stale="<true-or-false>"] | 174 // [stale="<true-or-false>"] |
| 256 // [algorithm="<digest-algorithm>"] | 175 // [algorithm="<digest-algorithm>"] |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 335 break; | 254 break; |
| 336 } | 255 } |
| 337 } | 256 } |
| 338 } else { | 257 } else { |
| 339 DVLOG(1) << "Skipping unrecognized digest property"; | 258 DVLOG(1) << "Skipping unrecognized digest property"; |
| 340 // TODO(eroman): perhaps we should fail instead of silently skipping? | 259 // TODO(eroman): perhaps we should fail instead of silently skipping? |
| 341 } | 260 } |
| 342 return true; | 261 return true; |
| 343 } | 262 } |
| 344 | 263 |
| 345 HttpAuthHandlerDigest::Factory::Factory() | 264 // static |
| 346 : nonce_generator_(new DynamicNonceGenerator()) { | 265 std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) { |
| 266 switch (qop) { |
| 267 case QOP_UNSPECIFIED: |
| 268 return ""; |
| 269 case QOP_AUTH: |
| 270 return "auth"; |
| 271 default: |
| 272 NOTREACHED(); |
| 273 return ""; |
| 274 } |
| 347 } | 275 } |
| 348 | 276 |
| 349 HttpAuthHandlerDigest::Factory::~Factory() { | 277 // static |
| 278 std::string HttpAuthHandlerDigest::AlgorithmToString( |
| 279 DigestAlgorithm algorithm) { |
| 280 switch (algorithm) { |
| 281 case ALGORITHM_UNSPECIFIED: |
| 282 return ""; |
| 283 case ALGORITHM_MD5: |
| 284 return "MD5"; |
| 285 case ALGORITHM_MD5_SESS: |
| 286 return "MD5-sess"; |
| 287 default: |
| 288 NOTREACHED(); |
| 289 return ""; |
| 290 } |
| 350 } | 291 } |
| 351 | 292 |
| 352 void HttpAuthHandlerDigest::Factory::set_nonce_generator( | 293 void HttpAuthHandlerDigest::GetRequestMethodAndPath( |
| 353 const NonceGenerator* nonce_generator) { | 294 const HttpRequestInfo* request, |
| 354 nonce_generator_.reset(nonce_generator); | 295 std::string* method, |
| 296 std::string* path) const { |
| 297 DCHECK(request); |
| 298 |
| 299 const GURL& url = request->url; |
| 300 |
| 301 if (target_ == HttpAuth::AUTH_PROXY && url.SchemeIs("https")) { |
| 302 *method = "CONNECT"; |
| 303 *path = GetHostAndPort(url); |
| 304 } else { |
| 305 *method = request->method; |
| 306 *path = HttpUtil::PathForRequest(url); |
| 307 } |
| 355 } | 308 } |
| 356 | 309 |
| 357 int HttpAuthHandlerDigest::Factory::CreateAuthHandler( | 310 std::string HttpAuthHandlerDigest::AssembleResponseDigest( |
| 358 HttpAuth::ChallengeTokenizer* challenge, | 311 const std::string& method, |
| 359 HttpAuth::Target target, | 312 const std::string& path, |
| 360 const GURL& origin, | 313 const string16& username, |
| 361 CreateReason reason, | 314 const string16& password, |
| 362 int digest_nonce_count, | 315 const std::string& cnonce, |
| 363 const BoundNetLog& net_log, | 316 const std::string& nc) const { |
| 364 scoped_ptr<HttpAuthHandler>* handler) { | 317 // ha1 = MD5(A1) |
| 365 // TODO(cbentzel): Move towards model of parsing in the factory | 318 // TODO(eroman): is this the right encoding? |
| 366 // method and only constructing when valid. | 319 std::string ha1 = MD5String(UTF16ToUTF8(username) + ":" + realm_ + ":" + |
| 367 scoped_ptr<HttpAuthHandler> tmp_handler( | 320 UTF16ToUTF8(password)); |
| 368 new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get())); | 321 if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS) |
| 369 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log)) | 322 ha1 = MD5String(ha1 + ":" + nonce_ + ":" + cnonce); |
| 370 return ERR_INVALID_RESPONSE; | 323 |
| 371 handler->swap(tmp_handler); | 324 // ha2 = MD5(A2) |
| 372 return OK; | 325 // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int. |
| 326 std::string ha2 = MD5String(method + ":" + path); |
| 327 |
| 328 std::string nc_part; |
| 329 if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) { |
| 330 nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":"; |
| 331 } |
| 332 |
| 333 return MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2); |
| 334 } |
| 335 |
| 336 std::string HttpAuthHandlerDigest::AssembleCredentials( |
| 337 const std::string& method, |
| 338 const std::string& path, |
| 339 const string16& username, |
| 340 const string16& password, |
| 341 const std::string& cnonce, |
| 342 int nonce_count) const { |
| 343 // the nonce-count is an 8 digit hex string. |
| 344 std::string nc = base::StringPrintf("%08x", nonce_count); |
| 345 |
| 346 // TODO(eroman): is this the right encoding? |
| 347 std::string authorization = (std::string("Digest username=") + |
| 348 HttpUtil::Quote(UTF16ToUTF8(username))); |
| 349 authorization += ", realm=" + HttpUtil::Quote(realm_); |
| 350 authorization += ", nonce=" + HttpUtil::Quote(nonce_); |
| 351 authorization += ", uri=" + HttpUtil::Quote(path); |
| 352 |
| 353 if (algorithm_ != ALGORITHM_UNSPECIFIED) { |
| 354 authorization += ", algorithm=" + AlgorithmToString(algorithm_); |
| 355 } |
| 356 std::string response = AssembleResponseDigest(method, path, username, |
| 357 password, cnonce, nc); |
| 358 // No need to call HttpUtil::Quote() as the response digest cannot contain |
| 359 // any characters needing to be escaped. |
| 360 authorization += ", response=\"" + response + "\""; |
| 361 |
| 362 if (!opaque_.empty()) { |
| 363 authorization += ", opaque=" + HttpUtil::Quote(opaque_); |
| 364 } |
| 365 if (qop_ != QOP_UNSPECIFIED) { |
| 366 // TODO(eroman): Supposedly IIS server requires quotes surrounding qop. |
| 367 authorization += ", qop=" + QopToString(qop_); |
| 368 authorization += ", nc=" + nc; |
| 369 authorization += ", cnonce=" + HttpUtil::Quote(cnonce); |
| 370 } |
| 371 |
| 372 return authorization; |
| 373 } | 373 } |
| 374 | 374 |
| 375 } // namespace net | 375 } // namespace net |
| OLD | NEW |