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_handler_digest.h"
13 #include "net/http/http_request_info.h"
14 #include "testing/gtest/include/gtest/gtest.h"
20 const char* const kSimpleChallenge
=
21 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
23 // RespondToChallenge creates an HttpAuthHandlerDigest for the specified
24 // |challenge|, and generates a response to the challenge which is returned in
27 // The return value indicates whether the |token| was successfully created.
29 // If |target| is HttpAuth::AUTH_PROXY, then |proxy_name| specifies the source
30 // of the |challenge|. Otherwise, the scheme and host and port of |request_url|
31 // indicates the origin of the challenge.
32 bool RespondToChallenge(HttpAuth::Target target
,
33 const std::string
& proxy_name
,
34 const std::string
& request_url
,
35 const std::string
& challenge
,
39 ADD_FAILURE() << "|token| must be non-NULL";
42 EXPECT_TRUE(target
!= HttpAuth::AUTH_PROXY
|| !proxy_name
.empty());
43 EXPECT_FALSE(request_url
.empty());
44 EXPECT_FALSE(challenge
.empty());
47 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
48 new HttpAuthHandlerDigest::Factory());
49 HttpAuthHandlerDigest::NonceGenerator
* nonce_generator
=
50 new HttpAuthHandlerDigest::FixedNonceGenerator("client_nonce");
51 factory
->set_nonce_generator(nonce_generator
);
52 scoped_ptr
<HttpAuthHandler
> handler
;
54 // Create a handler for a particular challenge.
55 GURL
url_origin(target
== HttpAuth::AUTH_SERVER
? request_url
: proxy_name
);
56 int rv_create
= factory
->CreateAuthHandlerFromString(
57 challenge
, target
, url_origin
.GetOrigin(), BoundNetLog(), &handler
);
58 if (rv_create
!= OK
|| handler
.get() == NULL
) {
59 ADD_FAILURE() << "Unable to create auth handler.";
63 // Create a token in response to the challenge.
64 // NOTE: HttpAuthHandlerDigest's implementation of GenerateAuthToken always
65 // completes synchronously. That's why this test can get away with a
66 // TestCompletionCallback without an IO thread.
67 TestCompletionCallback callback
;
68 scoped_ptr
<HttpRequestInfo
> request(new HttpRequestInfo());
69 request
->url
= GURL(request_url
);
70 AuthCredentials
credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar"));
71 int rv_generate
= handler
->GenerateAuthToken(
72 &credentials
, request
.get(), callback
.callback(), token
);
73 if (rv_generate
!= OK
) {
74 ADD_FAILURE() << "Problems generating auth token";
84 TEST(HttpAuthHandlerDigestTest
, ParseChallenge
) {
86 // The challenge string.
87 const char* challenge
;
88 // Expected return value of ParseChallenge.
90 // The expected values that were parsed.
91 const char* parsed_realm
;
92 const char* parsed_nonce
;
93 const char* parsed_domain
;
94 const char* parsed_opaque
;
99 { // Check that a minimal challenge works correctly.
100 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\"",
107 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
108 HttpAuthHandlerDigest::QOP_UNSPECIFIED
111 { // Realm does not need to be quoted, even though RFC2617 requires it.
112 "Digest nonce=\"xyz\", realm=ThunderBluff",
119 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
120 HttpAuthHandlerDigest::QOP_UNSPECIFIED
123 { // We allow the realm to be omitted, and will default it to empty string.
124 // See http://crbug.com/20984.
125 "Digest nonce=\"xyz\"",
132 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
133 HttpAuthHandlerDigest::QOP_UNSPECIFIED
136 { // Try with realm set to empty string.
137 "Digest realm=\"\", nonce=\"xyz\"",
144 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
145 HttpAuthHandlerDigest::QOP_UNSPECIFIED
148 // Handle ISO-8859-1 character as part of the realm. The realm is converted
149 // to UTF-8. However, the credentials will still use the original encoding.
151 "Digest nonce=\"xyz\", realm=\"foo-\xE5\"",
158 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
159 HttpAuthHandlerDigest::QOP_UNSPECIFIED
,
162 { // At a minimum, a nonce must be provided.
163 "Digest realm=\"Thunder Bluff\"",
170 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
171 HttpAuthHandlerDigest::QOP_UNSPECIFIED
174 { // The nonce does not need to be quoted, even though RFC2617
176 "Digest nonce=xyz, realm=\"Thunder Bluff\"",
183 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
184 HttpAuthHandlerDigest::QOP_UNSPECIFIED
187 { // Unknown authentication parameters are ignored.
188 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", foo=\"bar\"",
195 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
196 HttpAuthHandlerDigest::QOP_UNSPECIFIED
199 { // Check that when algorithm has an unsupported value, parsing fails.
200 "Digest nonce=\"xyz\", algorithm=\"awezum\", realm=\"Thunder\"",
202 // The remaining values don't matter (but some have been set already).
208 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
209 HttpAuthHandlerDigest::QOP_UNSPECIFIED
212 { // Check that algorithm's value is case insensitive, and that MD5 is
213 // a supported algorithm.
214 "Digest nonce=\"xyz\", algorithm=\"mD5\", realm=\"Oblivion\"",
221 HttpAuthHandlerDigest::ALGORITHM_MD5
,
222 HttpAuthHandlerDigest::QOP_UNSPECIFIED
225 { // Check that md5-sess is a supported algorithm.
226 "Digest nonce=\"xyz\", algorithm=\"md5-sess\", realm=\"Oblivion\"",
233 HttpAuthHandlerDigest::ALGORITHM_MD5_SESS
,
234 HttpAuthHandlerDigest::QOP_UNSPECIFIED
,
237 { // Check that qop's value is case insensitive, and that auth is known.
238 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"aUth\"",
245 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
246 HttpAuthHandlerDigest::QOP_AUTH
249 { // auth-int is not handled, but will fall back to default qop.
250 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth-int\"",
257 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
258 HttpAuthHandlerDigest::QOP_UNSPECIFIED
261 { // Unknown qop values are ignored.
262 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,foo\"",
269 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
270 HttpAuthHandlerDigest::QOP_AUTH
273 { // If auth-int is included with auth, then use auth.
274 "Digest nonce=\"xyz\", realm=\"Oblivion\", qop=\"auth,auth-int\"",
281 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
282 HttpAuthHandlerDigest::QOP_AUTH
285 { // Opaque parameter parsing should work correctly.
286 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=\"foobar\"",
293 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
294 HttpAuthHandlerDigest::QOP_UNSPECIFIED
297 { // Opaque parameters do not need to be quoted, even though RFC2617
298 // seems to require it.
299 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", opaque=foobar",
306 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
307 HttpAuthHandlerDigest::QOP_UNSPECIFIED
310 { // Domain can be parsed.
311 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
312 "domain=\"http://intranet.example.com/protection\"",
316 "http://intranet.example.com/protection",
319 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
320 HttpAuthHandlerDigest::QOP_UNSPECIFIED
323 { // Multiple domains can be parsed.
324 "Digest nonce=\"xyz\", realm=\"Thunder Bluff\", "
325 "domain=\"http://intranet.example.com/protection http://www.google.com\"",
329 "http://intranet.example.com/protection http://www.google.com",
332 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
333 HttpAuthHandlerDigest::QOP_UNSPECIFIED
336 { // If a non-Digest scheme is somehow passed in, it should be rejected.
337 "Basic realm=\"foo\"",
344 HttpAuthHandlerDigest::ALGORITHM_UNSPECIFIED
,
345 HttpAuthHandlerDigest::QOP_UNSPECIFIED
349 GURL
origin("http://www.example.com");
350 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
351 new HttpAuthHandlerDigest::Factory());
352 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(tests
); ++i
) {
353 scoped_ptr
<HttpAuthHandler
> handler
;
354 int rv
= factory
->CreateAuthHandlerFromString(tests
[i
].challenge
,
355 HttpAuth::AUTH_SERVER
,
359 if (tests
[i
].parsed_success
) {
363 EXPECT_TRUE(handler
.get() == NULL
);
366 ASSERT_TRUE(handler
.get() != NULL
);
367 HttpAuthHandlerDigest
* digest
=
368 static_cast<HttpAuthHandlerDigest
*>(handler
.get());
369 EXPECT_STREQ(tests
[i
].parsed_realm
, digest
->realm_
.c_str());
370 EXPECT_STREQ(tests
[i
].parsed_nonce
, digest
->nonce_
.c_str());
371 EXPECT_STREQ(tests
[i
].parsed_domain
, digest
->domain_
.c_str());
372 EXPECT_STREQ(tests
[i
].parsed_opaque
, digest
->opaque_
.c_str());
373 EXPECT_EQ(tests
[i
].parsed_stale
, digest
->stale_
);
374 EXPECT_EQ(tests
[i
].parsed_algorithm
, digest
->algorithm_
);
375 EXPECT_EQ(tests
[i
].parsed_qop
, digest
->qop_
);
376 EXPECT_TRUE(handler
->encrypts_identity());
377 EXPECT_FALSE(handler
->is_connection_based());
378 EXPECT_TRUE(handler
->NeedsIdentity());
379 EXPECT_FALSE(handler
->AllowsDefaultCredentials());
383 TEST(HttpAuthHandlerDigestTest
, AssembleCredentials
) {
384 static const struct {
385 const char* req_method
;
386 const char* req_path
;
387 const char* challenge
;
388 const char* username
;
389 const char* password
;
392 const char* expected_creds
;
394 { // MD5 with username/password
399 "Digest realm=\"DRealm1\", "
400 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
401 "algorithm=MD5, qop=\"auth\"",
403 "foo", "bar", // username/password
404 "082c875dcb2ca740", // cnonce
408 "Digest username=\"foo\", realm=\"DRealm1\", "
409 "nonce=\"claGgoRXBAA=7583377687842fdb7b56ba0555d175baa0b800e3\", "
410 "uri=\"/test/drealm1\", algorithm=MD5, "
411 "response=\"bcfaa62f1186a31ff1b474a19a17cf57\", "
412 "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
415 { // MD5 with username but empty password. username has space in it.
420 "Digest realm=\"DRealm1\", "
421 "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
422 "algorithm=MD5, qop=\"auth\"",
424 "foo bar", "", // Username/password
425 "082c875dcb2ca740", // cnonce
429 "Digest username=\"foo bar\", realm=\"DRealm1\", "
430 "nonce=\"Ure30oRXBAA=7eca98bbf521ac6642820b11b86bd2d9ed7edc70\", "
431 "uri=\"/test/drealm1/\", algorithm=MD5, "
432 "response=\"93c9c6d5930af3b0eb26c745e02b04a0\", "
433 "qop=auth, nc=00000001, cnonce=\"082c875dcb2ca740\""
436 { // MD5 with no username.
441 "Digest realm=\"DRealm1\", "
442 "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
443 "algorithm=MD5, qop=\"auth\"",
445 "", "pass", // Username/password
446 "6509bc74daed8263", // cnonce
450 "Digest username=\"\", realm=\"DRealm1\", "
451 "nonce=\"7thGplhaBAA=41fb92453c49799cf353c8cd0aabee02d61a98a8\", "
452 "uri=\"/test/drealm1/\", algorithm=MD5, "
453 "response=\"bc597110f41a62d07f8b70b6977fcb61\", "
454 "qop=auth, nc=00000001, cnonce=\"6509bc74daed8263\""
457 { // MD5 with no username and no password.
462 "Digest realm=\"DRealm1\", "
463 "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
464 "algorithm=MD5, qop=\"auth\"",
466 "", "", // Username/password
467 "1522e61005789929", // cnonce
471 "Digest username=\"\", realm=\"DRealm1\", "
472 "nonce=\"s3MzvFhaBAA=4c520af5acd9d8d7ae26947529d18c8eae1e98f4\", "
473 "uri=\"/test/drealm1/\", algorithm=MD5, "
474 "response=\"22cfa2b30cb500a9591c6d55ec5590a8\", "
475 "qop=auth, nc=00000001, cnonce=\"1522e61005789929\""
478 { // No algorithm, and no qop.
483 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"",
485 "FooBar", "pass", // Username/password
490 "Digest username=\"FooBar\", realm=\"Oblivion\", "
491 "nonce=\"nonce-value\", uri=\"/\", "
492 "response=\"f72ff54ebde2f928860f806ec04acd1b\""
500 "Digest realm=\"Baztastic\", nonce=\"AAAAAAAA\", "
501 "algorithm=\"md5-sess\", qop=auth",
503 "USER", "123", // Username/password
504 "15c07961ed8575c4", // cnonce
508 "Digest username=\"USER\", realm=\"Baztastic\", "
509 "nonce=\"AAAAAAAA\", uri=\"/\", algorithm=MD5-sess, "
510 "response=\"cbc1139821ee7192069580570c541a03\", "
511 "qop=auth, nc=00000001, cnonce=\"15c07961ed8575c4\""
514 GURL
origin("http://www.example.com");
515 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
516 new HttpAuthHandlerDigest::Factory());
517 for (size_t i
= 0; i
< ARRAYSIZE_UNSAFE(tests
); ++i
) {
518 scoped_ptr
<HttpAuthHandler
> handler
;
519 int rv
= factory
->CreateAuthHandlerFromString(tests
[i
].challenge
,
520 HttpAuth::AUTH_SERVER
,
525 ASSERT_TRUE(handler
!= NULL
);
527 HttpAuthHandlerDigest
* digest
=
528 static_cast<HttpAuthHandlerDigest
*>(handler
.get());
530 digest
->AssembleCredentials(tests
[i
].req_method
,
533 ASCIIToUTF16(tests
[i
].username
),
534 ASCIIToUTF16(tests
[i
].password
)),
536 tests
[i
].nonce_count
);
538 EXPECT_STREQ(tests
[i
].expected_creds
, creds
.c_str());
542 TEST(HttpAuthHandlerDigest
, HandleAnotherChallenge
) {
543 scoped_ptr
<HttpAuthHandlerDigest::Factory
> factory(
544 new HttpAuthHandlerDigest::Factory());
545 scoped_ptr
<HttpAuthHandler
> handler
;
546 std::string default_challenge
=
547 "Digest realm=\"Oblivion\", nonce=\"nonce-value\"";
548 GURL
origin("intranet.google.com");
549 int rv
= factory
->CreateAuthHandlerFromString(
550 default_challenge
, HttpAuth::AUTH_SERVER
, origin
, BoundNetLog(),
553 ASSERT_TRUE(handler
.get() != NULL
);
554 HttpAuth::ChallengeTokenizer
tok_default(default_challenge
.begin(),
555 default_challenge
.end());
556 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT
,
557 handler
->HandleAnotherChallenge(&tok_default
));
559 std::string stale_challenge
= default_challenge
+ ", stale=true";
560 HttpAuth::ChallengeTokenizer
tok_stale(stale_challenge
.begin(),
561 stale_challenge
.end());
562 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_STALE
,
563 handler
->HandleAnotherChallenge(&tok_stale
));
565 std::string stale_false_challenge
= default_challenge
+ ", stale=false";
566 HttpAuth::ChallengeTokenizer
tok_stale_false(stale_false_challenge
.begin(),
567 stale_false_challenge
.end());
568 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT
,
569 handler
->HandleAnotherChallenge(&tok_stale_false
));
571 std::string realm_change_challenge
=
572 "Digest realm=\"SomethingElse\", nonce=\"nonce-value2\"";
573 HttpAuth::ChallengeTokenizer
tok_realm_change(realm_change_challenge
.begin(),
574 realm_change_challenge
.end());
575 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_DIFFERENT_REALM
,
576 handler
->HandleAnotherChallenge(&tok_realm_change
));
579 TEST(HttpAuthHandlerDigest
, RespondToServerChallenge
) {
580 std::string auth_token
;
581 EXPECT_TRUE(RespondToChallenge(
582 HttpAuth::AUTH_SERVER
,
584 "http://www.example.com/path/to/resource",
587 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
588 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
589 "response=\"6779f90bd0d658f937c1af967614fe84\"",
593 TEST(HttpAuthHandlerDigest
, RespondToHttpsServerChallenge
) {
594 std::string auth_token
;
595 EXPECT_TRUE(RespondToChallenge(
596 HttpAuth::AUTH_SERVER
,
598 "https://www.example.com/path/to/resource",
601 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
602 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
603 "response=\"6779f90bd0d658f937c1af967614fe84\"",
607 TEST(HttpAuthHandlerDigest
, RespondToProxyChallenge
) {
608 std::string auth_token
;
609 EXPECT_TRUE(RespondToChallenge(
610 HttpAuth::AUTH_PROXY
,
611 "http://proxy.intranet.corp.com:3128",
612 "http://www.example.com/path/to/resource",
615 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
616 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
617 "response=\"6779f90bd0d658f937c1af967614fe84\"",
621 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeHttps
) {
622 std::string auth_token
;
623 EXPECT_TRUE(RespondToChallenge(
624 HttpAuth::AUTH_PROXY
,
625 "http://proxy.intranet.corp.com:3128",
626 "https://www.example.com/path/to/resource",
629 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
630 "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
631 "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
635 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeWs
) {
636 std::string auth_token
;
637 EXPECT_TRUE(RespondToChallenge(
638 HttpAuth::AUTH_PROXY
,
639 "http://proxy.intranet.corp.com:3128",
640 "ws://www.example.com/echo",
643 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
644 "nonce=\"nonce-value\", uri=\"www.example.com:80\", "
645 "response=\"aa1df184f68d5b6ab9d9aa4f88e41b4c\"",
649 TEST(HttpAuthHandlerDigest
, RespondToProxyChallengeWss
) {
650 std::string auth_token
;
651 EXPECT_TRUE(RespondToChallenge(
652 HttpAuth::AUTH_PROXY
,
653 "http://proxy.intranet.corp.com:3128",
654 "wss://www.example.com/echo",
657 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
658 "nonce=\"nonce-value\", uri=\"www.example.com:443\", "
659 "response=\"3270da8467afbe9ddf2334a48d46e9b9\"",
663 TEST(HttpAuthHandlerDigest
, RespondToChallengeAuthQop
) {
664 std::string auth_token
;
665 EXPECT_TRUE(RespondToChallenge(
666 HttpAuth::AUTH_SERVER
,
668 "http://www.example.com/path/to/resource",
669 "Digest realm=\"Oblivion\", nonce=\"nonce-value\", qop=\"auth\"",
671 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
672 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
673 "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
674 "qop=auth, nc=00000001, cnonce=\"client_nonce\"",
678 TEST(HttpAuthHandlerDigest
, RespondToChallengeOpaque
) {
679 std::string auth_token
;
680 EXPECT_TRUE(RespondToChallenge(
681 HttpAuth::AUTH_SERVER
,
683 "http://www.example.com/path/to/resource",
684 "Digest realm=\"Oblivion\", nonce=\"nonce-value\", "
685 "qop=\"auth\", opaque=\"opaque text\"",
687 EXPECT_EQ("Digest username=\"foo\", realm=\"Oblivion\", "
688 "nonce=\"nonce-value\", uri=\"/path/to/resource\", "
689 "response=\"5b1459beda5cee30d6ff9e970a69c0ea\", "
690 "opaque=\"opaque text\", "
691 "qop=auth, nc=00000001, cnonce=\"client_nonce\"",