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"
12 #include "SignedCertificateTimestamp.h"
14 #include "mozpkix/pkixnss.h"
15 #include "mozpkix/pkixutil.h"
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
;
47 case DigestAlgorithm::sha256
:
48 digestAlgorithmLength
= SHA256_LENGTH
;
49 digestAlgorithmId
= SEC_OID_SHA256
;
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
);
64 Result rv
= ReadVariableBytes
<kLogIdPrefixLengthBytes
>(reader
, logId
);
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
);
78 rv
= ReadUint
<kBTTreeSizeLength
>(reader
, result
.treeSize
);
84 rv
= ReadVariableBytes
<kNodeHashPrefixLengthBytes
>(reader
, hash
);
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
);
97 Input signedDataInput
;
98 rv
= reader
.GetInput(signedDataMark
, signedDataInput
);
103 SECOidTag unusedDigestAlgorithmId
;
104 size_t digestAlgorithmLength
;
105 rv
= GetDigestAlgorithmLengthAndIdentifier(
106 digestAlgorithm
, digestAlgorithmLength
, unusedDigestAlgorithmId
);
111 uint8_t digestBuf
[MAX_DIGEST_SIZE_IN_BYTES
];
112 rv
= DigestBufNSS(signedDataInput
, digestAlgorithm
, digestBuf
,
113 digestAlgorithmLength
);
119 rv
= digestInput
.Init(digestBuf
, digestAlgorithmLength
);
124 Input signatureInput
;
125 rv
= ReadVariableBytes
<kSTHSignatureLengthBytes
>(reader
, signatureInput
);
130 SignedDigest signedDigest
= {digestInput
, digestAlgorithm
, signatureInput
};
131 switch (publicKeyAlgorithm
) {
132 case der::PublicKeyAlgorithm::ECDSA
:
133 rv
= VerifyECDSASignedDigestNSS(signedDigest
, signerSubjectPublicKeyInfo
,
136 case der::PublicKeyAlgorithm::RSA_PKCS1
:
137 case der::PublicKeyAlgorithm::Uninitialized
:
139 return Result::FATAL_ERROR_INVALID_ARGS
;
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
;
154 if (!reader
.AtEnd()) {
155 return pkix::Result::ERROR_BAD_DER
;
158 signedTreeHead
= std::move(result
);
162 Result
DecodeInclusionProof(Input input
, InclusionProofDataV2
& output
) {
163 InclusionProofDataV2 result
;
164 Reader
reader(input
);
167 Result rv
= ReadVariableBytes
<kLogIdPrefixLengthBytes
>(reader
, logId
);
172 rv
= ReadUint
<kBTTreeSizeLength
>(reader
, result
.treeSize
);
177 if (result
.treeSize
< 1) {
178 return pkix::Result::ERROR_BAD_DER
;
181 rv
= ReadUint
<kLeafIndexLength
>(reader
, result
.leafIndex
);
186 if (result
.leafIndex
>= result
.treeSize
) {
187 return pkix::Result::ERROR_BAD_DER
;
191 rv
= ReadVariableBytes
<kInclusionPathLengthBytes
>(reader
, pathInput
);
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()) {
205 rv
= ReadVariableBytes
<kNodeHashPrefixLengthBytes
>(pathReader
, hash
);
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
);
228 static Result
CommonFinishDigest(UniquePK11Context
& context
,
229 size_t digestAlgorithmLength
,
230 /* out */ Buffer
& outputBuffer
) {
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
;
243 static Result
LeafHash(Input leafEntry
, size_t digestAlgorithmLength
,
244 SECOidTag digestAlgorithmId
,
245 /* out */ Buffer
& calculatedHash
) {
246 UniquePK11Context
context(PK11_CreateDigestContext(digestAlgorithmId
));
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
) !=
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
));
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
);
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
,
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
);
322 if (leafIndex
% 2 == 0) {
323 while (leafIndex
% 2 == 0 && lastNodeIndex
> 0) {
329 rv
= NodeHash(calculatedHash
, hash
, digestAlgorithmLength
,
330 digestAlgorithmId
, calculatedHash
);
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
;
353 } // namespace mozilla