| /* 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); |
| } |