summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDana Keeler <dkeeler@mozilla.com>2021-05-18 17:30:21 +0000
committerDana Keeler <dkeeler@mozilla.com>2021-05-18 17:30:21 +0000
commit1afcaab5dbb8816221287d880fe4ec85e8b4b24e (patch)
treeb956a3afc900a2b3927797aa4c9ecafe2a2aa229
parent1eaaf9b21673a9e62355a8efe452e50f35dd712d (diff)
downloadnss-hg-1afcaab5dbb8816221287d880fe4ec85e8b4b24e.tar.gz
Bug 1709291 - add VerifyCodeSigningCertificateChain r=bbeurdouche
Differential Revision: https://phabricator.services.mozilla.com/D114660
-rw-r--r--gtests/mozpkix_gtest/mozpkix_gtest.gyp1
-rw-r--r--gtests/mozpkix_gtest/pkixc_tests.cpp182
-rw-r--r--gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp23
-rw-r--r--gtests/mozpkix_gtest/pkixgtest.h27
-rw-r--r--lib/mozpkix/exports.gyp3
-rw-r--r--lib/mozpkix/include/pkix/pkixc.h47
-rw-r--r--lib/mozpkix/lib/pkixc.cpp218
-rw-r--r--lib/mozpkix/mozpkix.gyp1
8 files changed, 475 insertions, 27 deletions
diff --git a/gtests/mozpkix_gtest/mozpkix_gtest.gyp b/gtests/mozpkix_gtest/mozpkix_gtest.gyp
index 1623d76bb..80c348888 100644
--- a/gtests/mozpkix_gtest/mozpkix_gtest.gyp
+++ b/gtests/mozpkix_gtest/mozpkix_gtest.gyp
@@ -13,6 +13,7 @@
'sources': [
'<(DEPTH)/gtests/common/gtests.cc',
'pkixbuild_tests.cpp',
+ 'pkixc_tests.cpp',
'pkixcert_extension_tests.cpp',
'pkixcert_signature_algorithm_tests.cpp',
'pkixcheck_CheckExtendedKeyUsage_tests.cpp',
diff --git a/gtests/mozpkix_gtest/pkixc_tests.cpp b/gtests/mozpkix_gtest/pkixc_tests.cpp
new file mode 100644
index 000000000..5d79aeb23
--- /dev/null
+++ b/gtests/mozpkix_gtest/pkixc_tests.cpp
@@ -0,0 +1,182 @@
+/* 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/.
+ */
+
+#include "pkixgtest.h"
+
+#include "mozpkix/pkixc.h"
+#include "mozpkix/pkixder.h"
+#include "mozpkix/pkixnss.h"
+#include "secerr.h"
+#include "sslerr.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static ByteString CreateCert(
+ const char* issuerCN, const char* subjectCN, EndEntityOrCA endEntityOrCA,
+ /*optional*/ const ByteString* subjectAlternativeNameExtension = nullptr,
+ /*optional*/ const ByteString* extendedKeyUsageExtension = nullptr) {
+ EXPECT_TRUE(issuerCN);
+ EXPECT_TRUE(subjectCN);
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(CNToDERName(issuerCN));
+ ByteString subjectDER(CNToDERName(subjectCN));
+
+ std::time_t notBefore = 1620000000;
+ std::time_t notAfter = 1630000000;
+
+ std::vector<ByteString> extensions;
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ ByteString basicConstraints =
+ CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
+ extensions.push_back(basicConstraints);
+ }
+ if (subjectAlternativeNameExtension) {
+ extensions.push_back(*subjectAlternativeNameExtension);
+ }
+ if (extendedKeyUsageExtension) {
+ extensions.push_back(*extendedKeyUsageExtension);
+ }
+ extensions.push_back(ByteString()); // marks the end of the list
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER, notBefore,
+ notAfter, subjectDER, *reusedKey, extensions.data(), *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ return certDER;
+}
+
+class pkixc_tests : public ::testing::Test {};
+
+TEST_F(pkixc_tests, Valid_VerifyCodeSigningCertificateChain) {
+ ByteString root(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA));
+ ByteString intermediate(
+ CreateCert("CA", "intermediate", EndEntityOrCA::MustBeCA));
+ ByteString subjectAltNameExtension =
+ CreateEncodedSubjectAltName(DNSName("example.com"));
+ ByteString endEntity(CreateCert("intermediate", "end-entity",
+ EndEntityOrCA::MustBeEndEntity,
+ &subjectAltNameExtension));
+ const uint8_t* certificates[] = {endEntity.data(), intermediate.data(),
+ root.data()};
+ const uint16_t certificateLengths[] = {
+ static_cast<uint16_t>(endEntity.length()),
+ static_cast<uint16_t>(intermediate.length()),
+ static_cast<uint16_t>(root.length())};
+ const size_t numCertificates = 3;
+ const uint64_t secondsSinceEpoch = 1625000000;
+ uint8_t rootSHA256Digest[32] = {0};
+ Input rootInput;
+ Result rv = rootInput.Init(root.data(), root.length());
+ ASSERT_EQ(rv, Success);
+ rv = DigestBufNSS(rootInput, DigestAlgorithm::sha256, rootSHA256Digest,
+ sizeof(rootSHA256Digest));
+ ASSERT_EQ(rv, Success);
+ const uint8_t hostname[] = {"example.com"};
+ size_t hostnameLength = strlen("example.com");
+ PRErrorCode error = 0;
+ ASSERT_TRUE(VerifyCodeSigningCertificateChain(
+ &certificates[0], &certificateLengths[0], numCertificates,
+ secondsSinceEpoch, &rootSHA256Digest[0], &hostname[0], hostnameLength,
+ &error));
+
+ // If the extended key usage extension is present, it must have the code
+ // signing usage.
+ ByteString extendedKeyUsageExtension(
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_codeSigning)));
+ ByteString endEntityWithEKU(
+ CreateCert("intermediate", "end-entity", EndEntityOrCA::MustBeEndEntity,
+ &subjectAltNameExtension, &extendedKeyUsageExtension));
+ const uint8_t* certificatesWithEKU[] = {endEntityWithEKU.data(),
+ intermediate.data(), root.data()};
+ const uint16_t certificateLengthsWithEKU[] = {
+ static_cast<uint16_t>(endEntityWithEKU.length()),
+ static_cast<uint16_t>(intermediate.length()),
+ static_cast<uint16_t>(root.length())};
+ ASSERT_TRUE(VerifyCodeSigningCertificateChain(
+ &certificatesWithEKU[0], &certificateLengthsWithEKU[0], numCertificates,
+ secondsSinceEpoch, &rootSHA256Digest[0], &hostname[0], hostnameLength,
+ &error));
+}
+
+TEST_F(pkixc_tests, Invalid_VerifyCodeSigningCertificateChain) {
+ ByteString root(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA));
+ ByteString subjectAltNameExtension =
+ CreateEncodedSubjectAltName(DNSName("example.com"));
+ ByteString endEntity(CreateCert("CA", "end-entity",
+ EndEntityOrCA::MustBeEndEntity,
+ &subjectAltNameExtension));
+ const uint8_t* certificates[] = {endEntity.data(), root.data()};
+ const uint16_t certificateLengths[] = {
+ static_cast<uint16_t>(endEntity.length()),
+ static_cast<uint16_t>(root.length())};
+ const size_t numCertificates = 2;
+ const uint64_t secondsSinceEpoch = 1625000000;
+ uint8_t rootSHA256Digest[32] = {0};
+ Input rootInput;
+ Result rv = rootInput.Init(root.data(), root.length());
+ ASSERT_EQ(rv, Success);
+ rv = DigestBufNSS(rootInput, DigestAlgorithm::sha256, rootSHA256Digest,
+ sizeof(rootSHA256Digest));
+ ASSERT_EQ(rv, Success);
+ const uint8_t hostname[] = {"example.com"};
+ size_t hostnameLength = strlen("example.com");
+ PRErrorCode error = 0;
+ // Consistency check first to ensure these tests are meaningful.
+ ASSERT_TRUE(VerifyCodeSigningCertificateChain(
+ &certificates[0], &certificateLengths[0], numCertificates,
+ secondsSinceEpoch, &rootSHA256Digest[0], &hostname[0], hostnameLength,
+ &error));
+ ASSERT_EQ(error, 0);
+
+ // Test with "now" after the certificates have expired.
+ ASSERT_FALSE(VerifyCodeSigningCertificateChain(
+ &certificates[0], &certificateLengths[0], numCertificates,
+ secondsSinceEpoch + 10000000, &rootSHA256Digest[0], &hostname[0],
+ hostnameLength, &error));
+ ASSERT_EQ(error, SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE);
+
+ // Test with a different root digest.
+ uint8_t wrongRootSHA256Digest[32] = {1};
+ ASSERT_FALSE(VerifyCodeSigningCertificateChain(
+ &certificates[0], &certificateLengths[0], numCertificates,
+ secondsSinceEpoch, &wrongRootSHA256Digest[0], &hostname[0],
+ hostnameLength, &error));
+ ASSERT_EQ(error, SEC_ERROR_UNKNOWN_ISSUER);
+
+ // Test with a different host name.
+ const uint8_t wrongHostname[] = "example.org";
+ size_t wrongHostnameLength = strlen("example.org");
+ ASSERT_FALSE(VerifyCodeSigningCertificateChain(
+ &certificates[0], &certificateLengths[0], numCertificates,
+ secondsSinceEpoch, &rootSHA256Digest[0], &wrongHostname[0],
+ wrongHostnameLength, &error));
+ ASSERT_EQ(error, SSL_ERROR_BAD_CERT_DOMAIN);
+
+ // Test with a certificate with an extended key usage that doesn't include
+ // code signing.
+ ByteString extendedKeyUsageExtension(
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)));
+ ByteString endEntityWithEKU(
+ CreateCert("CA", "end-entity", EndEntityOrCA::MustBeEndEntity,
+ &subjectAltNameExtension, &extendedKeyUsageExtension));
+ const uint8_t* certificatesWithEKU[] = {endEntityWithEKU.data(), root.data()};
+ const uint16_t certificateLengthsWithEKU[] = {
+ static_cast<uint16_t>(endEntityWithEKU.length()),
+ static_cast<uint16_t>(root.length())};
+ ASSERT_FALSE(VerifyCodeSigningCertificateChain(
+ &certificatesWithEKU[0], &certificateLengthsWithEKU[0], numCertificates,
+ secondsSinceEpoch, &rootSHA256Digest[0], &hostname[0], hostnameLength,
+ &error));
+ ASSERT_EQ(error, SEC_ERROR_INADEQUATE_CERT_TYPE);
+}
diff --git a/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
index 3fd169517..549d030af 100644
--- a/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
+++ b/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
@@ -49,15 +49,7 @@ protected:
// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h
-// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2
-static const uint8_t tlv_id_kp_clientAuth[] = {
- 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
-};
-
-// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3
-static const uint8_t tlv_id_kp_codeSigning[] = {
- 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03
-};
+// tlv_id_kp_clientAuth and tlv_id_kp_codeSigning are defined in pkixgtest.h
// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4
static const uint8_t tlv_id_kp_emailProtection[] = {
@@ -595,19 +587,6 @@ TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase)
nullptr));
}
-// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
-static const uint8_t tlv_id_ce_extKeyUsage[] = {
- 0x06, 0x03, 0x55, 0x1d, 0x25
-};
-
-static inline ByteString
-CreateEKUExtension(ByteString ekuOIDs)
-{
- return TLV(der::SEQUENCE,
- BytesToByteString(tlv_id_ce_extKeyUsage) +
- TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs)));
-}
-
static const EKUChainTestcase EKU_CHAIN_TESTCASES[] =
{
{
diff --git a/gtests/mozpkix_gtest/pkixgtest.h b/gtests/mozpkix_gtest/pkixgtest.h
index 719b87d54..777f123ca 100644
--- a/gtests/mozpkix_gtest/pkixgtest.h
+++ b/gtests/mozpkix_gtest/pkixgtest.h
@@ -57,6 +57,7 @@
#endif
#include "mozpkix/pkix.h"
+#include "mozpkix/pkixder.h"
#include "mozpkix/test/pkixtestutil.h"
// PrintTo must be in the same namespace as the type we're overloading it for.
@@ -71,8 +72,8 @@ inline void PrintTo(const Result& result, ::std::ostream* os) {
*os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")";
}
}
-}
-} // namespace mozilla::pkix
+} // namespace pkix
+} // namespace mozilla
namespace mozilla {
namespace pkix {
@@ -223,8 +224,26 @@ class DefaultNameMatchingPolicy : public NameMatchingPolicy {
return Success;
}
};
+
+// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2
+const uint8_t tlv_id_kp_clientAuth[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x02};
+
+// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3
+const uint8_t tlv_id_kp_codeSigning[] = {0x06, 0x08, 0x2b, 0x06, 0x01,
+ 0x05, 0x05, 0x07, 0x03, 0x03};
+
+// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
+const uint8_t tlv_id_ce_extKeyUsage[] = {0x06, 0x03, 0x55, 0x1d, 0x25};
+
+inline ByteString CreateEKUExtension(ByteString ekuOIDs) {
+ return TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_extKeyUsage) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs)));
}
-}
-} // namespace mozilla::pkix::test
+
+} // namespace test
+} // namespace pkix
+} // namespace mozilla
#endif // mozilla_pkix_pkixgtest_h
diff --git a/lib/mozpkix/exports.gyp b/lib/mozpkix/exports.gyp
index 248efc910..d592bb70f 100644
--- a/lib/mozpkix/exports.gyp
+++ b/lib/mozpkix/exports.gyp
@@ -17,6 +17,7 @@
'include/pkix/Time.h',
'include/pkix/Result.h',
'include/pkix/pkix.h',
+ 'include/pkix/pkixc.h',
'include/pkix/pkixnss.h',
'include/pkix/pkixtypes.h',
'include/pkix/pkixutil.h',
@@ -44,4 +45,4 @@
'variables': {
'module': 'nss'
}
-} \ No newline at end of file
+}
diff --git a/lib/mozpkix/include/pkix/pkixc.h b/lib/mozpkix/include/pkix/pkixc.h
new file mode 100644
index 000000000..a1cc7bf2e
--- /dev/null
+++ b/lib/mozpkix/include/pkix/pkixc.h
@@ -0,0 +1,47 @@
+/* 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/.
+ */
+
+#ifndef mozilla_pkix_pkixc_h
+#define mozilla_pkix_pkixc_h
+
+#include "prerror.h"
+#include "stdint.h"
+
+// VerifyCertificateChain will attempt to build a verified certificate chain
+// starting from the 0th certificate in the given array to the indicated trust
+// anchor. It returns true on success and false otherwise. No particular key
+// usage is required, and no particular policy is required. The code signing
+// extended key usage is required. No revocation checking is performed. RSA
+// keys must be at least 2048 bits long, and EC keys must be from one of the
+// curves secp256r1, secp384r1, or secp521r1. Only SHA256, SHA384, and SHA512
+// are acceptable digest algorithms. When doing name checking, the subject
+// common name field is ignored.
+// certificate is an array of pointers to certificates.
+// certificateLengths is an array of the lengths of each certificate.
+// numCertificates indicates how many certificates are in certificates.
+// secondsSinceEpoch indicates the time at which the certificate chain must be
+// valid, in seconds since the epoch.
+// rootSHA256Hash identifies a trust anchor by the SHA256 hash of its contents.
+// It must be an array of 32 bytes.
+// hostname is a doman name for which the end-entity certificate must be valid.
+// error will be set if and only if the return value is false. Its value may
+// indicate why verification failed.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+bool VerifyCodeSigningCertificateChain(const uint8_t** certificates,
+ const uint16_t* certificateLengths,
+ size_t numCertificates,
+ uint64_t secondsSinceEpoch,
+ const uint8_t* rootSHA256Hash,
+ const uint8_t* hostname,
+ size_t hostnameLength,
+ /* out */ PRErrorCode* error);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // mozilla_pkix_pkixc_h
diff --git a/lib/mozpkix/lib/pkixc.cpp b/lib/mozpkix/lib/pkixc.cpp
new file mode 100644
index 000000000..ec8cc120a
--- /dev/null
+++ b/lib/mozpkix/lib/pkixc.cpp
@@ -0,0 +1,218 @@
+/* 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/.
+ */
+
+#include "mozpkix/pkixc.h"
+
+#include "mozpkix/pkix.h"
+#include "mozpkix/pkixnss.h"
+#include "mozpkix/pkixtypes.h"
+#include "secerr.h"
+
+using namespace mozilla::pkix;
+
+const size_t SHA256_DIGEST_LENGTH = 256 / 8;
+
+class CodeSigningTrustDomain final : public TrustDomain {
+ public:
+ explicit CodeSigningTrustDomain(const uint8_t** certificates,
+ const uint16_t* certificateLengths,
+ size_t numCertificates,
+ const uint8_t* rootSHA256Digest)
+ : mCertificates(certificates),
+ mCertificateLengths(certificateLengths),
+ mNumCertificates(numCertificates),
+ mRootSHA256Digest(rootSHA256Digest) {}
+
+ virtual Result GetCertTrust(EndEntityOrCA endEntityOrCA,
+ const CertPolicyId& policy,
+ Input candidateCertDER,
+ /*out*/ TrustLevel& trustLevel) override {
+ uint8_t digestBuf[SHA256_DIGEST_LENGTH] = {0};
+ Result rv = DigestBufNSS(candidateCertDER, DigestAlgorithm::sha256,
+ digestBuf, SHA256_DIGEST_LENGTH);
+ if (rv != Success) {
+ return rv;
+ }
+ Input candidateDigestInput;
+ rv = candidateDigestInput.Init(digestBuf, SHA256_DIGEST_LENGTH);
+ if (rv != Success) {
+ return rv;
+ }
+ Input rootDigestInput;
+ rv = rootDigestInput.Init(mRootSHA256Digest, SHA256_DIGEST_LENGTH);
+ if (rv != Success) {
+ return rv;
+ }
+ if (InputsAreEqual(candidateDigestInput, rootDigestInput)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ } else {
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+ return Success;
+ }
+
+ virtual Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker,
+ Time time) override {
+ for (size_t i = 0; i < mNumCertificates; i++) {
+ Input certInput;
+ Result rv = certInput.Init(mCertificates[i], mCertificateLengths[i]);
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ rv = checker.Check(certInput, nullptr /*additionalNameConstraints*/,
+ keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ if (!keepGoing) {
+ break;
+ }
+ }
+
+ return Success;
+ }
+
+ virtual Result CheckRevocation(
+ EndEntityOrCA endEntityOrCA, const CertID& certID, Time time,
+ Duration validityDuration,
+ /*optional*/ const Input* stapledOCSPresponse,
+ /*optional*/ const Input* aiaExtension,
+ /*optional*/ const Input* sctExtension) override {
+ return Success;
+ }
+
+ virtual Result IsChainValid(const DERArray& certChain, Time time,
+ const CertPolicyId& requiredPolicy) override {
+ return Success;
+ }
+
+ virtual Result CheckSignatureDigestAlgorithm(DigestAlgorithm digestAlg,
+ EndEntityOrCA endEntityOrCA,
+ Time notBefore) override {
+ switch (digestAlg) {
+ case DigestAlgorithm::sha256: // fall through
+ case DigestAlgorithm::sha384: // fall through
+ case DigestAlgorithm::sha512:
+ return Success;
+ default:
+ return Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+ }
+ }
+
+ virtual Result CheckRSAPublicKeyModulusSizeInBits(
+ EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) override {
+ if (modulusSizeInBits < 2048) {
+ return Result::ERROR_INADEQUATE_KEY_SIZE;
+ }
+ return Success;
+ }
+
+ virtual Result VerifyRSAPKCS1SignedDigest(
+ const SignedDigest& signedDigest, Input subjectPublicKeyInfo) override {
+ return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+ }
+
+ virtual Result CheckECDSACurveIsAcceptable(EndEntityOrCA endEntityOrCA,
+ NamedCurve curve) override {
+ switch (curve) {
+ case NamedCurve::secp256r1: // fall through
+ case NamedCurve::secp384r1: // fall through
+ case NamedCurve::secp521r1:
+ return Success;
+ }
+
+ return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
+ }
+
+ virtual Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) override {
+ return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+ }
+
+ virtual Result CheckValidityIsAcceptable(Time notBefore, Time notAfter,
+ EndEntityOrCA endEntityOrCA,
+ KeyPurposeId keyPurpose) override {
+ return Success;
+ }
+
+ virtual Result NetscapeStepUpMatchesServerAuth(
+ Time notBefore, /*out*/ bool& matches) override {
+ matches = false;
+ return Success;
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override {}
+
+ virtual Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf,
+ size_t digestBufLen) override {
+ return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ private:
+ const uint8_t** mCertificates;
+ const uint16_t* mCertificateLengths;
+ size_t mNumCertificates;
+ const uint8_t* mRootSHA256Digest;
+};
+
+class CodeSigningNameMatchingPolicy : public NameMatchingPolicy {
+ public:
+ virtual Result FallBackToCommonName(
+ Time notBefore,
+ /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override {
+ fallBackToCommonName = FallBackToSearchWithinSubject::No;
+ return Success;
+ }
+};
+
+bool VerifyCodeSigningCertificateChain(
+ const uint8_t** certificates, const uint16_t* certificateLengths,
+ size_t numCertificates, uint64_t secondsSinceEpoch,
+ const uint8_t* rootSHA256Digest, const uint8_t* hostname,
+ size_t hostnameLength, PRErrorCode* error) {
+ if (!error) {
+ return false;
+ }
+ if (!certificates || !certificateLengths || !rootSHA256Digest) {
+ *error = SEC_ERROR_INVALID_ARGS;
+ return false;
+ }
+
+ CodeSigningTrustDomain trustDomain(certificates, certificateLengths,
+ numCertificates, rootSHA256Digest);
+ Input certificate;
+ Result rv = certificate.Init(certificates[0], certificateLengths[0]);
+ if (rv != Success) {
+ *error = MapResultToPRErrorCode(rv);
+ return false;
+ }
+ Time time = TimeFromEpochInSeconds(secondsSinceEpoch);
+ rv = BuildCertChain(
+ trustDomain, certificate, time, EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired, KeyPurposeId::id_kp_codeSigning,
+ CertPolicyId::anyPolicy, nullptr);
+ if (rv != Success) {
+ *error = MapResultToPRErrorCode(rv);
+ return false;
+ }
+ Input hostnameInput;
+ rv = hostnameInput.Init(hostname, hostnameLength);
+ if (rv != Success) {
+ *error = MapResultToPRErrorCode(rv);
+ return false;
+ }
+ CodeSigningNameMatchingPolicy nameMatchingPolicy;
+ rv = CheckCertHostname(certificate, hostnameInput, nameMatchingPolicy);
+ if (rv != Success) {
+ *error = MapResultToPRErrorCode(rv);
+ return false;
+ }
+ return true;
+}
diff --git a/lib/mozpkix/mozpkix.gyp b/lib/mozpkix/mozpkix.gyp
index 1c552ba5f..abc3d149f 100644
--- a/lib/mozpkix/mozpkix.gyp
+++ b/lib/mozpkix/mozpkix.gyp
@@ -12,6 +12,7 @@
'standalone_static_library': 1,
'sources': [
'lib/pkixbuild.cpp',
+ 'lib/pkixc.cpp',
'lib/pkixcert.cpp',
'lib/pkixcheck.cpp',
'lib/pkixder.cpp',