summaryrefslogtreecommitdiffstats
path: root/security/ct/CTObjectsExtractor.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/ct/CTObjectsExtractor.cpp378
1 files changed, 378 insertions, 0 deletions
diff --git a/security/ct/CTObjectsExtractor.cpp b/security/ct/CTObjectsExtractor.cpp
new file mode 100644
index 0000000000..7285bf90d5
--- /dev/null
+++ b/security/ct/CTObjectsExtractor.cpp
@@ -0,0 +1,378 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTObjectsExtractor.h"
+
+#include <limits>
+#include <vector>
+
+#include "hasht.h"
+#include "mozpkix/pkixnss.h"
+#include "mozpkix/pkixutil.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace mozilla::pkix;
+
+// Holds a non-owning pointer to a byte buffer and allows writing chunks of data
+// to the buffer, placing the later chunks after the earlier ones
+// in a stream-like fashion.
+// Note that writing to Output always succeeds. If the internal buffer
+// overflows, an error flag is turned on and you won't be able to retrieve
+// the final data.
+class Output {
+ public:
+ Output(uint8_t* buffer, size_t length)
+ : begin(buffer),
+ end(buffer + length),
+ current(begin),
+ overflowed(false) {}
+
+ template <size_t N>
+ explicit Output(uint8_t (&buffer)[N]) : Output(buffer, N) {}
+
+ void Write(Input data) { Write(data.UnsafeGetData(), data.GetLength()); }
+
+ void Write(uint8_t b) { Write(&b, 1); }
+
+ bool IsOverflowed() const { return overflowed; }
+
+ Result GetInput(/*out*/ Input& input) const {
+ if (overflowed || current < begin) {
+ return Result::FATAL_ERROR_INVALID_STATE;
+ }
+ size_t length = static_cast<size_t>(current - begin);
+ return input.Init(begin, length);
+ }
+
+ private:
+ uint8_t* begin;
+ uint8_t* end;
+ uint8_t* current;
+ bool overflowed;
+
+ Output(const Output&) = delete;
+ void operator=(const Output&) = delete;
+
+ void Write(const uint8_t* data, size_t length) {
+ if (end < current) {
+ overflowed = true;
+ }
+ size_t available = static_cast<size_t>(end - current);
+ if (available < length) {
+ overflowed = true;
+ }
+ if (overflowed) {
+ return;
+ }
+ memcpy(current, data, length);
+ current += length;
+ }
+};
+
+// For reference:
+//
+// Certificate ::= SEQUENCE {
+// tbsCertificate TBSCertificate,
+// signatureAlgorithm AlgorithmIdentifier,
+// signatureValue BIT STRING }
+//
+// TBSCertificate ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// serialNumber CertificateSerialNumber,
+// signature AlgorithmIdentifier,
+// issuer Name,
+// validity Validity,
+// subject Name,
+// subjectPublicKeyInfo SubjectPublicKeyInfo,
+// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+// -- If present, version MUST be v2 or v3
+// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+// -- If present, version MUST be v2 or v3
+// extensions [3] EXPLICIT Extensions OPTIONAL
+// -- If present, version MUST be v3
+// }
+
+// python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+// See Section 3.3 of RFC 6962.
+static const uint8_t EMBEDDED_SCT_LIST_OID[] = {0x2b, 0x06, 0x01, 0x04, 0x01,
+ 0xd6, 0x79, 0x02, 0x04, 0x02};
+// Maximum length of DER TLV header
+static const size_t MAX_TLV_HEADER_LENGTH = 4;
+// DER tag of the "extensions [3]" field from TBSCertificate
+static const uint8_t EXTENSIONS_CONTEXT_TAG =
+ der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3;
+
+Result CheckForInputSizeTypeOverflow(size_t length) {
+ if (length > std::numeric_limits<Input::size_type>::max()) {
+ return Result::FATAL_ERROR_INVALID_STATE;
+ }
+ return Success;
+}
+
+// Given a leaf certificate, extracts the DER-encoded TBSCertificate component
+// of the corresponding Precertificate.
+// Basically, the extractor needs to remove the embedded SCTs extension
+// from the certificate and return its TBSCertificate. We do it in an ad hoc
+// manner by breaking the source DER into several parts and then joining
+// the right parts, taking care to update the relevant TLV headers.
+// See WriteOutput for more details on the parts involved.
+class PrecertTBSExtractor {
+ public:
+ // |buffer| is the buffer to be used for writing the output. Since the
+ // required buffer size is not generally known in advance, it's best
+ // to use at least the size of the input certificate DER.
+ PrecertTBSExtractor(Input der, uint8_t* buffer, size_t bufferLength)
+ : mDER(der), mOutput(buffer, bufferLength) {}
+
+ // Performs the extraction.
+ Result Init() {
+ Reader tbsReader;
+ Result rv = GetTBSCertificate(tbsReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = ExtractTLVsBeforeExtensions(tbsReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ rv = ExtractOptionalExtensionsExceptSCTs(tbsReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ return WriteOutput();
+ }
+
+ // Use to retrieve the result after a successful call to Init.
+ // The returned Input points to the buffer supplied in the constructor.
+ Input GetPrecertTBS() { return mPrecertTBS; }
+
+ private:
+ Result GetTBSCertificate(Reader& tbsReader) {
+ Reader certificateReader;
+ Result rv =
+ der::ExpectTagAndGetValueAtEnd(mDER, der::SEQUENCE, certificateReader);
+ if (rv != Success) {
+ return rv;
+ }
+ return ExpectTagAndGetValue(certificateReader, der::SEQUENCE, tbsReader);
+ }
+
+ Result ExtractTLVsBeforeExtensions(Reader& tbsReader) {
+ Reader::Mark tbsBegin = tbsReader.GetMark();
+ while (!tbsReader.AtEnd()) {
+ if (tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
+ break;
+ }
+ uint8_t tag;
+ Input tagValue;
+ Result rv = der::ReadTagAndGetValue(tbsReader, tag, tagValue);
+ if (rv != Success) {
+ return rv;
+ }
+ }
+ return tbsReader.GetInput(tbsBegin, mTLVsBeforeExtensions);
+ }
+
+ Result ExtractOptionalExtensionsExceptSCTs(Reader& tbsReader) {
+ if (!tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
+ return Success;
+ }
+
+ Reader extensionsContextReader;
+ Result rv = der::ExpectTagAndGetValueAtEnd(
+ tbsReader, EXTENSIONS_CONTEXT_TAG, extensionsContextReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ Reader extensionsReader;
+ rv = der::ExpectTagAndGetValueAtEnd(extensionsContextReader, der::SEQUENCE,
+ extensionsReader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ while (!extensionsReader.AtEnd()) {
+ Reader::Mark extensionTLVBegin = extensionsReader.GetMark();
+ Reader extension;
+ rv =
+ der::ExpectTagAndGetValue(extensionsReader, der::SEQUENCE, extension);
+ if (rv != Success) {
+ return rv;
+ }
+ Reader extensionID;
+ rv = der::ExpectTagAndGetValue(extension, der::OIDTag, extensionID);
+ if (rv != Success) {
+ return rv;
+ }
+ if (!extensionID.MatchRest(EMBEDDED_SCT_LIST_OID)) {
+ Input extensionTLV;
+ rv = extensionsReader.GetInput(extensionTLVBegin, extensionTLV);
+ if (rv != Success) {
+ return rv;
+ }
+ mExtensionTLVs.push_back(std::move(extensionTLV));
+ }
+ }
+ return Success;
+ }
+
+ Result WriteOutput() {
+ // What should be written here:
+ //
+ // TBSCertificate ::= SEQUENCE (TLV with header |tbsHeader|)
+ // dump of |mTLVsBeforeExtensions|
+ // extensions [3] OPTIONAL (TLV with header |extensionsContextHeader|)
+ // SEQUENCE (TLV with with header |extensionsHeader|)
+ // dump of |mExtensionTLVs|
+
+ Result rv;
+ if (!mExtensionTLVs.empty()) {
+ uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
+ uint8_t extensionsContextHeaderBuffer[MAX_TLV_HEADER_LENGTH];
+ uint8_t extensionsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
+
+ Input tbsHeader;
+ Input extensionsContextHeader;
+ Input extensionsHeader;
+
+ // Count the total size of the extensions. Note that since
+ // the extensions data is contained within mDER (an Input),
+ // their combined length won't overflow Input::size_type.
+ Input::size_type extensionsValueLength = 0;
+ for (auto& extensionTLV : mExtensionTLVs) {
+ extensionsValueLength += extensionTLV.GetLength();
+ }
+
+ rv = MakeTLVHeader(der::SEQUENCE, extensionsValueLength,
+ extensionsHeaderBuffer, extensionsHeader);
+ if (rv != Success) {
+ return rv;
+ }
+ // Since we're getting these extensions from a certificate that has
+ // already fit in an Input, this shouldn't overflow.
+ size_t extensionsContextLengthAsSizeT =
+ static_cast<size_t>(extensionsHeader.GetLength()) +
+ static_cast<size_t>(extensionsValueLength);
+ rv = CheckForInputSizeTypeOverflow(extensionsContextLengthAsSizeT);
+ if (rv != Success) {
+ return rv;
+ }
+ Input::size_type extensionsContextLength =
+ static_cast<Input::size_type>(extensionsContextLengthAsSizeT);
+ rv =
+ MakeTLVHeader(EXTENSIONS_CONTEXT_TAG, extensionsContextLength,
+ extensionsContextHeaderBuffer, extensionsContextHeader);
+ if (rv != Success) {
+ return rv;
+ }
+ size_t tbsLengthAsSizeT =
+ static_cast<size_t>(mTLVsBeforeExtensions.GetLength()) +
+ static_cast<size_t>(extensionsContextHeader.GetLength()) +
+ static_cast<size_t>(extensionsHeader.GetLength()) +
+ static_cast<size_t>(extensionsValueLength);
+ rv = CheckForInputSizeTypeOverflow(tbsLengthAsSizeT);
+ if (rv != Success) {
+ return rv;
+ }
+ Input::size_type tbsLength =
+ static_cast<Input::size_type>(tbsLengthAsSizeT);
+ rv = MakeTLVHeader(der::SEQUENCE, tbsLength, tbsHeaderBuffer, tbsHeader);
+ if (rv != Success) {
+ return rv;
+ }
+
+ mOutput.Write(tbsHeader);
+ mOutput.Write(mTLVsBeforeExtensions);
+ mOutput.Write(extensionsContextHeader);
+ mOutput.Write(extensionsHeader);
+ for (auto& extensionTLV : mExtensionTLVs) {
+ mOutput.Write(extensionTLV);
+ }
+ } else {
+ uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
+ Input tbsHeader;
+ rv = MakeTLVHeader(der::SEQUENCE, mTLVsBeforeExtensions.GetLength(),
+ tbsHeaderBuffer, tbsHeader);
+ if (rv != Success) {
+ return rv;
+ }
+ mOutput.Write(tbsHeader);
+ mOutput.Write(mTLVsBeforeExtensions);
+ }
+
+ return mOutput.GetInput(mPrecertTBS);
+ }
+
+ Result MakeTLVHeader(uint8_t tag, size_t length,
+ uint8_t (&buffer)[MAX_TLV_HEADER_LENGTH],
+ /*out*/ Input& header) {
+ Output output(buffer);
+ output.Write(tag);
+ if (length < 128) {
+ output.Write(static_cast<uint8_t>(length));
+ } else if (length < 256) {
+ output.Write(0x81u);
+ output.Write(static_cast<uint8_t>(length));
+ } else if (length < 65536) {
+ output.Write(0x82u);
+ output.Write(static_cast<uint8_t>(length / 256));
+ output.Write(static_cast<uint8_t>(length % 256));
+ } else {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ return output.GetInput(header);
+ }
+
+ Input mDER;
+ Input mTLVsBeforeExtensions;
+ std::vector<Input> mExtensionTLVs;
+ Output mOutput;
+ Input mPrecertTBS;
+};
+
+Result GetPrecertLogEntry(Input leafCertificate,
+ Input issuerSubjectPublicKeyInfo, LogEntry& output) {
+ assert(leafCertificate.GetLength() > 0);
+ assert(issuerSubjectPublicKeyInfo.GetLength() > 0);
+ output.Reset();
+
+ Buffer precertTBSBuffer;
+ precertTBSBuffer.resize(leafCertificate.GetLength());
+
+ PrecertTBSExtractor extractor(leafCertificate, precertTBSBuffer.data(),
+ precertTBSBuffer.size());
+ Result rv = extractor.Init();
+ if (rv != Success) {
+ return rv;
+ }
+ Input precertTBS(extractor.GetPrecertTBS());
+ assert(precertTBS.UnsafeGetData() == precertTBSBuffer.data());
+ assert(precertTBS.GetLength() <= precertTBSBuffer.size());
+ precertTBSBuffer.resize(precertTBS.GetLength());
+
+ output.type = LogEntry::Type::Precert;
+ output.tbsCertificate = std::move(precertTBSBuffer);
+
+ output.issuerKeyHash.resize(SHA256_LENGTH);
+ return DigestBufNSS(issuerSubjectPublicKeyInfo, DigestAlgorithm::sha256,
+ output.issuerKeyHash.data(), output.issuerKeyHash.size());
+}
+
+void GetX509LogEntry(Input leafCertificate, LogEntry& output) {
+ assert(leafCertificate.GetLength() > 0);
+ output.Reset();
+ output.type = LogEntry::Type::X509;
+ InputToBuffer(leafCertificate, output.leafCertificate);
+}
+
+} // namespace ct
+} // namespace mozilla