зеркало из https://github.com/mozilla/gecko-dev.git
Bug 472823 - support SHA-256 HTTP Digest auth r=necko-reviewers,dragana
fixes: Bug 472823 SHA 256 Digest Authentication Original patch by Teun van Eijsden Tests added by Vit Hampl <mozilla@bugear.com> Original patch updated and tests fixed by gstrauss fixes: Bug 281851 CVE-2005-2395 Wrong scheme used when server offers both Basic and Digest auth fixes: Bug 669675 failure to skip unknown HTTP authentication schemes in WWW-Authenticate Differential Revision: https://phabricator.services.mozilla.com/D106241
This commit is contained in:
Родитель
b8cb4bb082
Коммит
988a105817
|
@ -504,6 +504,194 @@ nsresult nsHttpChannelAuthProvider::PrepareForAuthentication(bool proxyAuth) {
|
|||
nsresult nsHttpChannelAuthProvider::GetCredentials(const char* challenges,
|
||||
bool proxyAuth,
|
||||
nsCString& creds) {
|
||||
// separate challenges by type
|
||||
const char* p = challenges; // ptr
|
||||
const char* s = nullptr; // challenge start (saved position)
|
||||
const char* b = nullptr; // challenge begin (once end is reached)
|
||||
const char* e = nullptr; // end of challenge end (updated each token)
|
||||
size_t n;
|
||||
struct {
|
||||
const char* p;
|
||||
const char* eol;
|
||||
} authpref[16]; /*(4 per auth type)*/
|
||||
memset(authpref, 0, sizeof(authpref));
|
||||
|
||||
do {
|
||||
// get the challenge string (see nsHttpHeaderArray)
|
||||
while (nsCRT::IsAsciiSpace(*p) || *p == ',') ++p;
|
||||
const char* const t = p; // token start
|
||||
while (!nsCRT::IsAsciiSpace(*p) && *p != ',' && *p != '=' && *p) ++p;
|
||||
const char* const te = p; // token end
|
||||
while (nsCRT::IsAsciiSpace(*p)) ++p; // BWS if followed by '='
|
||||
if (*p == '=') {
|
||||
do {
|
||||
++p;
|
||||
} while (nsCRT::IsAsciiSpace(*p)); // BWS
|
||||
if (*p == '"') { // parse over quoted-string (not strict)
|
||||
do {
|
||||
++p;
|
||||
} while (*p && *p != '"' && *p != '\r' && *p != '\n' &&
|
||||
(*p != '\\' || *++p != '\0'));
|
||||
if (*p == '"') ++p;
|
||||
// else unterminated quoted-string; missing '"' before end of line
|
||||
} // not strict: includes non-WS chars after quoted-string
|
||||
while (!nsCRT::IsAsciiSpace(*p) && *p != ',' && *p) ++p;
|
||||
e = p;
|
||||
if (!*p) b = s;
|
||||
} else if (te != t || !*t) {
|
||||
b = s;
|
||||
s = t;
|
||||
if (!b) n = te - t;
|
||||
}
|
||||
|
||||
if (b) {
|
||||
// reached end of a challenge
|
||||
int i = -1; /* preference order: 0 .. 3 */
|
||||
switch (n) {
|
||||
case 9:
|
||||
if (nsCRT::strncasecmp(b, "negotiate", 9) == 0) i = 0;
|
||||
break;
|
||||
case 4:
|
||||
if (nsCRT::strncasecmp(b, "ntlm", 4) == 0) i = 1;
|
||||
break;
|
||||
case 6:
|
||||
if (nsCRT::strncasecmp(b, "digest", 6) == 0) i = 2;
|
||||
break;
|
||||
case 5:
|
||||
if (nsCRT::strncasecmp(b, "basic", 5) == 0) i = 3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (i != -1) {
|
||||
// save challenge
|
||||
i <<= 2; /* support up to 4 challenges per auth type */
|
||||
int j = 0;
|
||||
while (j < 4 && authpref[i + j].p) ++j;
|
||||
if (j < 4) {
|
||||
authpref[i + j].p = b;
|
||||
authpref[i + j].eol = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (b != s) {
|
||||
if (!*s) {
|
||||
break;
|
||||
}
|
||||
e = te;
|
||||
n = te - s;
|
||||
} else {
|
||||
s = nullptr;
|
||||
}
|
||||
b = nullptr;
|
||||
}
|
||||
|
||||
} while (*p);
|
||||
|
||||
if (authpref[(2 << 2) + 1].p) {
|
||||
// more than one Digest challenge (i=2); parse and choose the strongest
|
||||
uint16_t algo_pref = 0;
|
||||
for (int i = 0; i < 4 && authpref[(2 << 2) + i].p; ++i) {
|
||||
nsAutoCString challenge;
|
||||
challenge.Assign(authpref[(2 << 2) + i].p,
|
||||
authpref[(2 << 2) + i].eol - authpref[(2 << 2) + i].p);
|
||||
#if 0 // nsHttpDigestAuth::ParseChallenge is a non-static, protected member
|
||||
nsAutoCString realm, domain, nonce, opaque;
|
||||
bool stale;
|
||||
uint16_t algorithm, qop;
|
||||
nsresult rv = nsHttpDigestAuth::ParseChallenge(challenge, realm, domain,
|
||||
nonce, opaque, &stale,
|
||||
&algorithm, &qop);
|
||||
if (NS_FAILED(rv))
|
||||
continue;
|
||||
uint16_t cmp = 0;
|
||||
switch (algorithm) {
|
||||
case ALGO_SHA256_SESS:
|
||||
cmp = ALGO_SHA256_SESS;
|
||||
break;
|
||||
case ALGO_SHA256:
|
||||
cmp = ALGO_SHA256_SESS|ALGO_SHA256;
|
||||
break;
|
||||
case ALGO_MD5_SESS:
|
||||
cmp = ALGO_SHA256_SESS|ALGO_SHA256|ALGO_MD5_SESS;
|
||||
break;
|
||||
case ALGO_MD5:
|
||||
cmp = ALGO_SHA256_SESS|ALGO_SHA256|ALGO_MD5_SESS|ALGO_MD5;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
const char* p = authpref[(2 << 2) + i].p + 6; // +6 for "Digest"
|
||||
const char* const end = authpref[(2 << 2) + i].eol;
|
||||
uint16_t algorithm = 0;
|
||||
uint16_t cmp = 0;
|
||||
do {
|
||||
while (nsCRT::IsAsciiSpace(*p) || *p == ',') ++p;
|
||||
const char* const t = p; // token start
|
||||
while (!nsCRT::IsAsciiSpace(*p) && *p != ',' && *p != '=' && *p) ++p;
|
||||
const char* const te = p; // token end
|
||||
while (nsCRT::IsAsciiSpace(*p)) ++p; // BWS if followed by '='
|
||||
const char* v = nullptr;
|
||||
if (*p == '=') {
|
||||
do {
|
||||
++p;
|
||||
} while (nsCRT::IsAsciiSpace(*p)); // BWS
|
||||
v = p;
|
||||
if (*p == '"') { // parse over quoted-string (not strict)
|
||||
do {
|
||||
++p;
|
||||
} while (*p && *p != '"' && *p != '\r' && *p != '\n' &&
|
||||
(*p != '\\' || *++p != '\0'));
|
||||
if (*p == '"') ++p;
|
||||
// else unterminated quoted-string; missing '"' before end of line
|
||||
} // not strict: includes non-WS chars after quoted-string
|
||||
while (!nsCRT::IsAsciiSpace(*p) && *p != ',' && *p) ++p;
|
||||
}
|
||||
if (!v) continue;
|
||||
if (te - t != 9 || nsCRT::strncasecmp(t, "algorithm", 9) != 0) continue;
|
||||
cmp = 0;
|
||||
switch (p - v) {
|
||||
case 3:
|
||||
if (nsCRT::strncasecmp(v, "MD5", 3) == 0) {
|
||||
algorithm = ALGO_MD5;
|
||||
cmp = ALGO_SHA256_SESS | ALGO_SHA256 | ALGO_MD5_SESS | ALGO_MD5;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
if (nsCRT::strncasecmp(v, "MD5-sess", 8) == 0) {
|
||||
algorithm = ALGO_MD5_SESS;
|
||||
cmp = ALGO_SHA256_SESS | ALGO_SHA256 | ALGO_MD5_SESS;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (nsCRT::strncasecmp(v, "SHA-256", 7) == 0) {
|
||||
algorithm = ALGO_SHA256;
|
||||
cmp = ALGO_SHA256_SESS | ALGO_SHA256;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if (nsCRT::strncasecmp(v, "SHA-256-sess", 12) == 0) {
|
||||
algorithm = ALGO_SHA256_SESS;
|
||||
cmp = ALGO_SHA256_SESS;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} while (p < end);
|
||||
#endif
|
||||
if (!cmp || (algo_pref & cmp)) continue;
|
||||
algo_pref = algorithm;
|
||||
// overwrite first Digest slot in authpref[] since only one is tried below
|
||||
if (i) {
|
||||
authpref[2 << 2].p = authpref[(2 << 2) + i].p;
|
||||
authpref[2 << 2].eol = authpref[(2 << 2) + i].eol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpAuthenticator> auth;
|
||||
nsAutoCString challenge;
|
||||
|
||||
|
@ -527,14 +715,14 @@ nsresult nsHttpChannelAuthProvider::GetCredentials(const char* challenges,
|
|||
bool gotCreds = false;
|
||||
|
||||
// figure out which challenge we can handle and which authenticator to use.
|
||||
for (const char* eol = challenges - 1; eol;) {
|
||||
const char* p = eol + 1;
|
||||
for (int i = 0; i < (int)(sizeof(authpref) / sizeof(*authpref)); ++i) {
|
||||
if (authpref[i].p == nullptr) continue;
|
||||
|
||||
// get the challenge string (LF separated -- see nsHttpHeaderArray)
|
||||
if ((eol = strchr(p, '\n')) != nullptr)
|
||||
challenge.Assign(p, eol - p);
|
||||
if (authpref[i].eol != nullptr)
|
||||
challenge.Assign(authpref[i].p, authpref[i].eol - authpref[i].p);
|
||||
else
|
||||
challenge.Assign(p);
|
||||
challenge.Assign(authpref[i].p);
|
||||
|
||||
rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
|
@ -574,7 +762,9 @@ nsresult nsHttpChannelAuthProvider::GetCredentials(const char* challenges,
|
|||
// processed and all remaining challenges to use later in
|
||||
// OnAuthAvailable and now immediately return
|
||||
mCurrentChallenge = challenge;
|
||||
mRemainingChallenges = eol ? eol + 1 : nullptr;
|
||||
// imperfect; does not save server-side preference ordering.
|
||||
// instead, continues with remaining string as provided by client
|
||||
mRemainingChallenges = authpref[i].eol ? authpref[i].eol + 1 : nullptr;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include "nsICryptoHash.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
|
||||
#define DigestLength(algorithm) \
|
||||
(((algorithm) & (ALGO_SHA256 | ALGO_SHA256_SESS)) ? SHA256_DIGEST_LENGTH \
|
||||
: MD5_DIGEST_LENGTH)
|
||||
|
||||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
|
@ -51,7 +55,8 @@ NS_IMPL_ISUPPORTS(nsHttpDigestAuth, nsIHttpAuthenticator)
|
|||
// nsHttpDigestAuth <protected>
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsresult nsHttpDigestAuth::MD5Hash(const char* buf, uint32_t len) {
|
||||
nsresult nsHttpDigestAuth::DigestHash(const char* buf, uint32_t len,
|
||||
uint16_t algorithm) {
|
||||
nsresult rv;
|
||||
|
||||
// Cache a reference to the nsICryptoHash instance since we'll be calling
|
||||
|
@ -64,7 +69,14 @@ nsresult nsHttpDigestAuth::MD5Hash(const char* buf, uint32_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
rv = mVerifier->Init(nsICryptoHash::MD5);
|
||||
uint32_t dlen;
|
||||
if (algorithm & (ALGO_SHA256 | ALGO_SHA256_SESS)) {
|
||||
rv = mVerifier->Init(nsICryptoHash::SHA256);
|
||||
dlen = SHA256_DIGEST_LENGTH;
|
||||
} else {
|
||||
rv = mVerifier->Init(nsICryptoHash::MD5);
|
||||
dlen = MD5_DIGEST_LENGTH;
|
||||
}
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = mVerifier->Update((unsigned char*)buf, len);
|
||||
|
@ -74,7 +86,7 @@ nsresult nsHttpDigestAuth::MD5Hash(const char* buf, uint32_t len) {
|
|||
rv = mVerifier->Finish(false, hashString);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
NS_ENSURE_STATE(hashString.Length() == sizeof(mHashBuf));
|
||||
NS_ENSURE_STATE(hashString.Length() == dlen);
|
||||
memcpy(mHashBuf, hashString.get(), hashString.Length());
|
||||
|
||||
return rv;
|
||||
|
@ -149,6 +161,14 @@ nsHttpDigestAuth::ChallengeReceived(nsIHttpAuthenticableChannel* authChannel,
|
|||
|
||||
nsresult rv = ParseChallenge(challenge, realm, domain, nonce, opaque, &stale,
|
||||
&algorithm, &qop);
|
||||
|
||||
if (!(algorithm &
|
||||
(ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
|
||||
// they asked for an algorithm that we do not support yet (like SHA-512/256)
|
||||
NS_WARNING("unsupported algorithm requested by Digest authentication");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// if the challenge has the "stale" flag set, then the user identity is not
|
||||
|
@ -219,10 +239,11 @@ nsHttpDigestAuth::GenerateCredentials(
|
|||
return rv;
|
||||
}
|
||||
|
||||
char ha1_digest[EXPANDED_DIGEST_LENGTH + 1];
|
||||
char ha2_digest[EXPANDED_DIGEST_LENGTH + 1];
|
||||
char response_digest[EXPANDED_DIGEST_LENGTH + 1];
|
||||
char upload_data_digest[EXPANDED_DIGEST_LENGTH + 1];
|
||||
const uint32_t dhexlen = 2 * DigestLength(algorithm) + 1;
|
||||
char ha1_digest[dhexlen];
|
||||
char ha2_digest[dhexlen];
|
||||
char response_digest[dhexlen];
|
||||
char upload_data_digest[dhexlen];
|
||||
|
||||
if (qop & QOP_AUTH_INT) {
|
||||
// we do not support auth-int "quality of protection" currently
|
||||
|
@ -258,7 +279,8 @@ nsHttpDigestAuth::GenerateCredentials(
|
|||
#endif
|
||||
}
|
||||
|
||||
if (!(algorithm & ALGO_MD5 || algorithm & ALGO_MD5_SESS)) {
|
||||
if (!(algorithm &
|
||||
(ALGO_MD5 | ALGO_MD5_SESS | ALGO_SHA256 | ALGO_SHA256_SESS))) {
|
||||
// they asked only for algorithms that we do not support
|
||||
NS_WARNING("unsupported algorithm requested by Digest authentication");
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
@ -309,11 +331,12 @@ nsHttpDigestAuth::GenerateCredentials(
|
|||
rv = CalculateHA1(cUser, cPass, realm, algorithm, nonce, cnonce, ha1_digest);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = CalculateHA2(httpMethod, path, qop, upload_data_digest, ha2_digest);
|
||||
rv = CalculateHA2(httpMethod, path, algorithm, qop, upload_data_digest,
|
||||
ha2_digest);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = CalculateResponse(ha1_digest, ha2_digest, nonce, qop, nonce_count,
|
||||
cnonce, response_digest);
|
||||
rv = CalculateResponse(ha1_digest, ha2_digest, algorithm, nonce, qop,
|
||||
nonce_count, cnonce, response_digest);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
//
|
||||
|
@ -346,6 +369,10 @@ nsHttpDigestAuth::GenerateCredentials(
|
|||
authString.AppendLiteral("\", algorithm=");
|
||||
if (algorithm & ALGO_MD5_SESS)
|
||||
authString.AppendLiteral("MD5-sess");
|
||||
else if (algorithm & ALGO_SHA256)
|
||||
authString.AppendLiteral("SHA-256");
|
||||
else if (algorithm & ALGO_SHA256_SESS)
|
||||
authString.AppendLiteral("SHA-256-sess");
|
||||
else
|
||||
authString.AppendLiteral("MD5");
|
||||
} else {
|
||||
|
@ -390,10 +417,11 @@ nsHttpDigestAuth::GetAuthFlags(uint32_t* flags) {
|
|||
}
|
||||
|
||||
nsresult nsHttpDigestAuth::CalculateResponse(
|
||||
const char* ha1_digest, const char* ha2_digest, const nsCString& nonce,
|
||||
uint16_t qop, const char* nonce_count, const nsCString& cnonce,
|
||||
char* result) {
|
||||
uint32_t len = 2 * EXPANDED_DIGEST_LENGTH + nonce.Length() + 2;
|
||||
const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
|
||||
const nsCString& nonce, uint16_t qop, const char* nonce_count,
|
||||
const nsCString& cnonce, char* result) {
|
||||
const uint32_t dhexlen = 2 * DigestLength(algorithm);
|
||||
uint32_t len = 2 * dhexlen + nonce.Length() + 2;
|
||||
|
||||
if (qop & QOP_AUTH || qop & QOP_AUTH_INT) {
|
||||
len += cnonce.Length() + NONCE_COUNT_LENGTH + 3;
|
||||
|
@ -406,7 +434,7 @@ nsresult nsHttpDigestAuth::CalculateResponse(
|
|||
nsAutoCString contents;
|
||||
contents.SetCapacity(len);
|
||||
|
||||
contents.Append(ha1_digest, EXPANDED_DIGEST_LENGTH);
|
||||
contents.Append(ha1_digest, dhexlen);
|
||||
contents.Append(':');
|
||||
contents.Append(nonce);
|
||||
contents.Append(':');
|
||||
|
@ -422,17 +450,19 @@ nsresult nsHttpDigestAuth::CalculateResponse(
|
|||
contents.AppendLiteral("auth:");
|
||||
}
|
||||
|
||||
contents.Append(ha2_digest, EXPANDED_DIGEST_LENGTH);
|
||||
contents.Append(ha2_digest, dhexlen);
|
||||
|
||||
nsresult rv = MD5Hash(contents.get(), contents.Length());
|
||||
if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result);
|
||||
nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
|
||||
if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result, algorithm);
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result) {
|
||||
nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result,
|
||||
uint16_t algorithm) {
|
||||
int16_t index, value;
|
||||
const int16_t dlen = DigestLength(algorithm);
|
||||
|
||||
for (index = 0; index < DIGEST_LENGTH; index++) {
|
||||
for (index = 0; index < dlen; index++) {
|
||||
value = (digest[index] >> 4) & 0xf;
|
||||
if (value < 10)
|
||||
result[index * 2] = value + '0';
|
||||
|
@ -446,7 +476,7 @@ nsresult nsHttpDigestAuth::ExpandToHex(const char* digest, char* result) {
|
|||
result[(index * 2) + 1] = value - 10 + 'a';
|
||||
}
|
||||
|
||||
result[EXPANDED_DIGEST_LENGTH] = 0;
|
||||
result[2 * dlen] = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -456,10 +486,10 @@ nsresult nsHttpDigestAuth::CalculateHA1(const nsCString& username,
|
|||
uint16_t algorithm,
|
||||
const nsCString& nonce,
|
||||
const nsCString& cnonce, char* result) {
|
||||
const int16_t dhexlen = 2 * DigestLength(algorithm);
|
||||
int16_t len = username.Length() + password.Length() + realm.Length() + 2;
|
||||
if (algorithm & ALGO_MD5_SESS) {
|
||||
int16_t exlen =
|
||||
EXPANDED_DIGEST_LENGTH + nonce.Length() + cnonce.Length() + 2;
|
||||
if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
|
||||
int16_t exlen = dhexlen + nonce.Length() + cnonce.Length() + 2;
|
||||
if (exlen > len) len = exlen;
|
||||
}
|
||||
|
||||
|
@ -473,36 +503,38 @@ nsresult nsHttpDigestAuth::CalculateHA1(const nsCString& username,
|
|||
contents.Append(password);
|
||||
|
||||
nsresult rv;
|
||||
rv = MD5Hash(contents.get(), contents.Length());
|
||||
rv = DigestHash(contents.get(), contents.Length(), algorithm);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
if (algorithm & ALGO_MD5_SESS) {
|
||||
char part1[EXPANDED_DIGEST_LENGTH + 1];
|
||||
rv = ExpandToHex(mHashBuf, part1);
|
||||
if (algorithm & (ALGO_MD5_SESS | ALGO_SHA256_SESS)) {
|
||||
char part1[dhexlen + 1];
|
||||
rv = ExpandToHex(mHashBuf, part1, algorithm);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
contents.Assign(part1, EXPANDED_DIGEST_LENGTH);
|
||||
contents.Assign(part1, dhexlen);
|
||||
contents.Append(':');
|
||||
contents.Append(nonce);
|
||||
contents.Append(':');
|
||||
contents.Append(cnonce);
|
||||
|
||||
rv = MD5Hash(contents.get(), contents.Length());
|
||||
rv = DigestHash(contents.get(), contents.Length(), algorithm);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
}
|
||||
|
||||
return ExpandToHex(mHashBuf, result);
|
||||
return ExpandToHex(mHashBuf, result, algorithm);
|
||||
}
|
||||
|
||||
nsresult nsHttpDigestAuth::CalculateHA2(const nsCString& method,
|
||||
const nsCString& path, uint16_t qop,
|
||||
const nsCString& path,
|
||||
uint16_t algorithm, uint16_t qop,
|
||||
const char* bodyDigest, char* result) {
|
||||
uint16_t methodLen = method.Length();
|
||||
uint32_t pathLen = path.Length();
|
||||
uint32_t len = methodLen + pathLen + 1;
|
||||
const uint32_t dhexlen = 2 * DigestLength(algorithm);
|
||||
|
||||
if (qop & QOP_AUTH_INT) {
|
||||
len += EXPANDED_DIGEST_LENGTH + 1;
|
||||
len += dhexlen + 1;
|
||||
}
|
||||
|
||||
nsAutoCString contents;
|
||||
|
@ -514,11 +546,11 @@ nsresult nsHttpDigestAuth::CalculateHA2(const nsCString& method,
|
|||
|
||||
if (qop & QOP_AUTH_INT) {
|
||||
contents.Append(':');
|
||||
contents.Append(bodyDigest, EXPANDED_DIGEST_LENGTH);
|
||||
contents.Append(bodyDigest, dhexlen);
|
||||
}
|
||||
|
||||
nsresult rv = MD5Hash(contents.get(), contents.Length());
|
||||
if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result);
|
||||
nsresult rv = DigestHash(contents.get(), contents.Length(), algorithm);
|
||||
if (NS_SUCCEEDED(rv)) rv = ExpandToHex(mHashBuf, result, algorithm);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
@ -600,6 +632,12 @@ nsresult nsHttpDigestAuth::ParseChallenge(const char* challenge,
|
|||
else if (valueLength == 8 &&
|
||||
nsCRT::strncasecmp(challenge + valueStart, "MD5-sess", 8) == 0)
|
||||
*algorithm |= ALGO_MD5_SESS;
|
||||
else if (valueLength == 7 &&
|
||||
nsCRT::strncasecmp(challenge + valueStart, "SHA-256", 7) == 0)
|
||||
*algorithm |= ALGO_SHA256;
|
||||
else if (valueLength == 12 && nsCRT::strncasecmp(challenge + valueStart,
|
||||
"SHA-256-sess", 12) == 0)
|
||||
*algorithm |= ALGO_SHA256_SESS;
|
||||
} else if (nameLength == 3 &&
|
||||
nsCRT::strncasecmp(challenge + nameStart, "qop", 3) == 0) {
|
||||
int32_t ipos = valueStart;
|
||||
|
|
|
@ -20,12 +20,18 @@ namespace net {
|
|||
#define ALGO_SPECIFIED 0x01
|
||||
#define ALGO_MD5 0x02
|
||||
#define ALGO_MD5_SESS 0x04
|
||||
#define ALGO_SHA256 0x08
|
||||
#define ALGO_SHA256_SESS 0x10
|
||||
#define QOP_AUTH 0x01
|
||||
#define QOP_AUTH_INT 0x02
|
||||
|
||||
#define DIGEST_LENGTH 16
|
||||
#define EXPANDED_DIGEST_LENGTH 32
|
||||
#define NONCE_COUNT_LENGTH 8
|
||||
#ifndef MD5_DIGEST_LENGTH
|
||||
# define MD5_DIGEST_LENGTH 16
|
||||
#endif
|
||||
#ifndef SHA256_DIGEST_LENGTH
|
||||
# define SHA256_DIGEST_LENGTH 32
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpDigestAuth
|
||||
|
@ -43,14 +49,13 @@ class nsHttpDigestAuth final : public nsIHttpAuthenticator {
|
|||
protected:
|
||||
~nsHttpDigestAuth() = default;
|
||||
|
||||
[[nodiscard]] nsresult ExpandToHex(const char* digest, char* result);
|
||||
[[nodiscard]] nsresult ExpandToHex(const char* digest, char* result,
|
||||
uint16_t algorithm);
|
||||
|
||||
[[nodiscard]] nsresult CalculateResponse(const char* ha1_digest,
|
||||
const char* ha2_digest,
|
||||
const nsCString& nonce, uint16_t qop,
|
||||
const char* nonce_count,
|
||||
const nsCString& cnonce,
|
||||
char* result);
|
||||
[[nodiscard]] nsresult CalculateResponse(
|
||||
const char* ha1_digest, const char* ha2_digest, uint16_t algorithm,
|
||||
const nsCString& nonce, uint16_t qop, const char* nonce_count,
|
||||
const nsCString& cnonce, char* result);
|
||||
|
||||
[[nodiscard]] nsresult CalculateHA1(const nsCString& username,
|
||||
const nsCString& password,
|
||||
|
@ -61,8 +66,8 @@ class nsHttpDigestAuth final : public nsIHttpAuthenticator {
|
|||
|
||||
[[nodiscard]] nsresult CalculateHA2(const nsCString& http_method,
|
||||
const nsCString& http_uri_path,
|
||||
uint16_t qop, const char* body_digest,
|
||||
char* result);
|
||||
uint16_t algorithm, uint16_t qop,
|
||||
const char* body_digest, char* result);
|
||||
|
||||
[[nodiscard]] nsresult ParseChallenge(const char* challenge,
|
||||
nsACString& realm, nsACString& domain,
|
||||
|
@ -71,7 +76,8 @@ class nsHttpDigestAuth final : public nsIHttpAuthenticator {
|
|||
uint16_t* qop);
|
||||
|
||||
// result is in mHashBuf
|
||||
[[nodiscard]] nsresult MD5Hash(const char* buf, uint32_t len);
|
||||
[[nodiscard]] nsresult DigestHash(const char* buf, uint32_t len,
|
||||
uint16_t algorithm);
|
||||
|
||||
[[nodiscard]] nsresult GetMethodAndPath(nsIHttpAuthenticableChannel*, bool,
|
||||
nsCString&, nsCString&);
|
||||
|
@ -82,7 +88,7 @@ class nsHttpDigestAuth final : public nsIHttpAuthenticator {
|
|||
|
||||
protected:
|
||||
nsCOMPtr<nsICryptoHash> mVerifier;
|
||||
char mHashBuf[DIGEST_LENGTH];
|
||||
char mHashBuf[SHA256_DIGEST_LENGTH];
|
||||
|
||||
static StaticRefPtr<nsHttpDigestAuth> gSingleton;
|
||||
};
|
||||
|
|
|
@ -307,7 +307,13 @@ var tests = [
|
|||
test_basicrealm,
|
||||
test_nonascii,
|
||||
test_digest_noauth,
|
||||
test_digest,
|
||||
test_digest_md5,
|
||||
test_digest_md5sess,
|
||||
test_digest_sha256,
|
||||
test_digest_sha256sess,
|
||||
test_digest_sha256_md5,
|
||||
test_digest_md5_sha256,
|
||||
test_digest_md5_sha256_oneline,
|
||||
test_digest_bogus_user,
|
||||
test_short_digest,
|
||||
test_large_realm,
|
||||
|
@ -343,7 +349,16 @@ function run_test() {
|
|||
httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
|
||||
httpserv.registerPathHandler("/auth/realm", authRealm);
|
||||
httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
|
||||
httpserv.registerPathHandler("/auth/digest", authDigest);
|
||||
httpserv.registerPathHandler("/auth/digest_md5", authDigestMD5);
|
||||
httpserv.registerPathHandler("/auth/digest_md5sess", authDigestMD5sess);
|
||||
httpserv.registerPathHandler("/auth/digest_sha256", authDigestSHA256);
|
||||
httpserv.registerPathHandler("/auth/digest_sha256sess", authDigestSHA256sess);
|
||||
httpserv.registerPathHandler("/auth/digest_sha256_md5", authDigestSHA256_MD5);
|
||||
httpserv.registerPathHandler("/auth/digest_md5_sha256", authDigestMD5_SHA256);
|
||||
httpserv.registerPathHandler(
|
||||
"/auth/digest_md5_sha256_oneline",
|
||||
authDigestMD5_SHA256_oneline
|
||||
);
|
||||
httpserv.registerPathHandler("/auth/short_digest", authShortDigest);
|
||||
httpserv.registerPathHandler("/largeRealm", largeRealm);
|
||||
httpserv.registerPathHandler("/largeDomain", largeDomain);
|
||||
|
@ -488,7 +503,7 @@ function test_nonascii_xhr() {
|
|||
}
|
||||
|
||||
function test_digest_noauth() {
|
||||
var chan = makeChan(URL + "/auth/digest", URL);
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
|
||||
//chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
|
||||
listener.expectedCode = 401; // Unauthorized
|
||||
|
@ -497,8 +512,68 @@ function test_digest_noauth() {
|
|||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest() {
|
||||
var chan = makeChan(URL + "/auth/digest", URL);
|
||||
function test_digest_md5() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_md5sess() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5sess", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_sha256() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_sha256sess() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256sess", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_sha256_md5() {
|
||||
var chan = makeChan(URL + "/auth/digest_sha256_md5", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_md5_sha256() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5_sha256", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
chan.asyncOpen(listener);
|
||||
|
||||
do_test_pending();
|
||||
}
|
||||
|
||||
function test_digest_md5_sha256_oneline() {
|
||||
var chan = makeChan(URL + "/auth/digest_md5_sha256_oneline", URL);
|
||||
|
||||
chan.notificationCallbacks = new Requestor(0, 2);
|
||||
listener.expectedCode = 200; // OK
|
||||
|
@ -508,7 +583,7 @@ function test_digest() {
|
|||
}
|
||||
|
||||
function test_digest_bogus_user() {
|
||||
var chan = makeChan(URL + "/auth/digest", URL);
|
||||
var chan = makeChan(URL + "/auth/digest_md5", URL);
|
||||
chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
|
||||
listener.expectedCode = 401; // unauthorized
|
||||
chan.asyncOpen(listener);
|
||||
|
@ -622,7 +697,7 @@ function toHexString(charCode) {
|
|||
return ("0" + charCode.toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
function H(str) {
|
||||
function HMD5(str) {
|
||||
var data = bytesFromString(str);
|
||||
var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
|
||||
ch.init(Ci.nsICryptoHash.MD5);
|
||||
|
@ -631,61 +706,218 @@ function H(str) {
|
|||
return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
|
||||
}
|
||||
|
||||
function HSHA256(str) {
|
||||
var data = bytesFromString(str);
|
||||
var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
|
||||
ch.init(Ci.nsICryptoHash.SHA256);
|
||||
ch.update(data, data.length);
|
||||
var hash = ch.finish(false);
|
||||
return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
|
||||
}
|
||||
|
||||
//
|
||||
// Digest handler
|
||||
//
|
||||
// /auth/digest
|
||||
function authDigest(metadata, response) {
|
||||
function authDigestMD5_helper(metadata, response, test_name) {
|
||||
var nonce = "6f93719059cf8d568005727f3250e798";
|
||||
var opaque = "1234opaque1234";
|
||||
var cnonceRE = /cnonce="(\w+)"/;
|
||||
var responseRE = /response="(\w+)"/;
|
||||
var usernameRE = /username="(\w+)"/;
|
||||
var authenticate =
|
||||
'Digest realm="secret", domain="/", qop=auth,' +
|
||||
'algorithm=MD5, nonce="' +
|
||||
nonce +
|
||||
'" opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
var body;
|
||||
var send_401 = 0;
|
||||
// check creds if we have them
|
||||
if (metadata.hasHeader("Authorization")) {
|
||||
var cnonceRE = /cnonce="(\w+)"/;
|
||||
var responseRE = /response="(\w+)"/;
|
||||
var usernameRE = /username="(\w+)"/;
|
||||
var algorithmRE = /algorithm=([\w-]+)/;
|
||||
var auth = metadata.getHeader("Authorization");
|
||||
var cnonce = auth.match(cnonceRE)[1];
|
||||
var clientDigest = auth.match(responseRE)[1];
|
||||
var username = auth.match(usernameRE)[1];
|
||||
var algorithm = auth.match(algorithmRE)[1];
|
||||
var nc = "00000001";
|
||||
|
||||
if (username != "guest") {
|
||||
response.setStatusLine(metadata.httpVersion, 400, "bad request");
|
||||
body = "should never get here";
|
||||
} else if (
|
||||
algorithm != null &&
|
||||
algorithm != "MD5" &&
|
||||
algorithm != "MD5-sess"
|
||||
) {
|
||||
response.setStatusLine(metadata.httpVersion, 400, "bad request");
|
||||
body = "Algorithm must be same as provided in WWW-Authenticate header";
|
||||
} else {
|
||||
// see RFC2617 for the description of this calculation
|
||||
var A1 = "guest:secret:guest";
|
||||
var A2 = "GET:/auth/digest";
|
||||
var noncebits = [nonce, nc, cnonce, "auth", H(A2)].join(":");
|
||||
var digest = H([H(A1), noncebits].join(":"));
|
||||
if (algorithm == "MD5-sess") {
|
||||
A1 = [HMD5(A1), nonce, cnonce].join(":");
|
||||
}
|
||||
var A2 = "GET:/auth/" + test_name;
|
||||
var noncebits = [nonce, nc, cnonce, "auth", HMD5(A2)].join(":");
|
||||
var digest = HMD5([HMD5(A1), noncebits].join(":"));
|
||||
|
||||
if (clientDigest == digest) {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
body = "success";
|
||||
} else {
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", authenticate, false);
|
||||
send_401 = 1;
|
||||
body = "auth failed";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no header, send one
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", authenticate, false);
|
||||
send_401 = 1;
|
||||
body = "failed, no header";
|
||||
}
|
||||
|
||||
if (send_401) {
|
||||
var authenticate_md5 =
|
||||
'Digest realm="secret", domain="/", qop=auth,' +
|
||||
'algorithm=MD5, nonce="' +
|
||||
nonce +
|
||||
'" opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
var authenticate_md5sess =
|
||||
'Digest realm="secret", domain="/", qop=auth,' +
|
||||
'algorithm=MD5, nonce="' +
|
||||
nonce +
|
||||
'" opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
if (test_name == "digest_md5") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_md5, false);
|
||||
} else if (test_name == "digest_md5sess") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_md5sess, false);
|
||||
}
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
}
|
||||
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function authDigestMD5(metadata, response) {
|
||||
authDigestMD5_helper(metadata, response, "digest_md5");
|
||||
}
|
||||
|
||||
function authDigestMD5sess(metadata, response) {
|
||||
authDigestMD5_helper(metadata, response, "digest_md5sess");
|
||||
}
|
||||
|
||||
function authDigestSHA256_helper(metadata, response, test_name) {
|
||||
var nonce = "6f93719059cf8d568005727f3250e798";
|
||||
var opaque = "1234opaque1234";
|
||||
var body;
|
||||
var send_401 = 0;
|
||||
// check creds if we have them
|
||||
if (metadata.hasHeader("Authorization")) {
|
||||
var cnonceRE = /cnonce="(\w+)"/;
|
||||
var responseRE = /response="(\w+)"/;
|
||||
var usernameRE = /username="(\w+)"/;
|
||||
var algorithmRE = /algorithm=([\w-]+)/;
|
||||
var auth = metadata.getHeader("Authorization");
|
||||
var cnonce = auth.match(cnonceRE)[1];
|
||||
var clientDigest = auth.match(responseRE)[1];
|
||||
var username = auth.match(usernameRE)[1];
|
||||
var algorithm = auth.match(algorithmRE)[1];
|
||||
var nc = "00000001";
|
||||
|
||||
if (username != "guest") {
|
||||
response.setStatusLine(metadata.httpVersion, 400, "bad request");
|
||||
body = "should never get here";
|
||||
} else if (algorithm != "SHA-256" && algorithm != "SHA-256-sess") {
|
||||
response.setStatusLine(metadata.httpVersion, 400, "bad request");
|
||||
body = "Algorithm must be same as provided in WWW-Authenticate header";
|
||||
} else {
|
||||
// see RFC7616 for the description of this calculation
|
||||
var A1 = "guest:secret:guest";
|
||||
if (algorithm == "SHA-256-sess") {
|
||||
A1 = [HSHA256(A1), nonce, cnonce].join(":");
|
||||
}
|
||||
var A2 = "GET:/auth/" + test_name;
|
||||
var noncebits = [nonce, nc, cnonce, "auth", HSHA256(A2)].join(":");
|
||||
var digest = HSHA256([HSHA256(A1), noncebits].join(":"));
|
||||
|
||||
if (clientDigest == digest) {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
body = "success";
|
||||
} else {
|
||||
send_401 = 1;
|
||||
body = "auth failed";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no header, send one
|
||||
send_401 = 1;
|
||||
body = "failed, no header";
|
||||
}
|
||||
|
||||
if (send_401) {
|
||||
var authenticate_sha256 =
|
||||
'Digest realm="secret", domain="/", qop=auth, ' +
|
||||
'algorithm=SHA-256, nonce="' +
|
||||
nonce +
|
||||
'", opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
var authenticate_sha256sess =
|
||||
'Digest realm="secret", domain="/", qop=auth, ' +
|
||||
'algorithm=SHA-256-sess, nonce="' +
|
||||
nonce +
|
||||
'", opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
var authenticate_md5 =
|
||||
'Digest realm="secret", domain="/", qop=auth, ' +
|
||||
'algorithm=MD5, nonce="' +
|
||||
nonce +
|
||||
'", opaque="' +
|
||||
opaque +
|
||||
'"';
|
||||
if (test_name == "digest_sha256") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_sha256, false);
|
||||
} else if (test_name == "digest_sha256sess") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_sha256sess, false);
|
||||
} else if (test_name == "digest_md5_sha256") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_md5, false);
|
||||
response.setHeader("WWW-Authenticate", authenticate_sha256, true);
|
||||
} else if (test_name == "digest_md5_sha256_oneline") {
|
||||
response.setHeader(
|
||||
"WWW-Authenticate",
|
||||
authenticate_md5 + " " + authenticate_sha256,
|
||||
false
|
||||
);
|
||||
} else if (test_name == "digest_sha256_md5") {
|
||||
response.setHeader("WWW-Authenticate", authenticate_sha256, false);
|
||||
response.setHeader("WWW-Authenticate", authenticate_md5, true);
|
||||
}
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
}
|
||||
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function authDigestSHA256(metadata, response) {
|
||||
authDigestSHA256_helper(metadata, response, "digest_sha256");
|
||||
}
|
||||
|
||||
function authDigestSHA256sess(metadata, response) {
|
||||
authDigestSHA256_helper(metadata, response, "digest_sha256sess");
|
||||
}
|
||||
|
||||
function authDigestSHA256_MD5(metadata, response) {
|
||||
authDigestSHA256_helper(metadata, response, "digest_sha256_md5");
|
||||
}
|
||||
|
||||
function authDigestMD5_SHA256(metadata, response) {
|
||||
authDigestSHA256_helper(metadata, response, "digest_md5_sha256");
|
||||
}
|
||||
|
||||
function authDigestMD5_SHA256_oneline(metadata, response) {
|
||||
authDigestSHA256_helper(metadata, response, "digest_md5_sha256_oneline");
|
||||
}
|
||||
|
||||
function authShortDigest(metadata, response) {
|
||||
// no header, send one
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
|
|
Загрузка…
Ссылка в новой задаче