зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1275238 - Certificate Transparency support in mozilla::pkix; r=keeler
MozReview-Commit-ID: HZwzSgxarTw --HG-- extra : transplant_source : %BF%F9%A8T%C6x%82%03%3Ez%9F%3BT%E3%1B%11s%294%F4
This commit is contained in:
Родитель
53c1367728
Коммит
edb1f658f6
|
@ -378,4 +378,10 @@ AppTrustDomain::NetscapeStepUpMatchesServerAuth(Time /*notBefore*/,
|
|||
return Success;
|
||||
}
|
||||
|
||||
void
|
||||
AppTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
|
||||
Input /*extensionData*/)
|
||||
{
|
||||
}
|
||||
|
||||
} } // namespace mozilla::psm
|
||||
|
|
|
@ -64,6 +64,9 @@ public:
|
|||
virtual Result NetscapeStepUpMatchesServerAuth(
|
||||
mozilla::pkix::Time notBefore,
|
||||
/*out*/ bool& matches) override;
|
||||
virtual void NoteAuxiliaryExtension(
|
||||
mozilla::pkix::AuxiliaryExtension extension,
|
||||
mozilla::pkix::Input extensionData) override;
|
||||
virtual Result DigestBuf(mozilla::pkix::Input item,
|
||||
mozilla::pkix::DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf,
|
||||
|
|
|
@ -958,6 +958,12 @@ NSSCertDBTrustDomain::NetscapeStepUpMatchesServerAuth(Time notBefore,
|
|||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
void
|
||||
NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
|
||||
Input /*extensionData*/)
|
||||
{
|
||||
}
|
||||
|
||||
SECStatus
|
||||
InitializeNSS(const char* dir, bool readOnly, bool loadPKCS11Modules)
|
||||
{
|
||||
|
|
|
@ -141,6 +141,10 @@ public:
|
|||
virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
|
||||
mozilla::pkix::Time time) override;
|
||||
|
||||
virtual void NoteAuxiliaryExtension(
|
||||
mozilla::pkix::AuxiliaryExtension extension,
|
||||
mozilla::pkix::Input extensionData) override;
|
||||
|
||||
CertVerifier::OCSPStaplingStatus GetOCSPStaplingStatus() const
|
||||
{
|
||||
return mOCSPStaplingStatus;
|
||||
|
|
|
@ -108,6 +108,13 @@ OCSPVerificationTrustDomain::NetscapeStepUpMatchesServerAuth(Time notBefore,
|
|||
return mCertDBTrustDomain.NetscapeStepUpMatchesServerAuth(notBefore, matches);
|
||||
}
|
||||
|
||||
void
|
||||
OCSPVerificationTrustDomain::NoteAuxiliaryExtension(
|
||||
AuxiliaryExtension extension, Input extensionData)
|
||||
{
|
||||
mCertDBTrustDomain.NoteAuxiliaryExtension(extension, extensionData);
|
||||
}
|
||||
|
||||
Result
|
||||
OCSPVerificationTrustDomain::DigestBuf(
|
||||
Input item, DigestAlgorithm digestAlg,
|
||||
|
|
|
@ -73,6 +73,10 @@ public:
|
|||
virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
|
||||
mozilla::pkix::Time time) override;
|
||||
|
||||
virtual void NoteAuxiliaryExtension(
|
||||
mozilla::pkix::AuxiliaryExtension extension,
|
||||
mozilla::pkix::Input extensionData) override;
|
||||
|
||||
private:
|
||||
NSSCertDBTrustDomain& mCertDBTrustDomain;
|
||||
};
|
||||
|
|
|
@ -215,6 +215,12 @@ CSTrustDomain::NetscapeStepUpMatchesServerAuth(Time notBefore,
|
|||
return Success;
|
||||
}
|
||||
|
||||
void
|
||||
CSTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
|
||||
Input /*extensionData*/)
|
||||
{
|
||||
}
|
||||
|
||||
Result
|
||||
CSTrustDomain::DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen)
|
||||
|
|
|
@ -62,6 +62,9 @@ public:
|
|||
mozilla::pkix::KeyPurposeId keyPurpose) override;
|
||||
virtual Result NetscapeStepUpMatchesServerAuth(
|
||||
mozilla::pkix::Time notBefore, /*out*/ bool& matches) override;
|
||||
virtual void NoteAuxiliaryExtension(
|
||||
mozilla::pkix::AuxiliaryExtension extension,
|
||||
mozilla::pkix::Input extensionData) override;
|
||||
virtual Result DigestBuf(mozilla::pkix::Input item,
|
||||
mozilla::pkix::DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf,
|
||||
|
|
|
@ -184,7 +184,7 @@ GetOCSPResponseForType(OCSPResponseType aORT, const UniqueCERTCertificate& aCert
|
|||
extension.value.push_back(0x05); // tag: NULL
|
||||
extension.value.push_back(0x00); // length: 0
|
||||
extension.next = nullptr;
|
||||
context.extensions = &extension;
|
||||
context.responseExtensions = &extension;
|
||||
}
|
||||
if (aORT == ORTEmptyExtensions) {
|
||||
context.includeEmptyExtensions = true;
|
||||
|
|
|
@ -105,6 +105,21 @@ enum class TrustLevel
|
|||
InheritsTrust = 3 // certificate must chain to a trust anchor
|
||||
};
|
||||
|
||||
// Extensions extracted during the verification flow.
|
||||
// See TrustDomain::NoteAuxiliaryExtension.
|
||||
enum class AuxiliaryExtension
|
||||
{
|
||||
// Certificate Transparency data, specifically Signed Certificate
|
||||
// Timestamps (SCTs). See RFC 6962.
|
||||
|
||||
// SCT list embedded in the end entity certificate. Called by BuildCertChain
|
||||
// after the certificate containing the SCTs has passed the revocation checks.
|
||||
EmbeddedSCTList = 1,
|
||||
// SCT list from OCSP response. Called by VerifyEncodedOCSPResponse
|
||||
// when its result is a success and the SCT list is present.
|
||||
SCTListFromOCSPResponse = 2
|
||||
};
|
||||
|
||||
// CertID references the information needed to do revocation checking for the
|
||||
// certificate issued by the given issuer with the given serial number.
|
||||
//
|
||||
|
@ -337,6 +352,13 @@ public:
|
|||
virtual Result NetscapeStepUpMatchesServerAuth(Time notBefore,
|
||||
/*out*/ bool& matches) = 0;
|
||||
|
||||
// Some certificate or OCSP response extensions do not directly participate
|
||||
// in the verification flow, but might still be of interest to the clients
|
||||
// (notably Certificate Transparency data, RFC 6962). Such extensions are
|
||||
// extracted and passed to this function for further processing.
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
|
||||
Input extensionData) = 0;
|
||||
|
||||
// Compute a digest of the data in item using the given digest algorithm.
|
||||
//
|
||||
// item contains the data to hash.
|
||||
|
|
|
@ -244,6 +244,20 @@ PathBuildingStep::Check(Input potentialIssuerDER,
|
|||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
|
||||
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
|
||||
const Input* sctExtension = subject.GetSignedCertificateTimestamps();
|
||||
if (sctExtension) {
|
||||
Input sctList;
|
||||
rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
|
||||
sctList);
|
||||
if (rv != Success) {
|
||||
return RecordResult(rv, keepGoing);
|
||||
}
|
||||
trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
|
||||
sctList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RecordResult(Success, keepGoing);
|
||||
|
|
|
@ -223,6 +223,11 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
|
|||
static const uint8_t id_pe_tlsfeature[] = {
|
||||
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
|
||||
};
|
||||
// python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
|
||||
// See Section 3.3 of RFC 6962.
|
||||
static const uint8_t id_embeddedSctList[] = {
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
|
||||
Input* out = nullptr;
|
||||
|
||||
|
@ -269,6 +274,8 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
|
|||
out = &authorityInfoAccess;
|
||||
} else if (extnID.MatchRest(id_pe_tlsfeature)) {
|
||||
out = &requiredTLSFeatures;
|
||||
} else if (extnID.MatchRest(id_embeddedSctList)) {
|
||||
out = &signedCertificateTimestamps;
|
||||
} else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
|
||||
// We need to make sure we don't reject delegated OCSP response signing
|
||||
// certificates that contain the id-pkix-ocsp-nocheck extension marked as
|
||||
|
@ -300,4 +307,17 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
|
||||
Input& sctList)
|
||||
{
|
||||
Reader decodedValue;
|
||||
Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
|
||||
decodedValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return decodedValue.SkipToEnd(sctList);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::pkix
|
||||
|
|
|
@ -76,6 +76,8 @@ public:
|
|||
Time* validThrough;
|
||||
bool expired;
|
||||
|
||||
Input signedCertificateTimestamps;
|
||||
|
||||
// Keep track of whether the OCSP response contains the status of the
|
||||
// certificate we're interested in. Responders might reply without
|
||||
// including the status of any of the requested certs, we should
|
||||
|
@ -168,6 +170,9 @@ static inline Result ResponseData(
|
|||
static inline Result SingleResponse(Reader& input, Context& context);
|
||||
static Result ExtensionNotUnderstood(Reader& extnID, Input extnValue,
|
||||
bool critical, /*out*/ bool& understood);
|
||||
static Result RememberSingleExtension(Context& context, Reader& extnID,
|
||||
Input extnValue, bool critical,
|
||||
/*out*/ bool& understood);
|
||||
static inline Result CertID(Reader& input,
|
||||
const Context& context,
|
||||
/*out*/ bool& match);
|
||||
|
@ -330,6 +335,16 @@ VerifyEncodedOCSPResponse(TrustDomain& trustDomain, const struct CertID& certID,
|
|||
if (expired) {
|
||||
return Result::ERROR_OCSP_OLD_RESPONSE;
|
||||
}
|
||||
if (context.signedCertificateTimestamps.GetLength()) {
|
||||
Input sctList;
|
||||
rv = ExtractSignedCertificateTimestampListFromExtension(
|
||||
context.signedCertificateTimestamps, sctList);
|
||||
if (rv != Success) {
|
||||
return MapBadDERToMalformedOCSPResponse(rv);
|
||||
}
|
||||
context.trustDomain.NoteAuxiliaryExtension(
|
||||
AuxiliaryExtension::SCTListFromOCSPResponse, sctList);
|
||||
}
|
||||
return Success;
|
||||
case CertStatus::Revoked:
|
||||
return Result::ERROR_REVOKED_CERTIFICATE;
|
||||
|
@ -651,9 +666,15 @@ SingleResponse(Reader& input, Context& context)
|
|||
context.expired = true;
|
||||
}
|
||||
|
||||
rv = der::OptionalExtensions(input,
|
||||
der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
|
||||
ExtensionNotUnderstood);
|
||||
rv = der::OptionalExtensions(
|
||||
input,
|
||||
der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
|
||||
[&context](Reader& extnID, const Input& extnValue, bool critical,
|
||||
/*out*/ bool& understood) {
|
||||
return RememberSingleExtension(context, extnID, extnValue, critical,
|
||||
understood);
|
||||
});
|
||||
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -826,6 +847,36 @@ ExtensionNotUnderstood(Reader& /*extnID*/, Input /*extnValue*/,
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
RememberSingleExtension(Context& context, Reader& extnID, Input extnValue,
|
||||
bool /*critical*/, /*out*/ bool& understood)
|
||||
{
|
||||
understood = false;
|
||||
|
||||
// SingleExtension for Signed Certificate Timestamp List.
|
||||
// See Section 3.3 of RFC 6962.
|
||||
// python DottedOIDToCode.py
|
||||
// id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
|
||||
static const uint8_t id_ocsp_singleExtensionSctList[] = {
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
|
||||
};
|
||||
|
||||
if (extnID.MatchRest(id_ocsp_singleExtensionSctList)) {
|
||||
// Empty values are not allowed for this extension. Note that
|
||||
// we assume this later, when checking if the extension was present.
|
||||
if (extnValue.GetLength() == 0) {
|
||||
return Result::ERROR_EXTENSION_VALUE_INVALID;
|
||||
}
|
||||
if (context.signedCertificateTimestamps.Init(extnValue) != Success) {
|
||||
// Duplicate extension.
|
||||
return Result::ERROR_EXTENSION_VALUE_INVALID;
|
||||
}
|
||||
understood = true;
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
// 1. The certificate identified in a received response corresponds to
|
||||
// the certificate that was identified in the corresponding request;
|
||||
// 2. The signature on the response is valid;
|
||||
|
|
|
@ -106,6 +106,10 @@ public:
|
|||
{
|
||||
return MaybeInput(requiredTLSFeatures);
|
||||
}
|
||||
const Input* GetSignedCertificateTimestamps() const
|
||||
{
|
||||
return MaybeInput(signedCertificateTimestamps);
|
||||
}
|
||||
|
||||
private:
|
||||
const Input der;
|
||||
|
@ -149,6 +153,7 @@ private:
|
|||
Input subjectAltName;
|
||||
Input criticalNetscapeCertificateType;
|
||||
Input requiredTLSFeatures;
|
||||
Input signedCertificateTimestamps; // RFC 6962 (Certificate Transparency)
|
||||
|
||||
Result RememberExtension(Reader& extnID, Input extnValue, bool critical,
|
||||
/*out*/ bool& understood);
|
||||
|
@ -197,6 +202,12 @@ private:
|
|||
void operator=(const NonOwningDERArray&) = delete;
|
||||
};
|
||||
|
||||
// Extracts the SignedCertificateTimestampList structure which is encoded as an
|
||||
// OCTET STRING within the X.509v3 / OCSP extensions (see RFC 6962 section 3.3).
|
||||
Result
|
||||
ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
|
||||
Input& sctList);
|
||||
|
||||
inline unsigned int
|
||||
DaysBeforeYear(unsigned int year)
|
||||
{
|
||||
|
|
|
@ -31,11 +31,13 @@
|
|||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include "pkixder.h"
|
||||
#include "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
@ -46,7 +48,8 @@ CreateCert(const char* issuerCN, // null means "empty name"
|
|||
const char* subjectCN, // null means "empty name"
|
||||
EndEntityOrCA endEntityOrCA,
|
||||
/*optional modified*/ std::map<ByteString, ByteString>*
|
||||
subjectDERToCertDER = nullptr)
|
||||
subjectDERToCertDER = nullptr,
|
||||
/*optional*/ const ByteString* extension = nullptr)
|
||||
{
|
||||
static long serialNumberValue = 0;
|
||||
++serialNumberValue;
|
||||
|
@ -56,18 +59,23 @@ CreateCert(const char* issuerCN, // null means "empty name"
|
|||
ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString()));
|
||||
ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString()));
|
||||
|
||||
ByteString extensions[2];
|
||||
std::vector<ByteString> extensions;
|
||||
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
|
||||
extensions[0] =
|
||||
ByteString basicConstraints =
|
||||
CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
|
||||
EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
|
||||
EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
|
||||
extensions.push_back(basicConstraints);
|
||||
}
|
||||
if (extension) {
|
||||
extensions.push_back(*extension);
|
||||
}
|
||||
extensions.push_back(ByteString()); // marks the end of the list
|
||||
|
||||
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
|
||||
ByteString certDER(CreateEncodedCertificate(
|
||||
v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
|
||||
oneDayBeforeNow, oneDayAfterNow, subjectDER,
|
||||
*reusedKey, extensions, *reusedKey,
|
||||
*reusedKey, extensions.data(), *reusedKey,
|
||||
sha256WithRSAEncryption()));
|
||||
EXPECT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
|
@ -239,15 +247,15 @@ TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
|
|||
}
|
||||
}
|
||||
|
||||
// A TrustDomain that explicitly fails if CheckRevocation is called.
|
||||
// A TrustDomain that checks certificates against a given root certificate.
|
||||
// It is initialized with the DER encoding of a root certificate that
|
||||
// is treated as a trust anchor and is assumed to have issued all certificates
|
||||
// (i.e. FindIssuer always attempts to build the next step in the chain with
|
||||
// it).
|
||||
class ExpiredCertTrustDomain final : public DefaultCryptoTrustDomain
|
||||
class SingleRootTrustDomain : public DefaultCryptoTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit ExpiredCertTrustDomain(ByteString rootDER)
|
||||
explicit SingleRootTrustDomain(ByteString rootDER)
|
||||
: rootDER(rootDER)
|
||||
{
|
||||
}
|
||||
|
@ -288,10 +296,36 @@ public:
|
|||
return Success;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
return Success;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteString rootDER;
|
||||
};
|
||||
|
||||
// A TrustDomain that explicitly fails if CheckRevocation is called.
|
||||
class ExpiredCertTrustDomain final : public SingleRootTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit ExpiredCertTrustDomain(ByteString rootDER)
|
||||
: SingleRootTrustDomain(rootDER)
|
||||
{
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
/*optional*/ const Input*, /*optional*/ const Input*)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return NotReached("CheckRevocation should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
|
||||
{
|
||||
const char* rootCN = "Root CA";
|
||||
|
@ -474,3 +508,71 @@ TEST_P(pkixbuild_IssuerNameCheck, MatchingName)
|
|||
|
||||
INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck,
|
||||
testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS));
|
||||
|
||||
|
||||
// Records the embedded SCT list extension for later examination.
|
||||
class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain
|
||||
{
|
||||
public:
|
||||
explicit EmbeddedSCTListTestTrustDomain(ByteString rootDER)
|
||||
: SingleRootTrustDomain(rootDER)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
|
||||
Input extensionData) override
|
||||
{
|
||||
if (extension == AuxiliaryExtension::EmbeddedSCTList) {
|
||||
signedCertificateTimestamps = InputToByteString(extensionData);
|
||||
} else {
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
|
||||
ByteString signedCertificateTimestamps;
|
||||
};
|
||||
|
||||
TEST_F(pkixbuild, CertificateTransparencyExtension)
|
||||
{
|
||||
// python security/pkix/tools/DottedOIDToCode.py --tlv
|
||||
// id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
|
||||
static const uint8_t tlv_id_embeddedSctList[] = {
|
||||
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
static const uint8_t dummySctList[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05
|
||||
};
|
||||
|
||||
ByteString ctExtension = TLV(der::SEQUENCE,
|
||||
BytesToByteString(tlv_id_embeddedSctList) +
|
||||
Boolean(false) +
|
||||
TLV(der::OCTET_STRING,
|
||||
// SignedCertificateTimestampList structure is encoded as an OCTET STRING
|
||||
// within the X.509v3 extension (see RFC 6962 section 3.3).
|
||||
// pkix decodes it internally and returns the actual structure.
|
||||
TLV(der::OCTET_STRING, BytesToByteString(dummySctList))));
|
||||
|
||||
const char* rootCN = "Root CA";
|
||||
ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA));
|
||||
ASSERT_FALSE(ENCODING_FAILED(rootDER));
|
||||
|
||||
ByteString certDER(CreateCert(rootCN, "Cert with SCT list",
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
nullptr, /*subjectDERToCertDER*/
|
||||
&ctExtension));
|
||||
ASSERT_FALSE(ENCODING_FAILED(certDER));
|
||||
|
||||
Input certInput;
|
||||
ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
|
||||
|
||||
EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER);
|
||||
ASSERT_EQ(Success,
|
||||
BuildCertChain(extTrustDomain, certInput, Now(),
|
||||
EndEntityOrCA::MustBeEndEntity,
|
||||
KeyUsage::noParticularKeyUsageRequired,
|
||||
KeyPurposeId::anyExtendedKeyUsage,
|
||||
CertPolicyId::anyPolicy,
|
||||
nullptr /*stapledOCSPResponse*/));
|
||||
ASSERT_EQ(BytesToByteString(dummySctList),
|
||||
extTrustDomain.signedCertificateTimestamps);
|
||||
}
|
||||
|
|
|
@ -178,6 +178,11 @@ public:
|
|||
return NotReached("NetscapeStepUpMatchesServerAuth should not be called",
|
||||
Result::FATAL_ERROR_LIBRARY_FAILURE);
|
||||
}
|
||||
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
}
|
||||
};
|
||||
|
||||
class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
|
||||
|
@ -228,6 +233,10 @@ class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
|
|||
matches = true;
|
||||
return Success;
|
||||
}
|
||||
|
||||
void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class DefaultNameMatchingPolicy : public NameMatchingPolicy
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "pkixder.h"
|
||||
#include "pkixgtest.h"
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
@ -43,6 +44,19 @@ public:
|
|||
trustLevel = TrustLevel::InheritsTrust;
|
||||
return Success;
|
||||
}
|
||||
|
||||
virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
|
||||
Input extensionData) override
|
||||
{
|
||||
if (extension == AuxiliaryExtension::SCTListFromOCSPResponse) {
|
||||
signedCertificateTimestamps = InputToByteString(extensionData);
|
||||
} else {
|
||||
// We do not currently expect to receive any other extension here.
|
||||
ADD_FAILURE();
|
||||
}
|
||||
}
|
||||
|
||||
ByteString signedCertificateTimestamps;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
@ -199,7 +213,9 @@ public:
|
|||
time_t producedAt, time_t thisUpdate,
|
||||
/*optional*/ const time_t* nextUpdate,
|
||||
const TestSignatureAlgorithm& signatureAlgorithm,
|
||||
/*optional*/ const ByteString* certs = nullptr)
|
||||
/*optional*/ const ByteString* certs = nullptr,
|
||||
/*optional*/ OCSPResponseExtension* singleExtensions = nullptr,
|
||||
/*optional*/ OCSPResponseExtension* responseExtensions = nullptr)
|
||||
{
|
||||
OCSPResponseContext context(certID, producedAt);
|
||||
if (signerName) {
|
||||
|
@ -212,6 +228,8 @@ public:
|
|||
context.producedAt = producedAt;
|
||||
context.signatureAlgorithm = signatureAlgorithm;
|
||||
context.certs = certs;
|
||||
context.singleExtensions = singleExtensions;
|
||||
context.responseExtensions = responseExtensions;
|
||||
|
||||
context.certStatus = static_cast<uint8_t>(certStatus);
|
||||
context.thisUpdate = thisUpdate;
|
||||
|
@ -397,6 +415,46 @@ TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough)
|
|||
}
|
||||
}
|
||||
|
||||
TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension)
|
||||
{
|
||||
// python DottedOIDToCode.py --tlv
|
||||
// id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
|
||||
static const uint8_t tlv_id_ocsp_singleExtensionSctList[] = {
|
||||
0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
|
||||
};
|
||||
static const uint8_t dummySctList[] = {
|
||||
0x01, 0x02, 0x03, 0x04, 0x05
|
||||
};
|
||||
|
||||
OCSPResponseExtension ctExtension;
|
||||
ctExtension.id = BytesToByteString(tlv_id_ocsp_singleExtensionSctList);
|
||||
// SignedCertificateTimestampList structure is encoded as an OCTET STRING
|
||||
// within the extension value (see RFC 6962 section 3.3).
|
||||
// pkix decodes it internally and returns the actual structure.
|
||||
ctExtension.value = TLV(der::OCTET_STRING, BytesToByteString(dummySctList));
|
||||
|
||||
ByteString responseString(
|
||||
CreateEncodedOCSPSuccessfulResponse(
|
||||
OCSPResponseContext::good, *endEntityCertID, byKey,
|
||||
*rootKeyPair, oneDayBeforeNow,
|
||||
oneDayBeforeNow, &oneDayAfterNow,
|
||||
sha256WithRSAEncryption(),
|
||||
/*certs*/ nullptr,
|
||||
&ctExtension));
|
||||
Input response;
|
||||
ASSERT_EQ(Success,
|
||||
response.Init(responseString.data(), responseString.length()));
|
||||
|
||||
bool expired;
|
||||
ASSERT_EQ(Success,
|
||||
VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
|
||||
Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
|
||||
response, expired));
|
||||
ASSERT_FALSE(expired);
|
||||
ASSERT_EQ(BytesToByteString(dummySctList),
|
||||
trustDomain.signedCertificateTimestamps);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// indirect responses (signed by a delegated OCSP responder cert)
|
||||
|
||||
|
|
|
@ -144,12 +144,21 @@ TLV(uint8_t tag, size_t length, const ByteString& value)
|
|||
return result;
|
||||
}
|
||||
|
||||
OCSPResponseExtension::OCSPResponseExtension()
|
||||
: id()
|
||||
, critical(false)
|
||||
, value()
|
||||
, next(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
OCSPResponseContext::OCSPResponseContext(const CertID& certID, time_t time)
|
||||
: certID(certID)
|
||||
, responseStatus(successful)
|
||||
, skipResponseBytes(false)
|
||||
, producedAt(time)
|
||||
, extensions(nullptr)
|
||||
, singleExtensions(nullptr)
|
||||
, responseExtensions(nullptr)
|
||||
, includeEmptyExtensions(false)
|
||||
, signatureAlgorithm(sha256WithRSAEncryption())
|
||||
, badSignature(false)
|
||||
|
@ -897,10 +906,10 @@ OCSPExtension(OCSPResponseExtension& extension)
|
|||
// SEQUENCE OF Extension
|
||||
// }
|
||||
static ByteString
|
||||
Extensions(OCSPResponseContext& context)
|
||||
OCSPExtensions(OCSPResponseExtension* extensions)
|
||||
{
|
||||
ByteString value;
|
||||
for (OCSPResponseExtension* extension = context.extensions;
|
||||
for (OCSPResponseExtension* extension = extensions;
|
||||
extension; extension = extension->next) {
|
||||
ByteString extensionEncoded(OCSPExtension(*extension));
|
||||
if (ENCODING_FAILED(extensionEncoded)) {
|
||||
|
@ -935,8 +944,8 @@ ResponseData(OCSPResponseContext& context)
|
|||
}
|
||||
ByteString responses(TLV(der::SEQUENCE, response));
|
||||
ByteString responseExtensions;
|
||||
if (context.extensions || context.includeEmptyExtensions) {
|
||||
responseExtensions = Extensions(context);
|
||||
if (context.responseExtensions || context.includeEmptyExtensions) {
|
||||
responseExtensions = OCSPExtensions(context.responseExtensions);
|
||||
}
|
||||
|
||||
ByteString value;
|
||||
|
@ -1015,12 +1024,17 @@ SingleResponse(OCSPResponseContext& context)
|
|||
nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
|
||||
nextUpdateEncoded);
|
||||
}
|
||||
ByteString singleExtensions;
|
||||
if (context.singleExtensions || context.includeEmptyExtensions) {
|
||||
singleExtensions = OCSPExtensions(context.singleExtensions);
|
||||
}
|
||||
|
||||
ByteString value;
|
||||
value.append(certID);
|
||||
value.append(certStatus);
|
||||
value.append(thisUpdateEncoded);
|
||||
value.append(nextUpdateEncodedNested);
|
||||
value.append(singleExtensions);
|
||||
return TLV(der::SEQUENCE, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -376,6 +376,8 @@ ByteString CreateEncodedEKUExtension(Input eku, Critical critical);
|
|||
class OCSPResponseExtension final
|
||||
{
|
||||
public:
|
||||
OCSPResponseExtension();
|
||||
|
||||
ByteString id;
|
||||
bool critical;
|
||||
ByteString value;
|
||||
|
@ -412,7 +414,10 @@ public:
|
|||
|
||||
std::time_t producedAt;
|
||||
|
||||
OCSPResponseExtension* extensions;
|
||||
// SingleResponse extensions (for the certID given in the constructor).
|
||||
OCSPResponseExtension* singleExtensions;
|
||||
// ResponseData extensions.
|
||||
OCSPResponseExtension* responseExtensions;
|
||||
bool includeEmptyExtensions; // If true, include the extension wrapper
|
||||
// regardless of if there are any actual
|
||||
// extensions.
|
||||
|
|
Загрузка…
Ссылка в новой задаче