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 |