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:
Glenn Strauss 2021-05-26 09:27:16 +00:00
Родитель b8cb4bb082
Коммит 988a105817
4 изменённых файлов: 547 добавлений и 81 удалений

Просмотреть файл

@ -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");