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