Skip to content

Commit

Permalink
Bug 1709291 - add VerifyCodeSigningCertificateChain r=bbeurdouche
Browse files Browse the repository at this point in the history
Differential Revision: https://phabricator.services.mozilla.com/D114660

--HG--
extra : moz-landing-system : lando
  • Loading branch information
mozkeeler committed May 18, 2021
1 parent 697a419 commit 6c61c1f
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 27 deletions.
1 change: 1 addition & 0 deletions gtests/mozpkix_gtest/mozpkix_gtest.gyp
Expand Up @@ -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',
Expand Down
182 changes: 182 additions & 0 deletions 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);
}
23 changes: 1 addition & 22 deletions gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
Expand Up @@ -49,15 +49,7 @@ class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test

// 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[] = {
Expand Down Expand Up @@ -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[] =
{
{
Expand Down
27 changes: 23 additions & 4 deletions gtests/mozpkix_gtest/pkixgtest.h
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion lib/mozpkix/exports.gyp
Expand Up @@ -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',
Expand Down Expand Up @@ -44,4 +45,4 @@
'variables': {
'module': 'nss'
}
}
}
47 changes: 47 additions & 0 deletions 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

0 comments on commit 6c61c1f

Please sign in to comment.