Bug 1058812 - mozilla::pkix: Test handling unsupported signature algorithms. r=briansmith, a=lmandel
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 10 Oct 2014 12:36:11 -0700
changeset 225713 a7b8a4567262
parent 225712 2535e75ff9c6
child 225714 6524ec11ce53
push id3988
push userryanvm@gmail.com
push date2014-10-17 01:37 +0000
treeherdermozilla-beta@c3fa7201e034 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbriansmith, lmandel
bugs1058812
milestone34.0
Bug 1058812 - mozilla::pkix: Test handling unsupported signature algorithms. r=briansmith, a=lmandel
security/pkix/test/gtest/moz.build
security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
security/pkix/test/gtest/pkixder_pki_types_tests.cpp
security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
security/pkix/test/lib/pkixtestutil.cpp
security/pkix/test/lib/pkixtestutil.h
--- a/security/pkix/test/gtest/moz.build
+++ b/security/pkix/test/gtest/moz.build
@@ -3,16 +3,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 SOURCES += [
     'nssgtest.cpp',
     'pkixbuild_tests.cpp',
     'pkixcert_extension_tests.cpp',
+    'pkixcert_signature_algorithm_tests.cpp',
     'pkixcheck_CheckKeyUsage_tests.cpp',
     'pkixcheck_CheckValidity_tests.cpp',
 
     # The naming conventions are described in ./README.txt.
 
     'pkixder_input_tests.cpp',
     'pkixder_pki_types_tests.cpp',
     'pkixder_universal_types_tests.cpp',
new file mode 100644
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "cert.h"
+#include "nssgtest.h"
+#include "pkix/pkix.h"
+#include "pkixgtest.h"
+#include "pkixtestutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+typedef ScopedPtr<CERTCertificate, CERT_DestroyCertificate>
+          ScopedCERTCertificate;
+
+static bool
+CreateCert(PLArenaPool* arena, const char* issuerCN,
+           const char* subjectCN, EndEntityOrCA endEntityOrCA,
+           SECOidTag signatureAlgorithm,
+           /*optional*/ SECKEYPrivateKey* issuerKey,
+           /*out*/ ScopedSECKEYPrivateKey& subjectKey,
+           /*out*/ ScopedCERTCertificate& subjectCert)
+{
+  static long serialNumberValue = 0;
+  ++serialNumberValue;
+  const SECItem* serialNumber(CreateEncodedSerialNumber(arena,
+                                                        serialNumberValue));
+  if (!serialNumber) {
+    return false;
+  }
+
+  const SECItem* issuerDER(ASCIIToDERName(arena, issuerCN));
+  if (!issuerDER) {
+    return false;
+  }
+  const SECItem* subjectDER(ASCIIToDERName(arena, subjectCN));
+  if (!subjectDER) {
+    return false;
+  }
+
+  const SECItem* extensions[2] = { nullptr, nullptr };
+  if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+    extensions[0] =
+      CreateEncodedBasicConstraints(arena, true, nullptr,
+                                    ExtensionCriticality::Critical);
+    if (!extensions[0]) {
+      return false;
+    }
+  }
+
+  SECOidTag signatureHashAlgorithm = SEC_OID_UNKNOWN;
+  if (signatureAlgorithm == SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION) {
+    signatureHashAlgorithm = SEC_OID_SHA256;
+  } else if (signatureAlgorithm == SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION) {
+    signatureHashAlgorithm = SEC_OID_MD5;
+  } else if (signatureAlgorithm == SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION) {
+    signatureHashAlgorithm = SEC_OID_MD2;
+  }
+
+  SECItem* certDER(CreateEncodedCertificate(arena, v3, signatureAlgorithm,
+                                            serialNumber, issuerDER,
+                                            oneDayBeforeNow,
+                                            oneDayAfterNow,
+                                            subjectDER, extensions,
+                                            issuerKey, signatureHashAlgorithm,
+                                            subjectKey));
+  if (!certDER) {
+    return false;
+  }
+  subjectCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), certDER,
+                                        nullptr, false, true);
+  return subjectCert.get() != nullptr;
+}
+
+class AlgorithmTestsTrustDomain : public TrustDomain
+{
+public:
+  AlgorithmTestsTrustDomain(CERTCertificate* rootCert,
+                            CERTCertificate* intermediateCert)
+    : rootCert(rootCert)
+    , intermediateCert(intermediateCert)
+  {
+  }
+
+private:
+  virtual Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+                              Input candidateCert,
+                              /*out*/ TrustLevel& trustLevel)
+  {
+    Input rootDER;
+    Result rv = rootDER.Init(rootCert->derCert.data,
+                             rootCert->derCert.len);
+    EXPECT_EQ(Success, rv);
+    if (InputsAreEqual(candidateCert, rootDER)) {
+      trustLevel = TrustLevel::TrustAnchor;
+    } else {
+      trustLevel = TrustLevel::InheritsTrust;
+    }
+    return Success;
+  }
+
+  virtual Result FindIssuer(Input encodedIssuerName,
+                            IssuerChecker& checker, Time time)
+  {
+    bool keepGoing;
+    Input rootSubject;
+    Result rv = rootSubject.Init(rootCert->derSubject.data,
+                                 rootCert->derSubject.len);
+    EXPECT_EQ(Success, rv);
+    if (InputsAreEqual(encodedIssuerName, rootSubject)) {
+      Input rootDER;
+      rv = rootDER.Init(rootCert->derCert.data, rootCert->derCert.len);
+      EXPECT_EQ(Success, rv);
+      return checker.Check(rootDER, nullptr, keepGoing);
+    }
+
+    Input intermediateSubject;
+    rv = intermediateSubject.Init(intermediateCert->derSubject.data,
+                                  intermediateCert->derSubject.len);
+    EXPECT_EQ(Success, rv);
+    if (InputsAreEqual(encodedIssuerName, intermediateSubject)) {
+      Input intermediateDER;
+      rv = intermediateDER.Init(intermediateCert->derCert.data,
+                                intermediateCert->derCert.len);
+      EXPECT_EQ(Success, rv);
+      return checker.Check(intermediateDER, nullptr, keepGoing);
+    }
+
+    // FindIssuer just returns success if it can't find a potential issuer.
+    return Success;
+  }
+
+  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+                                 /*optional*/ const Input*,
+                                 /*optional*/ const Input*)
+  {
+    return Success;
+  }
+
+  virtual Result IsChainValid(const DERArray&)
+  {
+    return Success;
+  }
+
+  virtual Result VerifySignedData(const SignedDataWithSignature& signedData,
+                                  Input subjectPublicKeyInfo)
+  {
+    EXPECT_NE(SignatureAlgorithm::unsupported_algorithm, signedData.algorithm);
+    return ::mozilla::pkix::VerifySignedData(signedData, subjectPublicKeyInfo,
+                                             nullptr);
+  }
+
+  virtual Result DigestBuf(Input, uint8_t*, size_t)
+  {
+    ADD_FAILURE();
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+
+  virtual Result CheckPublicKey(Input subjectPublicKeyInfo)
+  {
+    return ::mozilla::pkix::CheckPublicKey(subjectPublicKeyInfo);
+  }
+
+  ScopedCERTCertificate rootCert;
+  ScopedCERTCertificate intermediateCert;
+};
+
+static const SECOidTag NO_INTERMEDIATE = SEC_OID_UNKNOWN;
+
+struct ChainValidity
+{
+  // In general, a certificate is generated for each of these.  However, if
+  // optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2
+  // certificates are generated.
+  // The certificate generated for the given rootSignatureAlgorithm is the
+  // trust anchor.
+  SECOidTag endEntitySignatureAlgorithm;
+  SECOidTag optionalIntermediateSignatureAlgorithm;
+  SECOidTag rootSignatureAlgorithm;
+  bool isValid;
+};
+
+static const ChainValidity CHAIN_VALIDITY[] =
+{
+  // The trust anchor may have a signature with an unsupported signature
+  // algorithm.
+  { SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    NO_INTERMEDIATE,
+    SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
+    true
+  },
+  { SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    NO_INTERMEDIATE,
+    SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION,
+    true
+  },
+
+  // Certificates that are not trust anchors must not have a signature with an
+  // unsupported signature algorithm.
+  { SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
+    NO_INTERMEDIATE,
+    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    false
+  },
+  { SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION,
+    NO_INTERMEDIATE,
+    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    false
+  },
+  { SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION,
+    NO_INTERMEDIATE,
+    SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
+    false
+  },
+  { SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    false
+  },
+  { SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    false
+  },
+  { SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION,
+    SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
+    false
+  },
+};
+
+class pkixcert_IsValidChainForAlgorithm
+  : public NSSTest
+  , public ::testing::WithParamInterface<ChainValidity>
+{
+public:
+  static void SetUpTestCase()
+  {
+    NSSTest::SetUpTestCase();
+  }
+};
+
+TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm)
+{
+  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+  ASSERT_TRUE(arena.get());
+
+  const ChainValidity& chainValidity(GetParam());
+  const char* rootCN = "CN=Root";
+  ScopedSECKEYPrivateKey rootKey;
+  ScopedCERTCertificate rootCert;
+  EXPECT_TRUE(CreateCert(arena.get(), rootCN, rootCN, EndEntityOrCA::MustBeCA,
+                         chainValidity.rootSignatureAlgorithm, nullptr,
+                         rootKey, rootCert));
+
+  const char* issuerCN = rootCN;
+
+  const char* intermediateCN = "CN=Intermediate";
+  SECKEYPrivateKey* issuerKey = rootKey.get();
+  ScopedSECKEYPrivateKey intermediateKey;
+  ScopedCERTCertificate intermediateCert;
+  SECOidTag intermediateSignatureAlgorithm =
+    chainValidity.optionalIntermediateSignatureAlgorithm;
+  if (intermediateSignatureAlgorithm != NO_INTERMEDIATE) {
+    EXPECT_TRUE(CreateCert(arena.get(), rootCN, intermediateCN,
+                           EndEntityOrCA::MustBeCA,
+                           intermediateSignatureAlgorithm,
+                           rootKey.get(), intermediateKey, intermediateCert));
+    issuerCN = intermediateCN;
+    issuerKey = intermediateKey.get();
+  }
+
+  AlgorithmTestsTrustDomain trustDomain(rootCert.release(),
+                                        intermediateCert.release());
+
+  const char* endEntityCN = "CN=End Entity";
+  ScopedSECKEYPrivateKey unusedEndEntityKey;
+  ScopedCERTCertificate endEntityCert;
+  EXPECT_TRUE(CreateCert(arena.get(), issuerCN, endEntityCN,
+                         EndEntityOrCA::MustBeEndEntity,
+                         chainValidity.endEntitySignatureAlgorithm,
+                         issuerKey, unusedEndEntityKey, endEntityCert));
+  Result expectedResult = chainValidity.isValid
+                        ? Success
+                        : Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+  Input endEntityDER;
+  EXPECT_EQ(Success, endEntityDER.Init(endEntityCert->derCert.data,
+                                       endEntityCert->derCert.len));
+  ASSERT_EQ(expectedResult,
+            BuildCertChain(trustDomain, endEntityDER, Now(),
+                           EndEntityOrCA::MustBeEndEntity,
+                           KeyUsage::noParticularKeyUsageRequired,
+                           KeyPurposeId::id_kp_serverAuth,
+                           CertPolicyId::anyPolicy, nullptr));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm,
+                        pkixcert_IsValidChainForAlgorithm,
+                        testing::ValuesIn(CHAIN_VALIDITY));
--- a/security/pkix/test/gtest/pkixder_pki_types_tests.cpp
+++ b/security/pkix/test/gtest/pkixder_pki_types_tests.cpp
@@ -412,29 +412,29 @@ TEST_F(pkixder_SignatureAlgorithmIdentif
   static const uint8_t DER[] = {
     0x30, 0x0b, 0x06, 0x09,
     0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04
   };
   Input input(DER);
   Reader reader(input);
 
   SignatureAlgorithm alg;
-  ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            SignatureAlgorithmIdentifier(reader, alg));
+  ASSERT_EQ(Success, SignatureAlgorithmIdentifier(reader, alg));
+  ASSERT_EQ(SignatureAlgorithm::unsupported_algorithm, alg);
 }
 
 TEST_F(pkixder_SignatureAlgorithmIdentifier, Invalid_SignatureAlgorithm_SHA256)
 {
   // The OID identifies id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid
   // because SHA-256 is not a signature algorithm.
   static const uint8_t DER[] = {
     0x30, 0x0b, 0x06, 0x09,
     0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
   };
   Input input(DER);
   Reader reader(input);
 
   SignatureAlgorithm alg;
-  ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            SignatureAlgorithmIdentifier(reader, alg));
+  ASSERT_EQ(Success, SignatureAlgorithmIdentifier(reader, alg));
+  ASSERT_EQ(SignatureAlgorithm::unsupported_algorithm, alg);
 }
 
 } // unnamed namespace
