1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 #include "base/basictypes.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "net/base/net_errors.h"
11 #include "net/base/test_completion_callback.h"
12 #include "net/http/http_auth_challenge_tokenizer.h"
13 #include "net/http/http_auth_handler_digest.h"
14 #include "net/http/http_request_info.h"
15 #include "testing/gtest/include/gtest/gtest.h"
21 const char* const kSimpleChallenge
=
22 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
24 // RespondToChallenge creates an HttpAuthHandlerDigest for the specified
25 // |challenge|, and generates a response to the challenge which is returned in
28 // The return value indicates whether the |token| was successfully created.
30 // If |target| is HttpAuth::AUTH_PROXY, then |proxy_name| specifies the source
31 // of the |challenge|. Otherwise, the scheme and host and port of |request_url|
32 // indicates the origin of the challenge.
33 bool RespondToChallenge(HttpAuth::Target target
,
34 const std::string
& proxy_name
,
35 const std::string
& request_url
,
36 const std::string
& challenge
,
40 ADD_FAILURE() << "|token| must be non-NULL";
43 EXPECT_TRUE(target
!= HttpAuth::AUTH_PROXY
|| !proxy_name
.empty());
44 EXPECT_FALSE(request_url
.empty());
45 EXPECT_FALSE(challenge
.empty());
48 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
49 new HttpAuthHandlerDigest::Factory());
50 HttpAuthHandlerDigest::NonceGenerator
* nonce_generator
=
51 new HttpAuthHandlerDigest::FixedNonceGenerator("client_nonce");
52 factory
->set_nonce_generator(nonce_generator
);
53 scoped_ptr
<HttpAuthHandler
> handler
;
55 // Create a handler for a particular challenge.
56 GURL
url_origin(target
== HttpAuth::AUTH_SERVER
? request_url
: proxy_name
);
57 int rv_create
= factory
->CreateAuthHandlerFromString(
58 challenge
, target
, url_origin
.GetOrigin(), BoundNetLog(), &handler
);
59 if (rv_create
!= OK
|| handler
.get() == NULL
) {
60 ADD_FAILURE() << "Unable to create auth handler.";
64 // Create a token in response to the challenge.
65 // NOTE: HttpAuthHandlerDigest's implementation of GenerateAuthToken always
66 // completes synchronously. That's why this test can get away with a
67 // TestCompletionCallback without an IO thread.
68 TestCompletionCallback callback
;
69 scoped_ptr
<HttpRequestInfo
> request(new HttpRequestInfo());
70 request
->url
= GURL(request_url
);
71 AuthCredentials
credentials(base::ASCIIToUTF16("foo"),
72 base::ASCIIToUTF16("bar"));
73 int rv_generate
= handler
->GenerateAuthToken(
74 &credentials
, request
.get(), callback
.callback(), token
);
75 if (rv_generate
!= OK
) {
76 ADD_FAILURE() << "Problems generating auth token";
86 TEST(HttpAuthHandlerDigestTest
, ParseChallenge
) {
88 // The challenge string.
89 const char* challenge
;
90 // Expected return value of ParseChallenge.
92 // The expected values that were parsed.
93 const char* parsed_realm
;
94 const char* parsed_nonce
;
95 const char* parsed_domain
;
96 const char* parsed_opaque
;
101 { // Check that a minimal challenge works correctly.
102 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\"",
109 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
110 HttpAuthHandlerDigest::QOP_UNSPECIFIED
113 { // Realm does not need to be quoted, even though RFC2617 requires it.
114 "Digest nonce=\"xyz\", realm=ThunderBluff",
121 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
122 HttpAuthHandlerDigest::QOP_UNSPECIFIED
125 { // We allow the realm to be omitted, and will default it to empty string.
126 // See http://crbug.com/20984.
127 "Digest nonce=\"xyz\"",
134 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
135 HttpAuthHandlerDigest::QOP_UNSPECIFIED
138 { // Try with realm set to empty string.
139 "Digest realm=\"\", nonce=\"xyz\"",
146 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
147 HttpAuthHandlerDigest::QOP_UNSPECIFIED
150 // Handle ISO-8859-1 character as part of the realm. The realm is converted
151 // to UTF-8. However, the credentials will still use the original encoding.
153 "Digest nonce=\"xyz\", realm=\"foo-\xE5\"",
160 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
161 HttpAuthHandlerDigest::QOP_UNSPECIFIED
,
164 { // At a minimum, a nonce must be provided.
165 "Digest realm=\"Thunder Bluff\"",
172 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
173 HttpAuthHandlerDigest::QOP_UNSPECIFIED
176 { // The nonce does not need to be quoted, even though RFC2617
178 "Digest nonce=xyz, realm=\"Thunder Bluff\"",
185 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
186 HttpAuthHandlerDigest::QOP_UNSPECIFIED
189 { // Unknown authentication parameters are ignored.
190 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", foo=\"bar\"",
197 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
198 HttpAuthHandlerDigest::QOP_UNSPECIFIED
201 { // Check that when algorithm has an unsupported value, parsing fails.
202 "Digest nonce=\"xyz\", algorithm=\"awezum\", realm=\"Thunder\"",
204 // The remaining values don't matter (but some have been set already).
210 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
211 HttpAuthHandlerDigest::QOP_UNSPECIFIED
214 { // Check that algorithm's value is case insensitive, and that MD5 is
215 // a supported algorithm.
216 "Digest nonce=\"xyz\", algorithm=\"mD5\", realm=\"Oblivion\"",
223 HttpAuthHandlerDigest::ALGORITHM_MD5
,
224 HttpAuthHandlerDigest::QOP_UNSPECIFIED
227 { // Check that md5-sess is a supported algorithm.
228 "Digest nonce=\"xyz\", algorithm=\"md5-sess\", realm=\"Oblivion\"",
235 HttpAuthHandlerDigest::ALGORITHM_MD5_SESS
,
236 HttpAuthHandlerDigest::QOP_UNSPECIFIED
,
239 { // Check that qop's value is case insensitive, and that auth is known.
240 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"aUth\"",
247 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
248 HttpAuthHandlerDigest::QOP_AUTH
251 { // auth-int is not handled, but will fall back to default qop.
252 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth-int\"",
259 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
260 HttpAuthHandlerDigest::QOP_UNSPECIFIED
263 { // Unknown qop values are ignored.
264 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,foo\"",
271 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
272 HttpAuthHandlerDigest::QOP_AUTH
275 { // If auth-int is included with auth, then use auth.
276 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,auth-int\"",
283 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
284 HttpAuthHandlerDigest::QOP_AUTH
287 { // Opaque parameter parsing should work correctly.
288 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=\"foobar\"",
295 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
296 HttpAuthHandlerDigest::QOP_UNSPECIFIED
299 { // Opaque parameters do not need to be quoted, even though RFC2617
300 // seems to require it.
301 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=foobar",
308 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
309 HttpAuthHandlerDigest::QOP_UNSPECIFIED
312 { // Domain can be parsed.
313 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
314 "domain=\"http://intranet.example.com/protection\"",
318 "http://intranet.example.com/protection",
321 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
322 HttpAuthHandlerDigest::QOP_UNSPECIFIED
325 { // Multiple domains can be parsed.
326 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
327 "domain=\"http://intranet.example.com/protection http://www.google.com\"",
331 "http://intranet.example.com/protection http://www.google.com",
334 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
335 HttpAuthHandlerDigest::QOP_UNSPECIFIED
338 { // If a non-Digest scheme is somehow passed in, it should be rejected.
339 "Basic realm=\"foo\"",
346 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
347 HttpAuthHandlerDigest::QOP_UNSPECIFIED
351 GURL
origin("http://www.example.com");
352 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
353 new HttpAuthHandlerDigest::Factory());
354 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(tests
); ++i
) {
355 scoped_ptr
<HttpAuthHandler
> handler
;
356 int rv
= factory
->CreateAuthHandlerFromString(tests
[i
].challenge
,
357 HttpAuth::AUTH_SERVER
,
361 if (tests
[i
].parsed_success
) {
365 EXPECT_TRUE(handler
.get() == NULL
);
368 ASSERT_TRUE(handler
.get() != NULL
);
369 HttpAuthHandlerDigest
* digest
=
370 static_cast<HttpAuthHandlerDigest
*>(handler
.get());
371 EXPECT_STREQ(tests
[i
].parsed_realm
, digest
->realm_
.c_str());
372 EXPECT_STREQ(tests
[i
].parsed_nonce
, digest
->nonce_
.c_str());
373 EXPECT_STREQ(tests
[i
].parsed_domain
, digest
->domain_
.c_str());
374 EXPECT_STREQ(tests
[i
].parsed_opaque
, digest
->opaque_
.c_str());
375 EXPECT_EQ(tests
[i
].parsed_stale
, digest
->stale_
);
376 EXPECT_EQ(tests
[i
].parsed_algorithm
, digest
->algorithm_
);
377 EXPECT_EQ(tests
[i
].parsed_qop
, digest
->qop_
);
378 EXPECT_TRUE(handler
->encrypts_identity());
379 EXPECT_FALSE(handler
->is_connection_based());
380 EXPECT_TRUE(handler
->NeedsIdentity());
381 EXPECT_FALSE(handler
->AllowsDefaultCredentials());
385 TEST(HttpAuthHandlerDigestTest
, AssembleCredentials
) {
386 static const struct {
387 const char* req_method
;
388 const char* req_path
;
389 const char* challenge
;
390 const char* username
;
391 const char* password
;
394 const char* expected_creds
;
396 { // MD5 with username/password
401 "Digest realm=\"DRealm1\", "
402 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
403 "algorithm=MD5, qop=\"auth\"",
405 "foo", "bar", // username/password
406 "082c875dcb2ca740", // cnonce
410 "Digest username=\"foo\", realm=\"DRealm1\", "
411 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
412 "uri=\"/test/drealm1\", algorithm=MD5, "
413 "response=\"bcfaa62f1186a31ff1b474a19a17cf57\", "
414 "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
417 { // MD5 with username but empty password. username has space in it.
422 "Digest realm=\"DRealm1\", "
423 "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
424 "algorithm=MD5, qop=\"auth\"",
426 "foo bar", "", // Username/password
427 "082c875dcb2ca740", // cnonce
431 "Digest username=\"foo bar\", realm=\"DRealm1\", "
432 "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
433 "uri=\"/test/drealm1/\", algorithm=MD5, "
434 "response=\"93c9c6d5930af3b0eb26c745e02b04a0\", "
435 "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
438 { // MD5 with no username.
443 "Digest realm=\"DRealm1\", "
444 "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
445 "algorithm=MD5, qop=\"auth\"",
447 "", "pass", // Username/password
448 "6509bc74daed8263", // cnonce
452 "Digest username=\"\", realm=\"DRealm1\", "
453 "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
454 "uri=\"/test/drealm1/\", algorithm=MD5, "
455 "response=\"bc597110f41a62d07f8b70b6977fcb61\", "
456 "qop=auth, nc=00000001, cnonce=\"6509bc74daed8263\""
459 { // MD5 with no username and no password.
464 "Digest realm=\"DRealm1\", "
465 "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
466 "algorithm=MD5, qop=\"auth\"",
468 "", "", // Username/password
469 "1522e61005789929", // cnonce
473 "Digest username=\"\", realm=\"DRealm1\", "
474 "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
475 "uri=\"/test/drealm1/\", algorithm=MD5, "
476 "response=\"22cfa2b30cb500a9591c6d55ec5590a8\", "
477 "qop=auth, nc=00000001, cnonce=\"1522e61005789929\""
480 { // No algorithm, and no qop.
485 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"",
487 "FooBar", "pass", // Username/password
492 "Digest username=\"FooBar\", realm=\"Oblivion\", "
493 "nonce=\"nonce-value\", uri=\"/\", "
494 "response=\"f72ff54ebde2f928860f806ec04acd1b\""
502 "Digest realm=\"Baztastic\", nonce=\"AAAAAAAA\", "
503 "algorithm=\"md5-sess\", qop=auth",
505 "USER", "123", // Username/password
506 "15c07961ed8575c4", // cnonce
510 "Digest username=\"USER\", realm=\"Baztastic\", "
511 "nonce=\"AAAAAAAA\", uri=\"/\", algorithm=MD5-sess, "
512 "response=\"cbc1139821ee7192069580570c541a03\", "
513 "qop=auth, nc=00000001, cnonce=\"15c07961ed8575c4\""
516 GURL
origin("http://www.example.com");
517 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
518 new HttpAuthHandlerDigest::Factory());
519 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(tests
); ++i
) {
520 scoped_ptr
<HttpAuthHandler
> handler
;
521 int rv
= factory
->CreateAuthHandlerFromString(tests
[i
].challenge
,
522 HttpAuth::AUTH_SERVER
,
527 ASSERT_TRUE(handler
!= NULL
);
529 HttpAuthHandlerDigest
* digest
=
530 static_cast<HttpAuthHandlerDigest
*>(handler
.get());
532 digest
->AssembleCredentials(tests
[i
].req_method
,
535 base::ASCIIToUTF16(tests
[i
].username
),
536 base::ASCIIToUTF16(tests
[i
].password
)),
538 tests
[i
].nonce_count
);
540 EXPECT_STREQ(tests
[i
].expected_creds
, creds
.c_str());
544 TEST(HttpAuthHandlerDigest
, HandleAnotherChallenge
) {
545 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
546 new HttpAuthHandlerDigest::Factory());
547 scoped_ptr
<HttpAuthHandler
> handler
;
548 std::string default_challenge
=
549 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
550 GURL
origin("intranet.google.com");
551 int rv
= factory
->CreateAuthHandlerFromString(
552 default_challenge
, HttpAuth::AUTH_SERVER
, origin
, BoundNetLog(),
555 ASSERT_TRUE(handler
.get() != NULL
);
556 HttpAuthChallengeTokenizer
tok_default(default_challenge
.begin(),
557 default_challenge
.end());
558 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT
,
559 handler
->HandleAnotherChallenge(&tok_default
));
561 std::string stale_challenge
= default_challenge
+ ", stale=true";
562 HttpAuthChallengeTokenizer
tok_stale(stale_challenge
.begin(),
563 stale_challenge
.end());
564 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_STALE
,
565 handler
->HandleAnotherChallenge(&tok_stale
));
567 std::string stale_false_challenge
= default_challenge
+ ", stale=false";
568 HttpAuthChallengeTokenizer
tok_stale_false(stale_false_challenge
.begin(),
569 stale_false_challenge
.end());
570 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT
,
571 handler
->HandleAnotherChallenge(&tok_stale_false
));
573 std::string realm_change_challenge
=
574 "Digest realm=\"SomethingElse\", nonce=\"nonce-value2\"";
575 HttpAuthChallengeTokenizer
tok_realm_change(realm_change_challenge
.begin(),
576 realm_change_challenge
.end());
577 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM
,
578 handler
->HandleAnotherChallenge(&tok_realm_change
));
581 TEST(HttpAuthHandlerDigest
, RespondToServerChallenge
) {
582 std::string auth_token
;
583 EXPECT_TRUE(RespondToChallenge(
584 HttpAuth::AUTH_SERVER
,
586 "http://www.example.com/path/to/resource",
589 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
590 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
591 "response=\"6779f90bd0d658f937c1af967614fe84\"",
595 TEST(HttpAuthHandlerDigest
, RespondToHttpsServerChallenge
) {
596 std::string auth_token
;
597 EXPECT_TRUE(RespondToChallenge(
598 HttpAuth::AUTH_SERVER
,
600 "https://www.example.com/path/to/resource",
603 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
604 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
605 "response=\"6779f90bd0d658f937c1af967614fe84\"",
609 TEST(HttpAuthHandlerDigest
, RespondToProxyChallenge
) {
610 std::string auth_token
;
611 EXPECT_TRUE(RespondToChallenge(
612 HttpAuth::AUTH_PROXY
,
613 "http://proxy.intranet.corp.com:3128",
614 "http://www.example.com/path/to/resource",
617 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
618 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
619 "response=\"6779f90bd0d658f937c1af967614fe84\"",
623 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeHttps
) {
624 std::string auth_token
;
625 EXPECT_TRUE(RespondToChallenge(
626 HttpAuth::AUTH_PROXY
,
627 "http://proxy.intranet.corp.com:3128",
628 "https://www.example.com/path/to/resource",
631 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
632 "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
633 "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
637 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeWs
) {
638 std::string auth_token
;
639 EXPECT_TRUE(RespondToChallenge(
640 HttpAuth::AUTH_PROXY
,
641 "http://proxy.intranet.corp.com:3128",
642 "ws://www.example.com/echo",
645 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
646 "nonce=\"nonce-value\", uri=\"www.example.com:80\", "
647 "response=\"aa1df184f68d5b6ab9d9aa4f88e41b4c\"",
651 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeWss
) {
652 std::string auth_token
;
653 EXPECT_TRUE(RespondToChallenge(
654 HttpAuth::AUTH_PROXY
,
655 "http://proxy.intranet.corp.com:3128",
656 "wss://www.example.com/echo",
659 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
660 "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
661 "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
665 TEST(HttpAuthHandlerDigest
, RespondToChallengeAuthQop
) {
666 std::string auth_token
;
667 EXPECT_TRUE(RespondToChallenge(
668 HttpAuth::AUTH_SERVER
,
670 "http://www.example.com/path/to/resource",
671 "Digest realm=\"Oblivion\", nonce=\"nonce-value\", qop=\"auth\"",
673 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
674 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
675 "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
676 "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
680 TEST(HttpAuthHandlerDigest
, RespondToChallengeOpaque
) {
681 std::string auth_token
;
682 EXPECT_TRUE(RespondToChallenge(
683 HttpAuth::AUTH_SERVER
,
685 "http://www.example.com/path/to/resource",
686 "Digest realm=\"Oblivion\", nonce=\"nonce-value\", "
687 "qop=\"auth\", opaque=\"opaque text\"",
689 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
690 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
691 "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
692 "opaque=\"opaque text\", "
693 "qop=auth, nc=00000001, cnonce=\"client_nonce\"",