mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1284256 - Certificate Transparency - verification of Signed Certificate Timestamps (RFC 6962); r=keeler, r=Cykesiopka
MozReview-Commit-ID: IgcnyBH4Up --HG-- extra : transplant_source : %98%A3%5E%B4%DA%89qI1%01A%F8%FF%C7%1FS%D4%23v%B3
This commit is contained in:
parent
dda8b0ae6b
commit
21be681857
283
security/certverifier/CTLogVerifier.cpp
Normal file
283
security/certverifier/CTLogVerifier.cpp
Normal file
@ -0,0 +1,283 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "CTLogVerifier.h"
|
||||
|
||||
#include "CTSerialization.h"
|
||||
#include "hasht.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "pkix/pkixnss.h"
|
||||
#include "pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// A TrustDomain used to extract the SCT log signature parameters
|
||||
// given its subjectPublicKeyInfo.
|
||||
// Only RSASSA-PKCS1v15 with SHA-256 and ECDSA (using the NIST P-256 curve)
|
||||
// with SHA-256 are allowed.
|
||||
// RSA keys must be at least 2048 bits.
|
||||
// See See RFC 6962, Section 2.1.4.
|
||||
class SignatureParamsTrustDomain final : public TrustDomain
|
||||
{
|
||||
public:
|
||||
SignatureParamsTrustDomain()
|
||||
: mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous)
|
||||
{
|
||||
}
|
||||
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
|
||||
TrustLevel&) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input, DigestAlgorithm, uint8_t*, size_t) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
|
||||
Time) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve curve) override
|
||||
{
|
||||
MOZ_ASSERT(mSignatureAlgorithm ==
|
||||
DigitallySigned::SignatureAlgorithm::Anonymous);
|
||||
if (curve != NamedCurve::secp256r1) {
|
||||
return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE;
|
||||
}
|
||||
mSignatureAlgorithm = DigitallySigned::SignatureAlgorithm::ECDSA;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA,
|
||||
unsigned int modulusSizeInBits)
|
||||
override
|
||||
{
|
||||
MOZ_ASSERT(mSignatureAlgorithm ==
|
||||
DigitallySigned::SignatureAlgorithm::Anonymous);
|
||||
// Require RSA keys of at least 2048 bits. See RFC 6962, Section 2.1.4.
|
||||
if (modulusSizeInBits < 2048) {
|
||||
return Result::ERROR_INADEQUATE_KEY_SIZE;
|
||||
}
|
||||
mSignatureAlgorithm = DigitallySigned::SignatureAlgorithm::RSA;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
|
||||
KeyPurposeId) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result NetscapeStepUpMatchesServerAuth(Time, bool&) override
|
||||
{
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
|
||||
{
|
||||
}
|
||||
|
||||
DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
|
||||
};
|
||||
|
||||
|
||||
CTLogVerifier::CTLogVerifier()
|
||||
: mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous)
|
||||
{
|
||||
}
|
||||
|
||||
Result
|
||||
CTLogVerifier::Init(Input subjectPublicKeyInfo)
|
||||
{
|
||||
SignatureParamsTrustDomain trustDomain;
|
||||
Result rv = CheckSubjectPublicKeyInfo(subjectPublicKeyInfo, trustDomain,
|
||||
EndEntityOrCA::MustBeEndEntity);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
mSignatureAlgorithm = trustDomain.mSignatureAlgorithm;
|
||||
|
||||
rv = InputToBuffer(subjectPublicKeyInfo, mSubjectPublicKeyInfo);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!mKeyId.resizeUninitialized(SHA256_LENGTH)) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
rv = DigestBufNSS(subjectPublicKeyInfo, DigestAlgorithm::sha256,
|
||||
mKeyId.begin(), mKeyId.length());
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
CTLogVerifier::Verify(const LogEntry& entry,
|
||||
const SignedCertificateTimestamp& sct)
|
||||
{
|
||||
if (mKeyId.empty() || sct.logId != mKeyId) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (!SignatureParametersMatch(sct.signature)) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
Buffer serializedLogEntry;
|
||||
Result rv = EncodeLogEntry(entry, serializedLogEntry);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Input logEntryInput;
|
||||
rv = BufferToInput(serializedLogEntry, logEntryInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input sctExtensionsInput;
|
||||
rv = BufferToInput(sct.extensions, sctExtensionsInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Buffer serializedData;
|
||||
rv = EncodeV1SCTSignedData(sct.timestamp, logEntryInput, sctExtensionsInput,
|
||||
serializedData);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignature(serializedData, sct.signature.signatureData);
|
||||
}
|
||||
|
||||
Result
|
||||
CTLogVerifier::VerifySignedTreeHead(const SignedTreeHead& sth)
|
||||
{
|
||||
if (!SignatureParametersMatch(sth.signature)) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
Buffer serializedData;
|
||||
Result rv = EncodeTreeHeadSignature(sth, serializedData);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignature(serializedData, sth.signature.signatureData);
|
||||
}
|
||||
|
||||
bool
|
||||
CTLogVerifier::SignatureParametersMatch(const DigitallySigned& signature)
|
||||
{
|
||||
return signature.SignatureParametersMatch(
|
||||
DigitallySigned::HashAlgorithm::SHA256, mSignatureAlgorithm);
|
||||
}
|
||||
|
||||
Result
|
||||
CTLogVerifier::VerifySignature(Input data, Input signature)
|
||||
{
|
||||
uint8_t digest[SHA256_LENGTH];
|
||||
Result rv = DigestBufNSS(data, DigestAlgorithm::sha256, digest,
|
||||
ArrayLength(digest));
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
SignedDigest signedDigest;
|
||||
signedDigest.digestAlgorithm = DigestAlgorithm::sha256;
|
||||
rv = signedDigest.digest.Init(digest, ArrayLength(digest));
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = signedDigest.signature.Init(signature);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Input spki;
|
||||
rv = BufferToInput(mSubjectPublicKeyInfo, spki);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
switch (mSignatureAlgorithm) {
|
||||
case DigitallySigned::SignatureAlgorithm::RSA:
|
||||
rv = VerifyRSAPKCS1SignedDigestNSS(signedDigest, spki, nullptr);
|
||||
break;
|
||||
case DigitallySigned::SignatureAlgorithm::ECDSA:
|
||||
rv = VerifyECDSASignedDigestNSS(signedDigest, spki, nullptr);
|
||||
break;
|
||||
// We do not expect new values added to this enum any time soon,
|
||||
// so just listing all the available ones seems to be the easiest way
|
||||
// to suppress warning C4061 on MSVC (which expects all values of the
|
||||
// enum to be explicitly handled).
|
||||
case DigitallySigned::SignatureAlgorithm::Anonymous:
|
||||
case DigitallySigned::SignatureAlgorithm::DSA:
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("RSA/ECDSA expected");
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (rv != Success) {
|
||||
if (IsFatalError(rv)) {
|
||||
return rv;
|
||||
}
|
||||
// If the error is non-fatal, we assume the signature was invalid.
|
||||
return Result::ERROR_BAD_SIGNATURE;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
CTLogVerifier::VerifySignature(const Buffer& data, const Buffer& signature)
|
||||
{
|
||||
Input dataInput;
|
||||
Result rv = BufferToInput(data, dataInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input signatureInput;
|
||||
rv = BufferToInput(signature, signatureInput);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return VerifySignature(dataInput, signatureInput);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
68
security/certverifier/CTLogVerifier.h
Normal file
68
security/certverifier/CTLogVerifier.h
Normal file
@ -0,0 +1,68 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 CTLogVerifier_h
|
||||
#define CTLogVerifier_h
|
||||
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/pkix.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "SignedTreeHead.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
// Verifies Signed Certificate Timestamps (SCTs) provided by a specific log
|
||||
// using the public key of that log. Assumes the SCT being verified
|
||||
// matches the log by log key ID and signature parameters (an error is returned
|
||||
// otherwise).
|
||||
// The verification functions return Success if the provided SCT has passed
|
||||
// verification, ERROR_BAD_SIGNATURE if failed verification, or other result
|
||||
// on error.
|
||||
class CTLogVerifier
|
||||
{
|
||||
public:
|
||||
CTLogVerifier();
|
||||
|
||||
// Initializes the verifier with log-specific information.
|
||||
// |subjectPublicKeyInfo| is a DER-encoded SubjectPublicKeyInfo.
|
||||
// An error is returned if |subjectPublicKeyInfo| refers to an unsupported
|
||||
// public key.
|
||||
pkix::Result Init(pkix::Input subjectPublicKeyInfo);
|
||||
|
||||
// Returns the log's key ID, which is a SHA256 hash of its public key.
|
||||
// See RFC 6962, Section 3.2.
|
||||
const Buffer& keyId() const { return mKeyId; }
|
||||
|
||||
// Verifies that |sct| contains a valid signature for |entry|.
|
||||
// |sct| must be signed by the verifier's log.
|
||||
pkix::Result Verify(const LogEntry& entry,
|
||||
const SignedCertificateTimestamp& sct);
|
||||
|
||||
// Verifies the signature in |sth|.
|
||||
// |sth| must be signed by the verifier's log.
|
||||
pkix::Result VerifySignedTreeHead(const SignedTreeHead& sth);
|
||||
|
||||
// Returns true if the signature and hash algorithms in |signature|
|
||||
// match those of the log.
|
||||
bool SignatureParametersMatch(const DigitallySigned& signature);
|
||||
|
||||
private:
|
||||
// Performs the underlying verification using the log's public key. Note
|
||||
// that |signature| contains the raw signature data (i.e. without any
|
||||
// DigitallySigned struct encoding).
|
||||
// Returns Success if passed verification, ERROR_BAD_SIGNATURE if failed
|
||||
// verification, or other result on error.
|
||||
pkix::Result VerifySignature(pkix::Input data, pkix::Input signature);
|
||||
pkix::Result VerifySignature(const Buffer& data, const Buffer& signature);
|
||||
|
||||
Buffer mSubjectPublicKeyInfo;
|
||||
Buffer mKeyId;
|
||||
DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
|
||||
};
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
||||
#endif // CTLogVerifier_h
|
396
security/certverifier/CTObjectsExtractor.cpp
Normal file
396
security/certverifier/CTObjectsExtractor.cpp
Normal file
@ -0,0 +1,396 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "CTObjectsExtractor.h"
|
||||
|
||||
#include "hasht.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/RangedPtr.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "pkix/pkixnss.h"
|
||||
#include "pkixutil.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// Holds a non-owning pointer to a byte buffer and allows writing chunks of data
|
||||
// to the buffer, placing the later chunks after the earlier ones
|
||||
// in a stream-like fashion.
|
||||
// Note that writing to Output always succeeds. If the internal buffer
|
||||
// overflows, an error flag is turned on and you won't be able to retrieve
|
||||
// the final data.
|
||||
class Output
|
||||
{
|
||||
public:
|
||||
Output(uint8_t* buffer, size_t length)
|
||||
: begin(buffer)
|
||||
, end(buffer + length)
|
||||
, current(buffer, begin, end)
|
||||
, overflowed(false)
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit Output(uint8_t (&buffer)[N])
|
||||
: Output(buffer, N)
|
||||
{
|
||||
}
|
||||
|
||||
void Write(Input data)
|
||||
{
|
||||
Write(data.UnsafeGetData(), data.GetLength());
|
||||
}
|
||||
|
||||
void Write(uint8_t b)
|
||||
{
|
||||
Write(&b, 1);
|
||||
}
|
||||
|
||||
bool IsOverflowed() const { return overflowed; }
|
||||
|
||||
Result GetInput(/*out*/ Input& input) const
|
||||
{
|
||||
if (overflowed) {
|
||||
return Result::FATAL_ERROR_INVALID_STATE;
|
||||
}
|
||||
size_t length = AssertedCast<size_t>(current.get() - begin);
|
||||
return input.Init(begin, length);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* begin;
|
||||
uint8_t* end;
|
||||
RangedPtr<uint8_t> current;
|
||||
bool overflowed;
|
||||
|
||||
Output(const Output&) = delete;
|
||||
void operator=(const Output&) = delete;
|
||||
|
||||
void Write(const uint8_t* data, size_t length)
|
||||
{
|
||||
size_t available = AssertedCast<size_t>(end - current.get());
|
||||
if (available < length) {
|
||||
overflowed = true;
|
||||
}
|
||||
if (overflowed) {
|
||||
return;
|
||||
}
|
||||
PodCopy(current.get(), data, length);
|
||||
current += length;
|
||||
}
|
||||
};
|
||||
|
||||
// For reference:
|
||||
//
|
||||
// Certificate ::= SEQUENCE {
|
||||
// tbsCertificate TBSCertificate,
|
||||
// signatureAlgorithm AlgorithmIdentifier,
|
||||
// signatureValue BIT STRING }
|
||||
//
|
||||
// TBSCertificate ::= SEQUENCE {
|
||||
// version [0] EXPLICIT Version DEFAULT v1,
|
||||
// serialNumber CertificateSerialNumber,
|
||||
// signature AlgorithmIdentifier,
|
||||
// issuer Name,
|
||||
// validity Validity,
|
||||
// subject Name,
|
||||
// subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
||||
// -- If present, version MUST be v2 or v3
|
||||
// extensions [3] EXPLICIT Extensions OPTIONAL
|
||||
// -- If present, version MUST be v3
|
||||
// }
|
||||
|
||||
// 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 EMBEDDED_SCT_LIST_OID[] = {
|
||||
0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
|
||||
};
|
||||
// Maximum length of DER TLV header
|
||||
static const size_t MAX_TLV_HEADER_LENGTH = 4;
|
||||
// DER tag of the "extensions [3]" field from TBSCertificate
|
||||
static const uint8_t EXTENSIONS_CONTEXT_TAG =
|
||||
der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3;
|
||||
|
||||
// Given a leaf certificate, extracts the DER-encoded TBSCertificate component
|
||||
// of the corresponding Precertificate.
|
||||
// Basically, the extractor needs to remove the embedded SCTs extension
|
||||
// from the certificate and return its TBSCertificate. We do it in an ad hoc
|
||||
// manner by breaking the source DER into several parts and then joining
|
||||
// the right parts, taking care to update the relevant TLV headers.
|
||||
// See WriteOutput for more details on the parts involved.
|
||||
class PrecertTBSExtractor
|
||||
{
|
||||
public:
|
||||
// |buffer| is the buffer to be used for writing the output. Since the
|
||||
// required buffer size is not generally known in advance, it's best
|
||||
// to use at least the size of the input certificate DER.
|
||||
PrecertTBSExtractor(Input der, uint8_t* buffer, size_t bufferLength)
|
||||
: mDER(der)
|
||||
, mOutput(buffer, bufferLength)
|
||||
{
|
||||
}
|
||||
|
||||
// Performs the extraction.
|
||||
Result Init()
|
||||
{
|
||||
Reader tbsReader;
|
||||
Result rv = GetTBSCertificate(tbsReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = ExtractTLVsBeforeExtensions(tbsReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = ExtractOptionalExtensionsExceptSCTs(tbsReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
return WriteOutput();
|
||||
}
|
||||
|
||||
// Use to retrieve the result after a successful call to Init.
|
||||
// The returned Input points to the buffer supplied in the constructor.
|
||||
Input GetPrecertTBS()
|
||||
{
|
||||
return mPrecertTBS;
|
||||
}
|
||||
|
||||
private:
|
||||
Result GetTBSCertificate(Reader& tbsReader)
|
||||
{
|
||||
Reader certificateReader;
|
||||
Result rv = der::ExpectTagAndGetValueAtEnd(mDER, der::SEQUENCE,
|
||||
certificateReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return ExpectTagAndGetValue(certificateReader, der::SEQUENCE, tbsReader);
|
||||
}
|
||||
|
||||
Result ExtractTLVsBeforeExtensions(Reader& tbsReader)
|
||||
{
|
||||
Reader::Mark tbsBegin = tbsReader.GetMark();
|
||||
while (!tbsReader.AtEnd()) {
|
||||
if (tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
|
||||
break;
|
||||
}
|
||||
uint8_t tag;
|
||||
Input tagValue;
|
||||
Result rv = der::ReadTagAndGetValue(tbsReader, tag, tagValue);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return tbsReader.GetInput(tbsBegin, mTLVsBeforeExtensions);
|
||||
}
|
||||
|
||||
Result ExtractOptionalExtensionsExceptSCTs(Reader& tbsReader)
|
||||
{
|
||||
if (!tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
|
||||
return Success;
|
||||
}
|
||||
|
||||
Reader extensionsContextReader;
|
||||
Result rv = der::ExpectTagAndGetValueAtEnd(tbsReader,
|
||||
EXTENSIONS_CONTEXT_TAG,
|
||||
extensionsContextReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
Reader extensionsReader;
|
||||
rv = der::ExpectTagAndGetValueAtEnd(extensionsContextReader, der::SEQUENCE,
|
||||
extensionsReader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
while (!extensionsReader.AtEnd()) {
|
||||
Reader::Mark extensionTLVBegin = extensionsReader.GetMark();
|
||||
Reader extension;
|
||||
rv = der::ExpectTagAndGetValue(extensionsReader, der::SEQUENCE,
|
||||
extension);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Reader extensionID;
|
||||
rv = der::ExpectTagAndGetValue(extension, der::OIDTag, extensionID);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!extensionID.MatchRest(EMBEDDED_SCT_LIST_OID)) {
|
||||
Input extensionTLV;
|
||||
rv = extensionsReader.GetInput(extensionTLVBegin, extensionTLV);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!mExtensionTLVs.append(Move(extensionTLV))) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result WriteOutput()
|
||||
{
|
||||
// What should be written here:
|
||||
//
|
||||
// TBSCertificate ::= SEQUENCE (TLV with header |tbsHeader|)
|
||||
// dump of |mTLVsBeforeExtensions|
|
||||
// extensions [3] OPTIONAL (TLV with header |extensionsContextHeader|)
|
||||
// SEQUENCE (TLV with with header |extensionsHeader|)
|
||||
// dump of |mExtensionTLVs|
|
||||
|
||||
Result rv;
|
||||
if (mExtensionTLVs.length() > 0) {
|
||||
uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
|
||||
uint8_t extensionsContextHeaderBuffer[MAX_TLV_HEADER_LENGTH];
|
||||
uint8_t extensionsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
|
||||
|
||||
Input tbsHeader;
|
||||
Input extensionsContextHeader;
|
||||
Input extensionsHeader;
|
||||
|
||||
// Count the total size of the extensions. Note that since
|
||||
// the extensions data is contained within mDER (an Input),
|
||||
// their combined length won't overflow Input::size_type.
|
||||
Input::size_type extensionsValueLength = 0;
|
||||
for (auto& extensionTLV : mExtensionTLVs) {
|
||||
extensionsValueLength += extensionTLV.GetLength();
|
||||
}
|
||||
|
||||
rv = MakeTLVHeader(der::SEQUENCE, extensionsValueLength,
|
||||
extensionsHeaderBuffer, extensionsHeader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input::size_type extensionsContextLength =
|
||||
AssertedCast<Input::size_type>(extensionsHeader.GetLength() +
|
||||
extensionsValueLength);
|
||||
rv = MakeTLVHeader(EXTENSIONS_CONTEXT_TAG,
|
||||
extensionsContextLength,
|
||||
extensionsContextHeaderBuffer,
|
||||
extensionsContextHeader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input::size_type tbsLength =
|
||||
AssertedCast<Input::size_type>(mTLVsBeforeExtensions.GetLength() +
|
||||
extensionsContextHeader.GetLength() +
|
||||
extensionsHeader.GetLength() +
|
||||
extensionsValueLength);
|
||||
rv = MakeTLVHeader(der::SEQUENCE, tbsLength, tbsHeaderBuffer, tbsHeader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mOutput.Write(tbsHeader);
|
||||
mOutput.Write(mTLVsBeforeExtensions);
|
||||
mOutput.Write(extensionsContextHeader);
|
||||
mOutput.Write(extensionsHeader);
|
||||
for (auto& extensionTLV : mExtensionTLVs) {
|
||||
mOutput.Write(extensionTLV);
|
||||
}
|
||||
} else {
|
||||
uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
|
||||
Input tbsHeader;
|
||||
rv = MakeTLVHeader(der::SEQUENCE, mTLVsBeforeExtensions.GetLength(),
|
||||
tbsHeaderBuffer, tbsHeader);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
mOutput.Write(tbsHeader);
|
||||
mOutput.Write(mTLVsBeforeExtensions);
|
||||
}
|
||||
|
||||
return mOutput.GetInput(mPrecertTBS);
|
||||
}
|
||||
|
||||
Result MakeTLVHeader(uint8_t tag, size_t length,
|
||||
uint8_t (&buffer)[MAX_TLV_HEADER_LENGTH],
|
||||
/*out*/ Input& header)
|
||||
{
|
||||
Output output(buffer);
|
||||
output.Write(tag);
|
||||
if (length < 128) {
|
||||
output.Write(AssertedCast<uint8_t>(length));
|
||||
} else if (length < 256) {
|
||||
output.Write(0x81u);
|
||||
output.Write(AssertedCast<uint8_t>(length));
|
||||
} else if (length < 65536) {
|
||||
output.Write(0x82u);
|
||||
output.Write(AssertedCast<uint8_t>(length / 256));
|
||||
output.Write(AssertedCast<uint8_t>(length % 256));
|
||||
} else {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
return output.GetInput(header);
|
||||
}
|
||||
|
||||
Input mDER;
|
||||
Input mTLVsBeforeExtensions;
|
||||
Vector<Input, 16> mExtensionTLVs;
|
||||
Output mOutput;
|
||||
Input mPrecertTBS;
|
||||
};
|
||||
|
||||
Result
|
||||
GetPrecertLogEntry(Input leafCertificate, Input issuerSubjectPublicKeyInfo,
|
||||
LogEntry& output)
|
||||
{
|
||||
MOZ_ASSERT(leafCertificate.GetLength() > 0);
|
||||
MOZ_ASSERT(issuerSubjectPublicKeyInfo.GetLength() > 0);
|
||||
output.Reset();
|
||||
|
||||
Buffer precertTBSBuffer;
|
||||
if (!precertTBSBuffer.resize(leafCertificate.GetLength())) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
PrecertTBSExtractor extractor(leafCertificate,
|
||||
precertTBSBuffer.begin(),
|
||||
precertTBSBuffer.length());
|
||||
Result rv = extractor.Init();
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
Input precertTBS(extractor.GetPrecertTBS());
|
||||
MOZ_ASSERT(precertTBS.UnsafeGetData() == precertTBSBuffer.begin());
|
||||
precertTBSBuffer.shrinkTo(precertTBS.GetLength());
|
||||
|
||||
output.type = LogEntry::Type::Precert;
|
||||
output.tbsCertificate = Move(precertTBSBuffer);
|
||||
|
||||
if (!output.issuerKeyHash.resizeUninitialized(SHA256_LENGTH)) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
return DigestBufNSS(issuerSubjectPublicKeyInfo, DigestAlgorithm::sha256,
|
||||
output.issuerKeyHash.begin(),
|
||||
output.issuerKeyHash.length());
|
||||
}
|
||||
|
||||
Result
|
||||
GetX509LogEntry(Input leafCertificate, LogEntry& output)
|
||||
{
|
||||
MOZ_ASSERT(leafCertificate.GetLength() > 0);
|
||||
output.Reset();
|
||||
output.type = LogEntry::Type::X509;
|
||||
return InputToBuffer(leafCertificate, output.leafCertificate);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
43
security/certverifier/CTObjectsExtractor.h
Normal file
43
security/certverifier/CTObjectsExtractor.h
Normal file
@ -0,0 +1,43 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 CTObjectsExtractor_h
|
||||
#define CTObjectsExtractor_h
|
||||
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/Result.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
// Obtains a PrecertChain log entry for |leafCertificate|, a DER-encoded
|
||||
// X.509v3 certificate that contains an X.509v3 extension with the
|
||||
// OID 1.3.6.1.4.1.11129.2.4.2.
|
||||
// |issuerSubjectPublicKeyInfo| is a DER-encoded SPKI of |leafCertificate|'s
|
||||
// issuer.
|
||||
// On success, fills |output| with the data for a PrecertChain log entry.
|
||||
// If |leafCertificate| does not contain the required extension,
|
||||
// an error is returned.
|
||||
// The returned |output| is intended to be verified by CTLogVerifier::Verify.
|
||||
// Note that |leafCertificate| is not checked for validity or well-formedness.
|
||||
// You might want to validate it first using pkix::BuildCertChain or similar.
|
||||
pkix::Result GetPrecertLogEntry(pkix::Input leafCertificate,
|
||||
pkix::Input issuerSubjectPublicKeyInfo,
|
||||
LogEntry& output);
|
||||
|
||||
// Obtains an X509Chain log entry for |leafCertificate|, a DER-encoded
|
||||
// X.509v3 certificate that is not expected to contain an X.509v3 extension
|
||||
// with the OID 1.3.6.1.4.1.11129.2.4.2 (meaning a certificate without
|
||||
// an embedded SCT).
|
||||
// On success, fills |output| with the data for an X509Chain log entry.
|
||||
// The returned |output| is intended to be verified by CTLogVerifier::Verify.
|
||||
// Note that |leafCertificate| is not checked for validity or well-formedness.
|
||||
// You might want to validate it first using pkix::BuildCertChain or similar.
|
||||
pkix::Result GetX509LogEntry(pkix::Input leafCertificate, LogEntry& output);
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
||||
#endif // CTObjectsExtractor_h
|
@ -9,6 +9,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/TypeTraits.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
@ -241,6 +242,24 @@ WriteEncodedBytes(const Buffer& source, Buffer& output)
|
||||
return Success;
|
||||
}
|
||||
|
||||
// A variable-length byte array is prefixed by its length when serialized.
|
||||
// This writes the length prefix.
|
||||
// |prefixLength| indicates the number of bytes needed to represent the length.
|
||||
// |dataLength| is the length of the byte array following the prefix.
|
||||
// Fails if |dataLength| is more than 2^|prefixLength| - 1.
|
||||
template <size_t prefixLength>
|
||||
static Result
|
||||
WriteVariableBytesPrefix(size_t dataLength, Buffer& output)
|
||||
{
|
||||
const size_t maxAllowedInputSize =
|
||||
static_cast<size_t>(((1 << (prefixLength * 8)) - 1));
|
||||
if (dataLength > maxAllowedInputSize) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
return WriteUint<prefixLength>(dataLength, output);
|
||||
}
|
||||
|
||||
// Writes a variable-length array to |output|.
|
||||
// |prefixLength| indicates the number of bytes needed to represent the length.
|
||||
// |input| is the array itself.
|
||||
@ -249,14 +268,7 @@ template <size_t prefixLength>
|
||||
static Result
|
||||
WriteVariableBytes(Input input, Buffer& output)
|
||||
{
|
||||
size_t inputSize = input.GetLength();
|
||||
const size_t maxAllowedInputSize =
|
||||
static_cast<size_t>(((1 << (prefixLength * 8)) - 1));
|
||||
if (inputSize > maxAllowedInputSize) {
|
||||
return Result::FATAL_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
Result rv = WriteUint<prefixLength>(inputSize, output);
|
||||
Result rv = WriteVariableBytesPrefix<prefixLength>(input.GetLength(), output);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
@ -501,4 +513,36 @@ DecodeSignedCertificateTimestamp(Reader& reader,
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
EncodeSCTList(const Vector<pkix::Input>& scts, Buffer& output)
|
||||
{
|
||||
// Find out the total size of the SCT list to be written so we can
|
||||
// write the prefix for the list before writing its contents.
|
||||
size_t sctListLength = 0;
|
||||
for (auto& sct : scts) {
|
||||
sctListLength +=
|
||||
/* data size */ sct.GetLength() +
|
||||
/* length prefix size */ kSerializedSCTLengthBytes;
|
||||
}
|
||||
|
||||
if (!output.reserve(kSCTListLengthBytes + sctListLength)) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
// Write the prefix for the SCT list.
|
||||
Result rv = WriteVariableBytesPrefix<kSCTListLengthBytes>(sctListLength,
|
||||
output);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
// Now write each SCT from the list.
|
||||
for (auto& sct : scts) {
|
||||
rv = WriteVariableBytes<kSerializedSCTLengthBytes>(sct, output);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
@ -7,6 +7,7 @@
|
||||
#ifndef CTSerialization_h
|
||||
#define CTSerialization_h
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/Result.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
@ -61,6 +62,9 @@ pkix::Result ReadSCTListItem(pkix::Reader& listReader, pkix::Input& result);
|
||||
pkix::Result DecodeSignedCertificateTimestamp(pkix::Reader& input,
|
||||
SignedCertificateTimestamp& output);
|
||||
|
||||
// Encodes a list of SCTs (|scts|) to |output|.
|
||||
pkix::Result EncodeSCTList(const Vector<pkix::Input>& scts, Buffer& output);
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
||||
#endif // CTSerialization_h
|
||||
|
211
security/certverifier/MultiLogCTVerifier.cpp
Normal file
211
security/certverifier/MultiLogCTVerifier.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "MultiLogCTVerifier.h"
|
||||
|
||||
#include "CTObjectsExtractor.h"
|
||||
#include "CTSerialization.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// The possible verification statuses for a Signed Certificate Timestamp.
|
||||
enum class SCTVerifyStatus {
|
||||
UnknownLog, // The SCT is from an unknown log and can not be verified.
|
||||
Invalid, // The SCT is from a known log, but the signature is invalid.
|
||||
OK // The SCT is from a known log, and the signature is valid.
|
||||
};
|
||||
|
||||
// Note: this moves |sct| to the target list in |result|, invalidating |sct|.
|
||||
static Result
|
||||
StoreVerifiedSct(CTVerifyResult& result,
|
||||
SignedCertificateTimestamp&& sct,
|
||||
SCTVerifyStatus status)
|
||||
{
|
||||
SCTList* target;
|
||||
switch (status) {
|
||||
case SCTVerifyStatus::UnknownLog:
|
||||
target = &result.unknownLogsScts;
|
||||
break;
|
||||
case SCTVerifyStatus::Invalid:
|
||||
target = &result.invalidScts;
|
||||
break;
|
||||
case SCTVerifyStatus::OK:
|
||||
target = &result.verifiedScts;
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unexpected SCTVerifyStatus type");
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
if (!target->append(Move(sct))) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
void
|
||||
CTVerifyResult::Reset()
|
||||
{
|
||||
verifiedScts.clear();
|
||||
invalidScts.clear();
|
||||
unknownLogsScts.clear();
|
||||
decodingErrors = 0;
|
||||
}
|
||||
|
||||
Result
|
||||
MultiLogCTVerifier::AddLog(Input publicKey)
|
||||
{
|
||||
CTLogVerifier log;
|
||||
Result rv = log.Init(publicKey);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
if (!mLogs.append(Move(log))) {
|
||||
return Result::FATAL_ERROR_NO_MEMORY;
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
MultiLogCTVerifier::Verify(Input cert,
|
||||
Input issuerSubjectPublicKeyInfo,
|
||||
Input sctListFromCert,
|
||||
Input sctListFromOCSPResponse,
|
||||
Input sctListFromTLSExtension,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result)
|
||||
{
|
||||
MOZ_ASSERT(cert.GetLength() > 0);
|
||||
result.Reset();
|
||||
|
||||
Result rv;
|
||||
|
||||
// Verify embedded SCTs
|
||||
if (issuerSubjectPublicKeyInfo.GetLength() > 0 &&
|
||||
sctListFromCert.GetLength() > 0) {
|
||||
LogEntry precertEntry;
|
||||
rv = GetPrecertLogEntry(cert, issuerSubjectPublicKeyInfo, precertEntry);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = VerifySCTs(sctListFromCert, precertEntry,
|
||||
SignedCertificateTimestamp::Origin::Embedded, time,
|
||||
result);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
LogEntry x509Entry;
|
||||
rv = GetX509LogEntry(cert, x509Entry);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Verify SCTs from a stapled OCSP response
|
||||
if (sctListFromOCSPResponse.GetLength() > 0) {
|
||||
rv = VerifySCTs(sctListFromOCSPResponse, x509Entry,
|
||||
SignedCertificateTimestamp::Origin::OCSPResponse, time,
|
||||
result);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify SCTs from a TLS extension
|
||||
if (sctListFromTLSExtension.GetLength() > 0) {
|
||||
rv = VerifySCTs(sctListFromTLSExtension, x509Entry,
|
||||
SignedCertificateTimestamp::Origin::TLSExtension, time,
|
||||
result);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
|
||||
const LogEntry& expectedEntry,
|
||||
SignedCertificateTimestamp::Origin origin,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result)
|
||||
{
|
||||
Reader listReader;
|
||||
Result rv = DecodeSCTList(encodedSctList, listReader);
|
||||
if (rv != Success) {
|
||||
result.decodingErrors++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
while (!listReader.AtEnd()) {
|
||||
Input encodedSct;
|
||||
rv = ReadSCTListItem(listReader, encodedSct);
|
||||
if (rv != Success) {
|
||||
result.decodingErrors++;
|
||||
return Success;
|
||||
}
|
||||
|
||||
Reader encodedSctReader(encodedSct);
|
||||
SignedCertificateTimestamp sct;
|
||||
rv = DecodeSignedCertificateTimestamp(encodedSctReader, sct);
|
||||
if (rv != Success) {
|
||||
result.decodingErrors++;
|
||||
continue;
|
||||
}
|
||||
sct.origin = origin;
|
||||
|
||||
rv = VerifySingleSCT(Move(sct), expectedEntry, time, result);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
|
||||
const LogEntry& expectedEntry,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result)
|
||||
{
|
||||
CTLogVerifier* matchingLog = nullptr;
|
||||
for (auto& log : mLogs) {
|
||||
if (log.keyId() == sct.logId) {
|
||||
matchingLog = &log;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingLog) {
|
||||
// SCT does not match any known log.
|
||||
return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::UnknownLog);
|
||||
}
|
||||
|
||||
if (!matchingLog->SignatureParametersMatch(sct.signature)) {
|
||||
// SCT signature parameters do not match the log's.
|
||||
return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
|
||||
}
|
||||
|
||||
Result rv = matchingLog->Verify(expectedEntry, sct);
|
||||
if (rv != Success) {
|
||||
if (rv == Result::ERROR_BAD_SIGNATURE) {
|
||||
return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// SCT verified ok, just make sure the timestamp is legitimate.
|
||||
if (sct.timestamp > time) {
|
||||
return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
|
||||
}
|
||||
|
||||
return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::OK);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
119
security/certverifier/MultiLogCTVerifier.h
Normal file
119
security/certverifier/MultiLogCTVerifier.h
Normal file
@ -0,0 +1,119 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 MultiLogCTVerifier_h
|
||||
#define MultiLogCTVerifier_h
|
||||
|
||||
#include "CTLogVerifier.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/Result.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
typedef Vector<SignedCertificateTimestamp> SCTList;
|
||||
|
||||
// Holds Signed Certificate Timestamps, arranged by their verification results.
|
||||
class CTVerifyResult
|
||||
{
|
||||
public:
|
||||
// SCTs from known logs where the signature verified correctly.
|
||||
SCTList verifiedScts;
|
||||
|
||||
// SCTs from known logs where the signature failed to verify.
|
||||
SCTList invalidScts;
|
||||
|
||||
// SCTs from unknown logs and as such are unverifiable.
|
||||
SCTList unknownLogsScts;
|
||||
|
||||
// For a certificate to pass Certificate Transparency verification, at least
|
||||
// one of the provided SCTs must validate. The verifier makes the best effort
|
||||
// to extract the available SCTs from the binary sources provided to it.
|
||||
// If some SCT cannot be extracted due to encoding errors, the verifier
|
||||
// proceeds to the next available one. In other words, decoding errors are
|
||||
// effectively ignored.
|
||||
// Note that a serialized SCT may fail to decode for a "legitimate" reason,
|
||||
// e.g. if the SCT is from a future version of the Certificate Transparency
|
||||
// standard.
|
||||
// |decodingErrors| field counts the errors of the above kind.
|
||||
// This field is purely informational; there is probably nothing to do with it
|
||||
// in release builds, but it is useful in unit tests.
|
||||
size_t decodingErrors;
|
||||
|
||||
void Reset();
|
||||
};
|
||||
|
||||
// A Certificate Transparency verifier that can verify Signed Certificate
|
||||
// Timestamps from multiple logs.
|
||||
class MultiLogCTVerifier
|
||||
{
|
||||
public:
|
||||
// Adds a new log to the list of known logs to verify against.
|
||||
pkix::Result AddLog(pkix::Input publicKey);
|
||||
|
||||
// Verifies SCTs embedded in the certificate itself, SCTs embedded in a
|
||||
// stapled OCSP response, and SCTs obtained via the
|
||||
// signed_certificate_timestamp TLS extension on the given |cert|.
|
||||
//
|
||||
// A certificate is permitted but not required to use multiple sources for
|
||||
// SCTs. It is expected that most certificates will use only one source
|
||||
// (embedding, TLS extension or OCSP stapling).
|
||||
//
|
||||
// The verifier stops on fatal errors (such as out of memory or invalid
|
||||
// DER encoding of |cert|), but it does not stop on SCT decoding errors. See
|
||||
// CTVerifyResult for more details.
|
||||
//
|
||||
// The internal state of the verifier object is not modified
|
||||
// during the verification process.
|
||||
//
|
||||
// |cert| DER-encoded certificate to be validated using the provided SCTs.
|
||||
// |sctListFromCert| SCT list embedded in |cert|, empty if not present.
|
||||
// |issuerSubjectPublicKeyInfo| SPKI of |cert|'s issuer. Can be empty,
|
||||
// in which case the embedded SCT list
|
||||
// won't be verified.
|
||||
// |sctListFromOCSPResponse| SCT list included in a stapled OCSP response
|
||||
// for |cert|. Empty if not available.
|
||||
// |sctListFromTLSExtension| is the SCT list from the TLS extension. Empty
|
||||
// if no extension was present.
|
||||
// |time| the current time. Used to make sure SCTs are not in the future.
|
||||
// Measured in milliseconds since the epoch, ignoring leap seconds
|
||||
// (same format as used by the "timestamp" field of
|
||||
// SignedCertificateTimestamp).
|
||||
// |result| will be filled with the SCTs present, divided into categories
|
||||
// based on the verification result.
|
||||
pkix::Result Verify(pkix::Input cert,
|
||||
pkix::Input issuerSubjectPublicKeyInfo,
|
||||
pkix::Input sctListFromCert,
|
||||
pkix::Input sctListFromOCSPResponse,
|
||||
pkix::Input sctListFromTLSExtension,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result);
|
||||
|
||||
private:
|
||||
// Verifies a list of SCTs from |encodedSctList| over |expectedEntry|,
|
||||
// placing the verification results in |result|. The SCTs in the list
|
||||
// come from |origin| (as will be reflected in the origin field of each SCT).
|
||||
pkix::Result VerifySCTs(pkix::Input encodedSctList,
|
||||
const LogEntry& expectedEntry,
|
||||
SignedCertificateTimestamp::Origin origin,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result);
|
||||
|
||||
// Verifies a single, parsed SCT against all known logs.
|
||||
// Note: moves |sct| to the target list in |result|, invalidating |sct|.
|
||||
pkix::Result VerifySingleSCT(SignedCertificateTimestamp&& sct,
|
||||
const ct::LogEntry& expectedEntry,
|
||||
uint64_t time,
|
||||
CTVerifyResult& result);
|
||||
|
||||
// The list of known logs.
|
||||
Vector<CTLogVerifier> mLogs;
|
||||
};
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
||||
#endif // MultiLogCTVerifier_h
|
@ -8,7 +8,6 @@
|
||||
#define SignedCertificateTimestamp_h
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/Result.h"
|
||||
|
||||
@ -90,6 +89,8 @@ struct SignedCertificateTimestamp
|
||||
|
||||
Version version;
|
||||
Buffer logId;
|
||||
// "timestamp" is the current time in milliseconds, measured since the epoch,
|
||||
// ignoring leap seconds. See RFC 6962, Section 3.2.
|
||||
uint64_t timestamp;
|
||||
Buffer extensions;
|
||||
DigitallySigned signature;
|
||||
|
@ -13,7 +13,10 @@ EXPORTS += [
|
||||
UNIFIED_SOURCES += [
|
||||
'BRNameMatchingPolicy.cpp',
|
||||
'CertVerifier.cpp',
|
||||
'CTLogVerifier.cpp',
|
||||
'CTObjectsExtractor.cpp',
|
||||
'CTSerialization.cpp',
|
||||
'MultiLogCTVerifier.cpp',
|
||||
'NSSCertDBTrustDomain.cpp',
|
||||
'OCSPCache.cpp',
|
||||
'OCSPRequestor.cpp',
|
||||
@ -29,6 +32,7 @@ if not CONFIG['NSS_NO_EV_CERTS']:
|
||||
LOCAL_INCLUDES += [
|
||||
'/security/manager/ssl',
|
||||
'/security/pkix/include',
|
||||
'/security/pkix/lib',
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
|
129
security/certverifier/tests/gtest/CTLogVerifierTest.cpp
Normal file
129
security/certverifier/tests/gtest/CTLogVerifierTest.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "CTLogVerifier.h"
|
||||
#include "CTTestUtils.h"
|
||||
#include "nss.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace pkix;
|
||||
|
||||
class CTLogVerifierTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
// Does nothing if NSS is already initialized.
|
||||
MOZ_RELEASE_ASSERT(NSS_NoDB_Init(nullptr) == SECSuccess);
|
||||
|
||||
ASSERT_EQ(Success, mLog.Init(InputForBuffer(GetTestPublicKey())));
|
||||
ASSERT_EQ(GetTestPublicKeyId(), mLog.keyId());
|
||||
}
|
||||
|
||||
protected:
|
||||
CTLogVerifier mLog;
|
||||
};
|
||||
|
||||
TEST_F(CTLogVerifierTest, VerifiesCertSCT)
|
||||
{
|
||||
LogEntry certEntry;
|
||||
GetX509CertLogEntry(certEntry);
|
||||
|
||||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
|
||||
EXPECT_EQ(Success, mLog.Verify(certEntry, certSct));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, VerifiesPrecertSCT)
|
||||
{
|
||||
LogEntry precertEntry;
|
||||
GetPrecertLogEntry(precertEntry);
|
||||
|
||||
SignedCertificateTimestamp precertSct;
|
||||
GetPrecertSCT(precertSct);
|
||||
|
||||
EXPECT_EQ(Success, mLog.Verify(precertEntry, precertSct));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidTimestamp)
|
||||
{
|
||||
LogEntry certEntry;
|
||||
GetX509CertLogEntry(certEntry);
|
||||
|
||||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
|
||||
// Mangle the timestamp, so that it should fail signature validation.
|
||||
certSct.timestamp = 0;
|
||||
|
||||
EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidSignature)
|
||||
{
|
||||
LogEntry certEntry;
|
||||
GetX509CertLogEntry(certEntry);
|
||||
|
||||
// Mangle the signature, making VerifyECDSASignedDigestNSS (used by
|
||||
// CTLogVerifier) return ERROR_BAD_SIGNATURE.
|
||||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
certSct.signature.signatureData[20] ^= '\xFF';
|
||||
EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
|
||||
|
||||
// Make VerifyECDSASignedDigestNSS return ERROR_BAD_DER. We still expect
|
||||
// the verifier to return ERROR_BAD_SIGNATURE.
|
||||
SignedCertificateTimestamp certSct2;
|
||||
GetX509CertSCT(certSct2);
|
||||
certSct2.signature.signatureData[0] ^= '\xFF';
|
||||
EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct2));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, FailsInvalidLogID)
|
||||
{
|
||||
LogEntry certEntry;
|
||||
GetX509CertLogEntry(certEntry);
|
||||
|
||||
SignedCertificateTimestamp certSct;
|
||||
GetX509CertSCT(certSct);
|
||||
|
||||
// Mangle the log ID, which should cause it to match a different log before
|
||||
// attempting signature validation.
|
||||
MOZ_RELEASE_ASSERT(certSct.logId.append('\x0'));
|
||||
|
||||
EXPECT_EQ(Result::FATAL_ERROR_INVALID_ARGS, mLog.Verify(certEntry, certSct));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, VerifiesValidSTH)
|
||||
{
|
||||
SignedTreeHead sth;
|
||||
GetSampleSignedTreeHead(sth);
|
||||
EXPECT_EQ(Success, mLog.VerifySignedTreeHead(sth));
|
||||
}
|
||||
|
||||
TEST_F(CTLogVerifierTest, DoesNotVerifyInvalidSTH)
|
||||
{
|
||||
SignedTreeHead sth;
|
||||
GetSampleSignedTreeHead(sth);
|
||||
sth.sha256RootHash[0] ^= '\xFF';
|
||||
EXPECT_EQ(Result::ERROR_BAD_SIGNATURE, mLog.VerifySignedTreeHead(sth));
|
||||
}
|
||||
|
||||
// Test that excess data after the public key is rejected.
|
||||
TEST_F(CTLogVerifierTest, ExcessDataInPublicKey)
|
||||
{
|
||||
Buffer key = GetTestPublicKey();
|
||||
MOZ_RELEASE_ASSERT(key.append("extra", 5));
|
||||
|
||||
CTLogVerifier log;
|
||||
EXPECT_NE(Success, log.Init(InputForBuffer(key)));
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
83
security/certverifier/tests/gtest/CTObjectsExtractorTest.cpp
Normal file
83
security/certverifier/tests/gtest/CTObjectsExtractorTest.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "CTLogVerifier.h"
|
||||
#include "CTObjectsExtractor.h"
|
||||
#include "CTSerialization.h"
|
||||
#include "CTTestUtils.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "nss.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace pkix;
|
||||
|
||||
class CTObjectsExtractorTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
// Does nothing if NSS is already initialized.
|
||||
MOZ_RELEASE_ASSERT(NSS_NoDB_Init(nullptr) == SECSuccess);
|
||||
|
||||
mTestCert = GetDEREncodedX509Cert();
|
||||
mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
|
||||
mCaCert = GetDEREncodedCACert();
|
||||
mCaCertSPKI = ExtractCertSPKI(mCaCert);
|
||||
|
||||
Buffer logPublicKey = GetTestPublicKey();
|
||||
ASSERT_EQ(Success, mLog.Init(InputForBuffer(logPublicKey)));
|
||||
}
|
||||
|
||||
protected:
|
||||
Buffer mTestCert;
|
||||
Buffer mEmbeddedCert;
|
||||
Buffer mCaCert;
|
||||
Buffer mCaCertSPKI;
|
||||
CTLogVerifier mLog;
|
||||
};
|
||||
|
||||
TEST_F(CTObjectsExtractorTest, ExtractPrecert)
|
||||
{
|
||||
LogEntry entry;
|
||||
ASSERT_EQ(Success,
|
||||
GetPrecertLogEntry(InputForBuffer(mEmbeddedCert),
|
||||
InputForBuffer(mCaCertSPKI),
|
||||
entry));
|
||||
|
||||
EXPECT_EQ(LogEntry::Type::Precert, entry.type);
|
||||
// Should have empty leaf cert for this log entry type.
|
||||
EXPECT_TRUE(entry.leafCertificate.empty());
|
||||
EXPECT_EQ(GetDefaultIssuerKeyHash(), entry.issuerKeyHash);
|
||||
EXPECT_EQ(GetDEREncodedTestTbsCert(), entry.tbsCertificate);
|
||||
}
|
||||
|
||||
TEST_F(CTObjectsExtractorTest, ExtractOrdinaryX509Cert)
|
||||
{
|
||||
LogEntry entry;
|
||||
ASSERT_EQ(Success, GetX509LogEntry(InputForBuffer(mTestCert), entry));
|
||||
|
||||
EXPECT_EQ(LogEntry::Type::X509, entry.type);
|
||||
// Should have empty tbsCertificate / issuerKeyHash for this log entry type.
|
||||
EXPECT_TRUE(entry.tbsCertificate.empty());
|
||||
EXPECT_TRUE(entry.issuerKeyHash.empty());
|
||||
// Length of leafCertificate should be 718, see the CT Serialization tests.
|
||||
EXPECT_EQ(718U, entry.leafCertificate.length());
|
||||
}
|
||||
|
||||
// Test that an externally-provided SCT verifies over the LogEntry
|
||||
// of a regular X.509 Certificate
|
||||
TEST_F(CTObjectsExtractorTest, ComplementarySCTVerifies)
|
||||
{
|
||||
SignedCertificateTimestamp sct;
|
||||
GetX509CertSCT(sct);
|
||||
|
||||
LogEntry entry;
|
||||
ASSERT_EQ(Success, GetX509LogEntry(InputForBuffer(mTestCert), entry));
|
||||
EXPECT_EQ(Success, mLog.Verify(entry, sct));
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
@ -6,8 +6,8 @@
|
||||
|
||||
#include "CTSerialization.h"
|
||||
#include "CTTestUtils.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/Move.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
@ -177,6 +177,32 @@ TEST_F(CTSerializationTest, FailsDecodingInvalidSCTList)
|
||||
EXPECT_NE(Success, ReadSCTListItem(listReader, decoded2));
|
||||
}
|
||||
|
||||
TEST_F(CTSerializationTest, EncodesSCTList)
|
||||
{
|
||||
const uint8_t SCT_1[] = { 0x61, 0x62, 0x63 };
|
||||
const uint8_t SCT_2[] = { 0x64, 0x65, 0x66 };
|
||||
|
||||
Vector<Input> list;
|
||||
ASSERT_TRUE(list.append(Move(Input(SCT_1))));
|
||||
ASSERT_TRUE(list.append(Move(Input(SCT_2))));
|
||||
|
||||
Buffer encodedList;
|
||||
ASSERT_EQ(Success, EncodeSCTList(list, encodedList));
|
||||
|
||||
Reader listReader;
|
||||
ASSERT_EQ(Success, DecodeSCTList(InputForBuffer(encodedList), listReader));
|
||||
|
||||
Input decoded1;
|
||||
ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1));
|
||||
EXPECT_TRUE(InputsAreEqual(decoded1, Input(SCT_1)));
|
||||
|
||||
Input decoded2;
|
||||
ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2));
|
||||
EXPECT_TRUE(InputsAreEqual(decoded2, Input(SCT_2)));
|
||||
|
||||
EXPECT_TRUE(listReader.AtEnd());
|
||||
}
|
||||
|
||||
TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp)
|
||||
{
|
||||
Buffer encodedSctBuffer = GetTestSignedCertificateTimestamp();
|
||||
|
@ -9,12 +9,18 @@
|
||||
#include <stdint.h>
|
||||
#include <iomanip>
|
||||
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
#include "CTSerialization.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "mozilla/Vector.h"
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/pkix.h"
|
||||
#include "pkix/pkixnss.h"
|
||||
#include "pkix/pkixtypes.h"
|
||||
#include "pkix/Result.h"
|
||||
#include "pkixcheck.h"
|
||||
#include "pkixutil.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "SignedTreeHead.h"
|
||||
|
||||
@ -22,7 +28,7 @@ namespace mozilla { namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
// The following test vectors are from
|
||||
// The following test vectors are from the CT test data repository at
|
||||
// https://github.com/google/certificate-transparency/tree/master/test/testdata
|
||||
|
||||
// test-cert.pem
|
||||
@ -113,6 +119,212 @@ const char kSampleSTHTreeHeadSignature[] =
|
||||
const size_t kSampleSTHTreeSize = 21u;
|
||||
const uint64_t kSampleSTHTimestamp = 1396877277237u;
|
||||
|
||||
// test-embedded-cert.pem
|
||||
const char kTestEmbeddedCertData[] =
|
||||
"30820359308202c2a003020102020107300d06092a864886f70d01010505"
|
||||
"003055310b300906035504061302474231243022060355040a131b436572"
|
||||
"7469666963617465205472616e73706172656e6379204341310e300c0603"
|
||||
"550408130557616c65733110300e060355040713074572772057656e301e"
|
||||
"170d3132303630313030303030305a170d3232303630313030303030305a"
|
||||
"3052310b30090603550406130247423121301f060355040a131843657274"
|
||||
"69666963617465205472616e73706172656e6379310e300c060355040813"
|
||||
"0557616c65733110300e060355040713074572772057656e30819f300d06"
|
||||
"092a864886f70d010101050003818d0030818902818100beef98e7c26877"
|
||||
"ae385f75325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee4"
|
||||
"58bd7db86f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb4"
|
||||
"1cd0e72805a410cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39ee"
|
||||
"d0b88119dc154dc68f7da8e30caf158a33e6c9509f4a05b01409ff5dd87e"
|
||||
"b50203010001a382013a30820136301d0603551d0e041604142031541af2"
|
||||
"5c05ffd8658b6843794f5e9036f7b4307d0603551d230476307480145f9d"
|
||||
"880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603"
|
||||
"5504061302474231243022060355040a131b436572746966696361746520"
|
||||
"5472616e73706172656e6379204341310e300c0603550408130557616c65"
|
||||
"733110300e060355040713074572772057656e82010030090603551d1304"
|
||||
"02300030818a060a2b06010401d679020402047c047a0078007600df1c2e"
|
||||
"c11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d76400"
|
||||
"00013ddb27df9300000403004730450220482f6751af35dba65436be1fd6"
|
||||
"640f3dbf9a41429495924530288fa3e5e23e06022100e4edc0db3ac572b1"
|
||||
"e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08300d06092a86"
|
||||
"4886f70d0101050500038181008a0c4bef099d479279afa0a28e689f91e1"
|
||||
"c4421be2d269a2ea6ca4e8215ddeddca1504a11e7c87c4b77e80f0e97903"
|
||||
"5268f27ca20e166804ae556f316981f96a394ab7abfd3e255ac0044513fe"
|
||||
"76570c6795abe4703133d303f89f3afa6bbcfc517319dfd95b934241211f"
|
||||
"634035c3d078307a68c6075a2e20c89f36b8910ca0";
|
||||
|
||||
const char kTestTbsCertData[] =
|
||||
"30820233a003020102020107300d06092a864886f70d0101050500305531"
|
||||
"0b300906035504061302474231243022060355040a131b43657274696669"
|
||||
"63617465205472616e73706172656e6379204341310e300c060355040813"
|
||||
"0557616c65733110300e060355040713074572772057656e301e170d3132"
|
||||
"303630313030303030305a170d3232303630313030303030305a3052310b"
|
||||
"30090603550406130247423121301f060355040a13184365727469666963"
|
||||
"617465205472616e73706172656e6379310e300c0603550408130557616c"
|
||||
"65733110300e060355040713074572772057656e30819f300d06092a8648"
|
||||
"86f70d010101050003818d0030818902818100beef98e7c26877ae385f75"
|
||||
"325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee458bd7db8"
|
||||
"6f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb41cd0e728"
|
||||
"05a410cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39eed0b88119"
|
||||
"dc154dc68f7da8e30caf158a33e6c9509f4a05b01409ff5dd87eb5020301"
|
||||
"0001a381ac3081a9301d0603551d0e041604142031541af25c05ffd8658b"
|
||||
"6843794f5e9036f7b4307d0603551d230476307480145f9d880dc873e654"
|
||||
"d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406130247"
|
||||
"4231243022060355040a131b4365727469666963617465205472616e7370"
|
||||
"6172656e6379204341310e300c0603550408130557616c65733110300e06"
|
||||
"0355040713074572772057656e82010030090603551d1304023000";
|
||||
|
||||
// test-embedded-with-preca-cert.pem
|
||||
const char kTestEmbeddedWithPreCaCertData[] =
|
||||
"30820359308202c2a003020102020108300d06092a864886f70d01010505"
|
||||
"003055310b300906035504061302474231243022060355040a131b436572"
|
||||
"7469666963617465205472616e73706172656e6379204341310e300c0603"
|
||||
"550408130557616c65733110300e060355040713074572772057656e301e"
|
||||
"170d3132303630313030303030305a170d3232303630313030303030305a"
|
||||
"3052310b30090603550406130247423121301f060355040a131843657274"
|
||||
"69666963617465205472616e73706172656e6379310e300c060355040813"
|
||||
"0557616c65733110300e060355040713074572772057656e30819f300d06"
|
||||
"092a864886f70d010101050003818d0030818902818100afaeeacac51ab7"
|
||||
"cebdf9eacae7dd175295e193955a17989aef8d97ab7cdff7761093c0b823"
|
||||
"d2a4e3a51a17b86f28162b66a2538935ebecdc1036233da2dd6531b0c63b"
|
||||
"cc68761ebdc854037b77399246b870a7b72b14c9b1667de09a9640ed9f3f"
|
||||
"3c725d950b4d26559869fe7f1e919a66eb76d35c0117c6bcd0d8cfd21028"
|
||||
"b10203010001a382013a30820136301d0603551d0e04160414612c64efac"
|
||||
"79b728397c9d93e6df86465fa76a88307d0603551d230476307480145f9d"
|
||||
"880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603"
|
||||
"5504061302474231243022060355040a131b436572746966696361746520"
|
||||
"5472616e73706172656e6379204341310e300c0603550408130557616c65"
|
||||
"733110300e060355040713074572772057656e82010030090603551d1304"
|
||||
"02300030818a060a2b06010401d679020402047c047a0078007600df1c2e"
|
||||
"c11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d76400"
|
||||
"00013ddb27e05b000004030047304502207aa79604c47480f3727b084f90"
|
||||
"b3989f79091885e00484431a2a297cbf3a355c022100b49fd8120b0d644c"
|
||||
"d7e75269b4da6317a9356cb950224fc11cc296b2e39b2386300d06092a86"
|
||||
"4886f70d010105050003818100a3a86c41ad0088a25aedc4e7b529a2ddbf"
|
||||
"9e187ffb362157e9302d961b73b43cba0ae1e230d9e45049b7e8c924792e"
|
||||
"bbe7d175baa87b170dfad8ee788984599d05257994084e2e0e796fca5836"
|
||||
"881c3e053553e06ab230f919089b914e4a8e2da45f8a87f2c81a25a61f04"
|
||||
"fe1cace60155653827d41fad9f0658f287d058192c";
|
||||
|
||||
// ca-cert.pem
|
||||
const char kCaCertData[] =
|
||||
"308202d030820239a003020102020100300d06092a864886f70d01010505"
|
||||
"003055310b300906035504061302474231243022060355040a131b436572"
|
||||
"7469666963617465205472616e73706172656e6379204341310e300c0603"
|
||||
"550408130557616c65733110300e060355040713074572772057656e301e"
|
||||
"170d3132303630313030303030305a170d3232303630313030303030305a"
|
||||
"3055310b300906035504061302474231243022060355040a131b43657274"
|
||||
"69666963617465205472616e73706172656e6379204341310e300c060355"
|
||||
"0408130557616c65733110300e060355040713074572772057656e30819f"
|
||||
"300d06092a864886f70d010101050003818d0030818902818100d58a6853"
|
||||
"6210a27119936e778321181c2a4013c6d07b8c76eb9157d3d0fb4b3b516e"
|
||||
"cecbd1c98d91c52f743fab635d55099cd13abaf31ae541442451a74c7816"
|
||||
"f2243cf848cf2831cce67ba04a5a23819f3cba37e624d9c3bdb299b839dd"
|
||||
"fe2631d2cb3a84fc7bb2b5c52fcfc14fff406f5cd44669cbb2f7cfdf86fb"
|
||||
"6ab9d1b10203010001a381af3081ac301d0603551d0e041604145f9d880d"
|
||||
"c873e654d4f80dd8e6b0c124b447c355307d0603551d230476307480145f"
|
||||
"9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b300906"
|
||||
"035504061302474231243022060355040a131b4365727469666963617465"
|
||||
"205472616e73706172656e6379204341310e300c0603550408130557616c"
|
||||
"65733110300e060355040713074572772057656e820100300c0603551d13"
|
||||
"040530030101ff300d06092a864886f70d0101050500038181000608cc4a"
|
||||
"6d64f2205e146c04b276f92b0efa94a5daf23afc3806606d3990d0a1ea23"
|
||||
"3d40295769463b046661e7fa1d179915209aea2e0a775176411227d7c003"
|
||||
"07c7470e61584fd7334224727f51d690bc47a9df354db0f6eb25955de189"
|
||||
"3c4dd5202b24a2f3e440d274b54e1bd376269ca96289b76ecaa41090e14f"
|
||||
"3b0a942e";
|
||||
|
||||
// intermediate-cert.pem
|
||||
const char kIntermediateCertData[] =
|
||||
"308202dd30820246a003020102020109300d06092a864886f70d01010505"
|
||||
"003055310b300906035504061302474231243022060355040a131b436572"
|
||||
"7469666963617465205472616e73706172656e6379204341310e300c0603"
|
||||
"550408130557616c65733110300e060355040713074572772057656e301e"
|
||||
"170d3132303630313030303030305a170d3232303630313030303030305a"
|
||||
"3062310b30090603550406130247423131302f060355040a132843657274"
|
||||
"69666963617465205472616e73706172656e637920496e7465726d656469"
|
||||
"617465204341310e300c0603550408130557616c65733110300e06035504"
|
||||
"0713074572772057656e30819f300d06092a864886f70d01010105000381"
|
||||
"8d0030818902818100d76a678d116f522e55ff821c90642508b7074b14d7"
|
||||
"71159064f7927efdedb87135a1365ee7de18cbd5ce865f860c78f433b4d0"
|
||||
"d3d3407702e7a3ef542b1dfe9bbaa7cdf94dc5975fc729f86f105f381b24"
|
||||
"3535cf9c800f5ca780c1d3c84400ee65d16ee9cf52db8adffe50f5c49335"
|
||||
"0b2190bf50d5bc36f3cac5a8daae92cd8b0203010001a381af3081ac301d"
|
||||
"0603551d0e04160414965508050278479e8773764131bc143a47e229ab30"
|
||||
"7d0603551d230476307480145f9d880dc873e654d4f80dd8e6b0c124b447"
|
||||
"c355a159a4573055310b300906035504061302474231243022060355040a"
|
||||
"131b4365727469666963617465205472616e73706172656e637920434131"
|
||||
"0e300c0603550408130557616c65733110300e0603550407130745727720"
|
||||
"57656e820100300c0603551d13040530030101ff300d06092a864886f70d"
|
||||
"0101050500038181002206dab1c66b71dce095c3f6aa2ef72cf7761be7ab"
|
||||
"d7fc39c31a4cfe1bd96d6734ca82f22dde5a0c8bbbdd825d7b6f3e7612ad"
|
||||
"8db300a7e21169886023262284c3aa5d2191efda10bf9235d37b3a2a340d"
|
||||
"59419b94a48566f3fac3cd8b53d5a4e98270ead297b07210f9ce4a2138b1"
|
||||
"8811143b93fa4e7a87dd37e1385f2c2908";
|
||||
|
||||
// test-embedded-with-intermediate-cert.pem
|
||||
const char kTestEmbeddedWithIntermediateCertData[] =
|
||||
"30820366308202cfa003020102020102300d06092a864886f70d01010505"
|
||||
"003062310b30090603550406130247423131302f060355040a1328436572"
|
||||
"7469666963617465205472616e73706172656e637920496e7465726d6564"
|
||||
"69617465204341310e300c0603550408130557616c65733110300e060355"
|
||||
"040713074572772057656e301e170d3132303630313030303030305a170d"
|
||||
"3232303630313030303030305a3052310b30090603550406130247423121"
|
||||
"301f060355040a13184365727469666963617465205472616e7370617265"
|
||||
"6e6379310e300c0603550408130557616c65733110300e06035504071307"
|
||||
"4572772057656e30819f300d06092a864886f70d010101050003818d0030"
|
||||
"818902818100bb272b26e5deb5459d4acca027e8f12a4d839ac3730a6a10"
|
||||
"9ff7e25498ddbd3f1895d08ba41f8de34967a3a086ce13a90dd5adbb5418"
|
||||
"4bdc08e1ac7826adb8dc9c717bfd7da5b41b4db1736e00f1dac3cec9819c"
|
||||
"cb1a28ba120b020a820e940dd61f95b5432a4bc05d0818f18ce2154eb38d"
|
||||
"2fa7d22d72b976e560db0c7fc77f0203010001a382013a30820136301d06"
|
||||
"03551d0e04160414b1b148e658e703f5f7f3105f20b3c384d7eff1bf307d"
|
||||
"0603551d23047630748014965508050278479e8773764131bc143a47e229"
|
||||
"aba159a4573055310b300906035504061302474231243022060355040a13"
|
||||
"1b4365727469666963617465205472616e73706172656e6379204341310e"
|
||||
"300c0603550408130557616c65733110300e060355040713074572772057"
|
||||
"656e82010930090603551d130402300030818a060a2b06010401d6790204"
|
||||
"02047c047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7"
|
||||
"c6d388fc002e0bbd3f74d7640000013ddb27e2a400000403004730450221"
|
||||
"00a6d34517f3392d9ec5d257adf1c597dc45bd4cd3b73856c616a9fb99e5"
|
||||
"ae75a802205e26c8d1c7e222fe8cda29baeb04a834ee97d34fd81718f1aa"
|
||||
"e0cd66f4b8a93f300d06092a864886f70d0101050500038181000f95a5b4"
|
||||
"e128a914b1e88be8b32964221b58f4558433d020a8e246cca65a40bcbf5f"
|
||||
"2d48933ebc99be6927ca756472fb0bdc7f505f41f462f2bc19d0b299c990"
|
||||
"918df8820f3d31db37979e8bad563b17f00ae67b0f8731c106c943a73bf5"
|
||||
"36af168afe21ef4adfcae19a3cc074899992bf506bc5ce1decaaf07ffeeb"
|
||||
"c805c039";
|
||||
|
||||
// test-embedded-with-intermediate-preca-cert.pem
|
||||
const char kTestEmbeddedWithIntermediatePreCaCertData[] =
|
||||
"30820366308202cfa003020102020103300d06092a864886f70d01010505"
|
||||
"003062310b30090603550406130247423131302f060355040a1328436572"
|
||||
"7469666963617465205472616e73706172656e637920496e7465726d6564"
|
||||
"69617465204341310e300c0603550408130557616c65733110300e060355"
|
||||
"040713074572772057656e301e170d3132303630313030303030305a170d"
|
||||
"3232303630313030303030305a3052310b30090603550406130247423121"
|
||||
"301f060355040a13184365727469666963617465205472616e7370617265"
|
||||
"6e6379310e300c0603550408130557616c65733110300e06035504071307"
|
||||
"4572772057656e30819f300d06092a864886f70d010101050003818d0030"
|
||||
"818902818100d4497056cdfc65e1342cc3df6e654b8af0104702acd2275c"
|
||||
"7d3fb1fc438a89b212110d6419bcc13ae47d64bba241e6706b9ed627f8b3"
|
||||
"4a0d7dff1c44b96287c54bea9d10dc017bceb64f7b6aff3c35a474afec40"
|
||||
"38ab3640b0cd1fb0582ec03b179a2776c8c435d14ab4882d59d7b724fa37"
|
||||
"7ca6db08392173f9c6056b3abadf0203010001a382013a30820136301d06"
|
||||
"03551d0e0416041432da5518d87f1d26ea2767973c0bef286e786a4a307d"
|
||||
"0603551d23047630748014965508050278479e8773764131bc143a47e229"
|
||||
"aba159a4573055310b300906035504061302474231243022060355040a13"
|
||||
"1b4365727469666963617465205472616e73706172656e6379204341310e"
|
||||
"300c0603550408130557616c65733110300e060355040713074572772057"
|
||||
"656e82010930090603551d130402300030818a060a2b06010401d6790204"
|
||||
"02047c047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7"
|
||||
"c6d388fc002e0bbd3f74d7640000013ddb27e3be00000403004730450221"
|
||||
"00d9f61a07fee021e3159f3ca2f570d833ff01374b2096cba5658c5e16fb"
|
||||
"43eb3002200b76fe475138d8cf76833831304dabf043eb1213c96e13ff4f"
|
||||
"a37f7cd3c8dc1f300d06092a864886f70d01010505000381810088ee4e9e"
|
||||
"5eed6b112cc764b151ed929400e9406789c15fbbcfcdab2f10b400234139"
|
||||
"e6ce65c1e51b47bf7c8950f80bccd57168567954ed35b0ce9346065a5eae"
|
||||
"5bf95d41da8e27cee9eeac688f4bd343f9c2888327abd8b9f68dcb1e3050"
|
||||
"041d31bda8e2dd6d39b3664de5ce0870f5fc7e6a00d6ed00528458d953d2"
|
||||
"37586d73";
|
||||
|
||||
static uint8_t
|
||||
CharToByte(char c)
|
||||
@ -154,7 +366,7 @@ GetX509CertLogEntry(LogEntry& entry)
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDerEncodedX509Cert()
|
||||
GetDEREncodedX509Cert()
|
||||
{
|
||||
return HexToBytes(kDefaultDerCert);
|
||||
}
|
||||
@ -274,10 +486,214 @@ GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature)
|
||||
{
|
||||
Buffer ths = HexToBytes(kSampleSTHTreeHeadSignature);
|
||||
Input thsInput;
|
||||
MOZ_RELEASE_ASSERT(thsInput.Init(ths.begin(), ths.length()) == Success);
|
||||
ASSERT_EQ(Success, thsInput.Init(ths.begin(), ths.length()));
|
||||
Reader thsReader(thsInput);
|
||||
MOZ_RELEASE_ASSERT(DecodeDigitallySigned(thsReader, signature) == Success);
|
||||
MOZ_RELEASE_ASSERT(thsReader.AtEnd());
|
||||
ASSERT_EQ(Success, DecodeDigitallySigned(thsReader, signature));
|
||||
ASSERT_TRUE(thsReader.AtEnd());
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedTestEmbeddedCert()
|
||||
{
|
||||
return HexToBytes(kTestEmbeddedCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedTestTbsCert()
|
||||
{
|
||||
return HexToBytes(kTestTbsCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedTestEmbeddedWithPreCACert()
|
||||
{
|
||||
return HexToBytes(kTestEmbeddedWithPreCaCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedCACert()
|
||||
{
|
||||
return HexToBytes(kCaCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedIntermediateCert()
|
||||
{
|
||||
return HexToBytes(kIntermediateCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedTestEmbeddedWithIntermediateCert()
|
||||
{
|
||||
return HexToBytes(kTestEmbeddedWithIntermediateCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
GetDEREncodedTestEmbeddedWithIntermediatePreCACert()
|
||||
{
|
||||
return HexToBytes(kTestEmbeddedWithIntermediatePreCaCertData);
|
||||
}
|
||||
|
||||
Buffer
|
||||
ExtractCertSPKI(Input cert)
|
||||
{
|
||||
BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
|
||||
MOZ_RELEASE_ASSERT(backCert.Init() == Success);
|
||||
|
||||
Input spkiInput = backCert.GetSubjectPublicKeyInfo();
|
||||
Buffer spki;
|
||||
MOZ_RELEASE_ASSERT(InputToBuffer(spkiInput, spki) == Success);
|
||||
return spki;
|
||||
}
|
||||
|
||||
Buffer
|
||||
ExtractCertSPKI(const Buffer& cert)
|
||||
{
|
||||
return ExtractCertSPKI(InputForBuffer(cert));
|
||||
}
|
||||
|
||||
void
|
||||
ExtractEmbeddedSCTList(Input cert, Buffer& result)
|
||||
{
|
||||
result.clear();
|
||||
BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
|
||||
ASSERT_EQ(Success, backCert.Init());
|
||||
const Input* scts = backCert.GetSignedCertificateTimestamps();
|
||||
if (scts) {
|
||||
Input sctList;
|
||||
ASSERT_EQ(Success,
|
||||
ExtractSignedCertificateTimestampListFromExtension(*scts,
|
||||
sctList));
|
||||
ASSERT_EQ(Success, InputToBuffer(sctList, result));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ExtractEmbeddedSCTList(const Buffer& cert, Buffer& result)
|
||||
{
|
||||
ExtractEmbeddedSCTList(InputForBuffer(cert), result);
|
||||
}
|
||||
|
||||
class OCSPExtensionTrustDomain : public TrustDomain
|
||||
{
|
||||
public:
|
||||
Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
|
||||
Input, TrustLevel&) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result FindIssuer(Input, IssuerChecker&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
|
||||
const Input*, const Input*) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result IsChainValid(const DERArray&, Time) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result DigestBuf(Input item, DigestAlgorithm digestAlg,
|
||||
/*out*/ uint8_t* digestBuf, size_t digestBufLen) override
|
||||
{
|
||||
return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
|
||||
}
|
||||
|
||||
Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
|
||||
Input subjectPublicKeyInfo) override
|
||||
{
|
||||
return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
|
||||
override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
Result NetscapeStepUpMatchesServerAuth(Time, bool&) override
|
||||
{
|
||||
ADD_FAILURE();
|
||||
return Result::FATAL_ERROR_LIBRARY_FAILURE;
|
||||
}
|
||||
|
||||
void NoteAuxiliaryExtension(AuxiliaryExtension extension, Input data) override
|
||||
{
|
||||
if (extension != AuxiliaryExtension::SCTListFromOCSPResponse) {
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
if (InputToBuffer(data, signedCertificateTimestamps) != Success) {
|
||||
ADD_FAILURE();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Buffer signedCertificateTimestamps;
|
||||
};
|
||||
|
||||
void
|
||||
ExtractSCTListFromOCSPResponse(Input cert,
|
||||
Input issuerSPKI,
|
||||
Input encodedResponse,
|
||||
Time time,
|
||||
Buffer& result)
|
||||
{
|
||||
result.clear();
|
||||
|
||||
BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
|
||||
ASSERT_EQ(Success, backCert.Init());
|
||||
|
||||
CertID certID(backCert.GetIssuer(), issuerSPKI, backCert.GetSerialNumber());
|
||||
|
||||
bool expired;
|
||||
OCSPExtensionTrustDomain trustDomain;
|
||||
Result rv = VerifyEncodedOCSPResponse(trustDomain, certID,
|
||||
time, /*time*/
|
||||
1000, /*maxLifetimeInDays*/
|
||||
encodedResponse, expired);
|
||||
ASSERT_EQ(Success, rv);
|
||||
|
||||
result = Move(trustDomain.signedCertificateTimestamps);
|
||||
}
|
||||
|
||||
Buffer
|
||||
@ -293,7 +709,15 @@ InputForBuffer(const Buffer& buffer)
|
||||
{
|
||||
Input input;
|
||||
MOZ_RELEASE_ASSERT(Success ==
|
||||
input.Init(buffer.begin(), buffer.length()));
|
||||
input.Init(buffer.begin(), buffer.length()));
|
||||
return input;
|
||||
}
|
||||
|
||||
Input InputForSECItem(const SECItem& item)
|
||||
{
|
||||
Input input;
|
||||
MOZ_RELEASE_ASSERT(Success ==
|
||||
input.Init(item.data, item.len));
|
||||
return input;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "pkix/Input.h"
|
||||
#include "pkix/Time.h"
|
||||
#include "seccomon.h"
|
||||
#include "SignedCertificateTimestamp.h"
|
||||
#include "SignedTreeHead.h"
|
||||
|
||||
@ -24,7 +26,7 @@ void GetX509CertLogEntry(LogEntry& entry);
|
||||
|
||||
// Returns a DER-encoded X509 cert. The SCT provided by
|
||||
// GetX509CertSCT is signed over this certificate.
|
||||
Buffer GetDerEncodedX509Cert();
|
||||
Buffer GetDEREncodedX509Cert();
|
||||
|
||||
// Fills |entry| with test data for a Precertificate entry.
|
||||
void GetPrecertLogEntry(LogEntry& entry);
|
||||
@ -65,12 +67,58 @@ Buffer GetSampleSTHTreeHeadSignature();
|
||||
// The same signature as GetSampleSTHTreeHeadSignature, decoded.
|
||||
void GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature);
|
||||
|
||||
// Certificate with embedded SCT in an X509v3 extension.
|
||||
Buffer GetDEREncodedTestEmbeddedCert();
|
||||
|
||||
// For the above certificate, the corresponsing TBSCertificate without
|
||||
// the embedded SCT extension.
|
||||
Buffer GetDEREncodedTestTbsCert();
|
||||
|
||||
// As above, but signed with an intermediate CA certificate containing
|
||||
// the CT extended key usage OID 1.3.6.1.4.1.11129.2.4.4 for issuing precerts
|
||||
// (i.e. signed with a "precert CA certificate").
|
||||
Buffer GetDEREncodedTestEmbeddedWithPreCACert();
|
||||
|
||||
// The issuer of the above certificates (self-signed root CA certificate).
|
||||
Buffer GetDEREncodedCACert();
|
||||
|
||||
// An intermediate CA certificate issued by the above CA.
|
||||
Buffer GetDEREncodedIntermediateCert();
|
||||
|
||||
// Certificate with embedded SCT signed by the intermediate certificate above.
|
||||
Buffer GetDEREncodedTestEmbeddedWithIntermediateCert();
|
||||
|
||||
// As above, but signed by the precert CA certificate.
|
||||
Buffer GetDEREncodedTestEmbeddedWithIntermediatePreCACert();
|
||||
|
||||
// Given a DER-encoded certificate, returns its SubjectPublicKeyInfo.
|
||||
Buffer ExtractCertSPKI(pkix::Input cert);
|
||||
Buffer ExtractCertSPKI(const Buffer& cert);
|
||||
|
||||
// Extracts a SignedCertificateTimestampList from the provided leaf certificate
|
||||
// (kept in X.509v3 extension with OID 1.3.6.1.4.1.11129.2.4.2).
|
||||
void ExtractEmbeddedSCTList(pkix::Input cert, Buffer& result);
|
||||
void ExtractEmbeddedSCTList(const Buffer& cert, Buffer& result);
|
||||
|
||||
// Extracts a SignedCertificateTimestampList that has been embedded within
|
||||
// an OCSP response as an extension with the OID 1.3.6.1.4.1.11129.2.4.5.
|
||||
// The OCSP response is verified, and the verification must succeed for the
|
||||
// extension to be extracted.
|
||||
void ExtractSCTListFromOCSPResponse(pkix::Input cert,
|
||||
pkix::Input issuerSPKI,
|
||||
pkix::Input encodedResponse,
|
||||
pkix::Time time,
|
||||
Buffer& result);
|
||||
|
||||
// We need this in tests code since mozilla::Vector only allows move assignment.
|
||||
Buffer cloneBuffer(const Buffer& buffer);
|
||||
|
||||
// Returns Input for the data stored in the buffer, failing assertion on error.
|
||||
pkix::Input InputForBuffer(const Buffer& buffer);
|
||||
|
||||
// Returns Input for the data stored in the item, failing assertion on error.
|
||||
pkix::Input InputForSECItem(const SECItem& item);
|
||||
|
||||
} } // namespace mozilla::ct
|
||||
|
||||
|
||||
|
225
security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
Normal file
225
security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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 "MultiLogCTVerifier.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "CTLogVerifier.h"
|
||||
#include "CTObjectsExtractor.h"
|
||||
#include "CTSerialization.h"
|
||||
#include "CTTestUtils.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/EnumSet.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "nss.h"
|
||||
|
||||
namespace mozilla { namespace ct {
|
||||
|
||||
using namespace mozilla::pkix;
|
||||
|
||||
class MultiLogCTVerifierTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
// Does nothing if NSS is already initialized.
|
||||
MOZ_RELEASE_ASSERT(NSS_NoDB_Init(nullptr) == SECSuccess);
|
||||
|
||||
ASSERT_EQ(Success, mVerifier.AddLog(InputForBuffer(GetTestPublicKey())));
|
||||
|
||||
mTestCert = GetDEREncodedX509Cert();
|
||||
mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
|
||||
mCaCert = GetDEREncodedCACert();
|
||||
mCaCertSPKI = ExtractCertSPKI(mCaCert);
|
||||
mIntermediateCert = GetDEREncodedIntermediateCert();
|
||||
mIntermediateCertSPKI = ExtractCertSPKI(mIntermediateCert);
|
||||
|
||||
// Set the current time making sure all test timestamps are in the past.
|
||||
mNow = UINT64_MAX;
|
||||
}
|
||||
|
||||
void CheckForSingleVerifiedSCTInResult(const CTVerifyResult& result,
|
||||
SignedCertificateTimestamp::Origin origin)
|
||||
{
|
||||
EXPECT_EQ(0U, result.decodingErrors);
|
||||
EXPECT_TRUE(result.invalidScts.empty());
|
||||
EXPECT_TRUE(result.unknownLogsScts.empty());
|
||||
ASSERT_EQ(1U, result.verifiedScts.length());
|
||||
EXPECT_EQ(origin, result.verifiedScts[0].origin);
|
||||
}
|
||||
|
||||
// Writes an SCTList containing a single |sct| into |output|.
|
||||
void EncodeSCTListForTesting(Input sct, Buffer& output)
|
||||
{
|
||||
Vector<Input> list;
|
||||
ASSERT_TRUE(list.append(Move(sct)));
|
||||
ASSERT_EQ(Success, EncodeSCTList(list, output));
|
||||
}
|
||||
|
||||
void GetSCTListWithInvalidLogID(Buffer& result)
|
||||
{
|
||||
result.clear();
|
||||
Buffer sct(GetTestSignedCertificateTimestamp());
|
||||
// Change a byte inside the Log ID part of the SCT so it does
|
||||
// not match the log used in the tests.
|
||||
sct[15] ^= '\xFF';
|
||||
EncodeSCTListForTesting(InputForBuffer(sct), result);
|
||||
}
|
||||
|
||||
void CheckPrecertVerification(const Buffer& cert, const Buffer& issuerSPKI)
|
||||
{
|
||||
Buffer sctList;
|
||||
ExtractEmbeddedSCTList(cert, sctList);
|
||||
ASSERT_FALSE(sctList.empty());
|
||||
|
||||
CTVerifyResult result;
|
||||
ASSERT_EQ(Success,
|
||||
mVerifier.Verify(InputForBuffer(cert), InputForBuffer(issuerSPKI),
|
||||
InputForBuffer(sctList), Input(), Input(),
|
||||
mNow, result));
|
||||
CheckForSingleVerifiedSCTInResult(result,
|
||||
SignedCertificateTimestamp::Origin::Embedded);
|
||||
}
|
||||
|
||||
protected:
|
||||
MultiLogCTVerifier mVerifier;
|
||||
Buffer mTestCert;
|
||||
Buffer mEmbeddedCert;
|
||||
Buffer mCaCert;
|
||||
Buffer mCaCertSPKI;
|
||||
Buffer mIntermediateCert;
|
||||
Buffer mIntermediateCertSPKI;
|
||||
uint64_t mNow;
|
||||
};
|
||||
|
||||
// Test that an embedded SCT can be extracted and the extracted SCT contains
|
||||
// the expected data. This tests the ExtractEmbeddedSCTList function from
|
||||
// CTTestUtils.h that other tests here rely upon.
|
||||
TEST_F(MultiLogCTVerifierTest, ExtractEmbeddedSCT)
|
||||
{
|
||||
SignedCertificateTimestamp sct;
|
||||
|
||||
// Extract the embedded SCT.
|
||||
|
||||
Buffer sctList;
|
||||
ExtractEmbeddedSCTList(mEmbeddedCert, sctList);
|
||||
ASSERT_FALSE(sctList.empty());
|
||||
|
||||
Reader sctReader;
|
||||
ASSERT_EQ(Success, DecodeSCTList(InputForBuffer(sctList), sctReader));
|
||||
Input sctItemInput;
|
||||
ASSERT_EQ(Success, ReadSCTListItem(sctReader, sctItemInput));
|
||||
EXPECT_TRUE(sctReader.AtEnd()); // we only expect one sct in the list
|
||||
|
||||
Reader sctItemReader(sctItemInput);
|
||||
ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(sctItemReader, sct));
|
||||
|
||||
// Make sure the SCT contains the expected data.
|
||||
|
||||
EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
|
||||
EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
|
||||
|
||||
uint64_t expectedTimestamp = 1365181456275;
|
||||
EXPECT_EQ(expectedTimestamp, sct.timestamp);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCT)
|
||||
{
|
||||
CheckPrecertVerification(mEmbeddedCert, mCaCertSPKI);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithPreCA)
|
||||
{
|
||||
CheckPrecertVerification(GetDEREncodedTestEmbeddedWithPreCACert(),
|
||||
mCaCertSPKI);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediate)
|
||||
{
|
||||
CheckPrecertVerification(GetDEREncodedTestEmbeddedWithIntermediateCert(),
|
||||
mIntermediateCertSPKI);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediateAndPreCA)
|
||||
{
|
||||
CheckPrecertVerification(GetDEREncodedTestEmbeddedWithIntermediatePreCACert(),
|
||||
mIntermediateCertSPKI);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromOCSP)
|
||||
{
|
||||
Buffer sct(GetTestSignedCertificateTimestamp());
|
||||
Buffer sctList;
|
||||
EncodeSCTListForTesting(InputForBuffer(sct), sctList);
|
||||
|
||||
CTVerifyResult result;
|
||||
ASSERT_EQ(Success,
|
||||
mVerifier.Verify(InputForBuffer(mTestCert), Input(),
|
||||
Input(), InputForBuffer(sctList), Input(),
|
||||
mNow, result));
|
||||
|
||||
CheckForSingleVerifiedSCTInResult(result,
|
||||
SignedCertificateTimestamp::Origin::OCSPResponse);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromTLS)
|
||||
{
|
||||
Buffer sct(GetTestSignedCertificateTimestamp());
|
||||
Buffer sctList;
|
||||
EncodeSCTListForTesting(InputForBuffer(sct), sctList);
|
||||
|
||||
CTVerifyResult result;
|
||||
ASSERT_EQ(Success,
|
||||
mVerifier.Verify(InputForBuffer(mTestCert), Input(),
|
||||
Input(), Input(), InputForBuffer(sctList),
|
||||
mNow, result));
|
||||
|
||||
CheckForSingleVerifiedSCTInResult(result,
|
||||
SignedCertificateTimestamp::Origin::TLSExtension);
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromMultipleSources)
|
||||
{
|
||||
Buffer sct(GetTestSignedCertificateTimestamp());
|
||||
Buffer sctList;
|
||||
EncodeSCTListForTesting(InputForBuffer(sct), sctList);
|
||||
|
||||
CTVerifyResult result;
|
||||
ASSERT_EQ(Success,
|
||||
mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
|
||||
InputForBuffer(sctList), InputForBuffer(sctList),
|
||||
mNow, result));
|
||||
|
||||
// The result should contain verified SCTs from TLS and OCSP origins.
|
||||
EnumSet<SignedCertificateTimestamp::Origin> origins;
|
||||
for (auto& sct : result.verifiedScts) {
|
||||
origins += sct.origin;
|
||||
}
|
||||
EXPECT_FALSE(
|
||||
origins.contains(SignedCertificateTimestamp::Origin::Embedded));
|
||||
EXPECT_TRUE(
|
||||
origins.contains(SignedCertificateTimestamp::Origin::OCSPResponse));
|
||||
EXPECT_TRUE(
|
||||
origins.contains(SignedCertificateTimestamp::Origin::TLSExtension));
|
||||
}
|
||||
|
||||
TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog)
|
||||
{
|
||||
Buffer sctList;
|
||||
GetSCTListWithInvalidLogID(sctList);
|
||||
|
||||
CTVerifyResult result;
|
||||
ASSERT_EQ(Success,
|
||||
mVerifier.Verify(InputForBuffer(mTestCert), Input(),
|
||||
Input(), Input(), InputForBuffer(sctList),
|
||||
mNow, result));
|
||||
|
||||
EXPECT_EQ(1U, result.unknownLogsScts.length());
|
||||
EXPECT_EQ(0U, result.decodingErrors);
|
||||
}
|
||||
|
||||
} } // namespace mozilla::ct
|
@ -5,13 +5,17 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
SOURCES += [
|
||||
'CTLogVerifierTest.cpp',
|
||||
'CTObjectsExtractorTest.cpp',
|
||||
'CTSerializationTest.cpp',
|
||||
'CTTestUtils.cpp',
|
||||
'MultiLogCTVerifierTest.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/security/certverifier',
|
||||
'/security/pkix/include',
|
||||
'/security/pkix/lib',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
|
@ -177,8 +177,8 @@ CheckValidity(Time time, Time notBefore, Time notAfter)
|
||||
// 4.1.2.7 Subject Public Key Info
|
||||
|
||||
Result
|
||||
CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA)
|
||||
CheckSubjectPublicKeyInfoContents(Reader& input, TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA)
|
||||
{
|
||||
// Here, we validate the syntax and do very basic semantic validation of the
|
||||
// public key of the certificate. The intention here is to filter out the
|
||||
@ -355,6 +355,20 @@ CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain,
|
||||
return Success;
|
||||
}
|
||||
|
||||
Result
|
||||
CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo, TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA)
|
||||
{
|
||||
Reader spkiReader(subjectPublicKeyInfo);
|
||||
Result rv = der::Nested(spkiReader, der::SEQUENCE, [&](Reader& r) {
|
||||
return CheckSubjectPublicKeyInfoContents(r, trustDomain, endEntityOrCA);
|
||||
});
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
return der::End(spkiReader);
|
||||
}
|
||||
|
||||
// 4.2.1.3. Key Usage (id-ce-keyUsage)
|
||||
|
||||
// As explained in the comment in CheckKeyUsage, bit 0 is the most significant
|
||||
@ -968,14 +982,8 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain,
|
||||
// Check the SPKI early, because it is one of the most selective properties
|
||||
// of the certificate due to SHA-1 deprecation and the deprecation of
|
||||
// certificates with keys weaker than RSA 2048.
|
||||
Reader spki(cert.GetSubjectPublicKeyInfo());
|
||||
rv = der::Nested(spki, der::SEQUENCE, [&](Reader& r) {
|
||||
return CheckSubjectPublicKeyInfo(r, trustDomain, endEntityOrCA);
|
||||
});
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
rv = der::End(spki);
|
||||
rv = CheckSubjectPublicKeyInfo(cert.GetSubjectPublicKeyInfo(), trustDomain,
|
||||
endEntityOrCA);
|
||||
if (rv != Success) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -236,6 +236,12 @@ Result VerifySignedData(TrustDomain& trustDomain,
|
||||
const der::SignedDataWithSignature& signedData,
|
||||
Input signerSubjectPublicKeyInfo);
|
||||
|
||||
// Extracts the key parameters from |subjectPublicKeyInfo|, invoking
|
||||
// the relevant methods of |trustDomain|.
|
||||
Result
|
||||
CheckSubjectPublicKeyInfo(Input subjectPublicKeyInfo, TrustDomain& trustDomain,
|
||||
EndEntityOrCA endEntityOrCA);
|
||||
|
||||
// In a switch over an enum, sometimes some compilers are not satisfied that
|
||||
// all control flow paths have been considered unless there is a default case.
|
||||
// However, in our code, such a default case is almost always unreachable dead
|
||||
|
Loading…
Reference in New Issue
Block a user