1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
24 * Justin Bradford <jab@atdot.org> (original author of nsDigestAuth.cpp)
25 * An-Cheng Huang <pach@cs.cmu.edu>
26 * Darin Fisher <darin@netscape.com>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
44 #include "nsHttpDigestAuth.h"
45 #include "nsIHttpChannel.h"
46 #include "nsIServiceManager.h"
48 #include "nsISupportsPrimitives.h"
51 #include "nsReadableUtils.h"
60 //-----------------------------------------------------------------------------
61 // nsHttpDigestAuth <public>
62 //-----------------------------------------------------------------------------
64 nsHttpDigestAuth::nsHttpDigestAuth()
67 nsHttpDigestAuth::~nsHttpDigestAuth()
70 //-----------------------------------------------------------------------------
71 // nsHttpDigestAuth::nsISupports
72 //-----------------------------------------------------------------------------
74 NS_IMPL_ISUPPORTS1(nsHttpDigestAuth
, nsIHttpAuthenticator
)
76 //-----------------------------------------------------------------------------
77 // nsHttpDigestAuth <protected>
78 //-----------------------------------------------------------------------------
81 nsHttpDigestAuth::MD5Hash(const char *buf
, PRUint32 len
)
85 // Cache a reference to the nsICryptoHash instance since we'll be calling
86 // this function frequently.
88 mVerifier
= do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID
, &rv
);
90 LOG(("nsHttpDigestAuth: no crypto hash!\n"));
95 rv
= mVerifier
->Init(nsICryptoHash::MD5
);
96 if (NS_FAILED(rv
)) return rv
;
98 rv
= mVerifier
->Update((unsigned char*)buf
, len
);
99 if (NS_FAILED(rv
)) return rv
;
101 nsCAutoString hashString
;
102 rv
= mVerifier
->Finish(PR_FALSE
, hashString
);
103 if (NS_FAILED(rv
)) return rv
;
105 NS_ENSURE_STATE(hashString
.Length() == sizeof(mHashBuf
));
106 memcpy(mHashBuf
, hashString
.get(), hashString
.Length());
112 nsHttpDigestAuth::GetMethodAndPath(nsIHttpChannel
*httpChannel
,
114 nsCString
&httpMethod
,
118 nsCOMPtr
<nsIURI
> uri
;
119 rv
= httpChannel
->GetURI(getter_AddRefs(uri
));
120 if (NS_SUCCEEDED(rv
)) {
122 rv
= uri
->SchemeIs("https", &isSecure
);
123 if (NS_SUCCEEDED(rv
)) {
125 // if we are being called in response to a 407, and if the protocol
126 // is HTTPS, then we are really using a CONNECT method.
128 if (isSecure
&& isProxyAuth
) {
129 httpMethod
.AssignLiteral("CONNECT");
131 // generate hostname:port string. (unfortunately uri->GetHostPort
132 // leaves out the port if it matches the default value, so we can't
136 rv
= uri
->GetAsciiHost(path
);
137 rv
|= uri
->GetPort(&port
);
138 if (NS_SUCCEEDED(rv
)) {
140 path
.AppendInt(port
< 0 ? NS_HTTPS_DEFAULT_PORT
: port
);
144 rv
= httpChannel
->GetRequestMethod(httpMethod
);
145 rv
|= uri
->GetPath(path
);
146 if (NS_SUCCEEDED(rv
)) {
148 // strip any fragment identifier from the URL path.
150 PRInt32 ref
= path
.RFindChar('#');
151 if (ref
!= kNotFound
)
154 // make sure we escape any UTF-8 characters in the URI path. the
155 // digest auth uri attribute needs to match the request-URI.
157 // XXX we should really ask the HTTP channel for this string
158 // instead of regenerating it here.
161 path
= NS_EscapeURL(path
, esc_OnlyNonASCII
, buf
);
169 //-----------------------------------------------------------------------------
170 // nsHttpDigestAuth::nsIHttpAuthenticator
171 //-----------------------------------------------------------------------------
174 nsHttpDigestAuth::ChallengeReceived(nsIHttpChannel
*httpChannel
,
175 const char *challenge
,
177 nsISupports
**sessionState
,
178 nsISupports
**continuationState
,
181 nsCAutoString realm
, domain
, nonce
, opaque
;
183 PRUint16 algorithm
, qop
;
185 nsresult rv
= ParseChallenge(challenge
, realm
, domain
, nonce
, opaque
,
186 &stale
, &algorithm
, &qop
);
187 if (NS_FAILED(rv
)) return rv
;
189 // if the challenge has the "stale" flag set, then the user identity is not
190 // necessarily invalid. by returning FALSE here we can suppress username
191 // and password prompting that usually accompanies a 401/407 challenge.
194 // clear any existing nonce_count since we have a new challenge.
195 NS_IF_RELEASE(*sessionState
);
200 nsHttpDigestAuth::GenerateCredentials(nsIHttpChannel
*httpChannel
,
201 const char *challenge
,
203 const PRUnichar
*userdomain
,
204 const PRUnichar
*username
,
205 const PRUnichar
*password
,
206 nsISupports
**sessionState
,
207 nsISupports
**continuationState
,
211 LOG(("nsHttpDigestAuth::GenerateCredentials [challenge=%s]\n", challenge
));
213 NS_ENSURE_ARG_POINTER(creds
);
215 PRBool isDigestAuth
= !PL_strncasecmp(challenge
, "digest ", 7);
216 NS_ENSURE_TRUE(isDigestAuth
, NS_ERROR_UNEXPECTED
);
218 // IIS implementation requires extra quotes
219 PRBool requireExtraQuotes
= PR_FALSE
;
221 nsCAutoString serverVal
;
222 httpChannel
->GetResponseHeader(NS_LITERAL_CSTRING("Server"), serverVal
);
223 if (!serverVal
.IsEmpty()) {
224 requireExtraQuotes
= !PL_strncasecmp(serverVal
.get(), "Microsoft-IIS", 13);
229 nsCAutoString httpMethod
;
231 rv
= GetMethodAndPath(httpChannel
, isProxyAuth
, httpMethod
, path
);
232 if (NS_FAILED(rv
)) return rv
;
234 nsCAutoString realm
, domain
, nonce
, opaque
;
236 PRUint16 algorithm
, qop
;
238 rv
= ParseChallenge(challenge
, realm
, domain
, nonce
, opaque
,
239 &stale
, &algorithm
, &qop
);
241 LOG(("nsHttpDigestAuth::GenerateCredentials [ParseChallenge failed rv=%x]\n", rv
));
245 char ha1_digest
[EXPANDED_DIGEST_LENGTH
+1];
246 char ha2_digest
[EXPANDED_DIGEST_LENGTH
+1];
247 char response_digest
[EXPANDED_DIGEST_LENGTH
+1];
248 char upload_data_digest
[EXPANDED_DIGEST_LENGTH
+1];
250 if (qop
& QOP_AUTH_INT
) {
251 // we do not support auth-int "quality of protection" currently
252 qop
&= ~QOP_AUTH_INT
;
254 NS_WARNING("no support for Digest authentication with data integrity quality of protection");
256 /* TODO: to support auth-int, we need to get an MD5 digest of
257 * TODO: the data uploaded with this request.
258 * TODO: however, i am not sure how to read in the file in without
259 * TODO: disturbing the channel''s use of it. do i need to copy it
263 if (http_channel
!= nsnull
)
265 nsIInputStream
* upload
;
266 nsCOMPtr
<nsIUploadChannel
> uc
= do_QueryInterface(http_channel
);
267 NS_ENSURE_TRUE(uc
, NS_ERROR_UNEXPECTED
);
268 uc
->GetUploadStream(&upload
);
270 char * upload_buffer
;
271 int upload_buffer_length
= 0;
272 //TODO: read input stream into buffer
273 const char * digest
= (const char*)
274 nsNetwerkMD5Digest(upload_buffer
, upload_buffer_length
);
275 ExpandToHex(digest
, upload_data_digest
);
282 if (!(algorithm
& ALGO_MD5
|| algorithm
& ALGO_MD5_SESS
)) {
283 // they asked only for algorithms that we do not support
284 NS_WARNING("unsupported algorithm requested by Digest authentication");
285 return NS_ERROR_NOT_IMPLEMENTED
;
289 // the following are for increasing security. see RFC 2617 for more
292 // nonce_count allows the server to keep track of auth challenges (to help
293 // prevent spoofing). we increase this count every time.
295 char nonce_count
[NONCE_COUNT_LENGTH
+1] = "00000001"; // in hex
297 nsCOMPtr
<nsISupportsPRUint32
> v(do_QueryInterface(*sessionState
));
301 PR_snprintf(nonce_count
, sizeof(nonce_count
), "%08x", ++nc
);
306 nsCOMPtr
<nsISupportsPRUint32
> v(
307 do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID
));
310 NS_ADDREF(*sessionState
= v
);
313 LOG((" nonce_count=%s\n", nonce_count
));
316 // this lets the client verify the server response (via a server
317 // returned Authentication-Info header). also used for session info.
319 nsCAutoString cnonce
;
320 static const char hexChar
[] = "0123456789abcdef";
321 for (int i
=0; i
<16; ++i
) {
322 cnonce
.Append(hexChar
[(int)(15.0 * rand()/(RAND_MAX
+ 1.0))]);
324 LOG((" cnonce=%s\n", cnonce
.get()));
327 // calculate credentials
330 NS_ConvertUTF16toUTF8
cUser(username
), cPass(password
);
331 rv
= CalculateHA1(cUser
, cPass
, realm
, algorithm
, nonce
, cnonce
, ha1_digest
);
332 if (NS_FAILED(rv
)) return rv
;
334 rv
= CalculateHA2(httpMethod
, path
, qop
, upload_data_digest
, ha2_digest
);
335 if (NS_FAILED(rv
)) return rv
;
337 rv
= CalculateResponse(ha1_digest
, ha2_digest
, nonce
, qop
, nonce_count
,
338 cnonce
, response_digest
);
339 if (NS_FAILED(rv
)) return rv
;
342 // Values that need to match the quoted-string production from RFC 2616:
351 nsCAutoString authString
;
353 authString
.AssignLiteral("Digest username=");
354 rv
= AppendQuotedString(cUser
, authString
);
355 NS_ENSURE_SUCCESS(rv
, rv
);
357 authString
.AppendLiteral(", realm=");
358 rv
= AppendQuotedString(realm
, authString
);
359 NS_ENSURE_SUCCESS(rv
, rv
);
361 authString
.AppendLiteral(", nonce=");
362 rv
= AppendQuotedString(nonce
, authString
);
363 NS_ENSURE_SUCCESS(rv
, rv
);
365 authString
.AppendLiteral(", uri=\"");
367 if (algorithm
& ALGO_SPECIFIED
) {
368 authString
.AppendLiteral("\", algorithm=");
369 if (algorithm
& ALGO_MD5_SESS
)
370 authString
.AppendLiteral("MD5-sess");
372 authString
.AppendLiteral("MD5");
376 authString
.AppendLiteral(", response=\"");
377 authString
+= response_digest
;
380 if (!opaque
.IsEmpty()) {
381 authString
.AppendLiteral(", opaque=");
382 rv
= AppendQuotedString(opaque
, authString
);
383 NS_ENSURE_SUCCESS(rv
, rv
);
387 authString
.AppendLiteral(", qop=");
388 if (requireExtraQuotes
)
390 authString
.AppendLiteral("auth");
391 if (qop
& QOP_AUTH_INT
)
392 authString
.AppendLiteral("-int");
393 if (requireExtraQuotes
)
395 authString
.AppendLiteral(", nc=");
396 authString
+= nonce_count
;
398 authString
.AppendLiteral(", cnonce=");
399 rv
= AppendQuotedString(cnonce
, authString
);
400 NS_ENSURE_SUCCESS(rv
, rv
);
404 *creds
= ToNewCString(authString
);
409 nsHttpDigestAuth::GetAuthFlags(PRUint32
*flags
)
411 *flags
= REQUEST_BASED
| REUSABLE_CHALLENGE
| IDENTITY_ENCRYPTED
;
413 // NOTE: digest auth credentials must be uniquely computed for each request,
414 // so we do not set the REUSABLE_CREDENTIALS flag.
420 nsHttpDigestAuth::CalculateResponse(const char * ha1_digest
,
421 const char * ha2_digest
,
422 const nsAFlatCString
& nonce
,
424 const char * nonce_count
,
425 const nsAFlatCString
& cnonce
,
428 PRUint32 len
= 2*EXPANDED_DIGEST_LENGTH
+ nonce
.Length() + 2;
430 if (qop
& QOP_AUTH
|| qop
& QOP_AUTH_INT
) {
431 len
+= cnonce
.Length() + NONCE_COUNT_LENGTH
+ 3;
432 if (qop
& QOP_AUTH_INT
)
433 len
+= 8; // length of "auth-int"
435 len
+= 4; // length of "auth"
438 nsCAutoString contents
;
439 contents
.SetCapacity(len
);
441 contents
.Assign(ha1_digest
, EXPANDED_DIGEST_LENGTH
);
442 contents
.Append(':');
443 contents
.Append(nonce
);
444 contents
.Append(':');
446 if (qop
& QOP_AUTH
|| qop
& QOP_AUTH_INT
) {
447 contents
.Append(nonce_count
, NONCE_COUNT_LENGTH
);
448 contents
.Append(':');
449 contents
.Append(cnonce
);
450 contents
.Append(':');
451 if (qop
& QOP_AUTH_INT
)
452 contents
.AppendLiteral("auth-int:");
454 contents
.AppendLiteral("auth:");
457 contents
.Append(ha2_digest
, EXPANDED_DIGEST_LENGTH
);
459 nsresult rv
= MD5Hash(contents
.get(), contents
.Length());
460 if (NS_SUCCEEDED(rv
))
461 rv
= ExpandToHex(mHashBuf
, result
);
466 nsHttpDigestAuth::ExpandToHex(const char * digest
, char * result
)
468 PRInt16 index
, value
;
470 for (index
= 0; index
< DIGEST_LENGTH
; index
++) {
471 value
= (digest
[index
] >> 4) & 0xf;
473 result
[index
*2] = value
+ '0';
475 result
[index
*2] = value
- 10 + 'a';
477 value
= digest
[index
] & 0xf;
479 result
[(index
*2)+1] = value
+ '0';
481 result
[(index
*2)+1] = value
- 10 + 'a';
484 result
[EXPANDED_DIGEST_LENGTH
] = 0;
489 nsHttpDigestAuth::CalculateHA1(const nsAFlatCString
& username
,
490 const nsAFlatCString
& password
,
491 const nsAFlatCString
& realm
,
493 const nsAFlatCString
& nonce
,
494 const nsAFlatCString
& cnonce
,
497 PRInt16 len
= username
.Length() + password
.Length() + realm
.Length() + 2;
498 if (algorithm
& ALGO_MD5_SESS
) {
499 PRInt16 exlen
= EXPANDED_DIGEST_LENGTH
+ nonce
.Length() + cnonce
.Length() + 2;
504 nsCAutoString contents
;
505 contents
.SetCapacity(len
+ 1);
507 contents
.Assign(username
);
508 contents
.Append(':');
509 contents
.Append(realm
);
510 contents
.Append(':');
511 contents
.Append(password
);
514 rv
= MD5Hash(contents
.get(), contents
.Length());
518 if (algorithm
& ALGO_MD5_SESS
) {
519 char part1
[EXPANDED_DIGEST_LENGTH
+1];
520 ExpandToHex(mHashBuf
, part1
);
522 contents
.Assign(part1
, EXPANDED_DIGEST_LENGTH
);
523 contents
.Append(':');
524 contents
.Append(nonce
);
525 contents
.Append(':');
526 contents
.Append(cnonce
);
528 rv
= MD5Hash(contents
.get(), contents
.Length());
533 return ExpandToHex(mHashBuf
, result
);
537 nsHttpDigestAuth::CalculateHA2(const nsAFlatCString
& method
,
538 const nsAFlatCString
& path
,
540 const char * bodyDigest
,
543 PRInt16 methodLen
= method
.Length();
544 PRInt16 pathLen
= path
.Length();
545 PRInt16 len
= methodLen
+ pathLen
+ 1;
547 if (qop
& QOP_AUTH_INT
) {
548 len
+= EXPANDED_DIGEST_LENGTH
+ 1;
551 nsCAutoString contents
;
552 contents
.SetCapacity(len
);
554 contents
.Assign(method
);
555 contents
.Append(':');
556 contents
.Append(path
);
558 if (qop
& QOP_AUTH_INT
) {
559 contents
.Append(':');
560 contents
.Append(bodyDigest
, EXPANDED_DIGEST_LENGTH
);
563 nsresult rv
= MD5Hash(contents
.get(), contents
.Length());
564 if (NS_SUCCEEDED(rv
))
565 rv
= ExpandToHex(mHashBuf
, result
);
570 nsHttpDigestAuth::ParseChallenge(const char * challenge
,
576 PRUint16
* algorithm
,
579 const char *p
= challenge
+ 7; // first 7 characters are "Digest "
582 *algorithm
= ALGO_MD5
; // default is MD5
586 while (*p
&& (*p
== ',' || nsCRT::IsAsciiSpace(*p
)))
592 PRInt16 nameStart
= (p
- challenge
);
593 while (*p
&& !nsCRT::IsAsciiSpace(*p
) && *p
!= '=')
596 return NS_ERROR_INVALID_ARG
;
597 PRInt16 nameLength
= (p
- challenge
) - nameStart
;
599 while (*p
&& (nsCRT::IsAsciiSpace(*p
) || *p
== '='))
602 return NS_ERROR_INVALID_ARG
;
604 PRBool quoted
= PR_FALSE
;
611 PRInt16 valueStart
= (p
- challenge
);
612 PRInt16 valueLength
= 0;
614 while (*p
&& *p
!= '"')
617 return NS_ERROR_INVALID_ARG
;
618 valueLength
= (p
- challenge
) - valueStart
;
621 while (*p
&& !nsCRT::IsAsciiSpace(*p
) && *p
!= ',')
623 valueLength
= (p
- challenge
) - valueStart
;
626 // extract information
627 if (nameLength
== 5 &&
628 nsCRT::strncasecmp(challenge
+nameStart
, "realm", 5) == 0)
630 realm
.Assign(challenge
+valueStart
, valueLength
);
632 else if (nameLength
== 6 &&
633 nsCRT::strncasecmp(challenge
+nameStart
, "domain", 6) == 0)
635 domain
.Assign(challenge
+valueStart
, valueLength
);
637 else if (nameLength
== 5 &&
638 nsCRT::strncasecmp(challenge
+nameStart
, "nonce", 5) == 0)
640 nonce
.Assign(challenge
+valueStart
, valueLength
);
642 else if (nameLength
== 6 &&
643 nsCRT::strncasecmp(challenge
+nameStart
, "opaque", 6) == 0)
645 opaque
.Assign(challenge
+valueStart
, valueLength
);
647 else if (nameLength
== 5 &&
648 nsCRT::strncasecmp(challenge
+nameStart
, "stale", 5) == 0)
650 if (nsCRT::strncasecmp(challenge
+valueStart
, "true", 4) == 0)
655 else if (nameLength
== 9 &&
656 nsCRT::strncasecmp(challenge
+nameStart
, "algorithm", 9) == 0)
658 // we want to clear the default, so we use = not |= here
659 *algorithm
= ALGO_SPECIFIED
;
660 if (valueLength
== 3 &&
661 nsCRT::strncasecmp(challenge
+valueStart
, "MD5", 3) == 0)
662 *algorithm
|= ALGO_MD5
;
663 else if (valueLength
== 8 &&
664 nsCRT::strncasecmp(challenge
+valueStart
, "MD5-sess", 8) == 0)
665 *algorithm
|= ALGO_MD5_SESS
;
667 else if (nameLength
== 3 &&
668 nsCRT::strncasecmp(challenge
+nameStart
, "qop", 3) == 0)
670 PRInt16 ipos
= valueStart
;
671 while (ipos
< valueStart
+valueLength
) {
672 while (ipos
< valueStart
+valueLength
&&
673 (nsCRT::IsAsciiSpace(challenge
[ipos
]) ||
674 challenge
[ipos
] == ','))
676 PRInt16 algostart
= ipos
;
677 while (ipos
< valueStart
+valueLength
&&
678 !nsCRT::IsAsciiSpace(challenge
[ipos
]) &&
679 challenge
[ipos
] != ',')
681 if ((ipos
- algostart
) == 4 &&
682 nsCRT::strncasecmp(challenge
+algostart
, "auth", 4) == 0)
684 else if ((ipos
- algostart
) == 8 &&
685 nsCRT::strncasecmp(challenge
+algostart
, "auth-int", 8) == 0)
686 *qop
|= QOP_AUTH_INT
;
694 nsHttpDigestAuth::AppendQuotedString(const nsACString
& value
,
695 nsACString
& aHeaderLine
)
697 nsCAutoString quoted
;
698 nsACString::const_iterator s
, e
;
699 value
.BeginReading(s
);
703 // Encode string according to RFC 2616 quoted-string production
706 for ( ; s
!= e
; ++s
) {
708 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
710 if (*s
<= 31 || *s
== 127) {
711 return NS_ERROR_FAILURE
;
714 // Escape two syntactically significant characters
715 if (*s
== '"' || *s
== '\\') {
722 // We should RFC2047-encode non-Latin-1 values according to spec
724 aHeaderLine
.Append(quoted
);