/* -*- 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 #include "mozilla/Assertions.h" #include "mozilla/Move.h" #include "mozilla/TypeTraits.h" namespace mozilla { namespace ct { using namespace mozilla::pkix; typedef mozilla::pkix::Result Result; // 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 static inline Result ReadUint(Reader& in, T& out) { uint64_t value; static_assert(mozilla::IsUnsigned::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 bytes"); Result rv = UncheckedReadUint(length, in, value); if (rv != Success) { return rv; } out = static_cast(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 static inline Result ReadVariableBytes(Reader& in, Input& out) { size_t length; Result rv = ReadUint(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(in, value); if (rv != Success) { return rv; } DigitallySigned::HashAlgorithm algo = static_cast(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(in, value); if (rv != Success) { return rv; } DigitallySigned::SignatureAlgorithm algo = static_cast(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(in, value); if (rv != Success) { return rv; } SignedCertificateTimestamp::Version version = static_cast(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 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 bytes"); if (mozilla::IsSigned::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(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; } // A variable-length byte array is prefixed by its length when serialized. // This writes the length prefix. // |prefixLength| indicates the number of bytes needed to represent the length. // |dataLength| is the length of the byte array following the prefix. // Fails if |dataLength| is more than 2^|prefixLength| - 1. template static Result WriteVariableBytesPrefix(size_t dataLength, Buffer& output) { const size_t maxAllowedInputSize = static_cast(((1 << (prefixLength * 8)) - 1)); if (dataLength > maxAllowedInputSize) { return Result::FATAL_ERROR_INVALID_ARGS; } return WriteUint(dataLength, output); } // Writes a variable-length array to |output|. // |prefixLength| indicates the number of bytes needed to represent the length. // |input| is the array itself. // Fails if the size of |input| is more than 2^|prefixLength| - 1. template static Result WriteVariableBytes(Input input, Buffer& output) { Result rv = WriteVariableBytesPrefix(input.GetLength(), output); if (rv != Success) { return rv; } return WriteEncodedBytes(input, output); } // Same as above, but the source data is in a Buffer. template static Result WriteVariableBytes(const Buffer& source, Buffer& output) { Input input; Result rv = BufferToInput(source, input); if (rv != Success) { return rv; } return WriteVariableBytes(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(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(entry.tbsCertificate, output); } Result EncodeDigitallySigned(const DigitallySigned& data, Buffer& output) { Result rv = WriteUint( static_cast(data.hashAlgorithm), output); if (rv != Success) { return rv; } rv = WriteUint( static_cast(data.signatureAlgorithm), output); if (rv != Success) { return rv; } return WriteVariableBytes(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(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( static_cast(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(timestamp, output); } Result EncodeV1SCTSignedData(uint64_t timestamp, Input serializedLogEntry, Input extensions, Buffer& output) { Result rv = WriteUint(static_cast( SignedCertificateTimestamp::Version::V1), output); if (rv != Success) { return rv; } rv = WriteUint(static_cast( 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(extensions, output); } Result EncodeTreeHeadSignature(const SignedTreeHead& signedTreeHead, Buffer& output) { Result rv = WriteUint( static_cast(signedTreeHead.version), output); if (rv != Success) { return rv; } rv = WriteUint( static_cast(SignatureType::TreeHash), output); if (rv != Success) { return rv; } rv = WriteTimeSinceEpoch(signedTreeHead.timestamp, output); if (rv != Success) { return rv; } rv = WriteUint(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(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(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(reader, timestamp); if (rv != Success) { return rv; } rv = ReadVariableBytes(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; } Result EncodeSCTList(const Vector& scts, Buffer& output) { // Find out the total size of the SCT list to be written so we can // write the prefix for the list before writing its contents. size_t sctListLength = 0; for (auto& sct : scts) { sctListLength += /* data size */ sct.GetLength() + /* length prefix size */ kSerializedSCTLengthBytes; } if (!output.reserve(kSCTListLengthBytes + sctListLength)) { return Result::FATAL_ERROR_NO_MEMORY; } // Write the prefix for the SCT list. Result rv = WriteVariableBytesPrefix(sctListLength, output); if (rv != Success) { return rv; } // Now write each SCT from the list. for (auto& sct : scts) { rv = WriteVariableBytes(sct, output); if (rv != Success) { return rv; } } return Success; } } } // namespace mozilla::ct