123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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/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 <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;
- }
- // A variable-length byte array is prefixed by its length when serialized.
- // This writes the length prefix.
- // |prefixLength| indicates the number of bytes needed to represent the length.
- // |dataLength| is the length of the byte array following the prefix.
- // Fails if |dataLength| is more than 2^|prefixLength| - 1.
- template <size_t prefixLength>
- static Result
- WriteVariableBytesPrefix(size_t dataLength, Buffer& output)
- {
- const size_t maxAllowedInputSize =
- static_cast<size_t>(((1 << (prefixLength * 8)) - 1));
- if (dataLength > maxAllowedInputSize) {
- return Result::FATAL_ERROR_INVALID_ARGS;
- }
- return WriteUint<prefixLength>(dataLength, output);
- }
- // Writes a variable-length array to |output|.
- // |prefixLength| indicates the number of bytes needed to represent the length.
- // |input| is the array itself.
- // Fails if the size of |input| is more than 2^|prefixLength| - 1.
- template <size_t prefixLength>
- static Result
- WriteVariableBytes(Input input, Buffer& output)
- {
- Result rv = WriteVariableBytesPrefix<prefixLength>(input.GetLength(), 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;
- result.origin = SignedCertificateTimestamp::Origin::Unknown;
- result.verificationStatus =
- SignedCertificateTimestamp::VerificationStatus::None;
- output = Move(result);
- return Success;
- }
- Result
- EncodeSCTList(const Vector<pkix::Input>& scts, Buffer& output)
- {
- // Find out the total size of the SCT list to be written so we can
- // write the prefix for the list before writing its contents.
- size_t sctListLength = 0;
- for (auto& sct : scts) {
- sctListLength +=
- /* data size */ sct.GetLength() +
- /* length prefix size */ kSerializedSCTLengthBytes;
- }
- if (!output.reserve(kSCTListLengthBytes + sctListLength)) {
- return Result::FATAL_ERROR_NO_MEMORY;
- }
- // Write the prefix for the SCT list.
- Result rv = WriteVariableBytesPrefix<kSCTListLengthBytes>(sctListLength,
- output);
- if (rv != Success) {
- return rv;
- }
- // Now write each SCT from the list.
- for (auto& sct : scts) {
- rv = WriteVariableBytes<kSerializedSCTLengthBytes>(sct, output);
- if (rv != Success) {
- return rv;
- }
- }
- return Success;
- }
- } } // namespace mozilla::ct
|