Bug 472823 - support SHA-256 HTTP Digest auth r=necko-reviewers,dragana
authorGlenn Strauss <gstrauss@gluelogic.com>
Wed, 26 May 2021 09:27:16 +0000
changeset 580736 27e709923ecb82f8f35640a504e6b62962ff9f2c
parent 580735 6e12f7dbc39201a14ef3c9acd9635cf763e59860
child 580737 b6b51bc167ac7051caad6804327b69da527f5fa4
push id143767
push uservalentin.gosu@gmail.com
push dateWed, 26 May 2021 09:30:19 +0000
treeherderautoland@d920aa17a468 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnecko-reviewers, dragana
bugs472823, 281851, 669675
milestone90.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
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
netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
netwerk/protocol/http/nsHttpDigestAuth.cpp
netwerk/protocol/http/nsHttpDigestAuth.h
netwerk/test/unit/test_authentication.js
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.cpp
@@ -499,16 +499,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
@@ -522,24 +710,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);
+    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)) {
       //
       // 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.
@@ -569,17 +757,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;
   authString += '\"';
@@ -385,145 +412,150 @@ 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) {
@@ -595,16 +627,22 @@ 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] == ','))
           ipos++;
         int32_t algostart = 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* 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,
                                         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];
+  char mHashBuf[SHA256_DIGEST_LENGTH];
 
   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;