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 Input signatureInput
;
104 rv
= ReadVariableBytes
<kSTHSignatureLengthBytes
>(reader
, signatureInput
);
109 switch (publicKeyAlgorithm
) {
110 case der::PublicKeyAlgorithm::ECDSA
:
111 rv
= VerifyECDSASignedDataNSS(signedDataInput
, digestAlgorithm
,
112 signatureInput
, signerSubjectPublicKeyInfo
,
115 case der::PublicKeyAlgorithm::RSA_PKCS1
:
117 return Result::FATAL_ERROR_INVALID_ARGS
;
123 if (!reader
.AtEnd()) {
124 return pkix::Result::ERROR_BAD_DER
;
127 signedTreeHead
= std::move(result
);
131 Result
DecodeInclusionProof(Input input
, InclusionProofDataV2
& output
) {
132 InclusionProofDataV2 result
;
133 Reader
reader(input
);
136 Result rv
= ReadVariableBytes
<kLogIdPrefixLengthBytes
>(reader
, logId
);
141 rv
= ReadUint
<kBTTreeSizeLength
>(reader
, result
.treeSize
);
146 if (result
.treeSize
< 1) {
147 return pkix::Result::ERROR_BAD_DER
;
150 rv
= ReadUint
<kLeafIndexLength
>(reader
, result
.leafIndex
);
155 if (result
.leafIndex
>= result
.treeSize
) {
156 return pkix::Result::ERROR_BAD_DER
;
160 rv
= ReadVariableBytes
<kInclusionPathLengthBytes
>(reader
, pathInput
);
165 if (pathInput
.GetLength() < 1) {
166 return pkix::Result::ERROR_BAD_DER
;
169 Reader
pathReader(pathInput
);
170 std::vector
<Buffer
> inclusionPath
;
172 while (!pathReader
.AtEnd()) {
174 rv
= ReadVariableBytes
<kNodeHashPrefixLengthBytes
>(pathReader
, hash
);
180 InputToBuffer(hash
, hashBuffer
);
182 inclusionPath
.push_back(std::move(hashBuffer
));
185 if (!reader
.AtEnd()) {
186 return pkix::Result::ERROR_BAD_DER
;
189 InputToBuffer(logId
, result
.logId
);
191 result
.inclusionPath
= std::move(inclusionPath
);
193 output
= std::move(result
);
197 static Result
CommonFinishDigest(UniquePK11Context
& context
,
198 size_t digestAlgorithmLength
,
199 /* out */ Buffer
& outputBuffer
) {
201 outputBuffer
.assign(digestAlgorithmLength
, 0);
202 if (PK11_DigestFinal(context
.get(), outputBuffer
.data(), &outLen
,
203 digestAlgorithmLength
) != SECSuccess
) {
204 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
206 if (outLen
!= digestAlgorithmLength
) {
207 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
212 static Result
LeafHash(Input leafEntry
, size_t digestAlgorithmLength
,
213 SECOidTag digestAlgorithmId
,
214 /* out */ Buffer
& calculatedHash
) {
215 UniquePK11Context
context(PK11_CreateDigestContext(digestAlgorithmId
));
217 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
219 const unsigned char zero
= 0;
220 if (PK11_DigestOp(context
.get(), &zero
, 1u) != SECSuccess
) {
221 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
223 SECItem leafEntryItem
= UnsafeMapInputToSECItem(leafEntry
);
224 if (PK11_DigestOp(context
.get(), leafEntryItem
.data
, leafEntryItem
.len
) !=
226 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
228 return CommonFinishDigest(context
, digestAlgorithmLength
, calculatedHash
);
231 static Result
NodeHash(const Buffer
& left
, const Buffer
& right
,
232 size_t digestAlgorithmLength
,
233 SECOidTag digestAlgorithmId
,
234 /* out */ Buffer
& calculatedHash
) {
235 UniquePK11Context
context(PK11_CreateDigestContext(digestAlgorithmId
));
237 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
239 const unsigned char one
= 1;
240 if (PK11_DigestOp(context
.get(), &one
, 1u) != SECSuccess
) {
241 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
243 if (PK11_DigestOp(context
.get(), left
.data(), left
.size()) != SECSuccess
) {
244 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
246 if (PK11_DigestOp(context
.get(), right
.data(), right
.size()) != SECSuccess
) {
247 return Result::FATAL_ERROR_LIBRARY_FAILURE
;
249 return CommonFinishDigest(context
, digestAlgorithmLength
, calculatedHash
);
252 // This algorithm is specified by:
253 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-28#section-2.1.3.2
254 Result
VerifyInclusionProof(const InclusionProofDataV2
& proof
, Input leafEntry
,
255 Input expectedRootHash
,
256 DigestAlgorithm digestAlgorithm
) {
257 if (proof
.treeSize
== 0) {
258 return pkix::Result::ERROR_BAD_SIGNATURE
;
260 size_t digestAlgorithmLength
;
261 SECOidTag digestAlgorithmId
;
262 Result rv
= GetDigestAlgorithmLengthAndIdentifier(
263 digestAlgorithm
, digestAlgorithmLength
, digestAlgorithmId
);
267 if (proof
.leafIndex
>= proof
.treeSize
) {
268 return pkix::Result::ERROR_BAD_SIGNATURE
;
270 if (expectedRootHash
.GetLength() != digestAlgorithmLength
) {
271 return pkix::Result::ERROR_BAD_SIGNATURE
;
273 uint64_t leafIndex
= proof
.leafIndex
;
274 uint64_t lastNodeIndex
= proof
.treeSize
- 1;
275 Buffer calculatedHash
;
276 rv
= LeafHash(leafEntry
, digestAlgorithmLength
, digestAlgorithmId
,
281 for (const auto& hash
: proof
.inclusionPath
) {
282 if (lastNodeIndex
== 0) {
283 return pkix::Result::ERROR_BAD_SIGNATURE
;
285 if (leafIndex
% 2 == 1 || leafIndex
== lastNodeIndex
) {
286 rv
= NodeHash(hash
, calculatedHash
, digestAlgorithmLength
,
287 digestAlgorithmId
, calculatedHash
);
291 if (leafIndex
% 2 == 0) {
292 while (leafIndex
% 2 == 0 && lastNodeIndex
> 0) {
298 rv
= NodeHash(calculatedHash
, hash
, digestAlgorithmLength
,
299 digestAlgorithmId
, calculatedHash
);
307 if (lastNodeIndex
!= 0) {
308 return pkix::Result::ERROR_BAD_SIGNATURE
;
310 assert(calculatedHash
.size() == digestAlgorithmLength
);
311 if (calculatedHash
.size() != digestAlgorithmLength
) {
312 return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE
;
314 if (memcmp(calculatedHash
.data(), expectedRootHash
.UnsafeGetData(),
315 digestAlgorithmLength
) != 0) {
316 return pkix::Result::ERROR_BAD_SIGNATURE
;
322 } // namespace mozilla