• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/http/http_auth_handler_digest.h"
6 
7 #include <string>
8 #include <string_view>
9 
10 #include "base/strings/string_util.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "net/base/net_errors.h"
13 #include "net/base/network_anonymization_key.h"
14 #include "net/base/test_completion_callback.h"
15 #include "net/dns/mock_host_resolver.h"
16 #include "net/http/http_auth_challenge_tokenizer.h"
17 #include "net/http/http_request_info.h"
18 #include "net/log/net_log_with_source.h"
19 #include "net/ssl/ssl_info.h"
20 #include "net/test/gtest_util.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "url/gurl.h"
24 #include "url/scheme_host_port.h"
25 
26 using net::test::IsOk;
27 
28 namespace net {
29 
30 namespace {
31 
32 const char* const kSimpleChallenge =
33   "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
34 
35 // RespondToChallenge creates an HttpAuthHandlerDigest for the specified
36 // |challenge|, and generates a response to the challenge which is returned in
37 // |token|.
38 //
39 // The return value indicates whether the |token| was successfully created.
40 //
41 // If |target| is HttpAuth::AUTH_PROXY, then |proxy_name| specifies the source
42 // of the |challenge|. Otherwise, the scheme and host and port of |request_url|
43 // indicates the origin of the challenge.
RespondToChallenge(HttpAuth::Target target,const std::string & proxy_name,const std::string & request_url,const std::string & challenge,std::string * token)44 bool RespondToChallenge(HttpAuth::Target target,
45                         const std::string& proxy_name,
46                         const std::string& request_url,
47                         const std::string& challenge,
48                         std::string* token) {
49   // Input validation.
50   if (token == nullptr) {
51     ADD_FAILURE() << "|token| must be valid";
52     return false;
53   }
54   EXPECT_TRUE(target != HttpAuth::AUTH_PROXY || !proxy_name.empty());
55   EXPECT_FALSE(request_url.empty());
56   EXPECT_FALSE(challenge.empty());
57 
58   token->clear();
59   auto factory = std::make_unique<HttpAuthHandlerDigest::Factory>();
60   auto nonce_generator =
61       std::make_unique<HttpAuthHandlerDigest::FixedNonceGenerator>(
62           "client_nonce");
63   factory->set_nonce_generator(std::move(nonce_generator));
64   auto host_resolver = std::make_unique<MockHostResolver>();
65   std::unique_ptr<HttpAuthHandler> handler;
66 
67   // Create a handler for a particular challenge.
68   SSLInfo null_ssl_info;
69   url::SchemeHostPort scheme_host_port(
70       target == HttpAuth::AUTH_SERVER ? GURL(request_url) : GURL(proxy_name));
71   int rv_create = factory->CreateAuthHandlerFromString(
72       challenge, target, null_ssl_info, NetworkAnonymizationKey(),
73       scheme_host_port, NetLogWithSource(), host_resolver.get(), &handler);
74   if (rv_create != OK || handler.get() == nullptr) {
75     ADD_FAILURE() << "Unable to create auth handler.";
76     return false;
77   }
78 
79   // Create a token in response to the challenge.
80   // NOTE: HttpAuthHandlerDigest's implementation of GenerateAuthToken always
81   // completes synchronously. That's why this test can get away with a
82   // TestCompletionCallback without an IO thread.
83   TestCompletionCallback callback;
84   auto request = std::make_unique<HttpRequestInfo>();
85   request->url = GURL(request_url);
86   AuthCredentials credentials(u"foo", u"bar");
87   int rv_generate = handler->GenerateAuthToken(
88       &credentials, request.get(), callback.callback(), token);
89   if (rv_generate != OK) {
90     ADD_FAILURE() << "Problems generating auth token";
91     return false;
92   }
93 
94   return true;
95 }
96 
97 }  // namespace
98 
99 
TEST(HttpAuthHandlerDigestTest,ParseChallenge)100 TEST(HttpAuthHandlerDigestTest, ParseChallenge) {
101   // clang-format off
102   static const struct {
103     // The challenge string.
104     const char* challenge;
105     // Expected return value of ParseChallenge.
106     bool parsed_success;
107     // The expected values that were parsed.
108     const char* parsed_realm;
109     const char* parsed_nonce;
110     const char* parsed_domain;
111     const char* parsed_opaque;
112     bool parsed_stale;
113     HttpAuthHandlerDigest::Algorithm parsed_algorithm;
114     int parsed_qop;
115   } tests[] = {
116     { // Check that a minimal challenge works correctly.
117       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\"",
118       true,
119       "Thunder Bluff",
120       "xyz",
121       "",
122       "",
123       false,
124       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
125       HttpAuthHandlerDigest::QOP_UNSPECIFIED
126     },
127 
128     { // Realm does not need to be quoted, even though RFC2617 requires it.
129       "Digest nonce=\"xyz\", realm=ThunderBluff",
130       true,
131       "ThunderBluff",
132       "xyz",
133       "",
134       "",
135       false,
136       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
137       HttpAuthHandlerDigest::QOP_UNSPECIFIED
138     },
139 
140     { // We allow the realm to be omitted, and will default it to empty string.
141       // See http://crbug.com/20984.
142       "Digest nonce=\"xyz\"",
143       true,
144       "",
145       "xyz",
146       "",
147       "",
148       false,
149       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
150       HttpAuthHandlerDigest::QOP_UNSPECIFIED
151     },
152 
153     { // Try with realm set to empty string.
154       "Digest realm=\"\", nonce=\"xyz\"",
155       true,
156       "",
157       "xyz",
158       "",
159       "",
160       false,
161       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
162       HttpAuthHandlerDigest::QOP_UNSPECIFIED
163     },
164 
165     // Handle ISO-8859-1 character as part of the realm. The realm is converted
166     // to UTF-8. However, the credentials will still use the original encoding.
167     {
168       "Digest nonce=\"xyz\", realm=\"foo-\xE5\"",
169       true,
170       "foo-\xC3\xA5",
171       "xyz",
172       "",
173       "",
174       false,
175       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
176       HttpAuthHandlerDigest::QOP_UNSPECIFIED,
177     },
178 
179     { // At a minimum, a nonce must be provided.
180       "Digest realm=\"Thunder Bluff\"",
181       false,
182       "",
183       "",
184       "",
185       "",
186       false,
187       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
188       HttpAuthHandlerDigest::QOP_UNSPECIFIED
189     },
190 
191     { // The nonce does not need to be quoted, even though RFC2617
192       // requires it.
193       "Digest nonce=xyz, realm=\"Thunder Bluff\"",
194       true,
195       "Thunder Bluff",
196       "xyz",
197       "",
198       "",
199       false,
200       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
201       HttpAuthHandlerDigest::QOP_UNSPECIFIED
202     },
203 
204     { // Unknown authentication parameters are ignored.
205       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", foo=\"bar\"",
206       true,
207       "Thunder Bluff",
208       "xyz",
209       "",
210       "",
211       false,
212       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
213       HttpAuthHandlerDigest::QOP_UNSPECIFIED
214     },
215 
216     { // Check that when algorithm has an unsupported value, parsing fails.
217       "Digest nonce=\"xyz\", algorithm=\"awezum\", realm=\"Thunder\"",
218       false,
219       // The remaining values don't matter (but some have been set already).
220       "",
221       "xyz",
222       "",
223       "",
224       false,
225       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
226       HttpAuthHandlerDigest::QOP_UNSPECIFIED
227     },
228 
229     { // Check that algorithm's value is case insensitive, and that MD5 is
230       // a supported algorithm.
231       "Digest nonce=\"xyz\", algorithm=\"mD5\", realm=\"Oblivion\"",
232       true,
233       "Oblivion",
234       "xyz",
235       "",
236       "",
237       false,
238       HttpAuthHandlerDigest::Algorithm::MD5,
239       HttpAuthHandlerDigest::QOP_UNSPECIFIED
240     },
241 
242     { // Check that md5-sess is a supported algorithm.
243       "Digest nonce=\"xyz\", algorithm=\"md5-sess\", realm=\"Oblivion\"",
244       true,
245       "Oblivion",
246       "xyz",
247       "",
248       "",
249       false,
250       HttpAuthHandlerDigest::Algorithm::MD5_SESS,
251       HttpAuthHandlerDigest::QOP_UNSPECIFIED,
252     },
253 
254     { // Check that that SHA-256 is a supported algorithm.
255       "Digest nonce=\"xyz\", algorithm=SHA-256, realm=\"Oblivion\"",
256       true,
257       "Oblivion",
258       "xyz",
259       "",
260       "",
261       false,
262       HttpAuthHandlerDigest::Algorithm::SHA256,
263       HttpAuthHandlerDigest::QOP_UNSPECIFIED
264     },
265 
266     { // Check that that SHA-256-sess is a supported algorithm.
267       "Digest nonce=\"xyz\", algorithm=SHA-256-sess, realm=\"Oblivion\"",
268       true,
269       "Oblivion",
270       "xyz",
271       "",
272       "",
273       false,
274       HttpAuthHandlerDigest::Algorithm::SHA256_SESS,
275       HttpAuthHandlerDigest::QOP_UNSPECIFIED
276     },
277 
278     { // Check that md5-sess is a supported algorithm.
279       "Digest nonce=\"xyz\", algorithm=\"md5-sess\", realm=\"Oblivion\"",
280       true,
281       "Oblivion",
282       "xyz",
283       "",
284       "",
285       false,
286       HttpAuthHandlerDigest::Algorithm::MD5_SESS,
287       HttpAuthHandlerDigest::QOP_UNSPECIFIED,
288     },
289 
290     { // Check that qop's value is case insensitive, and that auth is known.
291       "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"aUth\"",
292       true,
293       "Oblivion",
294       "xyz",
295       "",
296       "",
297       false,
298       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
299       HttpAuthHandlerDigest::QOP_AUTH
300     },
301 
302     { // auth-int is not handled, but will fall back to default qop.
303       "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth-int\"",
304       true,
305       "Oblivion",
306       "xyz",
307       "",
308       "",
309       false,
310       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
311       HttpAuthHandlerDigest::QOP_UNSPECIFIED
312     },
313 
314     { // Unknown qop values are ignored.
315       "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,foo\"",
316       true,
317       "Oblivion",
318       "xyz",
319       "",
320       "",
321       false,
322       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
323       HttpAuthHandlerDigest::QOP_AUTH
324     },
325 
326     { // If auth-int is included with auth, then use auth.
327       "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,auth-int\"",
328       true,
329       "Oblivion",
330       "xyz",
331       "",
332       "",
333       false,
334       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
335       HttpAuthHandlerDigest::QOP_AUTH
336     },
337 
338     { // Opaque parameter parsing should work correctly.
339       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=\"foobar\"",
340       true,
341       "Thunder Bluff",
342       "xyz",
343       "",
344       "foobar",
345       false,
346       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
347       HttpAuthHandlerDigest::QOP_UNSPECIFIED
348     },
349 
350     { // Opaque parameters do not need to be quoted, even though RFC2617
351       // seems to require it.
352       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=foobar",
353       true,
354       "Thunder Bluff",
355       "xyz",
356       "",
357       "foobar",
358       false,
359       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
360       HttpAuthHandlerDigest::QOP_UNSPECIFIED
361     },
362 
363     { // Domain can be parsed.
364       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
365       "domain=\"http://intranet.example.com/protection\"",
366       true,
367       "Thunder Bluff",
368       "xyz",
369       "http://intranet.example.com/protection",
370       "",
371       false,
372       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
373       HttpAuthHandlerDigest::QOP_UNSPECIFIED
374     },
375 
376     { // Multiple domains can be parsed.
377       "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
378       "domain=\"http://intranet.example.com/protection http://www.google.com\"",
379       true,
380       "Thunder Bluff",
381       "xyz",
382       "http://intranet.example.com/protection http://www.google.com",
383       "",
384       false,
385       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
386       HttpAuthHandlerDigest::QOP_UNSPECIFIED
387     },
388 
389     { // If a non-Digest scheme is somehow passed in, it should be rejected.
390       "Basic realm=\"foo\"",
391       false,
392       "",
393       "",
394       "",
395       "",
396       false,
397       HttpAuthHandlerDigest::Algorithm::UNSPECIFIED,
398       HttpAuthHandlerDigest::QOP_UNSPECIFIED
399     },
400   };
401   // clang-format on
402 
403   url::SchemeHostPort scheme_host_port(GURL("http://www.example.com"));
404   auto factory = std::make_unique<HttpAuthHandlerDigest::Factory>();
405   for (const auto& test : tests) {
406     SSLInfo null_ssl_info;
407     auto host_resolver = std::make_unique<MockHostResolver>();
408     std::unique_ptr<HttpAuthHandler> handler;
409     int rv = factory->CreateAuthHandlerFromString(
410         test.challenge, HttpAuth::AUTH_SERVER, null_ssl_info,
411         NetworkAnonymizationKey(), scheme_host_port, NetLogWithSource(),
412         host_resolver.get(), &handler);
413     if (test.parsed_success) {
414       EXPECT_THAT(rv, IsOk());
415     } else {
416       EXPECT_NE(OK, rv);
417       EXPECT_TRUE(handler.get() == nullptr);
418       continue;
419     }
420     ASSERT_TRUE(handler.get() != nullptr);
421     HttpAuthHandlerDigest* digest =
422         static_cast<HttpAuthHandlerDigest*>(handler.get());
423     EXPECT_STREQ(test.parsed_realm, digest->realm_.c_str());
424     EXPECT_STREQ(test.parsed_nonce, digest->nonce_.c_str());
425     EXPECT_STREQ(test.parsed_domain, digest->domain_.c_str());
426     EXPECT_STREQ(test.parsed_opaque, digest->opaque_.c_str());
427     EXPECT_EQ(test.parsed_stale, digest->stale_);
428     EXPECT_EQ(test.parsed_algorithm, digest->algorithm_);
429     EXPECT_EQ(test.parsed_qop, digest->qop_);
430     EXPECT_TRUE(handler->encrypts_identity());
431     EXPECT_FALSE(handler->is_connection_based());
432     EXPECT_TRUE(handler->NeedsIdentity());
433     EXPECT_FALSE(handler->AllowsDefaultCredentials());
434   }
435 }
436 
TEST(HttpAuthHandlerDigestTest,AssembleCredentials)437 TEST(HttpAuthHandlerDigestTest, AssembleCredentials) {
438   // clang-format off
439   static const struct {
440     const char* req_method;
441     const char* req_path;
442     const char* challenge;
443     const char* username;
444     const char* password;
445     const char* cnonce;
446     int nonce_count;
447     const char* expected_creds;
448   } tests[] = {
449     { // MD5 (default) with username/password
450       "GET",
451       "/test/drealm1",
452 
453       // Challenge
454       "Digest realm=\"DRealm1\", "
455       "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
456       "qop=\"auth\"",
457 
458       "foo", "bar", // username/password
459       "082c875dcb2ca740", // cnonce
460       1, // nc
461 
462       // Authorization
463       "Digest username=\"foo\", realm=\"DRealm1\", "
464       "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
465       "uri=\"/test/drealm1\", "
466       "response=\"bcfaa62f1186a31ff1b474a19a17cf57\", "
467       "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
468     },
469 
470     { // MD5 with username but empty password. username has space in it.
471       "GET",
472       "/test/drealm1/",
473 
474       // Challenge
475       "Digest realm=\"DRealm1\", "
476       "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
477       "algorithm=MD5, qop=\"auth\"",
478 
479       "foo bar", "", // Username/password
480       "082c875dcb2ca740", // cnonce
481       1, // nc
482 
483       // Authorization
484       "Digest username=\"foo bar\", realm=\"DRealm1\", "
485       "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
486       "uri=\"/test/drealm1/\", algorithm=MD5, "
487       "response=\"93c9c6d5930af3b0eb26c745e02b04a0\", "
488       "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
489     },
490 
491     { // MD5 with no username.
492       "GET",
493       "/test/drealm1/",
494 
495       // Challenge
496       "Digest realm=\"DRealm1\", "
497       "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
498       "algorithm=MD5, qop=\"auth\"",
499 
500       "", "pass", // Username/password
501       "6509bc74daed8263", // cnonce
502       1, // nc
503 
504       // Authorization
505       "Digest username=\"\", realm=\"DRealm1\", "
506       "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
507       "uri=\"/test/drealm1/\", algorithm=MD5, "
508       "response=\"bc597110f41a62d07f8b70b6977fcb61\", "
509       "qop=auth, nc=00000001, cnonce=\"6509bc74daed8263\""
510     },
511 
512     { // MD5 with no username and no password.
513       "GET",
514       "/test/drealm1/",
515 
516       // Challenge
517       "Digest realm=\"DRealm1\", "
518       "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
519       "algorithm=MD5, qop=\"auth\"",
520 
521       "", "", // Username/password
522       "1522e61005789929", // cnonce
523       1, // nc
524 
525       // Authorization
526       "Digest username=\"\", realm=\"DRealm1\", "
527       "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
528       "uri=\"/test/drealm1/\", algorithm=MD5, "
529       "response=\"22cfa2b30cb500a9591c6d55ec5590a8\", "
530       "qop=auth, nc=00000001, cnonce=\"1522e61005789929\""
531     },
532 
533     { // No algorithm, and no qop.
534       "GET",
535       "/",
536 
537       // Challenge
538       "Digest realm=\"Oblivion\", nonce=\"nonce-value\"",
539 
540       "FooBar", "pass", // Username/password
541       "", // cnonce
542       1, // nc
543 
544       // Authorization
545       "Digest username=\"FooBar\", realm=\"Oblivion\", "
546       "nonce=\"nonce-value\", uri=\"/\", "
547       "response=\"f72ff54ebde2f928860f806ec04acd1b\""
548     },
549 
550     { // MD5-sess
551       "GET",
552       "/",
553 
554       // Challenge
555       "Digest realm=\"Baztastic\", nonce=\"AAAAAAAA\", "
556       "algorithm=\"md5-sess\", qop=auth",
557 
558       "USER", "123", // Username/password
559       "15c07961ed8575c4", // cnonce
560       1, // nc
561 
562       // Authorization
563       "Digest username=\"USER\", realm=\"Baztastic\", "
564       "nonce=\"AAAAAAAA\", uri=\"/\", algorithm=MD5-sess, "
565       "response=\"cbc1139821ee7192069580570c541a03\", "
566       "qop=auth, nc=00000001, cnonce=\"15c07961ed8575c4\""
567     },
568 
569     { // RFC MD5 (https://www.rfc-editor.org/rfc/rfc7616#section-3.9.1)
570       "GET",
571       "/dir/index.html",
572 
573       // Challenge
574       "Digest realm=\"http-auth@example.org\", "
575       "qop=\"auth, auth-int\", "
576       "algorithm=MD5, "
577       "nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\","
578       "opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"",
579 
580       "Mufasa", "Circle of Life", // Username/password
581       "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", // cnonce
582       1, // nc
583 
584       // Authorization
585       "Digest username=\"Mufasa\", realm=\"http-auth@example.org\", "
586       "nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", "
587       "uri=\"/dir/index.html\", algorithm=MD5, "
588       "response=\"8ca523f5e9506fed4657c9700eebdbec\", "
589       "opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\", "
590       "qop=auth, nc=00000001, "
591       "cnonce=\"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\""
592     },
593 
594     { // RFC SHA-256 (https://www.rfc-editor.org/rfc/rfc7616#section-3.9.1)
595       "GET",
596       "/dir/index.html",
597 
598       // Challenge
599       "Digest realm=\"http-auth@example.org\", "
600       "qop=\"auth, auth-int\", "
601       "algorithm=SHA-256, "
602       "nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\","
603       "opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\"",
604 
605       "Mufasa", "Circle of Life", // Username/password
606       "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ", // cnonce
607       1, // nc
608 
609       // Authorization
610       "Digest username=\"Mufasa\", realm=\"http-auth@example.org\", "
611       "nonce=\"7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v\", "
612       "uri=\"/dir/index.html\", algorithm=SHA-256, "
613       "response=\"753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1\", "
614       "opaque=\"FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS\", "
615       "qop=auth, nc=00000001, "
616       "cnonce=\"f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ\""
617     },
618 
619     { // RFC SHA-256 and userhash
620       "GET",
621       "/doe.json",
622 
623       // Challenge
624       "Digest realm=\"api@example.org\", "
625       "qop=\"auth\", "
626       "algorithm=SHA-256, "
627       "nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", "
628       "opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", "
629       "charset=UTF-8, userhash=true",
630 
631       "J\xc3\xa4s\xc3\xb8n Doe", "Secret, or not?", // Username/password
632       "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", // cnonce
633       0x123, // nc
634 
635       // Authorization
636       "Digest username=\"5a1a8a47df5c298551b9b42ba9b05835174a5bd7d511ff7fe9191d8e946fc4e7\", "
637       "realm=\"api@example.org\", "
638       "nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", "
639       "uri=\"/doe.json\", algorithm=SHA-256, "
640       "response=\"61baba8a218e4b207f158ed9b9b3a95ed940c1872ef3ff4522eb10110720a145\", "
641       "opaque=\"HRPCssKJSGjCrkzDg8OhwpzCiGPChXYjwrI2QmXDnsOS\", "
642       "qop=auth, nc=00000123, "
643       "cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\", "
644       "userhash=true"
645     },
646   };
647   // clang-format on
648   url::SchemeHostPort scheme_host_port(GURL("http://www.example.com"));
649   auto factory = std::make_unique<HttpAuthHandlerDigest::Factory>();
650   for (const auto& test : tests) {
651     SSLInfo null_ssl_info;
652     auto host_resolver = std::make_unique<MockHostResolver>();
653     std::unique_ptr<HttpAuthHandler> handler;
654     int rv = factory->CreateAuthHandlerFromString(
655         test.challenge, HttpAuth::AUTH_SERVER, null_ssl_info,
656         NetworkAnonymizationKey(), scheme_host_port, NetLogWithSource(),
657         host_resolver.get(), &handler);
658     EXPECT_THAT(rv, IsOk());
659     ASSERT_TRUE(handler != nullptr);
660 
661     HttpAuthHandlerDigest* digest =
662         static_cast<HttpAuthHandlerDigest*>(handler.get());
663     std::string creds = digest->AssembleCredentials(
664         test.req_method, test.req_path,
665         AuthCredentials(base::UTF8ToUTF16(test.username),
666                         base::UTF8ToUTF16(test.password)),
667         test.cnonce, test.nonce_count);
668 
669     EXPECT_STREQ(test.expected_creds, creds.c_str());
670   }
671 }
672 
TEST(HttpAuthHandlerDigest,HandleAnotherChallenge)673 TEST(HttpAuthHandlerDigest, HandleAnotherChallenge) {
674   auto factory = std::make_unique<HttpAuthHandlerDigest::Factory>();
675   auto host_resolver = std::make_unique<MockHostResolver>();
676   std::unique_ptr<HttpAuthHandler> handler;
677   std::string default_challenge =
678       "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
679   url::SchemeHostPort scheme_host_port(GURL("http://intranet.google.com"));
680   SSLInfo null_ssl_info;
681   int rv = factory->CreateAuthHandlerFromString(
682       default_challenge, HttpAuth::AUTH_SERVER, null_ssl_info,
683       NetworkAnonymizationKey(), scheme_host_port, NetLogWithSource(),
684       host_resolver.get(), &handler);
685   EXPECT_THAT(rv, IsOk());
686   ASSERT_TRUE(handler.get() != nullptr);
687   HttpAuthChallengeTokenizer tok_default(default_challenge);
688   EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
689             handler->HandleAnotherChallenge(&tok_default));
690 
691   std::string stale_challenge = default_challenge + ", stale=true";
692   HttpAuthChallengeTokenizer tok_stale(stale_challenge);
693   EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_STALE,
694             handler->HandleAnotherChallenge(&tok_stale));
695 
696   std::string stale_false_challenge = default_challenge + ", stale=false";
697   HttpAuthChallengeTokenizer tok_stale_false(stale_false_challenge);
698   EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
699             handler->HandleAnotherChallenge(&tok_stale_false));
700 
701   std::string realm_change_challenge =
702       "Digest realm=\"SomethingElse\", nonce=\"nonce-value2\"";
703   HttpAuthChallengeTokenizer tok_realm_change(realm_change_challenge);
704   EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM,
705             handler->HandleAnotherChallenge(&tok_realm_change));
706 }
707 
TEST(HttpAuthHandlerDigest,RespondToServerChallenge)708 TEST(HttpAuthHandlerDigest, RespondToServerChallenge) {
709   std::string auth_token;
710   EXPECT_TRUE(RespondToChallenge(
711       HttpAuth::AUTH_SERVER,
712       std::string(),
713       "http://www.example.com/path/to/resource",
714       kSimpleChallenge,
715       &auth_token));
716   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
717             "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
718             "response=\"6779f90bd0d658f937c1af967614fe84\"",
719             auth_token);
720 }
721 
TEST(HttpAuthHandlerDigest,RespondToHttpsServerChallenge)722 TEST(HttpAuthHandlerDigest, RespondToHttpsServerChallenge) {
723   std::string auth_token;
724   EXPECT_TRUE(RespondToChallenge(
725       HttpAuth::AUTH_SERVER,
726       std::string(),
727       "https://www.example.com/path/to/resource",
728       kSimpleChallenge,
729       &auth_token));
730   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
731             "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
732             "response=\"6779f90bd0d658f937c1af967614fe84\"",
733             auth_token);
734 }
735 
TEST(HttpAuthHandlerDigest,RespondToProxyChallenge)736 TEST(HttpAuthHandlerDigest, RespondToProxyChallenge) {
737   std::string auth_token;
738   EXPECT_TRUE(RespondToChallenge(
739       HttpAuth::AUTH_PROXY,
740       "http://proxy.intranet.corp.com:3128",
741       "http://www.example.com/path/to/resource",
742       kSimpleChallenge,
743       &auth_token));
744   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
745             "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
746             "response=\"6779f90bd0d658f937c1af967614fe84\"",
747             auth_token);
748 }
749 
TEST(HttpAuthHandlerDigest,RespondToProxyChallengeHttps)750 TEST(HttpAuthHandlerDigest, RespondToProxyChallengeHttps) {
751   std::string auth_token;
752   EXPECT_TRUE(RespondToChallenge(
753       HttpAuth::AUTH_PROXY,
754       "http://proxy.intranet.corp.com:3128",
755       "https://www.example.com/path/to/resource",
756       kSimpleChallenge,
757       &auth_token));
758   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
759             "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
760             "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
761             auth_token);
762 }
763 
TEST(HttpAuthHandlerDigest,RespondToProxyChallengeWs)764 TEST(HttpAuthHandlerDigest, RespondToProxyChallengeWs) {
765   std::string auth_token;
766   EXPECT_TRUE(RespondToChallenge(
767       HttpAuth::AUTH_PROXY,
768       "http://proxy.intranet.corp.com:3128",
769       "ws://www.example.com/echo",
770       kSimpleChallenge,
771       &auth_token));
772   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
773             "nonce=\"nonce-value\", uri=\"www.example.com:80\", "
774             "response=\"aa1df184f68d5b6ab9d9aa4f88e41b4c\"",
775             auth_token);
776 }
777 
TEST(HttpAuthHandlerDigest,RespondToProxyChallengeWss)778 TEST(HttpAuthHandlerDigest, RespondToProxyChallengeWss) {
779   std::string auth_token;
780   EXPECT_TRUE(RespondToChallenge(
781       HttpAuth::AUTH_PROXY,
782       "http://proxy.intranet.corp.com:3128",
783       "wss://www.example.com/echo",
784       kSimpleChallenge,
785       &auth_token));
786   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
787             "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
788             "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
789             auth_token);
790 }
791 
TEST(HttpAuthHandlerDigest,RespondToChallengeAuthQop)792 TEST(HttpAuthHandlerDigest, RespondToChallengeAuthQop) {
793   std::string auth_token;
794   EXPECT_TRUE(RespondToChallenge(
795       HttpAuth::AUTH_SERVER,
796       std::string(),
797       "http://www.example.com/path/to/resource",
798       "Digest realm=\"Oblivion\", nonce=\"nonce-value\", qop=\"auth\"",
799       &auth_token));
800   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
801             "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
802             "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
803             "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
804             auth_token);
805 }
806 
TEST(HttpAuthHandlerDigest,RespondToChallengeOpaque)807 TEST(HttpAuthHandlerDigest, RespondToChallengeOpaque) {
808   std::string auth_token;
809   EXPECT_TRUE(RespondToChallenge(
810       HttpAuth::AUTH_SERVER,
811       std::string(),
812       "http://www.example.com/path/to/resource",
813       "Digest realm=\"Oblivion\", nonce=\"nonce-value\", "
814       "qop=\"auth\", opaque=\"opaque text\"",
815       &auth_token));
816   EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
817             "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
818             "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
819             "opaque=\"opaque text\", "
820             "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
821             auth_token);
822 }
823 
824 
825 } // namespace net
826