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