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:
Sergei Chernov 2016-07-05 08:35:06 +03:00
parent dda8b0ae6b
commit 21be681857
19 changed files with 2156 additions and 30 deletions

View 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

View 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

View 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

View 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

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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;

View File

@ -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 += [

View 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

View 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

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View 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

View File

@ -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'

View File

@ -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;
}

View File

@ -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