Bug 1058812 - mozilla::pkix: Test handling unsupported signature algorithms. r=briansmith, a=sledru
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 10 Oct 2014 12:36:11 -0700
changeset 258704 fe4f4c9342b1
parent 258703 4c62d5e8d5fc
child 258705 3f092c058c62
child 258728 bfeaec35449a
push id4700
push userryanvm@gmail.com
push date2015-04-21 23:53 +0000
treeherdermozilla-beta@d27c9211ebb3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbriansmith, sledru
bugs1058812
milestone33.0
Bug 1058812 - mozilla::pkix: Test handling unsupported signature algorithms. r=briansmith, a=sledru
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
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 LIBRARY_NAME = 'mozillapkix_gtest'
 
 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,273 @@
+/* -*- 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 "nssgtest.h"
+#include "pkix/pkix.h"
+#include "pkixgtest.h"
+#include "pkixtestutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+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,
+                                            PR_Now() - ONE_DAY,
+                                            PR_Now() + ONE_DAY,
+                                            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&,
+                              const SECItem& candidateCert,
+                              /*out*/ TrustLevel* trustLevel)
+  {
+    if (SECITEM_ItemsAreEqual(&candidateCert, &rootCert->derCert)) {
+      *trustLevel = TrustLevel::TrustAnchor;
+    } else {
+      *trustLevel = TrustLevel::InheritsTrust;
+    }
+    return Success;
+  }
+
+  virtual Result FindIssuer(const SECItem& encodedIssuerName,
+                            IssuerChecker& checker, PRTime)
+  {
+    bool keepGoing;
+    if (SECITEM_ItemsAreEqual(&encodedIssuerName, &rootCert->derSubject)) {
+      return checker.Check(rootCert->derCert, nullptr, keepGoing);
+    }
+    if (SECITEM_ItemsAreEqual(&encodedIssuerName,
+                              &intermediateCert->derSubject)) {
+      return checker.Check(intermediateCert->derCert, nullptr, keepGoing);
+    }
+    // FindIssuer just returns success if it can't find a potential issuer.
+    return Success;
+  }
+
+  virtual Result CheckRevocation(EndEntityOrCA, const CertID&, PRTime,
+                                 const SECItem*, const SECItem*)
+  {
+    return Success;
+  }
+
+  virtual Result IsChainValid(const DERArray&)
+  {
+    return Success;
+  }
+
+  virtual Result VerifySignedData(const SignedDataWithSignature& signedData,
+                                  const SECItem& subjectPublicKeyInfo)
+  {
+    EXPECT_NE(SignatureAlgorithm::unsupported_algorithm, signedData.algorithm);
+    return ::mozilla::pkix::VerifySignedData(signedData, subjectPublicKeyInfo,
+                                             nullptr);
+  }
+
+  virtual Result DigestBuf(const SECItem&, uint8_t*, size_t)
+  {
+    ADD_FAILURE();
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+
+  virtual Result CheckPublicKey(const SECItem& 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_NE(nullptr, 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;
+  ASSERT_EQ(expectedResult,
+            BuildCertChain(trustDomain, endEntityCert->derCert, PR_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
@@ -424,29 +424,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;
   ASSERT_EQ(Success, input.Init(DER, sizeof(DER)));
   SignatureAlgorithm alg;
-  ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            SignatureAlgorithmIdentifier(input, alg));
+  ASSERT_EQ(Success, SignatureAlgorithmIdentifier(input, 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;
   ASSERT_EQ(Success, input.Init(DER, sizeof(DER)));
   SignatureAlgorithm alg;
-  ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            SignatureAlgorithmIdentifier(input, alg));
+  ASSERT_EQ(Success, SignatureAlgorithmIdentifier(input, 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
@@ -245,31 +245,33 @@ public:
 
   SECItem* CreateEncodedOCSPSuccessfulResponse(
                     OCSPResponseContext::CertStatus certStatus,
                     const CertID& certID,
                     /*optional*/ const char* signerName,
                     const ScopedSECKEYPrivateKey& signerPrivateKey,
                     PRTime producedAt, PRTime thisUpdate,
                     /*optional*/ const PRTime* 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);
       if (!context.signerNameDER) {
         return nullptr;
       }
     }
     context.signerPrivateKey = SECKEY_CopyPrivateKey(signerPrivateKey.get());
     if (!context.signerPrivateKey) {
       return nullptr;
     }
     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;
 
@@ -347,16 +349,32 @@ 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)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, byKey,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow, SEC_OID_MD5));
+  ASSERT_TRUE(response);
+  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
@@ -364,41 +382,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
   SECItem* CreateEncodedIndirectOCSPSuccessfulResponse(
               const char* certSubjectName,
               OCSPResponseContext::CertStatus certStatus,
               const char* signerName,
+              SECOidTag certSignatureAlgorithm,
               SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER,
               /*optional, out*/ const SECItem** signerDEROut = nullptr)
   {
     PR_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 (!signerDER) {
       return nullptr;
     }
 
@@ -411,21 +434,23 @@ protected:
     }
     if (signerDEROut) {
       *signerDEROut = signerDER;
     }
     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,
                                            PRTime notBefore,
                                            PRTime notAfter,
                                            const char* subject,
                               /*optional*/ SECItem const* const* extensions,
                               /*optional*/ SECKEYPrivateKey* signerKey,
                                    /*out*/ ScopedSECKEYPrivateKey& privateKey)
   {
@@ -437,44 +462,52 @@ 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)
 {
   SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
                       "CN=good_indirect_byKey", OCSPResponseContext::good,
-                      byKey));
+                      byKey, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION));
   ASSERT_TRUE(response);
   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)
 {
   SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
                       "CN=good_indirect_byName", OCSPResponseContext::good,
-                      "CN=good_indirect_byName"));
+                      "CN=good_indirect_byName",
+                      SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION));
   ASSERT_TRUE(response);
   bool expired;
   ASSERT_EQ(Success,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       *response, expired));
   ASSERT_FALSE(expired);
 }
