Bug 1720475 [wpt PR 29659] - Update wpt metadata, a=testonly
[gecko.git] / security / ct / BTVerifier.cpp
blob720d41bc76517db9fc666dc962753e6b28bd108f
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 "BTVerifier.h"
9 #include <stdint.h>
11 #include "CTUtils.h"
12 #include "SignedCertificateTimestamp.h"
13 #include "hasht.h"
14 #include "mozpkix/pkixnss.h"
15 #include "mozpkix/pkixutil.h"
17 namespace mozilla {
18 namespace ct {
20 using namespace mozilla::pkix;
22 typedef mozilla::pkix::Result Result;
24 // Common prefix lengths
25 static const size_t kLogIdPrefixLengthBytes = 1;
26 static const size_t kBTTreeSizeLength = 8;
27 static const size_t kNodeHashPrefixLengthBytes = 1;
29 // Members of a SignedTreeHeadDataV2 struct
30 static const size_t kSTHTimestampLength = 8;
31 static const size_t kSTHExtensionsLengthBytes = 2;
32 static const size_t kSTHSignatureLengthBytes = 2;
34 // Members of a Inclusion Proof struct
35 static const size_t kLeafIndexLength = 8;
36 static const size_t kInclusionPathLengthBytes = 2;
38 static Result GetDigestAlgorithmLengthAndIdentifier(
39 DigestAlgorithm digestAlgorithm,
40 /* out */ size_t& digestAlgorithmLength,
41 /* out */ SECOidTag& digestAlgorithmId) {
42 switch (digestAlgorithm) {
43 case DigestAlgorithm::sha512:
44 digestAlgorithmLength = SHA512_LENGTH;
45 digestAlgorithmId = SEC_OID_SHA512;
46 return Success;
47 case DigestAlgorithm::sha256:
48 digestAlgorithmLength = SHA256_LENGTH;
49 digestAlgorithmId = SEC_OID_SHA256;
50 return Success;
51 default:
52 return pkix::Result::FATAL_ERROR_INVALID_ARGS;
56 Result DecodeAndVerifySignedTreeHead(
57 Input signerSubjectPublicKeyInfo, DigestAlgorithm digestAlgorithm,
58 der::PublicKeyAlgorithm publicKeyAlgorithm, Input signedTreeHeadInput,
59 /* out */ SignedTreeHeadDataV2& signedTreeHead) {
60 SignedTreeHeadDataV2 result;
61 Reader reader(signedTreeHeadInput);
63 Input logId;
64 Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
65 if (rv != Success) {
66 return rv;
68 InputToBuffer(logId, result.logId);
70 // This is the beginning of the data covered by the signature.
71 Reader::Mark signedDataMark = reader.GetMark();
73 rv = ReadUint<kSTHTimestampLength>(reader, result.timestamp);
74 if (rv != Success) {
75 return rv;
78 rv = ReadUint<kBTTreeSizeLength>(reader, result.treeSize);
79 if (rv != Success) {
80 return rv;
83 Input hash;
84 rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(reader, hash);
85 if (rv != Success) {
86 return rv;
88 InputToBuffer(hash, result.rootHash);
90 // We ignore any extensions, but we have to read them.
91 Input extensionsInput;
92 rv = ReadVariableBytes<kSTHExtensionsLengthBytes>(reader, extensionsInput);
93 if (rv != Success) {
94 return rv;
97 Input signedDataInput;
98 rv = reader.GetInput(signedDataMark, signedDataInput);
99 if (rv != Success) {
100 return rv;
103 SECOidTag unusedDigestAlgorithmId;
104 size_t digestAlgorithmLength;
105 rv = GetDigestAlgorithmLengthAndIdentifier(
106 digestAlgorithm, digestAlgorithmLength, unusedDigestAlgorithmId);
107 if (rv != Success) {
108 return rv;
111 uint8_t digestBuf[MAX_DIGEST_SIZE_IN_BYTES];
112 rv = DigestBufNSS(signedDataInput, digestAlgorithm, digestBuf,
113 digestAlgorithmLength);
114 if (rv != Success) {
115 return rv;
118 Input digestInput;
119 rv = digestInput.Init(digestBuf, digestAlgorithmLength);
120 if (rv != Success) {
121 return rv;
124 Input signatureInput;
125 rv = ReadVariableBytes<kSTHSignatureLengthBytes>(reader, signatureInput);
126 if (rv != Success) {
127 return rv;
130 SignedDigest signedDigest = {digestInput, digestAlgorithm, signatureInput};
131 switch (publicKeyAlgorithm) {
132 case der::PublicKeyAlgorithm::ECDSA:
133 rv = VerifyECDSASignedDigestNSS(signedDigest, signerSubjectPublicKeyInfo,
134 nullptr);
135 break;
136 case der::PublicKeyAlgorithm::RSA_PKCS1:
137 case der::PublicKeyAlgorithm::Uninitialized:
138 default:
139 return Result::FATAL_ERROR_INVALID_ARGS;
141 if (rv != Success) {
142 // VerifyECDSASignedDigestNSS eventually calls VFY_VerifyDigestDirect, which
143 // can set the PR error code to SEC_ERROR_PKCS7_KEYALG_MISMATCH if the type
144 // of key decoded from the SPKI does not match the given signature
145 // algorithm. mozilla::pkix does not have a corresponding Result value and
146 // turns this error code into Result::ERROR_UNKNOWN_ERROR. Since this is
147 // uninformative, we'll turn that result into a bad signature error.
148 if (rv == Result::ERROR_UNKNOWN_ERROR) {
149 return Result::ERROR_BAD_SIGNATURE;
151 return rv;
154 if (!reader.AtEnd()) {
155 return pkix::Result::ERROR_BAD_DER;
158 signedTreeHead = std::move(result);
159 return Success;
162 Result DecodeInclusionProof(Input input, InclusionProofDataV2& output) {
163 InclusionProofDataV2 result;
164 Reader reader(input);
166 Input logId;
167 Result rv = ReadVariableBytes<kLogIdPrefixLengthBytes>(reader, logId);
168 if (rv != Success) {
169 return rv;
172 rv = ReadUint<kBTTreeSizeLength>(reader, result.treeSize);
173 if (rv != Success) {
174 return rv;
177 if (result.treeSize < 1) {
178 return pkix::Result::ERROR_BAD_DER;
181 rv = ReadUint<kLeafIndexLength>(reader, result.leafIndex);
182 if (rv != Success) {
183 return rv;
186 if (result.leafIndex >= result.treeSize) {
187 return pkix::Result::ERROR_BAD_DER;
190 Input pathInput;
191 rv = ReadVariableBytes<kInclusionPathLengthBytes>(reader, pathInput);
192 if (rv != Success) {
193 return rv;
196 if (pathInput.GetLength() < 1) {
197 return pkix::Result::ERROR_BAD_DER;
200 Reader pathReader(pathInput);
201 std::vector<Buffer> inclusionPath;
203 while (!pathReader.AtEnd()) {
204 Input hash;
205 rv = ReadVariableBytes<kNodeHashPrefixLengthBytes>(pathReader, hash);
206 if (rv != Success) {
207 return rv;
210 Buffer hashBuffer;
211 InputToBuffer(hash, hashBuffer);
213 inclusionPath.push_back(std::move(hashBuffer));
216 if (!reader.AtEnd()) {
217 return pkix::Result::ERROR_BAD_DER;
220 InputToBuffer(logId, result.logId);
222 result.inclusionPath = std::move(inclusionPath);
224 output = std::move(result);
225 return Success;
228 static Result CommonFinishDigest(UniquePK11Context& context,
229 size_t digestAlgorithmLength,
230 /* out */ Buffer& outputBuffer) {
231 uint32_t outLen = 0;
232 outputBuffer.assign(digestAlgorithmLength, 0);
233 if (PK11_DigestFinal(context.get(), outputBuffer.data(), &outLen,
234 digestAlgorithmLength) != SECSuccess) {
235 return Result::FATAL_ERROR_LIBRARY_FAILURE;
237 if (outLen != digestAlgorithmLength) {
238 return Result::FATAL_ERROR_LIBRARY_FAILURE;
240 return Success;
243 static Result LeafHash(Input leafEntry, size_t digestAlgorithmLength,
244 SECOidTag digestAlgorithmId,
245 /* out */ Buffer& calculatedHash) {
246 UniquePK11Context context(PK11_CreateDigestContext(digestAlgorithmId));
247 if (!context) {
248 return Result::FATAL_ERROR_LIBRARY_FAILURE;
250 const unsigned char zero = 0;
251 if (PK11_DigestOp(context.get(), &zero, 1u) != SECSuccess) {
252 return Result::FATAL_ERROR_LIBRARY_FAILURE;
254 SECItem leafEntryItem = UnsafeMapInputToSECItem(leafEntry);
255 if (PK11_DigestOp(context.get(), leafEntryItem.data, leafEntryItem.len) !=
256 SECSuccess) {
257 return Result::FATAL_ERROR_LIBRARY_FAILURE;
259 return CommonFinishDigest(context, digestAlgorithmLength, calculatedHash);
262 static Result NodeHash(const Buffer& left, const Buffer& right,
263 size_t digestAlgorithmLength,
264 SECOidTag digestAlgorithmId,
265 /* out */ Buffer& calculatedHash) {
266 UniquePK11Context context(PK11_CreateDigestContext(digestAlgorithmId));
267 if (!context) {
268 return Result::FATAL_ERROR_LIBRARY_FAILURE;
270 const unsigned char one = 1;
271 if (PK11_DigestOp(context.get(), &one, 1u) != SECSuccess) {
272 return Result::FATAL_ERROR_LIBRARY_FAILURE;
274 if (PK11_DigestOp(context.get(), left.data(), left.size()) != SECSuccess) {
275 return Result::FATAL_ERROR_LIBRARY_FAILURE;
277 if (PK11_DigestOp(context.get(), right.data(), right.size()) != SECSuccess) {
278 return Result::FATAL_ERROR_LIBRARY_FAILURE;
280 return CommonFinishDigest(context, digestAlgorithmLength, calculatedHash);
283 // This algorithm is specified by:
284 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-28#section-2.1.3.2
285 Result VerifyInclusionProof(const InclusionProofDataV2& proof, Input leafEntry,
286 Input expectedRootHash,
287 DigestAlgorithm digestAlgorithm) {
288 if (proof.treeSize == 0) {
289 return pkix::Result::ERROR_BAD_SIGNATURE;
291 size_t digestAlgorithmLength;
292 SECOidTag digestAlgorithmId;
293 Result rv = GetDigestAlgorithmLengthAndIdentifier(
294 digestAlgorithm, digestAlgorithmLength, digestAlgorithmId);
295 if (rv != Success) {
296 return rv;
298 if (proof.leafIndex >= proof.treeSize) {
299 return pkix::Result::ERROR_BAD_SIGNATURE;
301 if (expectedRootHash.GetLength() != digestAlgorithmLength) {
302 return pkix::Result::ERROR_BAD_SIGNATURE;
304 uint64_t leafIndex = proof.leafIndex;
305 uint64_t lastNodeIndex = proof.treeSize - 1;
306 Buffer calculatedHash;
307 rv = LeafHash(leafEntry, digestAlgorithmLength, digestAlgorithmId,
308 calculatedHash);
309 if (rv != Success) {
310 return rv;
312 for (const auto& hash : proof.inclusionPath) {
313 if (lastNodeIndex == 0) {
314 return pkix::Result::ERROR_BAD_SIGNATURE;
316 if (leafIndex % 2 == 1 || leafIndex == lastNodeIndex) {
317 rv = NodeHash(hash, calculatedHash, digestAlgorithmLength,
318 digestAlgorithmId, calculatedHash);
319 if (rv != Success) {
320 return rv;
322 if (leafIndex % 2 == 0) {
323 while (leafIndex % 2 == 0 && lastNodeIndex > 0) {
324 leafIndex >>= 1;
325 lastNodeIndex >>= 1;
328 } else {
329 rv = NodeHash(calculatedHash, hash, digestAlgorithmLength,
330 digestAlgorithmId, calculatedHash);
331 if (rv != Success) {
332 return rv;
335 leafIndex >>= 1;
336 lastNodeIndex >>= 1;
338 if (lastNodeIndex != 0) {
339 return pkix::Result::ERROR_BAD_SIGNATURE;
341 assert(calculatedHash.size() == digestAlgorithmLength);
342 if (calculatedHash.size() != digestAlgorithmLength) {
343 return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
345 if (memcmp(calculatedHash.data(), expectedRootHash.UnsafeGetData(),
346 digestAlgorithmLength) != 0) {
347 return pkix::Result::ERROR_BAD_SIGNATURE;
349 return Success;
352 } // namespace ct
353 } // namespace mozilla