--- a/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
+++ b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -260,27 +260,29 @@ public:
   // The result is owned by the arena
   Input CreateEncodedOCSPSuccessfulResponse(
                     OCSPResponseContext::CertStatus certStatus,
                     const CertID& certID,
                     /*optional*/ const char* signerName,
                     const ScopedSECKEYPrivateKey& signerPrivateKey,
                     time_t producedAt, time_t thisUpdate,
                     /*optional*/ const time_t* nextUpdate,
+                    /*optional*/ SECOidTag signatureHashAlgorithm = SEC_OID_SHA1,
                     /*optional*/ SECItem const* const* certs = nullptr)
   {
     OCSPResponseContext context(arena.get(), certID, producedAt);
     if (signerName) {
       context.signerNameDER = ASCIIToDERName(arena.get(), signerName);
       EXPECT_TRUE(context.signerNameDER);
     }
     context.signerPrivateKey = SECKEY_CopyPrivateKey(signerPrivateKey.get());
     EXPECT_TRUE(context.signerPrivateKey);
     context.responseStatus = OCSPResponseContext::successful;
     context.producedAt = producedAt;
+    context.signatureHashAlgorithm = signatureHashAlgorithm;
     context.certs = certs;
 
     context.certIDHashAlg = SEC_OID_SHA1;
     context.certStatus = certStatus;
     context.thisUpdate = thisUpdate;
     context.nextUpdate = nextUpdate ? *nextUpdate : 0;
     context.includeNextUpdate = nextUpdate != nullptr;
 
@@ -357,16 +359,31 @@ TEST_F(pkixocsp_VerifyEncodedResponse_su
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
+TEST_F(pkixocsp_VerifyEncodedResponse_successful,
+       good_unsupportedSignatureAlgorithm)
+{
+  Input response(CreateEncodedOCSPSuccessfulResponse(
+                         OCSPResponseContext::good, *endEntityCertID, byKey,
+                         rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                         &oneDayAfterNow, SEC_OID_MD5));
+  bool expired;
+  ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+            VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+                                      END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                      response, expired));
+  ASSERT_FALSE(expired);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // indirect responses (signed by a delegated OCSP responder cert)
 
 class pkixocsp_VerifyEncodedResponse_DelegatedResponder
   : public pkixocsp_VerifyEncodedResponse_successful
 {
 protected:
   // certSubjectName should be unique for each call. This way, we avoid any
@@ -374,41 +391,46 @@ protected:
   // we generate a new keypair on each call. Either one of these should be
   // sufficient to avoid issues with the NSS cache, but we do both to be
   // cautious.
   //
   // signerName should be byKey to use the byKey ResponderID construction, or
   // another value (usually equal to certSubjectName) to use the byName
   // ResponderID construction.
   //
+  // certSignatureAlgorithm specifies the signature algorithm that the
+  // certificate will be signed with, not the OCSP response.
+  //
   // If signerEKU is omitted, then the certificate will have the
   // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not
   // have any EKU extension. Otherwise, the certificate will have the given
   // EKU.
   //
   // signerDEROut is owned by the arena
   Input CreateEncodedIndirectOCSPSuccessfulResponse(
               const char* certSubjectName,
               OCSPResponseContext::CertStatus certStatus,
               const char* signerName,
+              SECOidTag certSignatureAlgorithm,
               SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER,
               /*optional, out*/ Input* signerDEROut = nullptr)
   {
     assert(certSubjectName);
 
     const SECItem* extensions[] = {
       signerEKU != SEC_OID_UNKNOWN
         ? CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                                     ExtensionCriticality::NotCritical)
         : nullptr,
       nullptr
     };
     ScopedSECKEYPrivateKey signerPrivateKey;
     SECItem* signerDER(CreateEncodedCertificate(
-                          arena.get(), ++rootIssuedCount, rootName,
+                          arena.get(), ++rootIssuedCount,
+                          certSignatureAlgorithm, rootName,
                           oneDayBeforeNow, oneDayAfterNow, certSubjectName,
                           signerEKU != SEC_OID_UNKNOWN ? extensions : nullptr,
                           rootPrivateKey.get(), signerPrivateKey));
     EXPECT_TRUE(signerDER);
     if (signerDEROut) {
       EXPECT_EQ(Success,
                 signerDEROut->Init(signerDER->data, signerDER->len));
     }
@@ -418,21 +440,23 @@ protected:
       signerNameDER = ASCIIToDERName(arena.get(), signerName);
       EXPECT_TRUE(signerNameDER);
     }
     SECItem const* const certs[] = { signerDER, nullptr };
     return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID,
                                                signerName, signerPrivateKey,
                                                oneDayBeforeNow,
                                                oneDayBeforeNow,
-                                               &oneDayAfterNow, certs);
+                                               &oneDayAfterNow, SEC_OID_SHA1,
+                                               certs);
   }
 
   static SECItem* CreateEncodedCertificate(PLArenaPool* arena,
                                            uint32_t serialNumber,
+                                           SECOidTag signatureAlgorithm,
                                            const char* issuer,
                                            time_t notBefore,
                                            time_t notAfter,
                                            const char* subject,
                               /*optional*/ SECItem const* const* extensions,
                               /*optional*/ SECKEYPrivateKey* signerKey,
                                    /*out*/ ScopedSECKEYPrivateKey& privateKey)
   {
@@ -444,43 +468,51 @@ protected:
     const SECItem* issuerDER(ASCIIToDERName(arena, issuer));
     if (!issuerDER) {
       return nullptr;
     }
     const SECItem* subjectDER(ASCIIToDERName(arena, subject));
     if (!subjectDER) {
       return nullptr;
     }
+    SECOidTag signatureHashAlgorithm = SEC_OID_UNKNOWN;
+    if (signatureAlgorithm == SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION) {
+      signatureHashAlgorithm = SEC_OID_SHA256;
+    } else if (signatureAlgorithm == SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION) {
+      signatureHashAlgorithm = SEC_OID_MD5;
+    }
     return ::mozilla::pkix::test::CreateEncodedCertificate(
                                     arena, v3,
-                                    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                                    signatureAlgorithm,
                                     serialNumberDER, issuerDER, notBefore,
                                     notAfter, subjectDER, extensions,
-                                    signerKey, SEC_OID_SHA256, privateKey);
+                                    signerKey, signatureHashAlgorithm,
+                                    privateKey);
   }
 };
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey)
 {
   Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
                          "CN=good_indirect_byKey", OCSPResponseContext::good,
-                         byKey));
+                         byKey, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION));
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName)
 {
   Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
                          "CN=good_indirect_byName", OCSPResponseContext::good,
-                         "CN=good_indirect_byName"));
+                         "CN=good_indirect_byName",
+                         SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION));
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -530,29 +562,31 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
 
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                              SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                                               rootName,
                                               now - (10 * Time::ONE_DAY_IN_SECONDS),
                                               now - (2 * Time::ONE_DAY_IN_SECONDS),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1,
