Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bug 1709291 - add VerifyCodeSigningCertificateChain r=bbeurdouche
Differential Revision: https://phabricator.services.mozilla.com/D114660 --HG-- extra : moz-landing-system : lando
- Loading branch information
Showing
8 changed files
with
475 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.