1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/cert/ct_log_verifier.h"
6
7 #include <string.h>
8
9 #include <bit>
10 #include <string_view>
11 #include <vector>
12
13 #include "base/logging.h"
14 #include "base/notreached.h"
15 #include "crypto/openssl_util.h"
16 #include "crypto/sha2.h"
17 #include "net/cert/ct_log_verifier_util.h"
18 #include "net/cert/ct_serialization.h"
19 #include "net/cert/merkle_audit_proof.h"
20 #include "net/cert/merkle_consistency_proof.h"
21 #include "net/cert/signed_tree_head.h"
22 #include "third_party/boringssl/src/include/openssl/bytestring.h"
23 #include "third_party/boringssl/src/include/openssl/evp.h"
24
25 namespace net {
26
27 namespace {
28
29 // The SHA-256 hash of the empty string.
30 const unsigned char kSHA256EmptyStringHash[ct::kSthRootHashLength] = {
31 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4,
32 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b,
33 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
34
GetEvpAlg(ct::DigitallySigned::HashAlgorithm alg)35 const EVP_MD* GetEvpAlg(ct::DigitallySigned::HashAlgorithm alg) {
36 switch (alg) {
37 case ct::DigitallySigned::HASH_ALGO_MD5:
38 return EVP_md5();
39 case ct::DigitallySigned::HASH_ALGO_SHA1:
40 return EVP_sha1();
41 case ct::DigitallySigned::HASH_ALGO_SHA224:
42 return EVP_sha224();
43 case ct::DigitallySigned::HASH_ALGO_SHA256:
44 return EVP_sha256();
45 case ct::DigitallySigned::HASH_ALGO_SHA384:
46 return EVP_sha384();
47 case ct::DigitallySigned::HASH_ALGO_SHA512:
48 return EVP_sha512();
49 case ct::DigitallySigned::HASH_ALGO_NONE:
50 default:
51 NOTREACHED();
52 return nullptr;
53 }
54 }
55
56 } // namespace
57
58 // static
Create(std::string_view public_key,std::string description)59 scoped_refptr<const CTLogVerifier> CTLogVerifier::Create(
60 std::string_view public_key,
61 std::string description) {
62 auto result = base::WrapRefCounted(new CTLogVerifier(std::move(description)));
63 if (!result->Init(public_key))
64 return nullptr;
65 return result;
66 }
67
CTLogVerifier(std::string description)68 CTLogVerifier::CTLogVerifier(std::string description)
69 : description_(std::move(description)) {}
70
Verify(const ct::SignedEntryData & entry,const ct::SignedCertificateTimestamp & sct) const71 bool CTLogVerifier::Verify(const ct::SignedEntryData& entry,
72 const ct::SignedCertificateTimestamp& sct) const {
73 std::string serialized_log_entry;
74 std::string serialized_data;
75
76 return sct.log_id == key_id_ && SignatureParametersMatch(sct.signature) &&
77 ct::EncodeSignedEntry(entry, &serialized_log_entry) &&
78 ct::EncodeV1SCTSignedData(sct.timestamp, serialized_log_entry,
79 sct.extensions, &serialized_data) &&
80 VerifySignature(serialized_data, sct.signature.signature_data);
81 }
82
VerifySignedTreeHead(const ct::SignedTreeHead & signed_tree_head) const83 bool CTLogVerifier::VerifySignedTreeHead(
84 const ct::SignedTreeHead& signed_tree_head) const {
85 std::string serialized_data;
86 if (!SignatureParametersMatch(signed_tree_head.signature) ||
87 !ct::EncodeTreeHeadSignature(signed_tree_head, &serialized_data) ||
88 !VerifySignature(serialized_data,
89 signed_tree_head.signature.signature_data)) {
90 return false;
91 }
92
93 if (signed_tree_head.tree_size == 0) {
94 // Root hash must equate SHA256 hash of the empty string.
95 return memcmp(signed_tree_head.sha256_root_hash, kSHA256EmptyStringHash,
96 ct::kSthRootHashLength) == 0;
97 }
98
99 return true;
100 }
101
SignatureParametersMatch(const ct::DigitallySigned & signature) const102 bool CTLogVerifier::SignatureParametersMatch(
103 const ct::DigitallySigned& signature) const {
104 return signature.SignatureParametersMatch(hash_algorithm_,
105 signature_algorithm_);
106 }
107
VerifyConsistencyProof(const ct::MerkleConsistencyProof & proof,const std::string & old_tree_hash,const std::string & new_tree_hash) const108 bool CTLogVerifier::VerifyConsistencyProof(
109 const ct::MerkleConsistencyProof& proof,
110 const std::string& old_tree_hash,
111 const std::string& new_tree_hash) const {
112 // Proof does not originate from this log.
113 if (key_id_ != proof.log_id)
114 return false;
115
116 // Cannot prove consistency from a tree of a certain size to a tree smaller
117 // than that - only the other way around.
118 if (proof.first_tree_size > proof.second_tree_size)
119 return false;
120
121 // If the proof is between trees of the same size, then the 'proof'
122 // is really just a statement that the tree hasn't changed. If this
123 // is the case, there should be no proof nodes, and both the old
124 // and new hash should be equivalent.
125 if (proof.first_tree_size == proof.second_tree_size)
126 return proof.nodes.empty() && old_tree_hash == new_tree_hash;
127
128 // It is possible to call this method to prove consistency between the
129 // initial state of a log (i.e. an empty tree) and a later root. In that
130 // case, the only valid proof is an empty proof.
131 if (proof.first_tree_size == 0)
132 return proof.nodes.empty();
133
134 // Implement the algorithm described in
135 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-12#section-9.4.2
136 //
137 // It maintains a pair of hashes |fr| and |sr|, initialized to the same
138 // value. Each node in |proof| will be hashed to the left of both |fr| and
139 // |sr| or to the right of only |sr|. The proof is then valid if |fr| is
140 // |old_tree_hash| and |sr| is |new_tree_hash|, proving that tree nodes which
141 // make up |old_tree_hash| are a prefix of |new_tree_hash|.
142
143 // At this point, the algorithm's preconditions must be satisfied.
144 DCHECK_LT(0u, proof.first_tree_size);
145 DCHECK_LT(proof.first_tree_size, proof.second_tree_size);
146
147 // 1. If "first" is an exact power of 2, then prepend "first_hash" to the
148 // "consistency_path" array.
149 std::string_view first_proof_node = old_tree_hash;
150 auto iter = proof.nodes.begin();
151 if (!std::has_single_bit(proof.first_tree_size)) {
152 if (iter == proof.nodes.end())
153 return false;
154 first_proof_node = *iter;
155 ++iter;
156 }
157 // iter now points to the second node in the modified proof.nodes.
158
159 // 2. Set "fn" to "first - 1" and "sn" to "second - 1".
160 uint64_t fn = proof.first_tree_size - 1;
161 uint64_t sn = proof.second_tree_size - 1;
162
163 // 3. If "LSB(fn)" is set, then right-shift both "fn" and "sn" equally until
164 // "LSB(fn)" is not set.
165 while (fn & 1) {
166 fn >>= 1;
167 sn >>= 1;
168 }
169
170 // 4. Set both "fr" and "sr" to the first value in the "consistency_path"
171 // array.
172 std::string fr(first_proof_node);
173 std::string sr(first_proof_node);
174
175 // 5. For each subsequent value "c" in the "consistency_path" array:
176 for (; iter != proof.nodes.end(); ++iter) {
177 // If "sn" is 0, stop the iteration and fail the proof verification.
178 if (sn == 0)
179 return false;
180 // If "LSB(fn)" is set, or if "fn" is equal to "sn", then:
181 if ((fn & 1) || fn == sn) {
182 // 1. Set "fr" to "HASH(0x01 || c || fr)"
183 // Set "sr" to "HASH(0x01 || c || sr)"
184 fr = ct::internal::HashNodes(*iter, fr);
185 sr = ct::internal::HashNodes(*iter, sr);
186
187 // 2. If "LSB(fn)" is not set, then right-shift both "fn" and "sn" equally
188 // until either "LSB(fn)" is set or "fn" is "0".
189 while (!(fn & 1) && fn != 0) {
190 fn >>= 1;
191 sn >>= 1;
192 }
193 } else { // Otherwise:
194 // Set "sr" to "HASH(0x01 || sr || c)"
195 sr = ct::internal::HashNodes(sr, *iter);
196 }
197
198 // Finally, right-shift both "fn" and "sn" one time.
199 fn >>= 1;
200 sn >>= 1;
201 }
202
203 // 6. After completing iterating through the "consistency_path" array as
204 // described above, verify that the "fr" calculated is equal to the
205 // "first_hash" supplied, that the "sr" calculated is equal to the
206 // "second_hash" supplied and that "sn" is 0.
207 return fr == old_tree_hash && sr == new_tree_hash && sn == 0;
208 }
209
VerifyAuditProof(const ct::MerkleAuditProof & proof,const std::string & root_hash,const std::string & leaf_hash) const210 bool CTLogVerifier::VerifyAuditProof(const ct::MerkleAuditProof& proof,
211 const std::string& root_hash,
212 const std::string& leaf_hash) const {
213 // Implements the algorithm described in
214 // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-19#section-10.4.1
215 //
216 // It maintains a hash |r|, initialized to |leaf_hash|, and hashes nodes from
217 // |proof| into it. The proof is then valid if |r| is |root_hash|, proving
218 // that |root_hash| includes |leaf_hash|.
219
220 // 1. Compare "leaf_index" against "tree_size". If "leaf_index" is
221 // greater than or equal to "tree_size" fail the proof verification.
222 if (proof.leaf_index >= proof.tree_size)
223 return false;
224
225 // 2. Set "fn" to "leaf_index" and "sn" to "tree_size - 1".
226 uint64_t fn = proof.leaf_index;
227 uint64_t sn = proof.tree_size - 1;
228 // 3. Set "r" to "hash".
229 std::string r = leaf_hash;
230
231 // 4. For each value "p" in the "inclusion_path" array:
232 for (const std::string& p : proof.nodes) {
233 // If "sn" is 0, stop the iteration and fail the proof verification.
234 if (sn == 0)
235 return false;
236
237 // If "LSB(fn)" is set, or if "fn" is equal to "sn", then:
238 if ((fn & 1) || fn == sn) {
239 // 1. Set "r" to "HASH(0x01 || p || r)"
240 r = ct::internal::HashNodes(p, r);
241
242 // 2. If "LSB(fn)" is not set, then right-shift both "fn" and "sn"
243 // equally until either "LSB(fn)" is set or "fn" is "0".
244 while (!(fn & 1) && fn != 0) {
245 fn >>= 1;
246 sn >>= 1;
247 }
248 } else { // Otherwise:
249 // Set "r" to "HASH(0x01 || r || p)"
250 r = ct::internal::HashNodes(r, p);
251 }
252
253 // Finally, right-shift both "fn" and "sn" one time.
254 fn >>= 1;
255 sn >>= 1;
256 }
257
258 // 5. Compare "sn" to 0. Compare "r" against the "root_hash". If "sn"
259 // is equal to 0, and "r" and the "root_hash" are equal, then the
260 // log has proven the inclusion of "hash". Otherwise, fail the
261 // proof verification.
262 return sn == 0 && r == root_hash;
263 }
264
265 CTLogVerifier::~CTLogVerifier() = default;
266
Init(std::string_view public_key)267 bool CTLogVerifier::Init(std::string_view public_key) {
268 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
269
270 CBS cbs;
271 CBS_init(&cbs, reinterpret_cast<const uint8_t*>(public_key.data()),
272 public_key.size());
273 public_key_.reset(EVP_parse_public_key(&cbs));
274 if (!public_key_ || CBS_len(&cbs) != 0)
275 return false;
276
277 key_id_ = crypto::SHA256HashString(public_key);
278
279 // Right now, only RSASSA-PKCS1v15 with SHA-256 and ECDSA with SHA-256 are
280 // supported.
281 switch (EVP_PKEY_id(public_key_.get())) {
282 case EVP_PKEY_RSA:
283 hash_algorithm_ = ct::DigitallySigned::HASH_ALGO_SHA256;
284 signature_algorithm_ = ct::DigitallySigned::SIG_ALGO_RSA;
285 break;
286 case EVP_PKEY_EC:
287 hash_algorithm_ = ct::DigitallySigned::HASH_ALGO_SHA256;
288 signature_algorithm_ = ct::DigitallySigned::SIG_ALGO_ECDSA;
289 break;
290 default:
291 return false;
292 }
293
294 // Extra safety check: Require RSA keys of at least 2048 bits.
295 // EVP_PKEY_size returns the size in bytes. 256 = 2048-bit RSA key.
296 if (signature_algorithm_ == ct::DigitallySigned::SIG_ALGO_RSA &&
297 EVP_PKEY_size(public_key_.get()) < 256) {
298 return false;
299 }
300
301 return true;
302 }
303
VerifySignature(std::string_view data_to_sign,std::string_view signature) const304 bool CTLogVerifier::VerifySignature(std::string_view data_to_sign,
305 std::string_view signature) const {
306 crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
307
308 const EVP_MD* hash_alg = GetEvpAlg(hash_algorithm_);
309 bssl::ScopedEVP_MD_CTX ctx;
310 return hash_alg &&
311 EVP_DigestVerifyInit(ctx.get(), nullptr, hash_alg, nullptr,
312 public_key_.get()) &&
313 EVP_DigestVerifyUpdate(ctx.get(), data_to_sign.data(),
314 data_to_sign.size()) &&
315 EVP_DigestVerifyFinal(
316 ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()),
317 signature.size());
318 }
319
320 } // namespace net
321