Clean up MFYI by adding suppressions for new bugs
[chromium-blink-merge.git] / net / http / http_auth_handler_digest.cc
blobe0a1ef687bdfe9baccd534b7ce02540c55b1a30c
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"
21 #include "url/gurl.h"
23 namespace net {
25 // Digest authentication is specified in RFC 2617.
26 // The expanded derivations are listed in the tables below.
28 //==========+==========+==========================================+
29 // qop |algorithm | response |
30 //==========+==========+==========================================+
31 // ? | ?, md5, | MD5(MD5(A1):nonce:MD5(A2)) |
32 // | md5-sess | |
33 //--------- +----------+------------------------------------------+
34 // auth, | ?, md5, | MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2)) |
35 // auth-int | md5-sess | |
36 //==========+==========+==========================================+
37 // qop |algorithm | A1 |
38 //==========+==========+==========================================+
39 // | ?, md5 | user:realm:password |
40 //----------+----------+------------------------------------------+
41 // | md5-sess | MD5(user:realm:password):nonce:cnonce |
42 //==========+==========+==========================================+
43 // qop |algorithm | A2 |
44 //==========+==========+==========================================+
45 // ?, auth | | req-method:req-uri |
46 //----------+----------+------------------------------------------+
47 // auth-int | | req-method:req-uri:MD5(req-entity-body) |
48 //=====================+==========================================+
50 HttpAuthHandlerDigest::NonceGenerator::NonceGenerator() {
53 HttpAuthHandlerDigest::NonceGenerator::~NonceGenerator() {
56 HttpAuthHandlerDigest::DynamicNonceGenerator::DynamicNonceGenerator() {
59 std::string HttpAuthHandlerDigest::DynamicNonceGenerator::GenerateNonce()
60 const {
61 // This is how mozilla generates their cnonce -- a 16 digit hex string.
62 static const char domain[] = "0123456789abcdef";
63 std::string cnonce;
64 cnonce.reserve(16);
65 for (int i = 0; i < 16; ++i)
66 cnonce.push_back(domain[base::RandInt(0, 15)]);
67 return cnonce;
70 HttpAuthHandlerDigest::FixedNonceGenerator::FixedNonceGenerator(
71 const std::string& nonce)
72 : nonce_(nonce) {
75 std::string HttpAuthHandlerDigest::FixedNonceGenerator::GenerateNonce() const {
76 return nonce_;
79 HttpAuthHandlerDigest::Factory::Factory()
80 : nonce_generator_(new DynamicNonceGenerator()) {
83 HttpAuthHandlerDigest::Factory::~Factory() {
86 void HttpAuthHandlerDigest::Factory::set_nonce_generator(
87 const NonceGenerator* nonce_generator) {
88 nonce_generator_.reset(nonce_generator);
91 int HttpAuthHandlerDigest::Factory::CreateAuthHandler(
92 HttpAuth::ChallengeTokenizer* challenge,
93 HttpAuth::Target target,
94 const GURL& origin,
95 CreateReason reason,
96 int digest_nonce_count,
97 const BoundNetLog& net_log,
98 scoped_ptr<HttpAuthHandler>* handler) {
99 // TODO(cbentzel): Move towards model of parsing in the factory
100 // method and only constructing when valid.
101 scoped_ptr<HttpAuthHandler> tmp_handler(
102 new HttpAuthHandlerDigest(digest_nonce_count, nonce_generator_.get()));
103 if (!tmp_handler->InitFromChallenge(challenge, target, origin, net_log))
104 return ERR_INVALID_RESPONSE;
105 handler->swap(tmp_handler);
106 return OK;
109 HttpAuth::AuthorizationResult HttpAuthHandlerDigest::HandleAnotherChallenge(
110 HttpAuth::ChallengeTokenizer* challenge) {
111 // Even though Digest is not connection based, a "second round" is parsed
112 // to differentiate between stale and rejected responses.
113 // Note that the state of the current handler is not mutated - this way if
114 // there is a rejection the realm hasn't changed.
115 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
116 return HttpAuth::AUTHORIZATION_RESULT_INVALID;
118 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
120 // Try to find the "stale" value, and also keep track of the realm
121 // for the new challenge.
122 std::string original_realm;
123 while (parameters.GetNext()) {
124 if (LowerCaseEqualsASCII(parameters.name(), "stale")) {
125 if (LowerCaseEqualsASCII(parameters.value(), "true"))
126 return HttpAuth::AUTHORIZATION_RESULT_STALE;
127 } else if (LowerCaseEqualsASCII(parameters.name(), "realm")) {
128 original_realm = parameters.value();
131 return (original_realm_ != original_realm) ?
132 HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM :
133 HttpAuth::AUTHORIZATION_RESULT_REJECT;
136 bool HttpAuthHandlerDigest::Init(HttpAuth::ChallengeTokenizer* challenge) {
137 return ParseChallenge(challenge);
140 int HttpAuthHandlerDigest::GenerateAuthTokenImpl(
141 const AuthCredentials* credentials, const HttpRequestInfo* request,
142 const CompletionCallback& callback, std::string* auth_token) {
143 // Generate a random client nonce.
144 std::string cnonce = nonce_generator_->GenerateNonce();
146 // Extract the request method and path -- the meaning of 'path' is overloaded
147 // in certain cases, to be a hostname.
148 std::string method;
149 std::string path;
150 GetRequestMethodAndPath(request, &method, &path);
152 *auth_token = AssembleCredentials(method, path, *credentials,
153 cnonce, nonce_count_);
154 return OK;
157 HttpAuthHandlerDigest::HttpAuthHandlerDigest(
158 int nonce_count, const NonceGenerator* nonce_generator)
159 : stale_(false),
160 algorithm_(ALGORITHM_UNSPECIFIED),
161 qop_(QOP_UNSPECIFIED),
162 nonce_count_(nonce_count),
163 nonce_generator_(nonce_generator) {
164 DCHECK(nonce_generator_);
167 HttpAuthHandlerDigest::~HttpAuthHandlerDigest() {
170 // The digest challenge header looks like:
171 // WWW-Authenticate: Digest
172 // [realm="<realm-value>"]
173 // nonce="<nonce-value>"
174 // [domain="<list-of-URIs>"]
175 // [opaque="<opaque-token-value>"]
176 // [stale="<true-or-false>"]
177 // [algorithm="<digest-algorithm>"]
178 // [qop="<list-of-qop-values>"]
179 // [<extension-directive>]
181 // Note that according to RFC 2617 (section 1.2) the realm is required.
182 // However we allow it to be omitted, in which case it will default to the
183 // empty string.
185 // This allowance is for better compatibility with webservers that fail to
186 // send the realm (See http://crbug.com/20984 for an instance where a
187 // webserver was not sending the realm with a BASIC challenge).
188 bool HttpAuthHandlerDigest::ParseChallenge(
189 HttpAuth::ChallengeTokenizer* challenge) {
190 auth_scheme_ = HttpAuth::AUTH_SCHEME_DIGEST;
191 score_ = 2;
192 properties_ = ENCRYPTS_IDENTITY;
194 // Initialize to defaults.
195 stale_ = false;
196 algorithm_ = ALGORITHM_UNSPECIFIED;
197 qop_ = QOP_UNSPECIFIED;
198 realm_ = original_realm_ = nonce_ = domain_ = opaque_ = std::string();
200 // FAIL -- Couldn't match auth-scheme.
201 if (!LowerCaseEqualsASCII(challenge->scheme(), "digest"))
202 return false;
204 HttpUtil::NameValuePairsIterator parameters = challenge->param_pairs();
206 // Loop through all the properties.
207 while (parameters.GetNext()) {
208 // FAIL -- couldn't parse a property.
209 if (!ParseChallengeProperty(parameters.name(),
210 parameters.value()))
211 return false;
214 // Check if tokenizer failed.
215 if (!parameters.valid())
216 return false;
218 // Check that a minimum set of properties were provided.
219 if (nonce_.empty())
220 return false;
222 return true;
225 bool HttpAuthHandlerDigest::ParseChallengeProperty(const std::string& name,
226 const std::string& value) {
227 if (LowerCaseEqualsASCII(name, "realm")) {
228 std::string realm;
229 if (!base::ConvertToUtf8AndNormalize(value, base::kCodepageLatin1, &realm))
230 return false;
231 realm_ = realm;
232 original_realm_ = value;
233 } else if (LowerCaseEqualsASCII(name, "nonce")) {
234 nonce_ = value;
235 } else if (LowerCaseEqualsASCII(name, "domain")) {
236 domain_ = value;
237 } else if (LowerCaseEqualsASCII(name, "opaque")) {
238 opaque_ = value;
239 } else if (LowerCaseEqualsASCII(name, "stale")) {
240 // Parse the stale boolean.
241 stale_ = LowerCaseEqualsASCII(value, "true");
242 } else if (LowerCaseEqualsASCII(name, "algorithm")) {
243 // Parse the algorithm.
244 if (LowerCaseEqualsASCII(value, "md5")) {
245 algorithm_ = ALGORITHM_MD5;
246 } else if (LowerCaseEqualsASCII(value, "md5-sess")) {
247 algorithm_ = ALGORITHM_MD5_SESS;
248 } else {
249 DVLOG(1) << "Unknown value of algorithm";
250 return false; // FAIL -- unsupported value of algorithm.
252 } else if (LowerCaseEqualsASCII(name, "qop")) {
253 // Parse the comma separated list of qops.
254 // auth is the only supported qop, and all other values are ignored.
255 HttpUtil::ValuesIterator qop_values(value.begin(), value.end(), ',');
256 qop_ = QOP_UNSPECIFIED;
257 while (qop_values.GetNext()) {
258 if (LowerCaseEqualsASCII(qop_values.value(), "auth")) {
259 qop_ = QOP_AUTH;
260 break;
263 } else {
264 DVLOG(1) << "Skipping unrecognized digest property";
265 // TODO(eroman): perhaps we should fail instead of silently skipping?
267 return true;
270 // static
271 std::string HttpAuthHandlerDigest::QopToString(QualityOfProtection qop) {
272 switch (qop) {
273 case QOP_UNSPECIFIED:
274 return std::string();
275 case QOP_AUTH:
276 return "auth";
277 default:
278 NOTREACHED();
279 return std::string();
283 // static
284 std::string HttpAuthHandlerDigest::AlgorithmToString(
285 DigestAlgorithm algorithm) {
286 switch (algorithm) {
287 case ALGORITHM_UNSPECIFIED:
288 return std::string();
289 case ALGORITHM_MD5:
290 return "MD5";
291 case ALGORITHM_MD5_SESS:
292 return "MD5-sess";
293 default:
294 NOTREACHED();
295 return std::string();
299 void HttpAuthHandlerDigest::GetRequestMethodAndPath(
300 const HttpRequestInfo* request,
301 std::string* method,
302 std::string* path) const {
303 DCHECK(request);
305 const GURL& url = request->url;
307 if (target_ == HttpAuth::AUTH_PROXY &&
308 (url.SchemeIs("https") || url.SchemeIsWSOrWSS())) {
309 *method = "CONNECT";
310 *path = GetHostAndPort(url);
311 } else {
312 *method = request->method;
313 *path = HttpUtil::PathForRequest(url);
317 std::string HttpAuthHandlerDigest::AssembleResponseDigest(
318 const std::string& method,
319 const std::string& path,
320 const AuthCredentials& credentials,
321 const std::string& cnonce,
322 const std::string& nc) const {
323 // ha1 = MD5(A1)
324 // TODO(eroman): is this the right encoding?
325 std::string ha1 = base::MD5String(base::UTF16ToUTF8(credentials.username()) +
326 ":" + original_realm_ + ":" +
327 base::UTF16ToUTF8(credentials.password()));
328 if (algorithm_ == HttpAuthHandlerDigest::ALGORITHM_MD5_SESS)
329 ha1 = base::MD5String(ha1 + ":" + nonce_ + ":" + cnonce);
331 // ha2 = MD5(A2)
332 // TODO(eroman): need to add MD5(req-entity-body) for qop=auth-int.
333 std::string ha2 = base::MD5String(method + ":" + path);
335 std::string nc_part;
336 if (qop_ != HttpAuthHandlerDigest::QOP_UNSPECIFIED) {
337 nc_part = nc + ":" + cnonce + ":" + QopToString(qop_) + ":";
340 return base::MD5String(ha1 + ":" + nonce_ + ":" + nc_part + ha2);
343 std::string HttpAuthHandlerDigest::AssembleCredentials(
344 const std::string& method,
345 const std::string& path,
346 const AuthCredentials& credentials,
347 const std::string& cnonce,
348 int nonce_count) const {
349 // the nonce-count is an 8 digit hex string.
350 std::string nc = base::StringPrintf("%08x", nonce_count);
352 // TODO(eroman): is this the right encoding?
353 std::string authorization = (std::string("Digest username=") +
354 HttpUtil::Quote(
355 base::UTF16ToUTF8(credentials.username())));
356 authorization += ", realm=" + HttpUtil::Quote(original_realm_);
357 authorization += ", nonce=" + HttpUtil::Quote(nonce_);
358 authorization += ", uri=" + HttpUtil::Quote(path);
360 if (algorithm_ != ALGORITHM_UNSPECIFIED) {
361 authorization += ", algorithm=" + AlgorithmToString(algorithm_);
363 std::string response = AssembleResponseDigest(method, path, credentials,
364 cnonce, nc);
365 // No need to call HttpUtil::Quote() as the response digest cannot contain
366 // any characters needing to be escaped.
367 authorization += ", response=\"" + response + "\"";
369 if (!opaque_.empty()) {
370 authorization += ", opaque=" + HttpUtil::Quote(opaque_);
372 if (qop_ != QOP_UNSPECIFIED) {
373 // TODO(eroman): Supposedly IIS server requires quotes surrounding qop.
374 authorization += ", qop=" + QopToString(qop_);
375 authorization += ", nc=" + nc;
376 authorization += ", cnonce=" + HttpUtil::Quote(cnonce);
379 return authorization;
382 } // namespace net