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