diff options
author | Dana Keeler <dkeeler@mozilla.com> | 2021-05-18 17:30:21 +0000 |
---|---|---|
committer | Dana Keeler <dkeeler@mozilla.com> | 2021-05-18 17:30:21 +0000 |
commit | 1afcaab5dbb8816221287d880fe4ec85e8b4b24e (patch) | |
tree | b956a3afc900a2b3927797aa4c9ecafe2a2aa229 /gtests/mozpkix_gtest | |
parent | 1eaaf9b21673a9e62355a8efe452e50f35dd712d (diff) | |
download | nss-hg-1afcaab5dbb8816221287d880fe4ec85e8b4b24e.tar.gz |
Bug 1709291 - add VerifyCodeSigningCertificateChain r=bbeurdouche
Differential Revision: https://phabricator.services.mozilla.com/D114660
Diffstat (limited to 'gtests/mozpkix_gtest')
-rw-r--r-- | gtests/mozpkix_gtest/mozpkix_gtest.gyp | 1 | ||||
-rw-r--r-- | gtests/mozpkix_gtest/pkixc_tests.cpp | 182 | ||||
-rw-r--r-- | gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp | 23 | ||||
-rw-r--r-- | gtests/mozpkix_gtest/pkixgtest.h | 27 |
4 files changed, 207 insertions, 26 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 |