/* 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 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(endEntity.length()), static_cast(intermediate.length()), static_cast(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(endEntityWithEKU.length()), static_cast(intermediate.length()), static_cast(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(endEntity.length()), static_cast(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(endEntityWithEKU.length()), static_cast(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); }