author | Glenn Strauss <gstrauss@gluelogic.com> |
Thu, 12 Aug 2021 12:39:19 +0000 | |
changeset 588663 | 7a4994734e00950bffd6fe82838b47e35efeaa93 |
parent 588662 | 2ed80a7a7b6c8ea8cd4707e8cfcd372ce35a7474 |
child 588664 | 090b4ff09c668e2ffdd209c725fc63c5ea3d0f37 |
push id | 38699 |
push user | [email protected] |
push date | Thu, 12 Aug 2021 16:01:16 +0000 |
treeherder | mozilla-central@e92759eacf36 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | necko-reviewers, dragana |
bugs | 472823, 281851, 669675 |
milestone | 93.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp +++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp @@ -497,16 +497,204 @@ nsresult nsHttpChannelAuthProvider::Prep } return NS_OK; } 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; nsCString authType; // force heap allocation to enable string sharing since // we'll be assigning this value into mAuthType. // set informations that depend on whether we're authenticating against a // proxy or a webserver @@ -520,25 +708,24 @@ nsresult nsHttpChannelAuthProvider::GetC currentContinuationState = &mAuthContinuationState; currentAuthType = &mAuthType; } nsresult rv = NS_ERROR_NOT_AVAILABLE; 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); - } else { - challenge.Assign(p); - } + if (authpref[i].eol != nullptr) + challenge.Assign(authpref[i].p, authpref[i].eol - authpref[i].p); + else + challenge.Assign(authpref[i].p); rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth)); if (NS_SUCCEEDED(rv)) { // // if we've already selected an auth type from a previous challenge // received while processing this channel, then skip others until // we find a challenge corresponding to the previously tried auth // type. @@ -568,17 +755,19 @@ nsresult nsHttpChannelAuthProvider::GetC break; } if (rv == NS_ERROR_IN_PROGRESS) { // authentication prompt has been invoked and result is // expected asynchronously, save current challenge being // 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; } // reset the auth type and continuation state NS_IF_RELEASE(*currentContinuationState); currentAuthType->Truncate(); } }
--- a/netwerk/protocol/http/nsHttpDigestAuth.cpp +++ b/netwerk/protocol/http/nsHttpDigestAuth.cpp @@ -18,16 +18,20 @@ #include "nsIURI.h" #include "nsString.h" #include "nsEscape.h" #include "nsNetCID.h" #include "nsCRT.h" #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 { StaticRefPtr<nsHttpDigestAuth> nsHttpDigestAuth::gSingleton; already_AddRefed<nsIHttpAuthenticator> nsHttpDigestAuth::GetOrCreate() { nsCOMPtr<nsIHttpAuthenticator> authenticator; if (gSingleton) { @@ -46,40 +50,48 @@ already_AddRefed<nsIHttpAuthenticator> n //----------------------------------------------------------------------------- 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 // this function frequently. if (!mVerifier) { mVerifier = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); if (NS_FAILED(rv)) { LOG(("nsHttpDigestAuth: no crypto hash!\n")); return rv; } } - 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); if (NS_FAILED(rv)) return rv; nsAutoCString hashString; 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; } nsresult nsHttpDigestAuth::GetMethodAndPath( nsIHttpAuthenticableChannel* authChannel, bool isProxyAuth, nsCString& httpMethod, nsCString& path) { @@ -144,16 +156,24 @@ nsHttpDigestAuth::ChallengeReceived(nsIH nsISupports** continuationState, bool* result) { nsAutoCString realm, domain, nonce, opaque; bool stale; uint16_t algorithm, qop; 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 // necessarily invalid. by returning FALSE here we can suppress username // and password prompting that usually accompanies a 401/407 challenge. *result = !stale; // clear any existing nonce_count since we have a new challenge. @@ -214,20 +234,21 @@ nsHttpDigestAuth::GenerateCredentials( if (NS_FAILED(rv)) { LOG( ("nsHttpDigestAuth::GenerateCredentials [ParseChallenge failed " "rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv))); 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 qop &= ~QOP_AUTH_INT; NS_WARNING( "no support for Digest authentication with data integrity quality of " "protection"); @@ -253,17 +274,18 @@ nsHttpDigestAuth::GenerateCredentials( nsNetwerkMD5Digest(upload_buffer, upload_buffer_length); ExpandToHex(digest, upload_data_digest); NS_RELEASE(upload); } } #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; } // // the following are for increasing security. see RFC 2617 for more // information. @@ -304,21 +326,22 @@ nsHttpDigestAuth::GenerateCredentials( // // calculate credentials // NS_ConvertUTF16toUTF8 cUser(username), cPass(password); 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; // // Values that need to match the quoted-string production from RFC 2616: // // username // realm // nonce @@ -341,16 +364,20 @@ nsHttpDigestAuth::GenerateCredentials( NS_ENSURE_SUCCESS(rv, rv); authString.AppendLiteral(", uri=\""); authString += path; if (algorithm & ALGO_SPECIFIED) { 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 { authString += '\"'; } authString.AppendLiteral(", response=\""); authString += response_digest; @@ -386,149 +413,154 @@ nsHttpDigestAuth::GetAuthFlags(uint32_t* // // NOTE: digest auth credentials must be uniquely computed for each request, // so we do not set the REUSABLE_CREDENTIALS flag. // return NS_OK; } 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; if (qop & QOP_AUTH_INT) { len += 8; // length of "auth-int" } else { len += 4; // length of "auth" } } nsAutoCString contents; contents.SetCapacity(len); - contents.Append(ha1_digest, EXPANDED_DIGEST_LENGTH); + contents.Append(ha1_digest, dhexlen); contents.Append(':'); contents.Append(nonce); contents.Append(':'); if (qop & QOP_AUTH || qop & QOP_AUTH_INT) { contents.Append(nonce_count, NONCE_COUNT_LENGTH); contents.Append(':'); contents.Append(cnonce); contents.Append(':'); if (qop & QOP_AUTH_INT) { contents.AppendLiteral("auth-int:"); } else { 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'; } else { result[index * 2] = value - 10 + 'a'; } value = digest[index] & 0xf; if (value < 10) { result[(index * 2) + 1] = value + '0'; } else { result[(index * 2) + 1] = value - 10 + 'a'; } } - result[EXPANDED_DIGEST_LENGTH] = 0; + result[2 * dlen] = 0; return NS_OK; } nsresult nsHttpDigestAuth::CalculateHA1(const nsCString& username, const nsCString& password, const nsCString& realm, 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; } nsAutoCString contents; contents.SetCapacity(len); contents.Append(username); contents.Append(':'); contents.Append(realm); contents.Append(':'); 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; contents.SetCapacity(len); contents.Assign(method); contents.Append(':'); contents.Append(path); 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; } nsresult nsHttpDigestAuth::ParseChallenge(const char* challenge, nsACString& realm, nsACString& domain, nsACString& nonce, nsACString& opaque, bool* stale, uint16_t* algorithm, uint16_t* qop) { @@ -601,16 +633,23 @@ nsresult nsHttpDigestAuth::ParseChalleng // we want to clear the default, so we use = not |= here *algorithm = ALGO_SPECIFIED; if (valueLength == 3 && nsCRT::strncasecmp(challenge + valueStart, "MD5", 3) == 0) { *algorithm |= ALGO_MD5; } 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; while (ipos < valueStart + valueLength) { while ( ipos < valueStart + valueLength && (nsCRT::IsAsciiSpace(challenge[ipos]) || challenge[ipos] == ',')) {
--- a/netwerk/protocol/http/nsHttpDigestAuth.h +++ b/netwerk/protocol/http/nsHttpDigestAuth.h @@ -15,22 +15,28 @@ #include "mozilla/StaticPtr.h" namespace mozilla { 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 //----------------------------------------------------------------------------- class nsHttpDigestAuth final : public nsIHttpAuthenticator { public: NS_DECL_ISUPPORTS @@ -38,56 +44,56 @@ class nsHttpDigestAuth final : public ns nsHttpDigestAuth() = default; static already_AddRefed<nsIHttpAuthenticator> GetOrCreate(); 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, const nsCString& realm, uint16_t algorithm, const nsCString& nonce, const nsCString& cnonce, char* result); [[nodiscard]] nsresult CalculateHA2(const nsCString& http_method, const nsCString& http_uri_path, - uint16_t qop, const char* bodyDigest, - char* result); + uint16_t algorithm, uint16_t qop, + const char* body_digest, char* result); [[nodiscard]] nsresult ParseChallenge(const char* challenge, nsACString& realm, nsACString& domain, nsACString& nonce, nsACString& opaque, bool* stale, uint16_t* algorithm, 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&); // append the quoted version of value to aHeaderLine [[nodiscard]] nsresult AppendQuotedString(const nsACString& value, nsACString& aHeaderLine); protected: nsCOMPtr<nsICryptoHash> mVerifier; - char mHashBuf[DIGEST_LENGTH]{0}; + char mHashBuf[SHA256_DIGEST_LENGTH]{0}; static StaticRefPtr<nsHttpDigestAuth> gSingleton; }; } // namespace net } // namespace mozilla #endif // nsHttpDigestAuth_h__
--- a/netwerk/test/unit/test_authentication.js +++ b/netwerk/test/unit/test_authentication.js @@ -302,17 +302,23 @@ var tests = [ test_prompt2CrossOrigin, test_returnfalse2, test_wrongpw2, test_prompt2, test_ntlm, 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, test_large_domain, test_nonascii_xhr, ]; var current_test = 0; @@ -338,17 +344,26 @@ function moveToNextTest() { function run_test() { httpserv = new HttpServer(); httpserv.registerPathHandler("/auth", authHandler); 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); httpserv.start(-1); tests[0](); } @@ -483,37 +498,97 @@ function test_nonascii_xhr() { } }; xhr.send(null); do_test_pending(); } 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 chan.asyncOpen(listener); 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 chan.asyncOpen(listener); do_test_pending(); } 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); do_test_pending(); } // Test header "WWW-Authenticate: Digest" - bug 1338876. @@ -617,80 +692,237 @@ function bytesFromString(str) { return data; } // return the two-digit hexadecimal code for a byte 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); ch.update(data, data.length); var hash = ch.finish(false); 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"); response.setHeader("WWW-Authenticate", "Digest", false); } let buildLargePayload = (function() { let size = 33 * 1024;