cc: Added inline to Tile::IsReadyToDraw
[chromium-blink-merge.git] / net / http / http_auth_handler_digest.cc
blob904430ecb409b22136d8f11fcf5645aca7fc6da6
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/http/http_auth_handler_digest.h"
7 #include <string>
9 #include "base/i18n/icu_string_conversions.h"
10 #include "base/logging.h"
11 #include "base/md5.h"
12 #include "base/rand_util.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "net/base/net_errors.h"
17 #include "net/base/net_util.h"
18 #include "net/http/http_auth.h"
19 #include "net/http/http_request_info.h"
20 #include "net/http/http_util.h"
22 namespace net {
24 // Digest authentication is specified in RFC 2617.
25 // The expanded derivations are listed in the tables below.
27 //==========+==========+==========================================+
28 // qop |algorithm | response |
29 //==========+==========+==========================================+
30 // ? | ?, md5, | MD5(MD5(A1):nonce:MD5(A2)) |
31 // | md5-sess | |
32 //--------- +----------+------------------------------------------+
33 // auth, | ?, md5, | MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2)) |
34 // auth-int | md5-sess | |
35 //==========+==========+==========================================+
36 // qop |algorithm | A1 |
37 //==========+==========+==========================================+
38 // | ?, md5 | user:realm:password |
39 //----------+----------+------------------------------------------+
40 // | md5-sess | MD5(user:realm:password):nonce:cnonce |
41 //==========+==========+==========================================+
42 // qop |algorithm | A2 |
43 //==========+==========+==========================================+
44 // ?, auth | | req-method:req-uri |
45 //----------+----------+------------------------------------------+
46 // auth-int | | req-method:req-uri:MD5(req-entity-body) |
47 //=====================+==========================================+
49 HttpAuthHandlerDigest::NonceGenerator::NonceGenerator() {
52 HttpAuthHandlerDigest::NonceGenerator::~NonceGenerator() {
55 HttpAuthHandlerDigest::DynamicNonceGenerator::DynamicNonceGenerator() {
58 std::string HttpAuthHandlerDigest::DynamicNonceGenerator::GenerateNonce()
59 const {
60 // This is how mozilla generates their cnonce -- a 16 digit hex string.
61 static const char domain[] = "0123456789abcdef";
62 std::string cnonce;
63 cnonce.reserve(16);
64 for (int i = 0; i < 16; ++i)
65 cnonce.push_back(domain[base::RandInt(0, 15)]);
66 return cnonce;
69 HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator(
70 const std::string& nonce)
71 : nonce_(nonce) {
74 std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const {
75 return nonce_;
78 HttpAuthHandlerDigest::Factory::Factory()
79 : nonce_generator_(new DynamicNonceGenerator()) {
82 HttpAuthHandlerDigest::Factory::~Factory() {
85 void HttpAuthHandlerDigest::Factory::set_nonce_generator(
86 const NonceGenerator* nonce_generator) {
87 nonce_generator_.reset(nonce_generator);
90 int HttpAuthHandlerDigest::Factory::CreateAuthHandler(
91 HttpAuth::ChallengeTokenizer* challenge,
92 HttpAuth::Target target,
93 const GURL& origin,
94 CreateReason reason,
95 int digest_nonce_count,
96 const BoundNetLog& net_log,
97 scoped_ptr<HttpAuthHandler>* handler) {
98 // TODO(cbentzel): Move towards model of parsing in the factory
99 // method and only constructing when valid.
100 scoped_ptr<HttpAuthHandler> tmp_handler(
101 new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get()));
102 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
103 return ERR_INVALID_RESPONSE;
104 handler->swap(tmp_handler);
105 return OK;
108 HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge(
109 HttpAuth::ChallengeTokenizer* challenge) {
110 // Even though Digest is not connection based, a "second round" is parsed
111 // to differentiate between stale and rejected responses.
112 // Note that the state of the current handler is not mutated - this way if
113 // there is a rejection the realm hasn't changed.
114 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
115 return HttpAuth::AUTHORIZATION_RESULT_INVALID;
117 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
119 // Try to find the "stale" value, and also keep track of the realm
120 // for the new challenge.
121 std::string original_realm;
122 while (parameters.GetNext()) {
123 if (LowerCaseEqualsASCII(parameters.name(), "stale")) {
124 if (LowerCaseEqualsASCII(parameters.value(), "true"))
125 return HttpAuth::AUTHORIZATION_RESULT_STALE;
126 } else if (LowerCaseEqualsASCII(parameters.name(), "realm")) {
127 original_realm = parameters.value();
130 return (original_realm_ != original_realm) ?
131 HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM :
132 HttpAuth::AUTHORIZATION_RESULT_REJECT;
135 bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) {
136 return ParseChallenge(challenge);
139 int HttpAuthHandlerDigest::GenerateAuthTokenImpl(
140 const AuthCredentials* credentials, const HttpRequestInfo* request,
141 const CompletionCallback& callback, std::string* auth_token) {
142 // Generate a random client nonce.
143 std::string cnonce = nonce_generator_->GenerateNonce();
145 // Extract the request method and path -- the meaning of 'path' is overloaded
146 // in certain cases, to be a hostname.
147 std::string method;
148 std::string path;
149 GetRequestMethodAndPath(request, &method, &path);
151 *auth_token = AssembleCredentials(method, path, *credentials,
152 cnonce, nonce_count_);
153 return OK;
156 HttpAuthHandlerDigest::HttpAuthHandlerDigest(
157 int nonce_count, const NonceGenerator* nonce_generator)
158 : stale_(false),
159 algorithm_(ALGORITHM_UNSPECIFIED),
160 qop_(QOP_UNSPECIFIED),
161 nonce_count_(nonce_count),
162 nonce_generator_(nonce_generator) {
163 DCHECK(nonce_generator_);
166 HttpAuthHandlerDigest::~HttpAuthHandlerDigest() {
169 // The digest challenge header looks like:
170 // WWW-Authenticate: Digest
171 // [realm="<realm-value>"]
172 // nonce="<nonce-value>"
173 // [domain="<list-of-URIs>"]
174 // [opaque="<opaque-token-value>"]
175 // [stale="<true-or-false>"]
176 // [algorithm="<digest-algorithm>"]
177 // [qop="<list-of-qop-values>"]
178 // [<extension-directive>]
180 // Note that according to RFC 2617 (section 1.2) the realm is required.
181 // However we allow it to be omitted, in which case it will default to the
182 // empty string.
184 // This allowance is for better compatibility with webservers that fail to
185 // send the realm (See http://crbug.com/20984 for an instance where a
186 // webserver was not sending the realm with a BASIC challenge).
187 bool HttpAuthHandlerDigest::ParseChallenge(
188 HttpAuth::ChallengeTokenizer* challenge) {
189 auth_scheme_ = HttpAuth::AUTH_SCHEME_DIGEST;
190 score_ = 2;
191 properties_ = ENCRYPTS_IDENTITY;
193 // Initialize to defaults.
194 stale_ = false;
195 algorithm_ = ALGORITHM_UNSPECIFIED;
196 qop_ = QOP_UNSPECIFIED;
197 realm_ = original_realm_ = nonce_ = domain_ = opaque_ = std::string();
199 // FAIL -- Couldn't match auth-scheme.
200 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
201 return false;
203 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
205 // Loop through all the properties.
206 while (parameters.GetNext()) {
207 // FAIL -- couldn't parse a property.
208 if (!ParseChallengeProperty(parameters.name(),
209 parameters.value()))
210 return false;
213 // Check if tokenizer failed.
214 if (!parameters.valid())
215 return false;
217 // Check that a minimum set of properties were provided.
218 if (nonce_.empty())
219 return false;
221 return true;
224 bool HttpAuthHandlerDigest::ParseChallengeProperty(const std::string& name,
225 const std::string& value) {
226 if (LowerCaseEqualsASCII(name, "realm")) {
227 std::string realm;
228 if (!base::ConvertToUtf8AndNormalize(value, base::kCodepageLatin1, &realm))
229 return false;
230 realm_ = realm;
231 original_realm_ = value;
232 } else if (LowerCaseEqualsASCII(name, "nonce")) {
233 nonce_ = value;
234 } else if (LowerCaseEqualsASCII(name, "domain")) {
235 domain_ = value;
236 } else if (LowerCaseEqualsASCII(name, "opaque")) {
237 opaque_ = value;
238 } else if (LowerCaseEqualsASCII(name, "stale")) {
239 // Parse the stale boolean.
240 stale_ = LowerCaseEqualsASCII(value, "true");
241 } else if (LowerCaseEqualsASCII(name, "algorithm")) {
242 // Parse the algorithm.
243 if (LowerCaseEqualsASCII(value, "md5")) {
244 algorithm_ = ALGORITHM_MD5;
245 } else if (LowerCaseEqualsASCII(value, "md5-sess")) {
246 algorithm_ = ALGORITHM_MD5_SESS;
247 } else {
248 DVLOG(1) << "Unknown value of algorithm";
249 return false; // FAIL -- unsupported value of algorithm.
251 } else if (LowerCaseEqualsASCII(name, "qop")) {
252 // Parse the comma separated list of qops.
253 // auth is the only supported qop, and all other values are ignored.
254 HttpUtil::ValuesIterator qop_values(value.begin(), value.end(), ',');
255 qop_ = QOP_UNSPECIFIED;
256 while (qop_values.GetNext()) {
257 if (LowerCaseEqualsASCII(qop_values.value(), "auth")) {
258 qop_ = QOP_AUTH;
259 break;
262 } else {
263 DVLOG(1) << "Skipping unrecognized digest property";
264 // TODO(eroman): perhaps we should fail instead of silently skipping?
266 return true;
269 // static
270 std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) {
271 switch (qop) {
272 case QOP_UNSPECIFIED:
273 return std::string();
274 case QOP_AUTH:
275 return "auth";
276 default:
277 NOTREACHED();
278 return std::string();
282 // static
283 std::string HttpAuthHandlerDigest::AlgorithmToString(
284 DigestAlgorithm algorithm) {
285 switch (algorithm) {
286 case ALGORITHM_UNSPECIFIED:
287 return std::string();
288 case ALGORITHM_MD5:
289 return "MD5";
290 case ALGORITHM_MD5_SESS:
291 return "MD5-sess";
292 default:
293 NOTREACHED();
294 return std::string();
298 void HttpAuthHandlerDigest::GetRequestMethodAndPath(
299 const HttpRequestInfo* request,
300 std::string* method,
301 std::string* path) const {
302 DCHECK(request);
304 const GURL& url = request->url;
306 if (target_ == HttpAuth::AUTH_PROXY &&
307 (url.SchemeIs("https") || url.SchemeIs("ws") || url.SchemeIs("wss"))) {
308 *method = "CONNECT";
309 *path = GetHostAndPort(url);
310 } else {
311 *method = request->method;
312 *path = HttpUtil::PathForRequest(url);
316 std::string HttpAuthHandlerDigest::AssembleResponseDigest(
317 const std::string& method,
318 const std::string& path,
319 const AuthCredentials& credentials,
320 const std::string& cnonce,
321 const std::string& nc) const {
322 // ha1 = MD5(A1)
323 // TODO(eroman): is this the right encoding?
324 std::string ha1 = base::MD5String(UTF16ToUTF8(credentials.username()) + ":" +
325 original_realm_ + ":" +
326 UTF16ToUTF8(credentials.password()));
327 if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
328 ha1 = base::MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
330 // ha2 = MD5(A2)
331 // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
332 std::string ha2 = base::MD5String(method + ":" + path);
334 std::string nc_part;
335 if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
336 nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
339 return base::MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
342 std::string HttpAuthHandlerDigest::AssembleCredentials(
343 const std::string& method,
344 const std::string& path,
345 const AuthCredentials& credentials,
346 const std::string& cnonce,
347 int nonce_count) const {
348 // the nonce-count is an 8 digit hex string.
349 std::string nc = base::StringPrintf("%08x", nonce_count);
351 // TODO(eroman): is this the right encoding?
352 std::string authorization = (std::string("Digest username=") +
353 HttpUtil::Quote(
354 UTF16ToUTF8(credentials.username())));
355 authorization += ", realm=" + HttpUtil::Quote(original_realm_);
356 authorization += ", nonce=" + HttpUtil::Quote(nonce_);
357 authorization += ", uri=" + HttpUtil::Quote(path);
359 if (algorithm_ != ALGORITHM_UNSPECIFIED) {
360 authorization += ", algorithm=" + AlgorithmToString(algorithm_);
362 std::string response = AssembleResponseDigest(method, path, credentials,
363 cnonce, nc);
364 // No need to call HttpUtil::Quote() as the response digest cannot contain
365 // any characters needing to be escaped.
366 authorization += ", response=\"" + response + "\"";
368 if (!opaque_.empty()) {
369 authorization += ", opaque=" + HttpUtil::Quote(opaque_);
371 if (qop_ != QOP_UNSPECIFIED) {
372 // TODO(eroman): Supposedly IIS server requires quotes surrounding qop.
373 authorization += ", qop=" + QopToString(qop_);
374 authorization += ", nc=" + nc;
375 authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
378 return authorization;
381 } // namespace net