+                         certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future)
@@ -563,71 +597,77 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
 
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                              SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                                               rootName,
                                               now + (2 * Time::ONE_DAY_IN_SECONDS),
                                               now + (10 * Time::ONE_DAY_IN_SECONDS),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1,
+                         certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku)
 {
   Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
                          "CN=good_indirect_wrong_eku",
-                         OCSPResponseContext::good, byKey, SEC_OID_UNKNOWN));
+                         OCSPResponseContext::good, byKey,
+                         SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                         SEC_OID_UNKNOWN));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
        good_indirect_wrong_eku)
 {
   Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
                         "CN=good_indirect_wrong_eku",
                         OCSPResponseContext::good, byKey,
+                        SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                         SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
 // Test that signature of OCSP response signer cert is verified
 TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku)
 {
   Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
                          "CN=good_indirect_tampered_eku",
                          OCSPResponseContext::good, byKey,
+                         SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                          SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
 
 #define EKU_PREFIX \
   0x06, 8, /* OBJECT IDENTIFIER, 8 bytes */ \
   0x2B, 6, 1, 5, 5, 7, /* id-pkix */ \
   0x03 /* id-kp */
   static const uint8_t EKU_SERVER_AUTH[] = { EKU_PREFIX, 0x01 }; // serverAuth
   static const uint8_t EKU_OCSP_SIGNER[] = { EKU_PREFIX, 0x09 }; // OCSPSigning
@@ -660,27 +700,28 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
   SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1,
+                        SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                         subCAName, oneDayBeforeNow, oneDayAfterNow,
                         signerName, extensions, unknownPrivateKey.get(),
                         signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by that delegated responder
   SECItem const* const certs[] = { signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1, certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -696,45 +737,49 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   // sub-CA of root (root is the direct issuer of endEntity)
   const SECItem* subCAExtensions[] = {
     CreateEncodedBasicConstraints(arena.get(), true, 0,
                                   ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey subCAPrivateKey;
   SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                             SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                                              rootName,
                                              oneDayBeforeNow, oneDayAfterNow,
                                              subCAName, subCAExtensions,
                                              rootPrivateKey.get(),
                                              subCAPrivateKey));
   ASSERT_TRUE(subCADER);
 
   // Delegated responder cert signed by that sub-CA
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
-  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1,
+                                              SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                                              subCAName,
                                               oneDayBeforeNow, oneDayAfterNow,
                                               signerName, extensions,
                                               subCAPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by the delegated responder issued by the sub-CA
   // that is trying to impersonate the root.
   SECItem const* const certs[] = { subCADER, signerDER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1,
+                         certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
@@ -750,65 +795,87 @@ TEST_F(pkixocsp_VerifyEncodedResponse_De
   // sub-CA of root (root is the direct issuer of endEntity)
   const SECItem* subCAExtensions[] = {
     CreateEncodedBasicConstraints(arena.get(), true, 0,
                                   ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey subCAPrivateKey;
   SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                             SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                                              rootName,
                                              oneDayBeforeNow, oneDayAfterNow,
                                              subCAName, subCAExtensions,
                                              rootPrivateKey.get(),
                                              subCAPrivateKey));
   ASSERT_TRUE(subCADER);
 
   // Delegated responder cert signed by that sub-CA
   static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
   const SECItem* extensions[] = {
     CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
                               ExtensionCriticality::NotCritical),
     nullptr
   };
   ScopedSECKEYPrivateKey signerPrivateKey;
-  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1,
+                                              SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                                              subCAName,
                                               oneDayBeforeNow, oneDayAfterNow,
                                               signerName, extensions,
                                               subCAPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   // OCSP response signed by the delegated responder issued by the sub-CA
   // that is trying to impersonate the root.
   SECItem const* const certs[] = { signerDER, subCADER, nullptr };
   Input response(CreateEncodedOCSPSuccessfulResponse(
                          OCSPResponseContext::good, *endEntityCertID,
                          signerName, signerPrivateKey, oneDayBeforeNow,
-                         oneDayBeforeNow, &oneDayAfterNow, certs));
+                         oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1,
+                         certs));
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       response, expired));
   ASSERT_FALSE(expired);
 }
 
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_unsupportedSignatureAlgorithmOnResponder)
+{
+  // Note that the algorithm ID (md5WithRSAEncryption) identifies the signature
+  // algorithm that will be used to sign the certificate that issues the
+  // OCSP responses, not the responses themselves.
+  Input response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                         "CN=good_indirect_unsupportedSignatureAlgorithm",
+                         OCSPResponseContext::good, byKey,
+                         SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION));
+  bool expired;
+  ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+            VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+                                      END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                      response, expired));
+}
+
 class pkixocsp_VerifyEncodedResponse_GetCertTrust
   : public pkixocsp_VerifyEncodedResponse_DelegatedResponder {
 public:
   void SetUp()
   {
     pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp();
 
     Input
       createdResponse(
         CreateEncodedIndirectOCSPSuccessfulResponse(
           "CN=OCSPGetCertTrustTest Signer", OCSPResponseContext::good,
-          byKey, SEC_OID_OCSP_RESPONDER, &signerCertDER));
+          byKey, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+          SEC_OID_OCSP_RESPONDER, &signerCertDER));
     if (response.Init(createdResponse) != Success) {
       abort();
     }
 
     if (response.GetLength() == 0 || signerCertDER.GetLength() == 0) {
       abort();
     }
   }
