new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTSerialization.cpp
@@ -0,0 +1,474 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#include "CTSerialization.h"
+
+#include <stdint.h>
+#include "mozilla/Assertions.h"
+
+namespace mozilla { namespace ct {
+
+using namespace mozilla::pkix;
+
+namespace {
+
+// Note: length is always specified in bytes.
+// Signed Certificate Timestamp (SCT) Version length
+const size_t kVersionLength = 1;
+
+// Members of a V1 SCT
+const size_t kLogIdLength = 32;
+const size_t kTimestampLength = 8;
+const size_t kExtensionsLengthBytes = 2;
+const size_t kHashAlgorithmLength = 1;
+const size_t kSigAlgorithmLength = 1;
+const size_t kSignatureLengthBytes = 2;
+
+// Members of the digitally-signed struct of a V1 SCT
+const size_t kSignatureTypeLength = 1;
+const size_t kLogEntryTypeLength = 2;
+const size_t kAsn1CertificateLengthBytes = 3;
+const size_t kTbsCertificateLengthBytes = 3;
+
+const size_t kSCTListLengthBytes = 2;
+const size_t kSerializedSCTLengthBytes = 2;
+
+// Members of digitally-signed struct of a STH
+const size_t kTreeSizeLength = 8;
+
+// Length of sha256_root_hash buffer of SignedTreeHead
+const size_t kSthRootHashLength = 32;
+
+enum SignatureType {
+ SIGNATURE_TYPE_CERTIFICATE_TIMESTAMP = 0,
+ TREE_HASH = 1,
+};
+
+Result BufferToInput(const Buffer& buffer, Input& input) {
+ return input.Init(buffer.begin(), buffer.length());
+}
+
+Result InputToBuffer(Input input, Buffer& buffer) {
+ buffer.clear();
+ if (!buffer.append(input.UnsafeGetData(), input.GetLength())) {
+ return Result::FATAL_ERROR_NO_MEMORY;
+ }
+ return Success;
+}
+
+// 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.
+// |length| indicates the size (in bytes) of the integer.
+template <typename T>
+Result ReadUint(size_t length, Reader& in, T& out) {
+ MOZ_ASSERT(length <= sizeof(T));
+ Result rv;
+ T result = 0;
+ for (size_t i = 0; i < length; ++i) {
+ uint8_t value;
+ rv = in.Read(value);
+ if (rv != Success) {
+ return rv;
+ }
+ result = (result << 8) | value;
+ }
+ out = result;
+ return Success;
+}
+
+// Reads a TLS-encoded field length from |in|.
+// |prefix_length| indicates the bytes needed to represent the length (e.g. 3)
+Result ReadLength(size_t prefix_length, Reader& in, size_t& out) {
+ return ReadUint(prefix_length, in, out);
+}
+
+// Reads |length| bytes from |in|.
+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. |prefix_length| indicates the number of bytes needed to represent
+// the length.
+Result ReadVariableBytes(size_t prefix_length, Reader& in, Input& out) {
+ size_t length;
+ Result rv = ReadLength(prefix_length, in, length);
+ if (rv != Success) {
+ return rv;
+ }
+ return ReadFixedBytes(length, in, out);
+}
+
+// Reads a variable-length list that has been TLS encoded.
+// |max_list_length| contains the overall length of the encoded list.
+// |max_item_length| contains the maximum length of a single item.
+// On success, updates |out| with the encoded list.
+Result ReadList(size_t max_list_length, size_t max_item_length,
+ Reader& in, Vector<Input>& out) {
+ Result rv;
+ Vector<Input> result;
+
+ Input list_data;
+ rv = ReadVariableBytes(max_list_length, in, list_data);
+ if (rv != Success) {
+ return rv;
+ }
+ Reader list_reader(list_data);
+ while (!list_reader.AtEnd()) {
+ Input list_item;
+ rv = ReadVariableBytes(max_item_length, list_reader, list_item);
+ if (rv != Success) {
+ return rv;
+ }
+ if (list_item.GetLength() == 0) {
+ return Result::ERROR_BAD_DER;
+ }
+ if (!result.append(list_item)) {
+ return Result::FATAL_ERROR_NO_MEMORY;
+ }
+ }
+
+ out.swap(result);
+ return Success;
+}
+
+// Checks and converts a hash algorithm.
+// |in| is the numeric representation of the algorithm.
+Result ConvertHashAlgorithm(unsigned in, DigitallySigned::HashAlgorithm& out) {
+ switch (in) {
+ case DigitallySigned::HASH_ALGO_NONE:
+ case DigitallySigned::HASH_ALGO_MD5:
+ case DigitallySigned::HASH_ALGO_SHA1:
+ case DigitallySigned::HASH_ALGO_SHA224:
+ case DigitallySigned::HASH_ALGO_SHA256:
+ case DigitallySigned::HASH_ALGO_SHA384:
+ case DigitallySigned::HASH_ALGO_SHA512:
+ break;
+ default:
+ return Result::ERROR_BAD_DER;
+ }
+ out = static_cast<DigitallySigned::HashAlgorithm>(in);
+ return Success;
+}
+
+// Checks and converts a signing algorithm.
+// |in| is the numeric representation of the algorithm.
+Result ConvertSignatureAlgorithm(
+ unsigned in,
+ DigitallySigned::SignatureAlgorithm& out) {
+ switch (in) {
+ case DigitallySigned::SIG_ALGO_ANONYMOUS:
+ case DigitallySigned::SIG_ALGO_RSA:
+ case DigitallySigned::SIG_ALGO_DSA:
+ case DigitallySigned::SIG_ALGO_ECDSA:
+ break;
+ default:
+ return Result::ERROR_BAD_DER;
+ }
+ out = static_cast<DigitallySigned::SignatureAlgorithm>(in);
+ return Success;
+}
+
+// Writes a TLS-encoded variable length unsigned integer to |output|.
+// |length| indicates the size (in bytes) of the integer.
+// |value| the value itself to be written.
+template <typename T>
+Result WriteUint(size_t length, T value, Buffer& output) {
+ MOZ_ASSERT(length <= sizeof(T));
+ MOZ_ASSERT(length == sizeof(T) || value >> (length * 8) == 0);
+
+ if (!output.reserve(length + output.length())) {
+ return Result::FATAL_ERROR_NO_MEMORY;
+ }
+ for (; length > 0; --length) {
+ uint8_t next_byte = (value >> ((length - 1)* 8)) & 0xFF;
+ output.infallibleAppend(next_byte);
+ }
+ return Success;
+}
+
+// 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.
+Result WriteEncodedBytes(Input input, Buffer& output) {
+ if (!output.append(input.UnsafeGetData(), input.GetLength())) {
+ return Result::FATAL_ERROR_NO_MEMORY;
+ }
+ return Success;
+}
+
+// Writes a variable-length array to |output|.
+// |prefix_length| indicates the number of bytes needed to represnt the length.
+// |input| is the array itself.
+// Fails if the size of |input| is more than 2^|prefix_length| - 1.
+Result WriteVariableBytes(size_t prefix_length, Input input, Buffer& output) {
+ Result rv;
+ size_t input_size = input.GetLength();
+ size_t max_allowed_input_size =
+ static_cast<size_t>(((1 << (prefix_length * 8)) - 1));
+ if (input_size > max_allowed_input_size) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+
+ rv = WriteUint(prefix_length, input_size, output);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteEncodedBytes(input, output);
+}
+
+// Writes a LogEntry of type X.509 cert to |output|.
+// |input| is the LogEntry containing the certificate.
+Result EncodeAsn1CertLogEntry(const LogEntry& entry, Buffer& output) {
+ Result rv;
+ Input leaf_certificate;
+ rv = BufferToInput(entry.leaf_certificate, leaf_certificate);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteVariableBytes(kAsn1CertificateLengthBytes,
+ leaf_certificate, output);
+}
+
+// Writes a LogEntry of type PreCertificate to |output|.
+// |input| is the LogEntry containing the TBSCertificate and issuer key hash.
+Result EncodePrecertLogEntry(const LogEntry& entry, Buffer& output) {
+ Result rv;
+ if (entry.issuer_key_hash.length() != kLogIdLength) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ Input issuer_key_hash;
+ rv = BufferToInput(entry.issuer_key_hash, issuer_key_hash);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteEncodedBytes(issuer_key_hash, output);
+ if (rv != Success) {
+ return rv;
+ }
+ Input tbs_certificate;
+ rv = BufferToInput(entry.tbs_certificate, tbs_certificate);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteVariableBytes(kTbsCertificateLengthBytes,
+ tbs_certificate, output);
+}
+
+} // namespace
+
+Result EncodeDigitallySigned(const DigitallySigned& data, Buffer& output) {
+ Result rv;
+ rv = WriteUint(kHashAlgorithmLength, data.hash_algorithm, output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteUint(kSigAlgorithmLength, data.signature_algorithm, output);
+ if (rv != Success) {
+ return rv;
+ }
+ Input signature_data;
+ rv = BufferToInput(data.signature_data, signature_data);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteVariableBytes(kSignatureLengthBytes, signature_data, output);
+}
+
+Result DecodeDigitallySigned(Reader& reader, DigitallySigned& output) {
+ Result rv;
+ unsigned hash_algo;
+ unsigned sig_algo;
+ DigitallySigned result;
+
+ rv = ReadUint(kHashAlgorithmLength, reader, hash_algo);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = ReadUint(kSigAlgorithmLength, reader, sig_algo);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Input signature_data;
+ rv = ReadVariableBytes(kSignatureLengthBytes, reader, signature_data);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = InputToBuffer(signature_data, result.signature_data);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = ConvertHashAlgorithm(hash_algo, result.hash_algorithm);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = ConvertSignatureAlgorithm(sig_algo, result.signature_algorithm);
+ if (rv != Success) {
+ return rv;
+ }
+
+ output = Move(result);
+ return Success;
+}
+
+Result EncodeLogEntry(const LogEntry& entry, Buffer& output) {
+ Result rv = WriteUint(kLogEntryTypeLength, entry.type, output);
+ if (rv != Success) {
+ return rv;
+ }
+ switch (entry.type) {
+ case LogEntry::LOG_ENTRY_TYPE_X509:
+ return EncodeAsn1CertLogEntry(entry, output);
+ case LogEntry::LOG_ENTRY_TYPE_PRECERT:
+ return EncodePrecertLogEntry(entry, output);
+ }
+ return Result::ERROR_BAD_DER;
+}
+
+static Result WriteTimeSinceEpoch(uint64_t timestamp, Buffer& output) {
+ return WriteUint(kTimestampLength, timestamp, output);
+}
+
+Result EncodeV1SCTSignedData(uint64_t timestamp,
+ Input serialized_log_entry,
+ Input extensions,
+ Buffer& output) {
+ Result rv;
+ rv = WriteUint(kVersionLength, SignedCertificateTimestamp::SCT_VERSION_1,
+ output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteUint(kSignatureTypeLength, SIGNATURE_TYPE_CERTIFICATE_TIMESTAMP,
+ output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteTimeSinceEpoch(timestamp, output);
+ // NOTE: serialized_log_entry must already be serialized and contain the
+ // length as the prefix.
+ rv = WriteEncodedBytes(serialized_log_entry, output);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteVariableBytes(kExtensionsLengthBytes, extensions, output);
+}
+
+pkix::Result EncodeTreeHeadSignature(const SignedTreeHead& signed_tree_head,
+ Buffer& output) {
+ Result rv;
+ rv = WriteUint(kVersionLength, signed_tree_head.version, output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteUint(kSignatureTypeLength, TREE_HASH, output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteTimeSinceEpoch(signed_tree_head.timestamp, output);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = WriteUint(kTreeSizeLength, signed_tree_head.tree_size, output);
+ if (rv != Success) {
+ return rv;
+ }
+ if (signed_tree_head.sha256_root_hash.length() != kSthRootHashLength) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ Input sha256_root_hash;
+ rv = BufferToInput(signed_tree_head.sha256_root_hash, sha256_root_hash);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteEncodedBytes(sha256_root_hash, output);
+}
+
+Result DecodeSCTList(Input input, Vector<Input>& output) {
+ Result rv;
+ Reader reader(input);
+ Vector<Input> result;
+
+ rv = ReadList(kSCTListLengthBytes, kSerializedSCTLengthBytes, reader, result);
+ if (rv != Success) {
+ return rv;
+ }
+ if (!reader.AtEnd() || result.empty()) {
+ return Result::ERROR_BAD_DER;
+ }
+ output.swap(result);
+ return Success;
+}
+
+Result DecodeSignedCertificateTimestamp(Reader& reader,
+ SignedCertificateTimestamp& output) {
+ Result rv;
+ SignedCertificateTimestamp result;
+ unsigned version;
+
+ rv = ReadUint(kVersionLength, reader, version);
+ if (rv != Success) {
+ return rv;
+ }
+ if (version != SignedCertificateTimestamp::SCT_VERSION_1) {
+ return Result::ERROR_BAD_DER;
+ }
+
+ result.version = SignedCertificateTimestamp::SCT_VERSION_1;
+ uint64_t timestamp;
+ Input log_id;
+ Input extensions;
+
+ rv = ReadFixedBytes(kLogIdLength, reader, log_id);
+ 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(log_id, result.log_id);
+ if (rv != Success) {
+ return rv;
+ }
+ rv = InputToBuffer(extensions, result.extensions);
+ if (rv != Success) {
+ return rv;
+ }
+ result.timestamp = timestamp;
+
+ output = Move(result);
+ return Success;
+}
+
+Result EncodeSCTListForTesting(Input sct, Buffer& output) {
+ Result rv;
+ Buffer encoded_sct;
+ rv = WriteVariableBytes(kSerializedSCTLengthBytes, sct, encoded_sct);
+ if (rv != Success) {
+ return rv;
+ }
+ Input encoded_sct_input;
+ rv = BufferToInput(encoded_sct, encoded_sct_input);
+ if (rv != Success) {
+ return rv;
+ }
+ return WriteVariableBytes(kSCTListLengthBytes, encoded_sct_input, output);
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTSerialization.h
@@ -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: */
+
+#ifndef mozilla_ct__SCTSerialization_h
+#define mozilla_ct__SCTSerialization_h
+
+#include "mozilla/Vector.h"
+#include "pkix/Result.h"
+#include "pkix/Input.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 |input|.
+// 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.
+// |serialized_log_entry| the log entry signed by the SCT.
+// |extensions| CT extensions.
+pkix::Result EncodeV1SCTSignedData(
+ uint64_t timestamp,
+ pkix::Input serialized_log_entry,
+ pkix::Input extensions,
+ Buffer& output);
+
+// Encodes the data signed by a Signed Tree Head (STH) |signed_tree_head| into
+// |output|. The signature included in the |signed_tree_head| can then be
+// verified over these bytes.
+pkix::Result EncodeTreeHeadSignature(
+ const SignedTreeHead& signed_tree_head,
+ Buffer& output);
+
+// Decode a list of Signed Certificate Timestamps
+// (SignedCertificateTimestampList as defined in RFC6962): from a single
+// string in |input| to a vector of individually-encoded SCTs |output|.
+// This list is typically obtained from the CT extension in a certificate.
+// Returns success if the list could be read and decoded successfully, and
+// no excess data is left. Note that the validity of each individual SCT
+// should be checked separately.
+pkix::Result DecodeSCTList(pkix::Input input, Vector<pkix::Input>& output);
+
+// Decodes a single SCT from |input| to |output|.
+pkix::Result DecodeSignedCertificateTimestamp(
+ pkix::Reader& input,
+ SignedCertificateTimestamp& output);
+
+// Writes an SCTList into |output|, containing a single |sct|.
+pkix::Result EncodeSCTListForTesting(pkix::Input sct, Buffer& output);
+
+} } // namespace mozilla::ct
+
+#endif // mozilla_ct__SCTSerialization_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/SignedCertificateTimestamp.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla { namespace ct {
+
+void LogEntry::Reset() {
+ type = LogEntry::LOG_ENTRY_TYPE_X509;
+ leaf_certificate.clear();
+ tbs_certificate.clear();
+}
+
+bool DigitallySigned::SignatureParametersMatch(
+ HashAlgorithm other_hash_algorithm,
+ SignatureAlgorithm other_signature_algorithm) const {
+ return (hash_algorithm == other_hash_algorithm) &&
+ (signature_algorithm == other_signature_algorithm);
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/SignedCertificateTimestamp.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#ifndef mozilla_ct__SignedCertificateTimestamp_h
+#define mozilla_ct__SignedCertificateTimestamp_h
+
+#include "mozilla/Vector.h"
+
+// Structures related to Certificate Transparency (RFC 6962).
+namespace mozilla { namespace ct {
+
+typedef mozilla::Vector<uint8_t> Buffer;
+
+// LogEntry struct in RFC 6962, Section 3.1.
+struct LogEntry
+{
+
+ // LogEntryType enum in RFC 6962, Section 3.1.
+ enum Type {
+ LOG_ENTRY_TYPE_X509 = 0,
+ LOG_ENTRY_TYPE_PRECERT = 1
+ };
+
+ void Reset();
+
+ Type type;
+
+ // Set if type == LOG_ENTRY_TYPE_X509.
+ Buffer leaf_certificate;
+
+ // Set if type == LOG_ENTRY_TYPE_PRECERT.
+ Buffer issuer_key_hash; // SHA256_LENGTH
+ Buffer tbs_certificate;
+};
+
+// 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 HashAlgorithm {
+ HASH_ALGO_NONE = 0,
+ HASH_ALGO_MD5 = 1,
+ HASH_ALGO_SHA1 = 2,
+ HASH_ALGO_SHA224 = 3,
+ HASH_ALGO_SHA256 = 4,
+ HASH_ALGO_SHA384 = 5,
+ HASH_ALGO_SHA512 = 6,
+ };
+
+ enum SignatureAlgorithm {
+ SIG_ALGO_ANONYMOUS = 0,
+ SIG_ALGO_RSA = 1,
+ SIG_ALGO_DSA = 2,
+ SIG_ALGO_ECDSA = 3
+ };
+
+ // Returns true if |other_hash_algorithm| and |other_signature_algorithm|
+ // match this DigitallySigned hash and signature algorithms.
+ bool SignatureParametersMatch(
+ HashAlgorithm other_hash_algorithm,
+ SignatureAlgorithm other_signature_algorithm) const;
+
+ HashAlgorithm hash_algorithm;
+ SignatureAlgorithm signature_algorithm;
+ // 'signature' field.
+ Buffer signature_data;
+};
+
+// SignedCertificateTimestamp struct in RFC 6962, Section 3.2.
+struct SignedCertificateTimestamp
+{
+ // Version enum in RFC 6962, Section 3.2.
+ enum Version {
+ SCT_VERSION_1 = 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 Origin {
+ SCT_EMBEDDED = 0,
+ SCT_FROM_TLS_EXTENSION = 1,
+ SCT_FROM_OCSP_RESPONSE = 2,
+ SCT_ORIGIN_MAX,
+ };
+
+ Version version;
+ Buffer log_id;
+ uint64_t timestamp;
+ Buffer extensions;
+ DigitallySigned signature;
+ Origin origin;
+ // The log description is not one of the SCT fields, but a user-readable
+ // name defined alongside the log key. It should not participate
+ // in equality checks as the log's description could change while
+ // the SCT would be the same.
+ Buffer log_description;
+};
+
+} } // namespace mozilla::ct
+
+#endif // mozilla_ct__SignedCertificateTimestamp_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/SignedTreeHead.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#ifndef mozilla_ct__SignedTreeHead_h
+#define mozilla_ct__SignedTreeHead_h
+
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla { namespace ct {
+
+// Signed Tree Head as defined in section 3.5. of RFC6962.
+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
+ // RFC6962-bis to use separate versions, so using a separate scheme here.
+ enum Version { V1 = 0, };
+
+ Version version;
+ uint64_t timestamp;
+ uint64_t tree_size;
+ Buffer sha256_root_hash;
+ DigitallySigned signature;
+};
+
+} } // namespace mozilla::ct
+
+#endif // mozilla_ct__SignedTreeHead_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -6,36 +6,42 @@
EXPORTS += [
'CertVerifier.h',
'OCSPCache.h',
]
UNIFIED_SOURCES += [
'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,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: */
+
+#include "SignedCertificateTimestamp.h"
+#include "SignedTreeHead.h"
+#include "CTSerialization.h"
+#include "CTTestUtils.h"
+
+#include "gtest/gtest.h"
+
+namespace mozilla { namespace ct {
+
+using namespace pkix;
+
+Input InputForBuffer(const Buffer& buffer) {
+ Input input;
+ MOZ_RELEASE_ASSERT(Success ==
+ input.Init(buffer.begin(), buffer.length()));
+ return input;
+}
+
+class CtSerializationTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ test_digitally_signed_ = GetTestDigitallySigned();
+ // The encoded data contains the signature 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.
+ MOZ_RELEASE_ASSERT(signature_data_.
+ append(test_digitally_signed_.begin() + 4, test_digitally_signed_.end()));
+ }
+
+ protected:
+ Buffer test_digitally_signed_;
+ Buffer signature_data_;
+};
+
+TEST_F(CtSerializationTest, DecodesDigitallySigned) {
+ Input digitally_signed = InputForBuffer(test_digitally_signed_);
+ Reader digitally_signed_reader(digitally_signed);
+
+ DigitallySigned parsed;
+ ASSERT_EQ(Success,
+ DecodeDigitallySigned(digitally_signed_reader, parsed));
+ EXPECT_EQ(
+ DigitallySigned::HASH_ALGO_SHA256,
+ parsed.hash_algorithm);
+ EXPECT_EQ(
+ DigitallySigned::SIG_ALGO_ECDSA,
+ parsed.signature_algorithm);
+
+ ASSERT_EQ(signature_data_, parsed.signature_data);
+}
+
+TEST_F(CtSerializationTest, FailsToDecodePartialDigitallySigned) {
+ Input partial;
+ ASSERT_EQ(Success,
+ partial.Init(test_digitally_signed_.begin(),
+ test_digitally_signed_.length() - 5));
+ Reader partial_reader(partial);
+
+ DigitallySigned parsed;
+
+ ASSERT_NE(Success, DecodeDigitallySigned(partial_reader, parsed));
+}
+
+TEST_F(CtSerializationTest, EncodesDigitallySigned) {
+ DigitallySigned digitally_signed;
+ digitally_signed.hash_algorithm = DigitallySigned::HASH_ALGO_SHA256;
+ digitally_signed.signature_algorithm = DigitallySigned::SIG_ALGO_ECDSA;
+ digitally_signed.signature_data = cloneBuffer(signature_data_);
+
+ Buffer encoded;
+
+ ASSERT_EQ(Success, EncodeDigitallySigned(digitally_signed, encoded));
+ EXPECT_EQ(test_digitally_signed_, 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 expected_prefix;
+ MOZ_RELEASE_ASSERT(expected_prefix.append("\0\0\0\x2\xCE", 5));
+ Buffer encoded_prefix;
+ MOZ_RELEASE_ASSERT(encoded_prefix.
+ append(encoded.begin(), encoded.begin() + 5));
+ EXPECT_EQ(expected_prefix, encoded_prefix);
+}
+
+TEST_F(CtSerializationTest, EncodesV1SCTSignedData) {
+ uint64_t timestamp = 1348589665525;
+ const uint8_t DUMMY_BYTES[] = { 0x61, 0x62, 0x63 }; // abc
+ Input dummy_entry(DUMMY_BYTES);
+ Input empty_extensions;
+ Buffer encoded;
+ ASSERT_EQ(Success, EncodeV1SCTSignedData(
+ timestamp, dummy_entry, empty_extensions, 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, 0x00, // log signature
+ 0x00 // extentions (empty)
+ };
+ Buffer expected_buffer;
+ MOZ_RELEASE_ASSERT(expected_buffer.append(EXPECTED_BYTES, 15));
+ EXPECT_EQ(expected_buffer, 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 };
+
+ Input encoded(ENCODED);
+ Vector<Input> decoded;
+
+ ASSERT_EQ(Success, DecodeSCTList(encoded, decoded));
+ ASSERT_TRUE(InputsAreEqual(decoded[0], Input(DECODED_1)));
+ ASSERT_TRUE(InputsAreEqual(decoded[1], Input(DECODED_2)));
+}
+
+TEST_F(CtSerializationTest, FailsDecodingInvalidSCTList) {
+ // A list with one item that's too short
+ const uint8_t ENCODED[] = {
+ 0x00, 0x0a, 0x00, 0x03, 0x61, 0x62, 0x63, 0x00, 0x05, 0x64, 0x65, 0x66
+ };
+
+ Input encoded(ENCODED);
+ Vector<Input> decoded;
+
+ ASSERT_NE(Success, DecodeSCTList(encoded, decoded));
+}
+
+TEST_F(CtSerializationTest, DecodesSignedCertificateTimestamp) {
+ Buffer encoded_sct_buffer = GetTestSignedCertificateTimestamp();
+ Input encoded_sct_input = InputForBuffer(encoded_sct_buffer);
+ Reader encoded_sct_reader(encoded_sct_input);
+
+ SignedCertificateTimestamp sct;
+ ASSERT_EQ(Success,
+ DecodeSignedCertificateTimestamp(encoded_sct_reader, sct));
+ EXPECT_EQ(0, sct.version);
+ EXPECT_EQ(GetTestPublicKeyId(), sct.log_id);
+ uint64_t expected_time = 1365181456089;
+ EXPECT_EQ(expected_time, sct.timestamp);
+ // Subtracting 4 bytes for signature data (hash & sig algs),
+ // actual signature data should be 71 bytes.
+ EXPECT_EQ((size_t) 71, sct.signature.signature_data.length());
+ EXPECT_TRUE(sct.extensions.empty());
+}
+
+TEST_F(CtSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) {
+ SignedCertificateTimestamp sct;
+
+ // Invalid version
+ const uint8_t INVALID_VERSION_BYTES[] = { 0x02, 0x00 };
+ Input invalid_version_sct_input(INVALID_VERSION_BYTES);
+ Reader invalid_version_sct_reader(invalid_version_sct_input);
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ DecodeSignedCertificateTimestamp(invalid_version_sct_reader, sct));
+
+ // Valid version, invalid length (missing data)
+ const uint8_t INVALID_LENGTH_BYTES[] = { 0x00, 0x0a, 0x0b, 0x0c };
+ Input invalid_length_sct_input(INVALID_LENGTH_BYTES);
+ Reader invalid_length_sct_reader(invalid_length_sct_input);
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ DecodeSignedCertificateTimestamp(invalid_length_sct_reader, sct));
+}
+
+TEST_F(CtSerializationTest, EncodesValidSignedTreeHead) {
+ SignedTreeHead signed_tree_head;
+ GetSampleSignedTreeHead(signed_tree_head);
+
+ Buffer encoded;
+ ASSERT_EQ(Success,
+ EncodeTreeHeadSignature(signed_tree_head, 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 expected_buffer;
+ MOZ_RELEASE_ASSERT(expected_buffer.append(EXPECTED_BYTES_PREFIX, 18));
+ Buffer hash = GetSampleSTHSHA256RootHash();
+ MOZ_RELEASE_ASSERT(expected_buffer.append(hash.begin(), hash.length()));
+ EXPECT_EQ(expected_buffer, encoded);
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/tests/gtest/CTTestUtils.cpp
@@ -0,0 +1,327 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#include <stdint.h>
+#include <iomanip>
+
+#include "mozilla/Vector.h"
+#include "mozilla/Assertions.h"
+#include "pkix/Result.h"
+#include "pkix/Input.h"
+#include "SignedCertificateTimestamp.h"
+#include "SignedTreeHead.h"
+#include "CTSerialization.h"
+
+#include "CTTestUtils.h"
+
+namespace mozilla { namespace ct {
+
+namespace {
+
+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);
+}
+
+Buffer HexToBytes(const char* hex_data) {
+ size_t hex_len = strlen(hex_data);
+ MOZ_RELEASE_ASSERT(hex_len > 0 && (hex_len % 2 == 0));
+ size_t result_len = hex_len / 2;
+ Buffer result;
+ MOZ_RELEASE_ASSERT(result.reserve(result_len));
+ for (size_t i = 0; i < result_len; ++i) {
+ uint8_t hi = CharToByte(hex_data[i*2]);
+ uint8_t lo = CharToByte(hex_data[i*2 + 1]);
+ result.infallibleAppend((hi << 4) | lo);
+ }
+ return result;
+}
+
+// The following test vectors are from
+// http://code.google.com/p/certificate-transparency
+
+const char kDefaultDerCert[] =
+ "308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b3009"
+ "06035504061302474231243022060355040a131b4365727469666963617465205472616e73"
+ "706172656e6379204341310e300c0603550408130557616c65733110300e06035504071307"
+ "4572772057656e301e170d3132303630313030303030305a170d3232303630313030303030"
+ "305a3052310b30090603550406130247423121301f060355040a1318436572746966696361"
+ "7465205472616e73706172656e6379310e300c0603550408130557616c65733110300e0603"
+ "55040713074572772057656e30819f300d06092a864886f70d010101050003818d00308189"
+ "02818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f"
+ "19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41"
+ "d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a"
+ "22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d060355"
+ "1d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d2304763074"
+ "80145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406"
+ "1302474231243022060355040a131b4365727469666963617465205472616e73706172656e"
+ "6379204341310e300c0603550408130557616c65733110300e060355040713074572772057"
+ "656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171c"
+ "d84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee"
+ "2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cb"
+ "f667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c6972"
+ "7d24c4fccd249295795814d1dac0e6";
+
+const char kDefaultIssuerKeyHash[] =
+ "02adddca08b8bf9861f035940c940156d8350fdff899a6239c6bd77255b8f8fc";
+
+const char kDefaultDerTbsCert[] =
+ "30820233a003020102020107300d06092a864886f70d01010505003055310b300906035504"
+ "061302474231243022060355040a131b4365727469666963617465205472616e7370617265"
+ "6e6379204341310e300c0603550408130557616c65733110300e0603550407130745727720"
+ "57656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052"
+ "310b30090603550406130247423121301f060355040a131843657274696669636174652054"
+ "72616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713"
+ "074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100"
+ "beef98e7c26877ae385f75325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee4"
+ "58bd7db86f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb41cd0e72805a410"
+ "cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39eed0b88119dc154dc68f7da8e30caf"
+ "158a33e6c9509f4a05b01409ff5dd87eb50203010001a381ac3081a9301d0603551d0e0416"
+ "04142031541af25c05ffd8658b6843794f5e9036f7b4307d0603551d230476307480145f9d"
+ "880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b3009060355040613024742"
+ "31243022060355040a131b4365727469666963617465205472616e73706172656e63792043"
+ "41310e300c0603550408130557616c65733110300e060355040713074572772057656e8201"
+ "0030090603551d1304023000";
+
+const char kTestDigitallySigned[] =
+ "0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef53"
+ "6cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5"
+ "a5";
+
+const char kTestSignedCertificateTimestamp[] =
+ "00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
+ "db27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
+ "08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
+ "89a2c0187ef5a5";
+
+const char kEcP256PublicKey[] =
+ "3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a"
+ "b45bf95d08a29cd0ea8dd4c84274e2be59ad15c676960cf0afa1074a57ac644b23479e5b3f"
+ "b7b245eb4b420ef370210371a944beaceb";
+
+const char kTestKeyId[] =
+ "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764";
+
+const char kTestSCTSignatureData[] =
+ "30450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f202"
+ "2100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5";
+
+const char kTestSCTPrecertSignatureData[] =
+ "30450220482f6751af35dba65436be1fd6640f3dbf9a41429495924530288fa3e5e23e0602"
+ "2100e4edc0db3ac572b1e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08";
+
+// A well-formed OCSP response with fake SCT contents. Does not come from
+// http://code.google.com/p/certificate-transparency, does not pertain to any
+// of the test certs here, and is only used to test extracting the extension
+// contents from the response.
+const char kFakeOCSPResponse[] =
+ "3082016e0a0100a08201673082016306092b060105050730010104820154308201503081ba"
+ "a21604144edfdf5ff9c90ffacfca66e7fbc436bc39ee3fc7180f3230313030313031303630"
+ "3030305a30818e30818b3049300906052b0e03021a050004141833a1e6a4f09577cca0e64c"
+ "e7d145ca4b93700904144edfdf5ff9c90ffacfca66e7fbc436bc39ee3fc7021001aef99bde"
+ "e0bb58c6f2b816bc3ae02f8000180f32303130303130313036303030305aa011180f323033"
+ "30303130313036303030305aa11830163014060a2b06010401d67902040504060404746573"
+ "74300d06092a864886f70d0101050500038181003586ffcf0794e64eb643d52a3d570a1c93"
+ "836395986a2f792dd4e9c70b05161186c55c1658e0607dc9ec0d0924ac37fb99506c870579"
+ "634be1de62ba2fced5f61f3b428f959fcee9bddf6f268c8e14c14fdf3b447786e638a5c8cc"
+ "b610893df17a60e4cff30f4780aeffe0086ef19910f0d9cd7414bc93d1945686f88ad0a3c3"
+ ;
+
+const char kFakeOCSPResponseCert[] =
+ "3082022930820192a003020102021001aef99bdee0bb58c6f2b816bc3ae02f300d06092a86"
+ "4886f70d01010505003015311330110603550403130a54657374696e67204341301e170d31"
+ "30303130313036303030305a170d3332313230313036303030305a30373112301006035504"
+ "0313093132372e302e302e31310b300906035504061302585831143012060355040a130b54"
+ "657374696e67204f726730819d300d06092a864886f70d010101050003818b003081870281"
+ "8100a71998f2930bfe73d031a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da99"
+ "9de4ed65832afcb0807f98787506539d258a0ce3c2c77967653099a9034a9b115a876c39a8"
+ "c4e4ed4acd0c64095946fb39eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1"
+ "afc45519ab9eca47c30185c771c64aa5ecf07d020103a35a3058303a06082b060105050701"
+ "01010100042b3029302706082b06010505073001861b687474703a2f2f3132372e302e302e"
+ "313a35353038312f6f637370301a0603551d200101000410300e300c060a2b06010401d679"
+ "020401300d06092a864886f70d01010505000381810065e04fadd3484197f3412479d917e1"
+ "9d8f7db57b526f2d0e4c046f86cebe643bf568ea0cd6570b228842aa057c6a7c79f209dfcd"
+ "3419a4d93b1ecfb1c0224f33083c7d4da023499fbd00d81d6711ad58ffcf65f1545247fe9d"
+ "83203425fd706b4fc5e797002af3d88151be5901eef56ec30aacdfc404be1bd35865ff1943"
+ "2516";
+
+const char kFakeOCSPResponseIssuerCert[] =
+ "308201d13082013aa003020102020101300d06092a864886f70d0101050500301531133011"
+ "0603550403130a54657374696e67204341301e170d3130303130313036303030305a170d33"
+ "32313230313036303030305a3015311330110603550403130a54657374696e672043413081"
+ "9d300d06092a864886f70d010101050003818b0030818702818100a71998f2930bfe73d031"
+ "a87f133d2f378eeeeed52a77e44d0fc9ff6f07ff32cbf3da999de4ed65832afcb0807f9878"
+ "7506539d258a0ce3c2c77967653099a9034a9b115a876c39a8c4e4ed4acd0c64095946fb39"
+ "eeeb47a0704dbb018acf48c3a1c4b895fc409fb4a340a986b1afc45519ab9eca47c30185c7"
+ "71c64aa5ecf07d020103a333303130120603551d130101ff040830060101ff020100301b06"
+ "03551d200101000411300f300d060b2b06010401d6790201ce0f300d06092a864886f70d01"
+ "01050500038181003f4936f8d00e83fbdde331f2c64335dcf7dec8b1a2597683edeed61af0"
+ "fa862412fad848938fe7ab77f1f9a43671ff6fdb729386e26f49e7aca0c0ea216e5970d933"
+ "3ea1e11df2ccb357a5fed5220f9c6239e8946b9b7517707631d51ab996833d58a022cff5a6"
+ "2169ac9258ec110efee78da9ab4a641e3b3c9ee5e8bd291460";
+
+
+const char kFakeOCSPExtensionValue[] = "74657374"; // "test"
+
+// For the sample STH
+const char kSampleSTHSHA256RootHash[] =
+ "726467216167397babca293dca398e4ce6b621b18b9bc42f30c900d1f92ac1e4";
+const char kSampleSTHTreeHeadSignature[] =
+ "0403004730450220365a91a2a88f2b9332f41d8959fa7086da7e6d634b7b089bc9da066426"
+ "6c7a20022100e38464f3c0fd066257b982074f7ac87655e0c8f714768a050b4be9a7b441cb"
+ "d3";
+size_t kSampleSTHTreeSize = 21u;
+int64_t kSampleSTHTimestamp = 1396877277237u;
+
+} // namespace
+
+void GetX509CertLogEntry(LogEntry& entry) {
+ entry.Reset();
+ entry.type = ct::LogEntry::LOG_ENTRY_TYPE_X509;
+ entry.leaf_certificate = HexToBytes(kDefaultDerCert);
+}
+
+Buffer GetDerEncodedX509Cert() {
+ return HexToBytes(kDefaultDerCert);
+}
+
+void GetPrecertLogEntry(LogEntry& entry) {
+ entry.Reset();
+ entry.type = ct::LogEntry::LOG_ENTRY_TYPE_PRECERT;
+ entry.issuer_key_hash = HexToBytes(kDefaultIssuerKeyHash);
+ entry.tbs_certificate = HexToBytes(kDefaultDerTbsCert);
+}
+
+Buffer GetTestDigitallySigned() {
+ return HexToBytes(kTestDigitallySigned);
+}
+
+Buffer GetTestSignedCertificateTimestamp() {
+ return HexToBytes(kTestSignedCertificateTimestamp);
+}
+
+Buffer GetTestPublicKey() {
+ return HexToBytes(kEcP256PublicKey);
+}
+
+Buffer GetTestPublicKeyId() {
+ return HexToBytes(kTestKeyId);
+}
+
+void GetX509CertSCT(SignedCertificateTimestamp& sct) {
+ sct.version = ct::SignedCertificateTimestamp::SCT_VERSION_1;
+ sct.log_id = 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.hash_algorithm = ct::DigitallySigned::HASH_ALGO_SHA256;
+ sct.signature.signature_algorithm = ct::DigitallySigned::SIG_ALGO_ECDSA;
+ sct.signature.signature_data = HexToBytes(kTestSCTSignatureData);
+}
+
+void GetPrecertSCT(SignedCertificateTimestamp& sct) {
+ sct.version = ct::SignedCertificateTimestamp::SCT_VERSION_1;
+ sct.log_id = 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.hash_algorithm = ct::DigitallySigned::HASH_ALGO_SHA256;
+ sct.signature.signature_algorithm = ct::DigitallySigned::SIG_ALGO_ECDSA;
+ sct.signature.signature_data = HexToBytes(kTestSCTPrecertSignatureData);
+}
+
+Buffer GetDefaultIssuerKeyHash() {
+ return HexToBytes(kDefaultIssuerKeyHash);
+}
+
+Buffer GetDerEncodedFakeOCSPResponse() {
+ return HexToBytes(kFakeOCSPResponse);
+}
+
+Buffer GetFakeOCSPExtensionValue() {
+ return HexToBytes(kFakeOCSPExtensionValue);
+}
+
+Buffer GetDerEncodedFakeOCSPResponseCert() {
+ return HexToBytes(kFakeOCSPResponseCert);
+}
+
+Buffer GetDerEncodedFakeOCSPResponseIssuerCert() {
+ return HexToBytes(kFakeOCSPResponseIssuerCert);
+}
+
+// A sample, valid STH
+void GetSampleSignedTreeHead(SignedTreeHead& sth) {
+ sth.version = SignedTreeHead::V1;
+ sth.timestamp = kSampleSTHTimestamp;
+ sth.tree_size = kSampleSTHTreeSize;
+ sth.sha256_root_hash = GetSampleSTHSHA256RootHash();
+ GetSampleSTHTreeHeadDecodedSignature(sth.signature);
+}
+
+Buffer GetSampleSTHSHA256RootHash() {
+ return HexToBytes(kSampleSTHSHA256RootHash);
+}
+
+Buffer GetSampleSTHTreeHeadSignature() {
+ return HexToBytes(kSampleSTHTreeHeadSignature);
+}
+
+void GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature) {
+ Buffer tree_head_signature = HexToBytes(kSampleSTHTreeHeadSignature);
+ pkix::Input ths_input;
+ MOZ_RELEASE_ASSERT(ths_input.Init(tree_head_signature.begin(),
+ tree_head_signature.length()) == pkix::Success);
+ pkix::Reader ths_reader(ths_input);
+ MOZ_RELEASE_ASSERT(
+ DecodeDigitallySigned(ths_reader, signature) == pkix::Success);
+ MOZ_RELEASE_ASSERT(ths_reader.AtEnd());
+}
+
+Buffer cloneBuffer(const Buffer& buffer) {
+ Buffer cloned_buffer;
+ MOZ_RELEASE_ASSERT(cloned_buffer.append(buffer.begin(), buffer.end()));
+ return cloned_buffer;
+}
+
+} } // namespace mozilla::ct
+
+namespace mozilla {
+
+bool operator==(const ct::Buffer& a, const ct::Buffer& b) {
+ return (a.empty() && b.empty()) ||
+ (a.length() == b.length() && 0 == memcmp(a.begin(), b.begin(), a.length()));
+}
+
+bool operator!=(const ct::Buffer& a, const ct::Buffer& b) {
+ return !(a == b);
+}
+
+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,92 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#ifndef mozilla_ct__SCTestUtils_h
+#define mozilla_ct__SCTestUtils_h
+
+#include <iostream>
+
+#include "mozilla/Vector.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.
+
+// 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 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();
+
+// Fake OCSP response with an embedded SCT list.
+Buffer GetDerEncodedFakeOCSPResponse();
+
+// The SCT list embedded in the response above.
+Buffer GetFakeOCSPExtensionValue();
+
+// The cert the OCSP response is for.
+Buffer GetDerEncodedFakeOCSPResponseCert();
+
+// The issuer of the previous cert.
+Buffer GetDerEncodedFakeOCSPResponseIssuerCert();
+
+// 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);
+
+} } // 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);
+
+// GTest needs this to be in Buffer's namespace.
+std::ostream& operator<<(std::ostream& stream, const ct::Buffer& buf);
+
+} // namespace mozilla
+
+#endif // mozilla_ct__SCTestUtils_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/tests/gtest/moz.build
@@ -0,0 +1,19 @@
+# -*- 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',
+]
+
+# include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul-gtest'