Bug 1882457 - Update the release process docs for the monorepo migration. r=ahal...
[gecko.git] / security / ct / CTObjectsExtractor.cpp
blob7285bf90d5eae2d95f10804a39e35cf9d2bffe2b
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CTObjectsExtractor.h"
9 #include <limits>
10 #include <vector>
12 #include "hasht.h"
13 #include "mozpkix/pkixnss.h"
14 #include "mozpkix/pkixutil.h"
16 namespace mozilla {
17 namespace ct {
19 using namespace mozilla::pkix;
21 // Holds a non-owning pointer to a byte buffer and allows writing chunks of data
22 // to the buffer, placing the later chunks after the earlier ones
23 // in a stream-like fashion.
24 // Note that writing to Output always succeeds. If the internal buffer
25 // overflows, an error flag is turned on and you won't be able to retrieve
26 // the final data.
27 class Output {
28 public:
29 Output(uint8_t* buffer, size_t length)
30 : begin(buffer),
31 end(buffer + length),
32 current(begin),
33 overflowed(false) {}
35 template <size_t N>
36 explicit Output(uint8_t (&buffer)[N]) : Output(buffer, N) {}
38 void Write(Input data) { Write(data.UnsafeGetData(), data.GetLength()); }
40 void Write(uint8_t b) { Write(&b, 1); }
42 bool IsOverflowed() const { return overflowed; }
44 Result GetInput(/*out*/ Input& input) const {
45 if (overflowed || current < begin) {
46 return Result::FATAL_ERROR_INVALID_STATE;
48 size_t length = static_cast<size_t>(current - begin);
49 return input.Init(begin, length);
52 private:
53 uint8_t* begin;
54 uint8_t* end;
55 uint8_t* current;
56 bool overflowed;
58 Output(const Output&) = delete;
59 void operator=(const Output&) = delete;
61 void Write(const uint8_t* data, size_t length) {
62 if (end < current) {
63 overflowed = true;
65 size_t available = static_cast<size_t>(end - current);
66 if (available < length) {
67 overflowed = true;
69 if (overflowed) {
70 return;
72 memcpy(current, data, length);
73 current += length;
77 // For reference:
79 // Certificate ::= SEQUENCE {
80 // tbsCertificate TBSCertificate,
81 // signatureAlgorithm AlgorithmIdentifier,
82 // signatureValue BIT STRING }
84 // TBSCertificate ::= SEQUENCE {
85 // version [0] EXPLICIT Version DEFAULT v1,
86 // serialNumber CertificateSerialNumber,
87 // signature AlgorithmIdentifier,
88 // issuer Name,
89 // validity Validity,
90 // subject Name,
91 // subjectPublicKeyInfo SubjectPublicKeyInfo,
92 // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
93 // -- If present, version MUST be v2 or v3
94 // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
95 // -- If present, version MUST be v2 or v3
96 // extensions [3] EXPLICIT Extensions OPTIONAL
97 // -- If present, version MUST be v3
98 // }
100 // python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
101 // See Section 3.3 of RFC 6962.
102 static const uint8_t EMBEDDED_SCT_LIST_OID[] = {0x2b, 0x06, 0x01, 0x04, 0x01,
103 0xd6, 0x79, 0x02, 0x04, 0x02};
104 // Maximum length of DER TLV header
105 static const size_t MAX_TLV_HEADER_LENGTH = 4;
106 // DER tag of the "extensions [3]" field from TBSCertificate
107 static const uint8_t EXTENSIONS_CONTEXT_TAG =
108 der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3;
110 Result CheckForInputSizeTypeOverflow(size_t length) {
111 if (length > std::numeric_limits<Input::size_type>::max()) {
112 return Result::FATAL_ERROR_INVALID_STATE;
114 return Success;
117 // Given a leaf certificate, extracts the DER-encoded TBSCertificate component
118 // of the corresponding Precertificate.
119 // Basically, the extractor needs to remove the embedded SCTs extension
120 // from the certificate and return its TBSCertificate. We do it in an ad hoc
121 // manner by breaking the source DER into several parts and then joining
122 // the right parts, taking care to update the relevant TLV headers.
123 // See WriteOutput for more details on the parts involved.
124 class PrecertTBSExtractor {
125 public:
126 // |buffer| is the buffer to be used for writing the output. Since the
127 // required buffer size is not generally known in advance, it's best
128 // to use at least the size of the input certificate DER.
129 PrecertTBSExtractor(Input der, uint8_t* buffer, size_t bufferLength)
130 : mDER(der), mOutput(buffer, bufferLength) {}
132 // Performs the extraction.
133 Result Init() {
134 Reader tbsReader;
135 Result rv = GetTBSCertificate(tbsReader);
136 if (rv != Success) {
137 return rv;
140 rv = ExtractTLVsBeforeExtensions(tbsReader);
141 if (rv != Success) {
142 return rv;
145 rv = ExtractOptionalExtensionsExceptSCTs(tbsReader);
146 if (rv != Success) {
147 return rv;
150 return WriteOutput();
153 // Use to retrieve the result after a successful call to Init.
154 // The returned Input points to the buffer supplied in the constructor.
155 Input GetPrecertTBS() { return mPrecertTBS; }
157 private:
158 Result GetTBSCertificate(Reader& tbsReader) {
159 Reader certificateReader;
160 Result rv =
161 der::ExpectTagAndGetValueAtEnd(mDER, der::SEQUENCE, certificateReader);
162 if (rv != Success) {
163 return rv;
165 return ExpectTagAndGetValue(certificateReader, der::SEQUENCE, tbsReader);
168 Result ExtractTLVsBeforeExtensions(Reader& tbsReader) {
169 Reader::Mark tbsBegin = tbsReader.GetMark();
170 while (!tbsReader.AtEnd()) {
171 if (tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
172 break;
174 uint8_t tag;
175 Input tagValue;
176 Result rv = der::ReadTagAndGetValue(tbsReader, tag, tagValue);
177 if (rv != Success) {
178 return rv;
181 return tbsReader.GetInput(tbsBegin, mTLVsBeforeExtensions);
184 Result ExtractOptionalExtensionsExceptSCTs(Reader& tbsReader) {
185 if (!tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) {
186 return Success;
189 Reader extensionsContextReader;
190 Result rv = der::ExpectTagAndGetValueAtEnd(
191 tbsReader, EXTENSIONS_CONTEXT_TAG, extensionsContextReader);
192 if (rv != Success) {
193 return rv;
196 Reader extensionsReader;
197 rv = der::ExpectTagAndGetValueAtEnd(extensionsContextReader, der::SEQUENCE,
198 extensionsReader);
199 if (rv != Success) {
200 return rv;
203 while (!extensionsReader.AtEnd()) {
204 Reader::Mark extensionTLVBegin = extensionsReader.GetMark();
205 Reader extension;
206 rv =
207 der::ExpectTagAndGetValue(extensionsReader, der::SEQUENCE, extension);
208 if (rv != Success) {
209 return rv;
211 Reader extensionID;
212 rv = der::ExpectTagAndGetValue(extension, der::OIDTag, extensionID);
213 if (rv != Success) {
214 return rv;
216 if (!extensionID.MatchRest(EMBEDDED_SCT_LIST_OID)) {
217 Input extensionTLV;
218 rv = extensionsReader.GetInput(extensionTLVBegin, extensionTLV);
219 if (rv != Success) {
220 return rv;
222 mExtensionTLVs.push_back(std::move(extensionTLV));
225 return Success;
228 Result WriteOutput() {
229 // What should be written here:
231 // TBSCertificate ::= SEQUENCE (TLV with header |tbsHeader|)
232 // dump of |mTLVsBeforeExtensions|
233 // extensions [3] OPTIONAL (TLV with header |extensionsContextHeader|)
234 // SEQUENCE (TLV with with header |extensionsHeader|)
235 // dump of |mExtensionTLVs|
237 Result rv;
238 if (!mExtensionTLVs.empty()) {
239 uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
240 uint8_t extensionsContextHeaderBuffer[MAX_TLV_HEADER_LENGTH];
241 uint8_t extensionsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
243 Input tbsHeader;
244 Input extensionsContextHeader;
245 Input extensionsHeader;
247 // Count the total size of the extensions. Note that since
248 // the extensions data is contained within mDER (an Input),
249 // their combined length won't overflow Input::size_type.
250 Input::size_type extensionsValueLength = 0;
251 for (auto& extensionTLV : mExtensionTLVs) {
252 extensionsValueLength += extensionTLV.GetLength();
255 rv = MakeTLVHeader(der::SEQUENCE, extensionsValueLength,
256 extensionsHeaderBuffer, extensionsHeader);
257 if (rv != Success) {
258 return rv;
260 // Since we're getting these extensions from a certificate that has
261 // already fit in an Input, this shouldn't overflow.
262 size_t extensionsContextLengthAsSizeT =
263 static_cast<size_t>(extensionsHeader.GetLength()) +
264 static_cast<size_t>(extensionsValueLength);
265 rv = CheckForInputSizeTypeOverflow(extensionsContextLengthAsSizeT);
266 if (rv != Success) {
267 return rv;
269 Input::size_type extensionsContextLength =
270 static_cast<Input::size_type>(extensionsContextLengthAsSizeT);
271 rv =
272 MakeTLVHeader(EXTENSIONS_CONTEXT_TAG, extensionsContextLength,
273 extensionsContextHeaderBuffer, extensionsContextHeader);
274 if (rv != Success) {
275 return rv;
277 size_t tbsLengthAsSizeT =
278 static_cast<size_t>(mTLVsBeforeExtensions.GetLength()) +
279 static_cast<size_t>(extensionsContextHeader.GetLength()) +
280 static_cast<size_t>(extensionsHeader.GetLength()) +
281 static_cast<size_t>(extensionsValueLength);
282 rv = CheckForInputSizeTypeOverflow(tbsLengthAsSizeT);
283 if (rv != Success) {
284 return rv;
286 Input::size_type tbsLength =
287 static_cast<Input::size_type>(tbsLengthAsSizeT);
288 rv = MakeTLVHeader(der::SEQUENCE, tbsLength, tbsHeaderBuffer, tbsHeader);
289 if (rv != Success) {
290 return rv;
293 mOutput.Write(tbsHeader);
294 mOutput.Write(mTLVsBeforeExtensions);
295 mOutput.Write(extensionsContextHeader);
296 mOutput.Write(extensionsHeader);
297 for (auto& extensionTLV : mExtensionTLVs) {
298 mOutput.Write(extensionTLV);
300 } else {
301 uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH];
302 Input tbsHeader;
303 rv = MakeTLVHeader(der::SEQUENCE, mTLVsBeforeExtensions.GetLength(),
304 tbsHeaderBuffer, tbsHeader);
305 if (rv != Success) {
306 return rv;
308 mOutput.Write(tbsHeader);
309 mOutput.Write(mTLVsBeforeExtensions);
312 return mOutput.GetInput(mPrecertTBS);
315 Result MakeTLVHeader(uint8_t tag, size_t length,
316 uint8_t (&buffer)[MAX_TLV_HEADER_LENGTH],
317 /*out*/ Input& header) {
318 Output output(buffer);
319 output.Write(tag);
320 if (length < 128) {
321 output.Write(static_cast<uint8_t>(length));
322 } else if (length < 256) {
323 output.Write(0x81u);
324 output.Write(static_cast<uint8_t>(length));
325 } else if (length < 65536) {
326 output.Write(0x82u);
327 output.Write(static_cast<uint8_t>(length / 256));
328 output.Write(static_cast<uint8_t>(length % 256));
329 } else {
330 return Result::FATAL_ERROR_INVALID_ARGS;
332 return output.GetInput(header);
335 Input mDER;
336 Input mTLVsBeforeExtensions;
337 std::vector<Input> mExtensionTLVs;
338 Output mOutput;
339 Input mPrecertTBS;
342 Result GetPrecertLogEntry(Input leafCertificate,
343 Input issuerSubjectPublicKeyInfo, LogEntry& output) {
344 assert(leafCertificate.GetLength() > 0);
345 assert(issuerSubjectPublicKeyInfo.GetLength() > 0);
346 output.Reset();
348 Buffer precertTBSBuffer;
349 precertTBSBuffer.resize(leafCertificate.GetLength());
351 PrecertTBSExtractor extractor(leafCertificate, precertTBSBuffer.data(),
352 precertTBSBuffer.size());
353 Result rv = extractor.Init();
354 if (rv != Success) {
355 return rv;
357 Input precertTBS(extractor.GetPrecertTBS());
358 assert(precertTBS.UnsafeGetData() == precertTBSBuffer.data());
359 assert(precertTBS.GetLength() <= precertTBSBuffer.size());
360 precertTBSBuffer.resize(precertTBS.GetLength());
362 output.type = LogEntry::Type::Precert;
363 output.tbsCertificate = std::move(precertTBSBuffer);
365 output.issuerKeyHash.resize(SHA256_LENGTH);
366 return DigestBufNSS(issuerSubjectPublicKeyInfo, DigestAlgorithm::sha256,
367 output.issuerKeyHash.data(), output.issuerKeyHash.size());
370 void GetX509LogEntry(Input leafCertificate, LogEntry& output) {
371 assert(leafCertificate.GetLength() > 0);
372 output.Reset();
373 output.type = LogEntry::Type::X509;
374 InputToBuffer(leafCertificate, output.leafCertificate);
377 } // namespace ct
378 } // namespace mozilla