--- a/security/pkix/test/lib/pkixtestutil.cpp
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -240,16 +240,17 @@ OCSPResponseContext::OCSPResponseContext
   : arena(arena)
   , certID(certID)
   , responseStatus(successful)
   , skipResponseBytes(false)
   , signerNameDER(nullptr)
   , producedAt(time)
   , extensions(nullptr)
   , includeEmptyExtensions(false)
+  , signatureHashAlgorithm(SEC_OID_SHA1)
   , badSignature(false)
   , certs(nullptr)
 
   , certIDHashAlg(SEC_OID_SHA1)
   , certStatus(good)
   , revocationTime(0)
   , thisUpdate(time)
   , nextUpdate(time + 10)
@@ -1141,17 +1142,18 @@ BasicOCSPResponse(OCSPResponseContext& c
 {
   SECItem* tbsResponseData = ResponseData(context);
   if (!tbsResponseData) {
     return nullptr;
   }
 
   // TODO(bug 980538): certs
   return SignedData(context.arena, tbsResponseData,
-                    context.signerPrivateKey.get(), SEC_OID_SHA1,
+                    context.signerPrivateKey.get(),
+                    context.signatureHashAlgorithm,
                     context.badSignature, context.certs);
 }
 
 // Extension ::= SEQUENCE {
 //   id               OBJECT IDENTIFIER,
 //   critical         BOOLEAN DEFAULT FALSE
 //   value            OCTET STRING
 // }
--- a/security/pkix/test/lib/pkixtestutil.h
+++ b/security/pkix/test/lib/pkixtestutil.h
@@ -189,16 +189,17 @@ public:
 
   std::time_t producedAt;
 
   OCSPResponseExtension* extensions;
   bool includeEmptyExtensions; // If true, include the extension wrapper
                                // regardless of if there are any actual
                                // extensions.
   ScopedSECKEYPrivateKey signerPrivateKey;
+  SECOidTag signatureHashAlgorithm;
   bool badSignature; // If true, alter the signature to fail verification
   SECItem const* const* certs; // non-owning pointer to certs to embed
 
   // The following fields are on a per-SingleResponse basis. In the future we
   // may support including multiple SingleResponses per response.
   SECOidTag certIDHashAlg;
   enum CertStatus {
     good = 0,