author | Sergei Chernov <sergei.cv@ndivi.com> |
Mon, 11 Apr 2016 16:17:25 +0300 | |
changeset 337853 | 35b845bf58f1abf677ae61393bc5f593c0a0b40f |
parent 337852 | d33cfe343732ae76307eb77504b28a813bd9f27c |
child 337854 | 80c71070fc08c90b9131ec676d7d021506d381d2 |
push id | 6249 |
push user | jlund@mozilla.com |
push date | Mon, 01 Aug 2016 13:59:36 +0000 |
treeherder | mozilla-beta@bad9d4f5bf7e [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | keeler, Cykesiopka |
bugs | 1241574 |
milestone | 49.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/security/certverifier/CTSerialization.cpp @@ -0,0 +1,504 @@ +/* -*- 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 "CTSerialization.h" + +#include <stdint.h> + +#include "mozilla/Assertions.h" +#include "mozilla/TypeTraits.h" + +namespace mozilla { namespace ct { + +using namespace mozilla::pkix; + +// Note: length is always specified in bytes. +// Signed Certificate Timestamp (SCT) Version length +static const size_t kVersionLength = 1; + +// Members of a V1 SCT +static const size_t kLogIdLength = 32; +static const size_t kTimestampLength = 8; +static const size_t kExtensionsLengthBytes = 2; +static const size_t kHashAlgorithmLength = 1; +static const size_t kSigAlgorithmLength = 1; +static const size_t kSignatureLengthBytes = 2; + +// Members of the digitally-signed struct of a V1 SCT +static const size_t kSignatureTypeLength = 1; +static const size_t kLogEntryTypeLength = 2; +static const size_t kAsn1CertificateLengthBytes = 3; +static const size_t kTbsCertificateLengthBytes = 3; + +static const size_t kSCTListLengthBytes = 2; +static const size_t kSerializedSCTLengthBytes = 2; + +// Members of digitally-signed struct of a STH +static const size_t kTreeSizeLength = 8; + +// Length of sha256RootHash buffer of SignedTreeHead +static const size_t kSthRootHashLength = 32; + +enum class SignatureType { + CertificateTimestamp = 0, + TreeHash = 1, +}; + +// Reads a TLS-encoded variable length unsigned integer from |in|. +// The integer is expected to be in big-endian order, which is used by TLS. +// Note: does not check if the output parameter overflows while reading. +// |length| indicates the size (in bytes) of the serialized integer. +static Result +UncheckedReadUint(size_t length, Reader& in, uint64_t& out) +{ + uint64_t result = 0; + for (size_t i = 0; i < length; ++i) { + uint8_t value; + Result rv = in.Read(value); + if (rv != Success) { + return rv; + } + result = (result << 8) | value; + } + out = result; + return Success; +} + +// Performs overflow sanity checks and calls UncheckedReadUint. +template <size_t length, typename T> +static inline Result +ReadUint(Reader& in, T& out) +{ + uint64_t value; + static_assert(mozilla::IsUnsigned<T>::value, "T must be unsigned"); + static_assert(length <= 8, "At most 8 byte integers can be read"); + static_assert(sizeof(T) >= length, "T must be able to hold <length> bytes"); + Result rv = UncheckedReadUint(length, in, value); + if (rv != Success) { + return rv; + } + out = static_cast<T>(value); + return Success; +} + +// Reads |length| bytes from |in|. +static Result +ReadFixedBytes(size_t length, Reader& in, Input& out) +{ + return in.Skip(length, out); +} + +// Reads a length-prefixed variable amount of bytes from |in|, updating |out| +// on success. |prefixLength| indicates the number of bytes needed to represent +// the length. +template <size_t prefixLength> +static inline Result +ReadVariableBytes(Reader& in, Input& out) +{ + size_t length; + Result rv = ReadUint<prefixLength>(in, length); + if (rv != Success) { + return rv; + } + return ReadFixedBytes(length, in, out); +} + +// Reads a serialized hash algorithm. +static Result +ReadHashAlgorithm(Reader& in, DigitallySigned::HashAlgorithm& out) +{ + unsigned int value; + Result rv = ReadUint<kHashAlgorithmLength>(in, value); + if (rv != Success) { + return rv; + } + DigitallySigned::HashAlgorithm algo = + static_cast<DigitallySigned::HashAlgorithm>(value); + switch (algo) { + case DigitallySigned::HashAlgorithm::None: + case DigitallySigned::HashAlgorithm::MD5: + case DigitallySigned::HashAlgorithm::SHA1: + case DigitallySigned::HashAlgorithm::SHA224: + case DigitallySigned::HashAlgorithm::SHA256: + case DigitallySigned::HashAlgorithm::SHA384: + case DigitallySigned::HashAlgorithm::SHA512: + out = algo; + return Success; + } + return Result::ERROR_BAD_DER; +} + +// Reads a serialized signature algorithm. +static Result +ReadSignatureAlgorithm(Reader& in, DigitallySigned::SignatureAlgorithm& out) +{ + unsigned int value; + Result rv = ReadUint<kSigAlgorithmLength>(in, value); + if (rv != Success) { + return rv; + } + DigitallySigned::SignatureAlgorithm algo = + static_cast<DigitallySigned::SignatureAlgorithm>(value); + switch (algo) { + case DigitallySigned::SignatureAlgorithm::Anonymous: + case DigitallySigned::SignatureAlgorithm::RSA: + case DigitallySigned::SignatureAlgorithm::DSA: + case DigitallySigned::SignatureAlgorithm::ECDSA: + out = algo; + return Success; + } + return Result::ERROR_BAD_DER; +} + +// Reads a serialized version enum. +static Result +ReadVersion(Reader& in, SignedCertificateTimestamp::Version& out) +{ + unsigned int value; + Result rv = ReadUint<kVersionLength>(in, value); + if (rv != Success) { + return rv; + } + SignedCertificateTimestamp::Version version = + static_cast<SignedCertificateTimestamp::Version>(value); + switch (version) { + case SignedCertificateTimestamp::Version::V1: + out = version; + return Success; + } + return Result::ERROR_BAD_DER; +} + +// Writes a TLS-encoded variable length unsigned integer to |output|. +// Note: range/overflow checks are not performed on the input parameters. +// |length| indicates the size (in bytes) of the integer to be written. +// |value| the value itself to be written. +static Result +UncheckedWriteUint(size_t length, uint64_t value, Buffer& output) +{ + if (!output.reserve(length + output.length())) { + return Result::FATAL_ERROR_NO_MEMORY; + } + for (; length > 0; --length) { + uint8_t nextByte = (value >> ((length - 1) * 8)) & 0xFF; + output.infallibleAppend(nextByte); + } + return Success; +} + +// Performs sanity checks on T and calls UncheckedWriteUint. +template <size_t length, typename T> +static inline Result +WriteUint(T value, Buffer& output) +{ + static_assert(length <= 8, "At most 8 byte integers can be written"); + static_assert(sizeof(T) >= length, "T must be able to hold <length> bytes"); + if (mozilla::IsSigned<T>::value) { + // We accept signed integer types assuming the actual value is non-negative. + if (value < 0) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + } + if (sizeof(T) > length) { + // We allow the value variable to take more bytes than is written, + // but the unwritten bytes must be zero. + // Note: when "sizeof(T) == length" holds, "value >> (length * 8)" is + // undefined since the shift is too big. On some compilers, this would + // produce a warning even though the actual code is unreachable. + if (value >> (length * 8 - 1) > 1) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + } + return UncheckedWriteUint(length, static_cast<uint64_t>(value), output); +} + +// Writes an array to |output| from |input|. +// Should be used in one of two cases: +// * The length of |input| has already been encoded into the |output| stream. +// * The length of |input| is fixed and the reader is expected to specify that +// length when reading. +// If the length of |input| is dynamic and data is expected to follow it, +// WriteVariableBytes must be used. +static Result +WriteEncodedBytes(Input input, Buffer& output) +{ + if (!output.append(input.UnsafeGetData(), input.GetLength())) { + return Result::FATAL_ERROR_NO_MEMORY; + } + return Success; +} + +// Same as above, but the source data is in a Buffer. +static Result +WriteEncodedBytes(const Buffer& source, Buffer& output) +{ + if (!output.appendAll(source)) { + return Result::FATAL_ERROR_NO_MEMORY; + } + return Success; +} + +// Writes a variable-length array to |output|. +// |prefixLength| indicates the number of bytes needed to represent the length. +// |input| is the array itself. +// Fails if the size of |input| is more than 2^|prefixLength| - 1. +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); + if (rv != Success) { + return rv; + } + return WriteEncodedBytes(input, output); +} + +// Same as above, but the source data is in a Buffer. +template <size_t prefixLength> +static Result +WriteVariableBytes(const Buffer& source, Buffer& output) +{ + Input input; + Result rv = BufferToInput(source, input); + if (rv != Success) { + return rv; + } + return WriteVariableBytes<prefixLength>(input, output); +} + +// Writes a LogEntry of type X.509 cert to |output|. +// |input| is the LogEntry containing the certificate. +static Result +EncodeAsn1CertLogEntry(const LogEntry& entry, Buffer& output) +{ + return WriteVariableBytes<kAsn1CertificateLengthBytes>(entry.leafCertificate, + output); +} + +// Writes a LogEntry of type PreCertificate to |output|. +// |input| is the LogEntry containing the TBSCertificate and issuer key hash. +static Result +EncodePrecertLogEntry(const LogEntry& entry, Buffer& output) +{ + if (entry.issuerKeyHash.length() != kLogIdLength) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + Result rv = WriteEncodedBytes(entry.issuerKeyHash, output); + if (rv != Success) { + return rv; + } + return WriteVariableBytes<kTbsCertificateLengthBytes>(entry.tbsCertificate, + output); +} + + +Result +EncodeDigitallySigned(const DigitallySigned& data, Buffer& output) +{ + Result rv = WriteUint<kHashAlgorithmLength>( + static_cast<unsigned int>(data.hashAlgorithm), output); + if (rv != Success) { + return rv; + } + rv = WriteUint<kSigAlgorithmLength>( + static_cast<unsigned int>(data.signatureAlgorithm), output); + if (rv != Success) { + return rv; + } + return WriteVariableBytes<kSignatureLengthBytes>(data.signatureData, output); +} + +Result +DecodeDigitallySigned(Reader& reader, DigitallySigned& output) +{ + DigitallySigned result; + + Result rv = ReadHashAlgorithm(reader, result.hashAlgorithm); + if (rv != Success) { + return rv; + } + rv = ReadSignatureAlgorithm(reader, result.signatureAlgorithm); + if (rv != Success) { + return rv; + } + + Input signatureData; + rv = ReadVariableBytes<kSignatureLengthBytes>(reader, signatureData); + if (rv != Success) { + return rv; + } + rv = InputToBuffer(signatureData, result.signatureData); + if (rv != Success) { + return rv; + } + + output = Move(result); + return Success; +} + +Result +EncodeLogEntry(const LogEntry& entry, Buffer& output) +{ + Result rv = WriteUint<kLogEntryTypeLength>( + static_cast<unsigned int>(entry.type), output); + if (rv != Success) { + return rv; + } + switch (entry.type) { + case LogEntry::Type::X509: + return EncodeAsn1CertLogEntry(entry, output); + case LogEntry::Type::Precert: + return EncodePrecertLogEntry(entry, output); + default: + MOZ_ASSERT_UNREACHABLE("Unexpected LogEntry type"); + } + return Result::ERROR_BAD_DER; +} + +static Result +WriteTimeSinceEpoch(uint64_t timestamp, Buffer& output) +{ + return WriteUint<kTimestampLength>(timestamp, output); +} + +Result +EncodeV1SCTSignedData(uint64_t timestamp, Input serializedLogEntry, + Input extensions, Buffer& output) +{ + Result rv = WriteUint<kVersionLength>(static_cast<unsigned int>( + SignedCertificateTimestamp::Version::V1), output); + if (rv != Success) { + return rv; + } + rv = WriteUint<kSignatureTypeLength>(static_cast<unsigned int>( + SignatureType::CertificateTimestamp), output); + if (rv != Success) { + return rv; + } + rv = WriteTimeSinceEpoch(timestamp, output); + if (rv != Success) { + return rv; + } + // NOTE: serializedLogEntry must already be serialized and contain the + // length as the prefix. + rv = WriteEncodedBytes(serializedLogEntry, output); + if (rv != Success) { + return rv; + } + return WriteVariableBytes<kExtensionsLengthBytes>(extensions, output); +} + +Result +EncodeTreeHeadSignature(const SignedTreeHead& signedTreeHead, + Buffer& output) +{ + Result rv = WriteUint<kVersionLength>( + static_cast<unsigned int>(signedTreeHead.version), output); + if (rv != Success) { + return rv; + } + rv = WriteUint<kSignatureTypeLength>( + static_cast<unsigned int>(SignatureType::TreeHash), output); + if (rv != Success) { + return rv; + } + rv = WriteTimeSinceEpoch(signedTreeHead.timestamp, output); + if (rv != Success) { + return rv; + } + rv = WriteUint<kTreeSizeLength>(signedTreeHead.treeSize, output); + if (rv != Success) { + return rv; + } + if (signedTreeHead.sha256RootHash.length() != kSthRootHashLength) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + return WriteEncodedBytes(signedTreeHead.sha256RootHash, output); +} + +Result +DecodeSCTList(Input input, Reader& listReader) +{ + Reader inputReader(input); + Input listData; + Result rv = ReadVariableBytes<kSCTListLengthBytes>(inputReader, listData); + if (rv != Success) { + return rv; + } + return listReader.Init(listData); +} + +Result +ReadSCTListItem(Reader& listReader, Input& output) +{ + if (listReader.AtEnd()) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + + Result rv = ReadVariableBytes<kSerializedSCTLengthBytes>(listReader, output); + if (rv != Success) { + return rv; + } + if (output.GetLength() == 0) { + return Result::ERROR_BAD_DER; + } + return Success; +} + +Result +DecodeSignedCertificateTimestamp(Reader& reader, + SignedCertificateTimestamp& output) +{ + SignedCertificateTimestamp result; + + Result rv = ReadVersion(reader, result.version); + if (rv != Success) { + return rv; + } + + uint64_t timestamp; + Input logId; + Input extensions; + + rv = ReadFixedBytes(kLogIdLength, reader, logId); + if (rv != Success) { + return rv; + } + rv = ReadUint<kTimestampLength>(reader, timestamp); + if (rv != Success) { + return rv; + } + rv = ReadVariableBytes<kExtensionsLengthBytes>(reader, extensions); + if (rv != Success) { + return rv; + } + rv = DecodeDigitallySigned(reader, result.signature); + if (rv != Success) { + return rv; + } + + rv = InputToBuffer(logId, result.logId); + if (rv != Success) { + return rv; + } + rv = InputToBuffer(extensions, result.extensions); + if (rv != Success) { + return rv; + } + result.timestamp = timestamp; + + output = Move(result); + return Success; +} + +} } // namespace mozilla::ct
new file mode 100644 --- /dev/null +++ b/security/certverifier/CTSerialization.h @@ -0,0 +1,66 @@ +/* -*- 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 CTSerialization_h +#define CTSerialization_h + +#include "pkix/Input.h" +#include "pkix/Result.h" +#include "SignedCertificateTimestamp.h" +#include "SignedTreeHead.h" + +// Utility functions for encoding/decoding structures used by Certificate +// Transparency to/from the TLS wire format encoding. +namespace mozilla { namespace ct { + +// Encodes the DigitallySigned |data| to |output|. +pkix::Result EncodeDigitallySigned(const DigitallySigned& data, + Buffer& output); + +// Reads and decodes a DigitallySigned object from |reader|. +// On failure, the cursor position of |reader| is undefined. +pkix::Result DecodeDigitallySigned(pkix::Reader& reader, + DigitallySigned& output); + +// Encodes the |input| LogEntry to |output|. The size of the entry +// must not exceed the allowed size in RFC6962. +pkix::Result EncodeLogEntry(const LogEntry& entry, Buffer& output); + +// Encodes the data signed by a Signed Certificate Timestamp (SCT) into +// |output|. The signature included in the SCT can then be verified over these +// bytes. +// |timestamp| timestamp from the SCT. +// |serializedLogEntry| the log entry signed by the SCT. +// |extensions| CT extensions. +pkix::Result EncodeV1SCTSignedData(uint64_t timestamp, + pkix::Input serializedLogEntry, + pkix::Input extensions, + Buffer& output); + +// Encodes the data signed by a Signed Tree Head (STH) |signedTreeHead| into +// |output|. The signature included in the |signedTreeHead| can then be +// verified over these bytes. +pkix::Result EncodeTreeHeadSignature(const SignedTreeHead& signedTreeHead, + Buffer& output); + +// Decodes a list of Signed Certificate Timestamps +// (SignedCertificateTimestampList as defined in RFC6962). This list +// is typically obtained from the CT extension in a certificate. +// To extract the individual items of the list, call ReadSCTListItem on +// the returned reader until the reader reaches its end. +// Note that the validity of each extracted SCT should be checked separately. +pkix::Result DecodeSCTList(pkix::Input input, pkix::Reader& listReader); + +// Reads a single SCT from the reader returned by DecodeSCTList. +pkix::Result ReadSCTListItem(pkix::Reader& listReader, pkix::Input& result); + +// Decodes a single SCT from |input| to |output|. +pkix::Result DecodeSignedCertificateTimestamp(pkix::Reader& input, + SignedCertificateTimestamp& output); + +} } // namespace mozilla::ct + +#endif // CTSerialization_h
new file mode 100644 --- /dev/null +++ b/security/certverifier/SignedCertificateTimestamp.cpp @@ -0,0 +1,45 @@ +/* -*- 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 "SignedCertificateTimestamp.h" + +namespace mozilla { namespace ct { + +void +LogEntry::Reset() +{ + type = LogEntry::Type::X509; + leafCertificate.clear(); + issuerKeyHash.clear(); + tbsCertificate.clear(); +} + +bool +DigitallySigned::SignatureParametersMatch(HashAlgorithm aHashAlgorithm, + SignatureAlgorithm aSignatureAlgorithm) const +{ + return (hashAlgorithm == aHashAlgorithm) && + (signatureAlgorithm == aSignatureAlgorithm); +} + +} } // namespace mozilla::ct + + +namespace mozilla { + +bool +operator==(const ct::Buffer& a, const ct::Buffer& b) +{ + return (a.empty() && b.empty()) || + (a.length() == b.length() && memcmp(a.begin(), b.begin(), a.length()) == 0); +} + +bool +operator!=(const ct::Buffer& a, const ct::Buffer& b) { + return !(a == b); +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/security/certverifier/SignedCertificateTimestamp.h @@ -0,0 +1,125 @@ +/* -*- 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 SignedCertificateTimestamp_h +#define SignedCertificateTimestamp_h + +#include "mozilla/Vector.h" + +#include "pkix/Input.h" +#include "pkix/Result.h" + +// Structures related to Certificate Transparency (RFC 6962). +namespace mozilla { namespace ct { + +typedef Vector<uint8_t> Buffer; + +// LogEntry struct in RFC 6962, Section 3.1. +struct LogEntry +{ + + // LogEntryType enum in RFC 6962, Section 3.1. + enum class Type { + X509 = 0, + Precert = 1 + }; + + void Reset(); + + Type type; + + // Set if type == X509. + Buffer leafCertificate; + + // Set if type == Precert. + Buffer issuerKeyHash; + Buffer tbsCertificate; +}; + +// Helper structure to represent Digitally Signed data, as described in +// Sections 4.7 and 7.4.1.4.1 of RFC 5246. +struct DigitallySigned +{ + enum class HashAlgorithm { + None = 0, + MD5 = 1, + SHA1 = 2, + SHA224 = 3, + SHA256 = 4, + SHA384 = 5, + SHA512 = 6, + }; + + enum class SignatureAlgorithm { + Anonymous = 0, + RSA = 1, + DSA = 2, + ECDSA = 3 + }; + + // Returns true if |aHashAlgorithm| and |aSignatureAlgorithm| + // match this DigitallySigned hash and signature algorithms. + bool SignatureParametersMatch(HashAlgorithm aHashAlgorithm, + SignatureAlgorithm aSignatureAlgorithm) const; + + HashAlgorithm hashAlgorithm; + SignatureAlgorithm signatureAlgorithm; + // 'signature' field. + Buffer signatureData; +}; + +// SignedCertificateTimestamp struct in RFC 6962, Section 3.2. +struct SignedCertificateTimestamp +{ + // Version enum in RFC 6962, Section 3.2. + enum class Version { + V1 = 0, + }; + + // Source of the SCT - supplementary, not defined in CT RFC. + // Note: The numeric values are used within histograms and should not change + // or be re-assigned. + enum class Origin { + Embedded = 0, + TLSExtension = 1, + OCSPResponse = 2, + }; + + Version version; + Buffer logId; + uint64_t timestamp; + Buffer extensions; + DigitallySigned signature; + Origin origin; +}; + + +inline pkix::Result BufferToInput(const Buffer& buffer, pkix::Input& input) +{ + return input.Init(buffer.begin(), buffer.length()); +} + +inline pkix::Result InputToBuffer(pkix::Input input, Buffer& buffer) +{ + buffer.clear(); + if (!buffer.append(input.UnsafeGetData(), input.GetLength())) { + return pkix::Result::FATAL_ERROR_NO_MEMORY; + } + return pkix::Success; +} + +} } // namespace mozilla::ct + +namespace mozilla { + +// Comparison operators are placed under mozilla namespace since +// mozilla::ct::Buffer is actually mozilla::Vector. +bool operator==(const ct::Buffer& a, const ct::Buffer& b); +bool operator!=(const ct::Buffer& a, const ct::Buffer& b); + +} // namespace mozilla + +#endif // SignedCertificateTimestamp_h
new file mode 100644 --- /dev/null +++ b/security/certverifier/SignedTreeHead.h @@ -0,0 +1,31 @@ +/* -*- 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 SignedTreeHead_h +#define SignedTreeHead_h + +#include "SignedCertificateTimestamp.h" + +namespace mozilla { namespace ct { + +// Signed Tree Head as defined in section 3.5. of RFC-6962. +struct SignedTreeHead +{ + // Version enum in RFC 6962, Section 3.2. Note that while in the current + // RFC the STH and SCT share the versioning scheme, there are plans in + // RFC-6962-bis to use separate versions, so using a separate scheme here. + enum class Version { V1 = 0, }; + + Version version; + uint64_t timestamp; + uint64_t treeSize; + Buffer sha256RootHash; + DigitallySigned signature; +}; + +} } // namespace mozilla::ct + +#endif // SignedTreeHead_h
--- a/security/certverifier/moz.build +++ b/security/certverifier/moz.build @@ -8,36 +8,42 @@ EXPORTS += [ 'BRNameMatchingPolicy.h', 'CertVerifier.h', 'OCSPCache.h', ] UNIFIED_SOURCES += [ 'BRNameMatchingPolicy.cpp', 'CertVerifier.cpp', + 'CTSerialization.cpp', 'NSSCertDBTrustDomain.cpp', 'OCSPCache.cpp', 'OCSPRequestor.cpp', 'OCSPVerificationTrustDomain.cpp', + 'SignedCertificateTimestamp.cpp', ] if not CONFIG['NSS_NO_EV_CERTS']: UNIFIED_SOURCES += [ 'ExtendedValidation.cpp', ] LOCAL_INCLUDES += [ '/security/manager/ssl', '/security/pkix/include', ] DIRS += [ '../pkix', ] +TEST_DIRS += [ + 'tests/gtest', +] + CXXFLAGS += ['-Wall'] if CONFIG['_MSC_VER']: # -Wall with Visual C++ enables too many problematic warnings CXXFLAGS += [ '-wd4355', # 'this' used in base member initializer list '-wd4464', # relative include path contains '..' '-wd4480', # nonstandard extension used: specifying underlying type for # enum 'enum'
new file mode 100644 --- /dev/null +++ b/security/certverifier/tests/gtest/CTSerializationTest.cpp @@ -0,0 +1,245 @@ +/* -*- 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 "CTSerialization.h" +#include "CTTestUtils.h" + +#include "gtest/gtest.h" + +namespace mozilla { namespace ct { + +using namespace pkix; + +class CTSerializationTest : public ::testing::Test +{ +public: + void SetUp() override + { + mTestDigitallySigned = GetTestDigitallySigned(); + mTestSignatureData = GetTestDigitallySignedData(); + } + +protected: + Buffer mTestDigitallySigned; + Buffer mTestSignatureData; +}; + +TEST_F(CTSerializationTest, DecodesDigitallySigned) +{ + Input digitallySigned = InputForBuffer(mTestDigitallySigned); + Reader digitallySignedReader(digitallySigned); + + DigitallySigned parsed; + ASSERT_EQ(Success, + DecodeDigitallySigned(digitallySignedReader, parsed)); + EXPECT_TRUE(digitallySignedReader.AtEnd()); + + EXPECT_EQ(DigitallySigned::HashAlgorithm::SHA256, + parsed.hashAlgorithm); + EXPECT_EQ(DigitallySigned::SignatureAlgorithm::ECDSA, + parsed.signatureAlgorithm); + EXPECT_EQ(mTestSignatureData, parsed.signatureData); +} + +TEST_F(CTSerializationTest, FailsToDecodePartialDigitallySigned) +{ + Input partial; + ASSERT_EQ(Success, + partial.Init(mTestDigitallySigned.begin(), + mTestDigitallySigned.length() - 5)); + Reader partialReader(partial); + + DigitallySigned parsed; + + EXPECT_NE(Success, DecodeDigitallySigned(partialReader, parsed)); +} + +TEST_F(CTSerializationTest, EncodesDigitallySigned) +{ + DigitallySigned digitallySigned; + digitallySigned.hashAlgorithm = + DigitallySigned::HashAlgorithm::SHA256; + digitallySigned.signatureAlgorithm = + DigitallySigned::SignatureAlgorithm::ECDSA; + digitallySigned.signatureData = cloneBuffer(mTestSignatureData); + + Buffer encoded; + + ASSERT_EQ(Success, EncodeDigitallySigned(digitallySigned, encoded)); + EXPECT_EQ(mTestDigitallySigned, encoded); +} + +TEST_F(CTSerializationTest, EncodesLogEntryForX509Cert) +{ + LogEntry entry; + GetX509CertLogEntry(entry); + + Buffer encoded; + ASSERT_EQ(Success, EncodeLogEntry(entry, encoded)); + EXPECT_EQ((718U + 5U), encoded.length()); + // First two bytes are log entry type. Next, length: + // Length is 718 which is 512 + 206, which is 0x2ce + Buffer expectedPrefix; + MOZ_RELEASE_ASSERT(expectedPrefix.append("\0\0\0\x2\xCE", 5)); + Buffer encodedPrefix; + MOZ_RELEASE_ASSERT(encodedPrefix. + append(encoded.begin(), encoded.begin() + 5)); + EXPECT_EQ(expectedPrefix, encodedPrefix); +} + +TEST_F(CTSerializationTest, EncodesLogEntryForPrecert) +{ + LogEntry entry; + GetPrecertLogEntry(entry); + + Buffer encoded; + ASSERT_EQ(Success, EncodeLogEntry(entry, encoded)); + // log entry type + issuer key + length + tbsCertificate + EXPECT_EQ((2U + 32U + 3U + entry.tbsCertificate.length()), encoded.length()); + + // First two bytes are log entry type. + Buffer expectedPrefix; + MOZ_RELEASE_ASSERT(expectedPrefix.append("\0\x1", 2)); + Buffer encodedPrefix; + MOZ_RELEASE_ASSERT(encodedPrefix. + append(encoded.begin(), encoded.begin() + 2)); + EXPECT_EQ(expectedPrefix, encodedPrefix); + + // Next is the issuer key (32 bytes). + Buffer encodedKeyHash; + MOZ_RELEASE_ASSERT(encodedKeyHash. + append(encoded.begin() + 2, encoded.begin() + 2 + 32)); + EXPECT_EQ(GetDefaultIssuerKeyHash(), encodedKeyHash); +} + +TEST_F(CTSerializationTest, EncodesV1SCTSignedData) +{ + uint64_t timestamp = UINT64_C(0x139fe353cf5); + const uint8_t DUMMY_BYTES[] = { 0x61, 0x62, 0x63 }; // abc + Input dummyEntry(DUMMY_BYTES); + Input emptyExtensions; + Buffer encoded; + ASSERT_EQ(Success, EncodeV1SCTSignedData( + timestamp, dummyEntry, emptyExtensions, encoded)); + EXPECT_EQ((size_t) 15, encoded.length()); + + const uint8_t EXPECTED_BYTES[] = { + 0x00, // version + 0x00, // signature type + 0x00, 0x00, 0x01, 0x39, 0xFE, 0x35, 0x3C, 0xF5, // timestamp + 0x61, 0x62, 0x63, // log signature + 0x00, 0x00 // extensions (empty) + }; + Buffer expectedBuffer; + MOZ_RELEASE_ASSERT( + expectedBuffer.append(EXPECTED_BYTES, sizeof(EXPECTED_BYTES))); + EXPECT_EQ(expectedBuffer, encoded); +} + +TEST_F(CTSerializationTest, DecodesSCTList) +{ + // Two items in the list: "abc", "def" + const uint8_t ENCODED[] = { + 0x00, 0x0a, 0x00, 0x03, 0x61, 0x62, 0x63, 0x00, 0x03, 0x64, 0x65, 0x66 + }; + const uint8_t DECODED_1[] = { 0x61, 0x62, 0x63 }; + const uint8_t DECODED_2[] = { 0x64, 0x65, 0x66 }; + + Reader listReader; + ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader)); + + Input decoded1; + ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1)); + + Input decoded2; + ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2)); + + EXPECT_TRUE(listReader.AtEnd()); + EXPECT_TRUE(InputsAreEqual(decoded1, Input(DECODED_1))); + EXPECT_TRUE(InputsAreEqual(decoded2, Input(DECODED_2))); +} + +TEST_F(CTSerializationTest, FailsDecodingInvalidSCTList) +{ + // A list with one item that's too short (the second one) + const uint8_t ENCODED[] = { + 0x00, 0x0a, 0x00, 0x03, 0x61, 0x62, 0x63, 0x00, 0x05, 0x64, 0x65, 0x66 + }; + + Reader listReader; + ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader)); + Input decoded1; + EXPECT_EQ(Success, ReadSCTListItem(listReader, decoded1)); + Input decoded2; + EXPECT_NE(Success, ReadSCTListItem(listReader, decoded2)); +} + +TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp) +{ + Buffer encodedSctBuffer = GetTestSignedCertificateTimestamp(); + Input encodedSctInput = InputForBuffer(encodedSctBuffer); + Reader encodedSctReader(encodedSctInput); + + SignedCertificateTimestamp sct; + ASSERT_EQ(Success, + DecodeSignedCertificateTimestamp(encodedSctReader, sct)); + EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version); + EXPECT_EQ(GetTestPublicKeyId(), sct.logId); + const uint64_t expectedTime = 1365181456089; + EXPECT_EQ(expectedTime, sct.timestamp); + const size_t expectedSignatureLength = 71; + EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.length()); + EXPECT_TRUE(sct.extensions.empty()); +} + +TEST_F(CTSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) +{ + SignedCertificateTimestamp sct; + + // Invalid version + const uint8_t INVALID_VERSION_BYTES[] = { 0x02, 0x00 }; + Input invalidVersionSctInput(INVALID_VERSION_BYTES); + Reader invalidVersionSctReader(invalidVersionSctInput); + EXPECT_EQ(Result::ERROR_BAD_DER, + DecodeSignedCertificateTimestamp(invalidVersionSctReader, sct)); + + // Valid version, invalid length (missing data) + const uint8_t INVALID_LENGTH_BYTES[] = { 0x00, 0x0a, 0x0b, 0x0c }; + Input invalidLengthSctInput(INVALID_LENGTH_BYTES); + Reader invalidLengthSctReader(invalidLengthSctInput); + EXPECT_EQ(Result::ERROR_BAD_DER, + DecodeSignedCertificateTimestamp(invalidLengthSctReader, sct)); +} + +TEST_F(CTSerializationTest, EncodesValidSignedTreeHead) +{ + SignedTreeHead signedTreeHead; + GetSampleSignedTreeHead(signedTreeHead); + + Buffer encoded; + ASSERT_EQ(Success, + EncodeTreeHeadSignature(signedTreeHead, encoded)); + // Expected size is 50 bytes: + // Byte 0 is version, byte 1 is signature type + // Bytes 2-9 are timestamp + // Bytes 10-17 are tree size + // Bytes 18-49 are sha256 root hash + ASSERT_EQ(50u, encoded.length()); + const uint8_t EXPECTED_BYTES_PREFIX[] = { + 0x00, // version + 0x01, // signature type + 0x00, 0x00, 0x01, 0x45, 0x3c, 0x5f, 0xb8, 0x35, // timestamp + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 // tree size + // sha256 root hash should follow + }; + Buffer expectedBuffer; + MOZ_RELEASE_ASSERT(expectedBuffer.append(EXPECTED_BYTES_PREFIX, 18)); + Buffer hash = GetSampleSTHSHA256RootHash(); + MOZ_RELEASE_ASSERT(expectedBuffer.append(hash.begin(), hash.length())); + EXPECT_EQ(expectedBuffer, encoded); +} + +} } // namespace mozilla::ct
new file mode 100644 --- /dev/null +++ b/security/certverifier/tests/gtest/CTTestUtils.cpp @@ -0,0 +1,322 @@ +/* -*- 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 "CTTestUtils.h" + +#include <stdint.h> +#include <iomanip> + +#include "mozilla/Assertions.h" +#include "mozilla/Vector.h" + +#include "CTSerialization.h" +#include "pkix/Input.h" +#include "pkix/Result.h" +#include "SignedCertificateTimestamp.h" +#include "SignedTreeHead.h" + +namespace mozilla { namespace ct { + +using namespace mozilla::pkix; + +// The following test vectors are from +// https://github.com/google/certificate-transparency/tree/master/test/testdata + +// test-cert.pem +const char kDefaultDerCert[] = + "308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b3009" + "06035504061302474231243022060355040a131b4365727469666963617465205472616e73" + "706172656e6379204341310e300c0603550408130557616c65733110300e06035504071307" + "4572772057656e301e170d3132303630313030303030305a170d3232303630313030303030" + "305a3052310b30090603550406130247423121301f060355040a1318436572746966696361" + "7465205472616e73706172656e6379310e300c0603550408130557616c65733110300e0603" + "55040713074572772057656e30819f300d06092a864886f70d010101050003818d00308189" + "02818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f" + "19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41" + "d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a" + "22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d060355" + "1d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d2304763074" + "80145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406" + "1302474231243022060355040a131b4365727469666963617465205472616e73706172656e" + "6379204341310e300c0603550408130557616c65733110300e060355040713074572772057" + "656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171c" + "d84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee" + "2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cb" + "f667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c6972" + "7d24c4fccd249295795814d1dac0e6"; + +// key hash of test-cert.pem's issuer (ca-cert.pem) +const char kDefaultIssuerKeyHash[] = + "02adddca08b8bf9861f035940c940156d8350fdff899a6239c6bd77255b8f8fc"; + +const char kDefaultDerTbsCert[] = + "30820233a003020102020107300d06092a864886f70d01010505003055310b300906035504" + "061302474231243022060355040a131b4365727469666963617465205472616e7370617265" + "6e6379204341310e300c0603550408130557616c65733110300e0603550407130745727720" + "57656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052" + "310b30090603550406130247423121301f060355040a131843657274696669636174652054" + "72616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713" + "074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100" + "beef98e7c26877ae385f75325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee4" + "58bd7db86f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb41cd0e72805a410" + "cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39eed0b88119dc154dc68f7da8e30caf" + "158a33e6c9509f4a05b01409ff5dd87eb50203010001a381ac3081a9301d0603551d0e0416" + "04142031541af25c05ffd8658b6843794f5e9036f7b4307d0603551d230476307480145f9d" + "880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b3009060355040613024742" + "31243022060355040a131b4365727469666963617465205472616e73706172656e63792043" + "41310e300c0603550408130557616c65733110300e060355040713074572772057656e8201" + "0030090603551d1304023000"; + +// DigitallySigned of test-cert.proof +const char kTestDigitallySigned[] = + "0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef53" + "6cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5" + "a5"; + +// test-cert.proof +const char kTestSignedCertificateTimestamp[] = + "00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d" + "db27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2" + "08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456" + "89a2c0187ef5a5"; + +// ct-server-key-public.pem +const char kEcP256PublicKey[] = + "3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a" + "b45bf95d08a29cd0ea8dd4c84274e2be59ad15c676960cf0afa1074a57ac644b23479e5b3f" + "b7b245eb4b420ef370210371a944beaceb"; + +// key id (sha256) of ct-server-key-public.pem +const char kTestKeyId[] = + "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764"; + +// signature field of DigitallySigned from test-cert.proof +const char kTestSCTSignatureData[] = + "30450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f202" + "2100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5"; + +// signature field of DigitallySigned from test-embedded-pre-cert.proof +const char kTestSCTPrecertSignatureData[] = + "30450220482f6751af35dba65436be1fd6640f3dbf9a41429495924530288fa3e5e23e0602" + "2100e4edc0db3ac572b1e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08"; + +// For the sample STH +const char kSampleSTHSHA256RootHash[] = + "726467216167397babca293dca398e4ce6b621b18b9bc42f30c900d1f92ac1e4"; +const char kSampleSTHTreeHeadSignature[] = + "0403004730450220365a91a2a88f2b9332f41d8959fa7086da7e6d634b7b089bc9da066426" + "6c7a20022100e38464f3c0fd066257b982074f7ac87655e0c8f714768a050b4be9a7b441cb" + "d3"; +const size_t kSampleSTHTreeSize = 21u; +const uint64_t kSampleSTHTimestamp = 1396877277237u; + + +static uint8_t +CharToByte(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + MOZ_RELEASE_ASSERT(false); +} + +static Buffer +HexToBytes(const char* hexData) +{ + size_t hexLen = strlen(hexData); + MOZ_RELEASE_ASSERT(hexLen > 0 && (hexLen % 2 == 0)); + size_t resultLen = hexLen / 2; + Buffer result; + MOZ_RELEASE_ASSERT(result.reserve(resultLen)); + for (size_t i = 0; i < resultLen; ++i) { + uint8_t hi = CharToByte(hexData[i*2]); + uint8_t lo = CharToByte(hexData[i*2 + 1]); + result.infallibleAppend((hi << 4) | lo); + } + return result; +} + + +void +GetX509CertLogEntry(LogEntry& entry) +{ + entry.Reset(); + entry.type = ct::LogEntry::Type::X509; + entry.leafCertificate = HexToBytes(kDefaultDerCert); +} + +Buffer +GetDerEncodedX509Cert() +{ + return HexToBytes(kDefaultDerCert); +} + +void +GetPrecertLogEntry(LogEntry& entry) +{ + entry.Reset(); + entry.type = ct::LogEntry::Type::Precert; + entry.issuerKeyHash = HexToBytes(kDefaultIssuerKeyHash); + entry.tbsCertificate = HexToBytes(kDefaultDerTbsCert); +} + +Buffer +GetTestDigitallySigned() +{ + return HexToBytes(kTestDigitallySigned); +} + +Buffer +GetTestDigitallySignedData() +{ + Buffer encoded = GetTestDigitallySigned(); + // The encoded buffer contains the signature data itself from the 4th byte. + // The first bytes are: + // 1 byte of hash algorithm + // 1 byte of signature algorithm + // 2 bytes - prefix containing length of the signature data. + Buffer result; + MOZ_RELEASE_ASSERT(result.append(encoded.begin() + 4, encoded.end())); + return result; +} + +Buffer +GetTestSignedCertificateTimestamp() +{ + return HexToBytes(kTestSignedCertificateTimestamp); +} + +Buffer +GetTestPublicKey() +{ + return HexToBytes(kEcP256PublicKey); +} + +Buffer +GetTestPublicKeyId() +{ + return HexToBytes(kTestKeyId); +} + +void +GetX509CertSCT(SignedCertificateTimestamp& sct) +{ + sct.version = ct::SignedCertificateTimestamp::Version::V1; + sct.logId = HexToBytes(kTestKeyId); + // Time the log issued a SCT for this certificate, which is + // Fri Apr 5 10:04:16.089 2013 + sct.timestamp = INT64_C(1365181456089); + sct.extensions.clear(); + + sct.signature.hashAlgorithm = + ct::DigitallySigned::HashAlgorithm::SHA256; + sct.signature.signatureAlgorithm = + ct::DigitallySigned::SignatureAlgorithm::ECDSA; + sct.signature.signatureData = HexToBytes(kTestSCTSignatureData); +} + +void +GetPrecertSCT(SignedCertificateTimestamp& sct) +{ + sct.version = ct::SignedCertificateTimestamp::Version::V1; + sct.logId = HexToBytes(kTestKeyId); + // Time the log issued a SCT for this Precertificate, which is + // Fri Apr 5 10:04:16.275 2013 + sct.timestamp = INT64_C(1365181456275); + sct.extensions.clear(); + + sct.signature.hashAlgorithm = + ct::DigitallySigned::HashAlgorithm::SHA256; + sct.signature.signatureAlgorithm = + ct::DigitallySigned::SignatureAlgorithm::ECDSA; + sct.signature.signatureData = HexToBytes(kTestSCTPrecertSignatureData); +} + +Buffer +GetDefaultIssuerKeyHash() +{ + return HexToBytes(kDefaultIssuerKeyHash); +} + +// A sample, valid STH +void +GetSampleSignedTreeHead(SignedTreeHead& sth) +{ + sth.version = SignedTreeHead::Version::V1; + sth.timestamp = kSampleSTHTimestamp; + sth.treeSize = kSampleSTHTreeSize; + sth.sha256RootHash = GetSampleSTHSHA256RootHash(); + GetSampleSTHTreeHeadDecodedSignature(sth.signature); +} + +Buffer +GetSampleSTHSHA256RootHash() +{ + return HexToBytes(kSampleSTHSHA256RootHash); +} + +Buffer +GetSampleSTHTreeHeadSignature() +{ + return HexToBytes(kSampleSTHTreeHeadSignature); +} + +void +GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature) +{ + Buffer ths = HexToBytes(kSampleSTHTreeHeadSignature); + Input thsInput; + MOZ_RELEASE_ASSERT(thsInput.Init(ths.begin(), ths.length()) == Success); + Reader thsReader(thsInput); + MOZ_RELEASE_ASSERT(DecodeDigitallySigned(thsReader, signature) == Success); + MOZ_RELEASE_ASSERT(thsReader.AtEnd()); +} + +Buffer +cloneBuffer(const Buffer& buffer) +{ + Buffer cloned; + MOZ_RELEASE_ASSERT(cloned.appendAll(buffer)); + return cloned; +} + +Input +InputForBuffer(const Buffer& buffer) +{ + Input input; + MOZ_RELEASE_ASSERT(Success == + input.Init(buffer.begin(), buffer.length())); + return input; +} + +} } // namespace mozilla::ct + +namespace mozilla { + +std::ostream& +operator<<(std::ostream& stream, const ct::Buffer& buffer) +{ + if (buffer.empty()) { + stream << "EMPTY"; + } else { + for (size_t i = 0; i < buffer.length(); ++i) { + if (i >= 1000) { + stream << "..."; + break; + } + stream << std::hex << std::setw(2) << std::setfill('0') + << static_cast<unsigned>(buffer[i]); + } + } + stream << std::dec; + return stream; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/security/certverifier/tests/gtest/CTTestUtils.h @@ -0,0 +1,84 @@ +/* -*- 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 CTTestUtils_h +#define CTTestUtils_h + +#include <iostream> + +#include "pkix/Input.h" +#include "SignedCertificateTimestamp.h" +#include "SignedTreeHead.h" + +namespace mozilla { namespace ct { + +// Note: unless specified otherwise, all test data is taken from +// Certificate Transparency test data repository at +// https://github.com/google/certificate-transparency/tree/master/test/testdata + +// Fills |entry| with test data for an X.509 entry. +void GetX509CertLogEntry(LogEntry& entry); + +// Returns a DER-encoded X509 cert. The SCT provided by +// GetX509CertSCT is signed over this certificate. +Buffer GetDerEncodedX509Cert(); + +// Fills |entry| with test data for a Precertificate entry. +void GetPrecertLogEntry(LogEntry& entry); + +// Returns the binary representation of a test DigitallySigned. +Buffer GetTestDigitallySigned(); + +// Returns the source data of the test DigitallySigned. +Buffer GetTestDigitallySignedData(); + +// Returns the binary representation of a test serialized SCT. +Buffer GetTestSignedCertificateTimestamp(); + +// Test log key. +Buffer GetTestPublicKey(); + +// ID of test log key. +Buffer GetTestPublicKeyId(); + +// SCT for the X509Certificate provided above. +void GetX509CertSCT(SignedCertificateTimestamp& sct); + +// SCT for the Precertificate log entry provided above. +void GetPrecertSCT(SignedCertificateTimestamp& sct); + +// Issuer key hash. +Buffer GetDefaultIssuerKeyHash(); + +// A sample, valid STH. +void GetSampleSignedTreeHead(SignedTreeHead& sth); + +// The SHA256 root hash for the sample STH. +Buffer GetSampleSTHSHA256RootHash(); + +// The tree head signature for the sample STH. +Buffer GetSampleSTHTreeHeadSignature(); + +// The same signature as GetSampleSTHTreeHeadSignature, decoded. +void GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature); + +// 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); + +} } // namespace mozilla::ct + + +namespace mozilla { + +// GTest needs this to be in Buffer's namespace (i.e. in mozilla::Vector's). +std::ostream& operator<<(std::ostream& stream, const ct::Buffer& buf); + +} // namespace mozilla + +#endif // CTTestUtils_h
new file mode 100644 --- /dev/null +++ b/security/certverifier/tests/gtest/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'CTSerializationTest.cpp', + 'CTTestUtils.cpp', +] + +LOCAL_INCLUDES += [ + '/security/certverifier', + '/security/pkix/include', +] + +FINAL_LIBRARY = 'xul-gtest'