/src/mozilla-central/security/certverifier/CTObjectsExtractor.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "CTObjectsExtractor.h" |
8 | | |
9 | | #include "hasht.h" |
10 | | #include "mozilla/Assertions.h" |
11 | | #include "mozilla/Casting.h" |
12 | | #include "mozilla/Move.h" |
13 | | #include "mozilla/PodOperations.h" |
14 | | #include "mozilla/RangedPtr.h" |
15 | | #include "mozilla/Vector.h" |
16 | | #include "pkix/pkixnss.h" |
17 | | #include "pkixutil.h" |
18 | | |
19 | | namespace mozilla { namespace ct { |
20 | | |
21 | | using namespace mozilla::pkix; |
22 | | |
23 | | // Holds a non-owning pointer to a byte buffer and allows writing chunks of data |
24 | | // to the buffer, placing the later chunks after the earlier ones |
25 | | // in a stream-like fashion. |
26 | | // Note that writing to Output always succeeds. If the internal buffer |
27 | | // overflows, an error flag is turned on and you won't be able to retrieve |
28 | | // the final data. |
29 | | class Output |
30 | | { |
31 | | public: |
32 | | Output(uint8_t* buffer, size_t length) |
33 | | : begin(buffer) |
34 | | , end(buffer + length) |
35 | | , current(buffer, begin, end) |
36 | | , overflowed(false) |
37 | 0 | { |
38 | 0 | } |
39 | | |
40 | | template <size_t N> |
41 | | explicit Output(uint8_t (&buffer)[N]) |
42 | | : Output(buffer, N) |
43 | 0 | { |
44 | 0 | } |
45 | | |
46 | | void Write(Input data) |
47 | 0 | { |
48 | 0 | Write(data.UnsafeGetData(), data.GetLength()); |
49 | 0 | } |
50 | | |
51 | | void Write(uint8_t b) |
52 | 0 | { |
53 | 0 | Write(&b, 1); |
54 | 0 | } |
55 | | |
56 | 0 | bool IsOverflowed() const { return overflowed; } |
57 | | |
58 | | Result GetInput(/*out*/ Input& input) const |
59 | 0 | { |
60 | 0 | if (overflowed) { |
61 | 0 | return Result::FATAL_ERROR_INVALID_STATE; |
62 | 0 | } |
63 | 0 | size_t length = AssertedCast<size_t>(current.get() - begin); |
64 | 0 | return input.Init(begin, length); |
65 | 0 | } |
66 | | |
67 | | private: |
68 | | uint8_t* begin; |
69 | | uint8_t* end; |
70 | | RangedPtr<uint8_t> current; |
71 | | bool overflowed; |
72 | | |
73 | | Output(const Output&) = delete; |
74 | | void operator=(const Output&) = delete; |
75 | | |
76 | | void Write(const uint8_t* data, size_t length) |
77 | 0 | { |
78 | 0 | size_t available = AssertedCast<size_t>(end - current.get()); |
79 | 0 | if (available < length) { |
80 | 0 | overflowed = true; |
81 | 0 | } |
82 | 0 | if (overflowed) { |
83 | 0 | return; |
84 | 0 | } |
85 | 0 | PodCopy(current.get(), data, length); |
86 | 0 | current += length; |
87 | 0 | } |
88 | | }; |
89 | | |
90 | | // For reference: |
91 | | // |
92 | | // Certificate ::= SEQUENCE { |
93 | | // tbsCertificate TBSCertificate, |
94 | | // signatureAlgorithm AlgorithmIdentifier, |
95 | | // signatureValue BIT STRING } |
96 | | // |
97 | | // TBSCertificate ::= SEQUENCE { |
98 | | // version [0] EXPLICIT Version DEFAULT v1, |
99 | | // serialNumber CertificateSerialNumber, |
100 | | // signature AlgorithmIdentifier, |
101 | | // issuer Name, |
102 | | // validity Validity, |
103 | | // subject Name, |
104 | | // subjectPublicKeyInfo SubjectPublicKeyInfo, |
105 | | // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, |
106 | | // -- If present, version MUST be v2 or v3 |
107 | | // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, |
108 | | // -- If present, version MUST be v2 or v3 |
109 | | // extensions [3] EXPLICIT Extensions OPTIONAL |
110 | | // -- If present, version MUST be v3 |
111 | | // } |
112 | | |
113 | | // python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 |
114 | | // See Section 3.3 of RFC 6962. |
115 | | static const uint8_t EMBEDDED_SCT_LIST_OID[] = { |
116 | | 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 |
117 | | }; |
118 | | // Maximum length of DER TLV header |
119 | | static const size_t MAX_TLV_HEADER_LENGTH = 4; |
120 | | // DER tag of the "extensions [3]" field from TBSCertificate |
121 | | static const uint8_t EXTENSIONS_CONTEXT_TAG = |
122 | | der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3; |
123 | | |
124 | | // Given a leaf certificate, extracts the DER-encoded TBSCertificate component |
125 | | // of the corresponding Precertificate. |
126 | | // Basically, the extractor needs to remove the embedded SCTs extension |
127 | | // from the certificate and return its TBSCertificate. We do it in an ad hoc |
128 | | // manner by breaking the source DER into several parts and then joining |
129 | | // the right parts, taking care to update the relevant TLV headers. |
130 | | // See WriteOutput for more details on the parts involved. |
131 | | class PrecertTBSExtractor |
132 | | { |
133 | | public: |
134 | | // |buffer| is the buffer to be used for writing the output. Since the |
135 | | // required buffer size is not generally known in advance, it's best |
136 | | // to use at least the size of the input certificate DER. |
137 | | PrecertTBSExtractor(Input der, uint8_t* buffer, size_t bufferLength) |
138 | | : mDER(der) |
139 | | , mOutput(buffer, bufferLength) |
140 | 0 | { |
141 | 0 | } |
142 | | |
143 | | // Performs the extraction. |
144 | | Result Init() |
145 | 0 | { |
146 | 0 | Reader tbsReader; |
147 | 0 | Result rv = GetTBSCertificate(tbsReader); |
148 | 0 | if (rv != Success) { |
149 | 0 | return rv; |
150 | 0 | } |
151 | 0 | |
152 | 0 | rv = ExtractTLVsBeforeExtensions(tbsReader); |
153 | 0 | if (rv != Success) { |
154 | 0 | return rv; |
155 | 0 | } |
156 | 0 | |
157 | 0 | rv = ExtractOptionalExtensionsExceptSCTs(tbsReader); |
158 | 0 | if (rv != Success) { |
159 | 0 | return rv; |
160 | 0 | } |
161 | 0 | |
162 | 0 | return WriteOutput(); |
163 | 0 | } |
164 | | |
165 | | // Use to retrieve the result after a successful call to Init. |
166 | | // The returned Input points to the buffer supplied in the constructor. |
167 | | Input GetPrecertTBS() |
168 | 0 | { |
169 | 0 | return mPrecertTBS; |
170 | 0 | } |
171 | | |
172 | | private: |
173 | | Result GetTBSCertificate(Reader& tbsReader) |
174 | 0 | { |
175 | 0 | Reader certificateReader; |
176 | 0 | Result rv = der::ExpectTagAndGetValueAtEnd(mDER, der::SEQUENCE, |
177 | 0 | certificateReader); |
178 | 0 | if (rv != Success) { |
179 | 0 | return rv; |
180 | 0 | } |
181 | 0 | return ExpectTagAndGetValue(certificateReader, der::SEQUENCE, tbsReader); |
182 | 0 | } |
183 | | |
184 | | Result ExtractTLVsBeforeExtensions(Reader& tbsReader) |
185 | 0 | { |
186 | 0 | Reader::Mark tbsBegin = tbsReader.GetMark(); |
187 | 0 | while (!tbsReader.AtEnd()) { |
188 | 0 | if (tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) { |
189 | 0 | break; |
190 | 0 | } |
191 | 0 | uint8_t tag; |
192 | 0 | Input tagValue; |
193 | 0 | Result rv = der::ReadTagAndGetValue(tbsReader, tag, tagValue); |
194 | 0 | if (rv != Success) { |
195 | 0 | return rv; |
196 | 0 | } |
197 | 0 | } |
198 | 0 | return tbsReader.GetInput(tbsBegin, mTLVsBeforeExtensions); |
199 | 0 | } |
200 | | |
201 | | Result ExtractOptionalExtensionsExceptSCTs(Reader& tbsReader) |
202 | 0 | { |
203 | 0 | if (!tbsReader.Peek(EXTENSIONS_CONTEXT_TAG)) { |
204 | 0 | return Success; |
205 | 0 | } |
206 | 0 | |
207 | 0 | Reader extensionsContextReader; |
208 | 0 | Result rv = der::ExpectTagAndGetValueAtEnd(tbsReader, |
209 | 0 | EXTENSIONS_CONTEXT_TAG, |
210 | 0 | extensionsContextReader); |
211 | 0 | if (rv != Success) { |
212 | 0 | return rv; |
213 | 0 | } |
214 | 0 | |
215 | 0 | Reader extensionsReader; |
216 | 0 | rv = der::ExpectTagAndGetValueAtEnd(extensionsContextReader, der::SEQUENCE, |
217 | 0 | extensionsReader); |
218 | 0 | if (rv != Success) { |
219 | 0 | return rv; |
220 | 0 | } |
221 | 0 | |
222 | 0 | while (!extensionsReader.AtEnd()) { |
223 | 0 | Reader::Mark extensionTLVBegin = extensionsReader.GetMark(); |
224 | 0 | Reader extension; |
225 | 0 | rv = der::ExpectTagAndGetValue(extensionsReader, der::SEQUENCE, |
226 | 0 | extension); |
227 | 0 | if (rv != Success) { |
228 | 0 | return rv; |
229 | 0 | } |
230 | 0 | Reader extensionID; |
231 | 0 | rv = der::ExpectTagAndGetValue(extension, der::OIDTag, extensionID); |
232 | 0 | if (rv != Success) { |
233 | 0 | return rv; |
234 | 0 | } |
235 | 0 | if (!extensionID.MatchRest(EMBEDDED_SCT_LIST_OID)) { |
236 | 0 | Input extensionTLV; |
237 | 0 | rv = extensionsReader.GetInput(extensionTLVBegin, extensionTLV); |
238 | 0 | if (rv != Success) { |
239 | 0 | return rv; |
240 | 0 | } |
241 | 0 | if (!mExtensionTLVs.append(std::move(extensionTLV))) { |
242 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | } |
246 | 0 | return Success; |
247 | 0 | } |
248 | | |
249 | | Result WriteOutput() |
250 | 0 | { |
251 | 0 | // What should be written here: |
252 | 0 | // |
253 | 0 | // TBSCertificate ::= SEQUENCE (TLV with header |tbsHeader|) |
254 | 0 | // dump of |mTLVsBeforeExtensions| |
255 | 0 | // extensions [3] OPTIONAL (TLV with header |extensionsContextHeader|) |
256 | 0 | // SEQUENCE (TLV with with header |extensionsHeader|) |
257 | 0 | // dump of |mExtensionTLVs| |
258 | 0 |
|
259 | 0 | Result rv; |
260 | 0 | if (mExtensionTLVs.length() > 0) { |
261 | 0 | uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH]; |
262 | 0 | uint8_t extensionsContextHeaderBuffer[MAX_TLV_HEADER_LENGTH]; |
263 | 0 | uint8_t extensionsHeaderBuffer[MAX_TLV_HEADER_LENGTH]; |
264 | 0 |
|
265 | 0 | Input tbsHeader; |
266 | 0 | Input extensionsContextHeader; |
267 | 0 | Input extensionsHeader; |
268 | 0 |
|
269 | 0 | // Count the total size of the extensions. Note that since |
270 | 0 | // the extensions data is contained within mDER (an Input), |
271 | 0 | // their combined length won't overflow Input::size_type. |
272 | 0 | Input::size_type extensionsValueLength = 0; |
273 | 0 | for (auto& extensionTLV : mExtensionTLVs) { |
274 | 0 | extensionsValueLength += extensionTLV.GetLength(); |
275 | 0 | } |
276 | 0 |
|
277 | 0 | rv = MakeTLVHeader(der::SEQUENCE, extensionsValueLength, |
278 | 0 | extensionsHeaderBuffer, extensionsHeader); |
279 | 0 | if (rv != Success) { |
280 | 0 | return rv; |
281 | 0 | } |
282 | 0 | Input::size_type extensionsContextLength = |
283 | 0 | AssertedCast<Input::size_type>(extensionsHeader.GetLength() + |
284 | 0 | extensionsValueLength); |
285 | 0 | rv = MakeTLVHeader(EXTENSIONS_CONTEXT_TAG, |
286 | 0 | extensionsContextLength, |
287 | 0 | extensionsContextHeaderBuffer, |
288 | 0 | extensionsContextHeader); |
289 | 0 | if (rv != Success) { |
290 | 0 | return rv; |
291 | 0 | } |
292 | 0 | Input::size_type tbsLength = |
293 | 0 | AssertedCast<Input::size_type>(mTLVsBeforeExtensions.GetLength() + |
294 | 0 | extensionsContextHeader.GetLength() + |
295 | 0 | extensionsHeader.GetLength() + |
296 | 0 | extensionsValueLength); |
297 | 0 | rv = MakeTLVHeader(der::SEQUENCE, tbsLength, tbsHeaderBuffer, tbsHeader); |
298 | 0 | if (rv != Success) { |
299 | 0 | return rv; |
300 | 0 | } |
301 | 0 | |
302 | 0 | mOutput.Write(tbsHeader); |
303 | 0 | mOutput.Write(mTLVsBeforeExtensions); |
304 | 0 | mOutput.Write(extensionsContextHeader); |
305 | 0 | mOutput.Write(extensionsHeader); |
306 | 0 | for (auto& extensionTLV : mExtensionTLVs) { |
307 | 0 | mOutput.Write(extensionTLV); |
308 | 0 | } |
309 | 0 | } else { |
310 | 0 | uint8_t tbsHeaderBuffer[MAX_TLV_HEADER_LENGTH]; |
311 | 0 | Input tbsHeader; |
312 | 0 | rv = MakeTLVHeader(der::SEQUENCE, mTLVsBeforeExtensions.GetLength(), |
313 | 0 | tbsHeaderBuffer, tbsHeader); |
314 | 0 | if (rv != Success) { |
315 | 0 | return rv; |
316 | 0 | } |
317 | 0 | mOutput.Write(tbsHeader); |
318 | 0 | mOutput.Write(mTLVsBeforeExtensions); |
319 | 0 | } |
320 | 0 |
|
321 | 0 | return mOutput.GetInput(mPrecertTBS); |
322 | 0 | } |
323 | | |
324 | | Result MakeTLVHeader(uint8_t tag, size_t length, |
325 | | uint8_t (&buffer)[MAX_TLV_HEADER_LENGTH], |
326 | | /*out*/ Input& header) |
327 | 0 | { |
328 | 0 | Output output(buffer); |
329 | 0 | output.Write(tag); |
330 | 0 | if (length < 128) { |
331 | 0 | output.Write(AssertedCast<uint8_t>(length)); |
332 | 0 | } else if (length < 256) { |
333 | 0 | output.Write(0x81u); |
334 | 0 | output.Write(AssertedCast<uint8_t>(length)); |
335 | 0 | } else if (length < 65536) { |
336 | 0 | output.Write(0x82u); |
337 | 0 | output.Write(AssertedCast<uint8_t>(length / 256)); |
338 | 0 | output.Write(AssertedCast<uint8_t>(length % 256)); |
339 | 0 | } else { |
340 | 0 | return Result::FATAL_ERROR_INVALID_ARGS; |
341 | 0 | } |
342 | 0 | return output.GetInput(header); |
343 | 0 | } |
344 | | |
345 | | Input mDER; |
346 | | Input mTLVsBeforeExtensions; |
347 | | Vector<Input, 16> mExtensionTLVs; |
348 | | Output mOutput; |
349 | | Input mPrecertTBS; |
350 | | }; |
351 | | |
352 | | Result |
353 | | GetPrecertLogEntry(Input leafCertificate, Input issuerSubjectPublicKeyInfo, |
354 | | LogEntry& output) |
355 | 0 | { |
356 | 0 | MOZ_ASSERT(leafCertificate.GetLength() > 0); |
357 | 0 | MOZ_ASSERT(issuerSubjectPublicKeyInfo.GetLength() > 0); |
358 | 0 | output.Reset(); |
359 | 0 |
|
360 | 0 | Buffer precertTBSBuffer; |
361 | 0 | if (!precertTBSBuffer.resize(leafCertificate.GetLength())) { |
362 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
363 | 0 | } |
364 | 0 | |
365 | 0 | PrecertTBSExtractor extractor(leafCertificate, |
366 | 0 | precertTBSBuffer.begin(), |
367 | 0 | precertTBSBuffer.length()); |
368 | 0 | Result rv = extractor.Init(); |
369 | 0 | if (rv != Success) { |
370 | 0 | return rv; |
371 | 0 | } |
372 | 0 | Input precertTBS(extractor.GetPrecertTBS()); |
373 | 0 | MOZ_ASSERT(precertTBS.UnsafeGetData() == precertTBSBuffer.begin()); |
374 | 0 | precertTBSBuffer.shrinkTo(precertTBS.GetLength()); |
375 | 0 |
|
376 | 0 | output.type = LogEntry::Type::Precert; |
377 | 0 | output.tbsCertificate = std::move(precertTBSBuffer); |
378 | 0 |
|
379 | 0 | if (!output.issuerKeyHash.resizeUninitialized(SHA256_LENGTH)) { |
380 | 0 | return Result::FATAL_ERROR_NO_MEMORY; |
381 | 0 | } |
382 | 0 | return DigestBufNSS(issuerSubjectPublicKeyInfo, DigestAlgorithm::sha256, |
383 | 0 | output.issuerKeyHash.begin(), |
384 | 0 | output.issuerKeyHash.length()); |
385 | 0 | } |
386 | | |
387 | | Result |
388 | | GetX509LogEntry(Input leafCertificate, LogEntry& output) |
389 | 0 | { |
390 | 0 | MOZ_ASSERT(leafCertificate.GetLength() > 0); |
391 | 0 | output.Reset(); |
392 | 0 | output.type = LogEntry::Type::X509; |
393 | 0 | return InputToBuffer(leafCertificate, output.leafCertificate); |
394 | 0 | } |
395 | | |
396 | | } } // namespace mozilla::ct |