@@ -526,30 +559,30 @@ 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 * ONE_DAY),
                                               now - (2 * ONE_DAY),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   SECItem* response(CreateEncodedOCSPSuccessfulResponse(
                       OCSPResponseContext::good, *endEntityCertID, signerName,
                       signerPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                      &oneDayAfterNow,
-                      certs));
+                      &oneDayAfterNow, SEC_OID_SHA1, certs));
   ASSERT_TRUE(response);
 
   bool expired;
   ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
             VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
                                       END_ENTITY_MAX_LIFETIME_IN_DAYS,
                                       *response, expired));
 }
@@ -561,74 +594,78 @@ 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 * ONE_DAY),
                                               now + (10 * ONE_DAY),
                                               signerName, extensions,
                                               rootPrivateKey.get(),
                                               signerPrivateKey));
   ASSERT_TRUE(signerDER);
 
   SECItem const* const certs[] = { signerDER, nullptr };
   SECItem* response(CreateEncodedOCSPSuccessfulResponse(
                       OCSPResponseContext::good, *endEntityCertID,
                       signerName, signerPrivateKey, oneDayBeforeNow,
-                      oneDayBeforeNow, &oneDayAfterNow, certs));
+                      oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1, certs));
   ASSERT_TRUE(response);
 
   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)
 {
   SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
                       "CN=good_indirect_wrong_eku", OCSPResponseContext::good,
-                      byKey, SEC_OID_UNKNOWN));
+                      byKey, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                      SEC_OID_UNKNOWN));
   ASSERT_TRUE(response);
   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)
 {
   SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
                       "CN=good_indirect_wrong_eku", OCSPResponseContext::good,
-                      byKey, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
+                      byKey, SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                      SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
   ASSERT_TRUE(response);
   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)
 {
   SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
                       "CN=good_indirect_tampered_eku",
                       OCSPResponseContext::good, byKey,
+                      SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                       SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
   ASSERT_TRUE(response);
 
 #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
@@ -660,27 +697,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 };
   SECItem* response(CreateEncodedOCSPSuccessfulResponse(
                         OCSPResponseContext::good, *endEntityCertID,
                         signerName, signerPrivateKey, oneDayBeforeNow,
-                        oneDayBeforeNow, &oneDayAfterNow, certs));
+                        oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1, certs));
   ASSERT_TRUE(response);
 
   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);
@@ -698,46 +736,48 @@ 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 };
   SECItem* response(CreateEncodedOCSPSuccessfulResponse(
                         OCSPResponseContext::good, *endEntityCertID, signerName,
                         signerPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
-                        &oneDayAfterNow,
-                        certs));
+                        &oneDayAfterNow, SEC_OID_SHA1, certs));
   ASSERT_TRUE(response);
 
   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);
@@ -755,70 +795,92 @@ 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 };
   SECItem* response(CreateEncodedOCSPSuccessfulResponse(
                         OCSPResponseContext::good, *endEntityCertID,
                         signerName, signerPrivateKey, oneDayBeforeNow,
-                        oneDayBeforeNow, &oneDayAfterNow, certs));
+                        oneDayBeforeNow, &oneDayAfterNow, SEC_OID_SHA1, certs));
   ASSERT_TRUE(response);
 
   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.
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_unsupportedSignatureAlgorithm",
+                      OCSPResponseContext::good, byKey,
+                      SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION));
+  ASSERT_TRUE(response);
+  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:
   pkixocsp_VerifyEncodedResponse_GetCertTrust()
     : signerCertDER(nullptr)
     , response(nullptr)
   {
   }
 
   void SetUp()
   {
     pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp();
     response = CreateEncodedIndirectOCSPSuccessfulResponse(
                           "CN=OCSPGetCertTrustTest Signer",
                           OCSPResponseContext::good, byKey,
+                          SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
                           SEC_OID_OCSP_RESPONDER, &signerCertDER);
     if (!response || !signerCertDER) {
       PR_Abort();
     }
   }
 
   class TrustDomain : public OCSPTestTrustDomain
   {
--- a/security/pkix/test/lib/pkixtestutil.cpp
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -238,16 +238,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 * PR_USEC_PER_SEC)
@@ -1089,17 +1090,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
@@ -184,16 +184,17 @@ public:
 
   PRTime 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,