• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   }
53 }
54 
55 }  // namespace
56 
57 // static
Create(std::string_view public_key,std::string description)58 scoped_refptr<const CTLogVerifier> CTLogVerifier::Create(
59     std::string_view public_key,
60     std::string description) {
61   auto result = base::WrapRefCounted(new CTLogVerifier(std::move(description)));
62   if (!result->Init(public_key))
63     return nullptr;
64   return result;
65 }
66 
CTLogVerifier(std::string description)67 CTLogVerifier::CTLogVerifier(std::string description)
68     : description_(std::move(description)) {}
69 
Verify(const ct::SignedEntryData & entry,const ct::SignedCertificateTimestamp & sct) const70 bool CTLogVerifier::Verify(const ct::SignedEntryData& entry,
71                            const ct::SignedCertificateTimestamp& sct) const {
72   std::string serialized_log_entry;
73   std::string serialized_data;
74 
75   return sct.log_id == key_id_ && SignatureParametersMatch(sct.signature) &&
76          ct::EncodeSignedEntry(entry, &serialized_log_entry) &&
77          ct::EncodeV1SCTSignedData(sct.timestamp, serialized_log_entry,
78                                    sct.extensions, &serialized_data) &&
79          VerifySignature(serialized_data, sct.signature.signature_data);
80 }
81 
VerifySignedTreeHead(const ct::SignedTreeHead & signed_tree_head) const82 bool CTLogVerifier::VerifySignedTreeHead(
83     const ct::SignedTreeHead& signed_tree_head) const {
84   std::string serialized_data;
85   if (!SignatureParametersMatch(signed_tree_head.signature) ||
86       !ct::EncodeTreeHeadSignature(signed_tree_head, &serialized_data) ||
87       !VerifySignature(serialized_data,
88                        signed_tree_head.signature.signature_data)) {
89     return false;
90   }
91 
92   if (signed_tree_head.tree_size == 0) {
93     // Root hash must equate SHA256 hash of the empty string.
94     return memcmp(signed_tree_head.sha256_root_hash, kSHA256EmptyStringHash,
95                   ct::kSthRootHashLength) == 0;
96   }
97 
98   return true;
99 }
100 
SignatureParametersMatch(const ct::DigitallySigned & signature) const101 bool CTLogVerifier::SignatureParametersMatch(
102     const ct::DigitallySigned& signature) const {
103   return signature.SignatureParametersMatch(hash_algorithm_,
104                                             signature_algorithm_);
105 }
106 
VerifyConsistencyProof(const ct::MerkleConsistencyProof & proof,const std::string & old_tree_hash,const std::string & new_tree_hash) const107 bool CTLogVerifier::VerifyConsistencyProof(
108     const ct::MerkleConsistencyProof& proof,
109     const std::string& old_tree_hash,
110     const std::string& new_tree_hash) const {
111   // Proof does not originate from this log.
112   if (key_id_ != proof.log_id)
113     return false;
114 
115   // Cannot prove consistency from a tree of a certain size to a tree smaller
116   // than that - only the other way around.
117   if (proof.first_tree_size > proof.second_tree_size)
118     return false;
119 
120   // If the proof is between trees of the same size, then the 'proof'
121   // is really just a statement that the tree hasn't changed. If this
122   // is the case, there should be no proof nodes, and both the old
123   // and new hash should be equivalent.
124   if (proof.first_tree_size == proof.second_tree_size)
125     return proof.nodes.empty() && old_tree_hash == new_tree_hash;
126 
127   // It is possible to call this method to prove consistency between the
128   // initial state of a log (i.e. an empty tree) and a later root. In that
129   // case, the only valid proof is an empty proof.
130   if (proof.first_tree_size == 0)
131     return proof.nodes.empty();
132 
133   // Implement the algorithm described in
134   // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-12#section-9.4.2
135   //
136   // It maintains a pair of hashes |fr| and |sr|, initialized to the same
137   // value. Each node in |proof| will be hashed to the left of both |fr| and
138   // |sr| or to the right of only |sr|. The proof is then valid if |fr| is
139   // |old_tree_hash| and |sr| is |new_tree_hash|, proving that tree nodes which
140   // make up |old_tree_hash| are a prefix of |new_tree_hash|.
141 
142   // At this point, the algorithm's preconditions must be satisfied.
143   DCHECK_LT(0u, proof.first_tree_size);
144   DCHECK_LT(proof.first_tree_size, proof.second_tree_size);
145 
146   // 1. If "first" is an exact power of 2, then prepend "first_hash" to the
147   // "consistency_path" array.
148   std::string_view first_proof_node = old_tree_hash;
149   auto iter = proof.nodes.begin();
150   if (!std::has_single_bit(proof.first_tree_size)) {
151     if (iter == proof.nodes.end())
152       return false;
153     first_proof_node = *iter;
154     ++iter;
155   }
156   // iter now points to the second node in the modified proof.nodes.
157 
158   // 2. Set "fn" to "first - 1" and "sn" to "second - 1".
159   uint64_t fn = proof.first_tree_size - 1;
160   uint64_t sn = proof.second_tree_size - 1;
161 
162   // 3. If "LSB(fn)" is set, then right-shift both "fn" and "sn" equally until
163   // "LSB(fn)" is not set.
164   while (fn & 1) {
165     fn >>= 1;
166     sn >>= 1;
167   }
168 
169   // 4. Set both "fr" and "sr" to the first value in the "consistency_path"
170   // array.
171   std::string fr(first_proof_node);
172   std::string sr(first_proof_node);
173 
174   // 5. For each subsequent value "c" in the "consistency_path" array:
175   for (; iter != proof.nodes.end(); ++iter) {
176     // If "sn" is 0, stop the iteration and fail the proof verification.
177     if (sn == 0)
178       return false;
179     // If "LSB(fn)" is set, or if "fn" is equal to "sn", then:
180     if ((fn & 1) || fn == sn) {
181       // 1. Set "fr" to "HASH(0x01 || c || fr)"
182       //    Set "sr" to "HASH(0x01 || c || sr)"
183       fr = ct::internal::HashNodes(*iter, fr);
184       sr = ct::internal::HashNodes(*iter, sr);
185 
186       // 2. If "LSB(fn)" is not set, then right-shift both "fn" and "sn" equally
187       // until either "LSB(fn)" is set or "fn" is "0".
188       while (!(fn & 1) && fn != 0) {
189         fn >>= 1;
190         sn >>= 1;
191       }
192     } else {  // Otherwise:
193       // Set "sr" to "HASH(0x01 || sr || c)"
194       sr = ct::internal::HashNodes(sr, *iter);
195     }
196 
197     // Finally, right-shift both "fn" and "sn" one time.
198     fn >>= 1;
199     sn >>= 1;
200   }
201 
202   // 6. After completing iterating through the "consistency_path" array as
203   // described above, verify that the "fr" calculated is equal to the
204   // "first_hash" supplied, that the "sr" calculated is equal to the
205   // "second_hash" supplied and that "sn" is 0.
206   return fr == old_tree_hash && sr == new_tree_hash && sn == 0;
207 }
208 
VerifyAuditProof(const ct::MerkleAuditProof & proof,const std::string & root_hash,const std::string & leaf_hash) const209 bool CTLogVerifier::VerifyAuditProof(const ct::MerkleAuditProof& proof,
210                                      const std::string& root_hash,
211                                      const std::string& leaf_hash) const {
212   // Implements the algorithm described in
213   // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-19#section-10.4.1
214   //
215   // It maintains a hash |r|, initialized to |leaf_hash|, and hashes nodes from
216   // |proof| into it. The proof is then valid if |r| is |root_hash|, proving
217   // that |root_hash| includes |leaf_hash|.
218 
219   // 1.  Compare "leaf_index" against "tree_size".  If "leaf_index" is
220   //     greater than or equal to "tree_size" fail the proof verification.
221   if (proof.leaf_index >= proof.tree_size)
222     return false;
223 
224   // 2.  Set "fn" to "leaf_index" and "sn" to "tree_size - 1".
225   uint64_t fn = proof.leaf_index;
226   uint64_t sn = proof.tree_size - 1;
227   // 3.  Set "r" to "hash".
228   std::string r = leaf_hash;
229 
230   // 4.  For each value "p" in the "inclusion_path" array:
231   for (const std::string& p : proof.nodes) {
232     // If "sn" is 0, stop the iteration and fail the proof verification.
233     if (sn == 0)
234       return false;
235 
236     // If "LSB(fn)" is set, or if "fn" is equal to "sn", then:
237     if ((fn & 1) || fn == sn) {
238       // 1.  Set "r" to "HASH(0x01 || p || r)"
239       r = ct::internal::HashNodes(p, r);
240 
241       // 2.  If "LSB(fn)" is not set, then right-shift both "fn" and "sn"
242       //     equally until either "LSB(fn)" is set or "fn" is "0".
243       while (!(fn & 1) && fn != 0) {
244         fn >>= 1;
245         sn >>= 1;
246       }
247     } else {  // Otherwise:
248       // Set "r" to "HASH(0x01 || r || p)"
249       r = ct::internal::HashNodes(r, p);
250     }
251 
252     // Finally, right-shift both "fn" and "sn" one time.
253     fn >>= 1;
254     sn >>= 1;
255   }
256 
257   // 5.  Compare "sn" to 0.  Compare "r" against the "root_hash".  If "sn"
258   //     is equal to 0, and "r" and the "root_hash" are equal, then the
259   //     log has proven the inclusion of "hash".  Otherwise, fail the
260   //     proof verification.
261   return sn == 0 && r == root_hash;
262 }
263 
264 CTLogVerifier::~CTLogVerifier() = default;
265 
Init(std::string_view public_key)266 bool CTLogVerifier::Init(std::string_view public_key) {
267   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
268 
269   CBS cbs;
270   CBS_init(&cbs, reinterpret_cast<const uint8_t*>(public_key.data()),
271            public_key.size());
272   public_key_.reset(EVP_parse_public_key(&cbs));
273   if (!public_key_ || CBS_len(&cbs) != 0)
274     return false;
275 
276   key_id_ = crypto::SHA256HashString(public_key);
277 
278   // Right now, only RSASSA-PKCS1v15 with SHA-256 and ECDSA with SHA-256 are
279   // supported.
280   switch (EVP_PKEY_id(public_key_.get())) {
281     case EVP_PKEY_RSA:
282       hash_algorithm_ = ct::DigitallySigned::HASH_ALGO_SHA256;
283       signature_algorithm_ = ct::DigitallySigned::SIG_ALGO_RSA;
284       break;
285     case EVP_PKEY_EC:
286       hash_algorithm_ = ct::DigitallySigned::HASH_ALGO_SHA256;
287       signature_algorithm_ = ct::DigitallySigned::SIG_ALGO_ECDSA;
288       break;
289     default:
290       return false;
291   }
292 
293   // Extra safety check: Require RSA keys of at least 2048 bits.
294   // EVP_PKEY_size returns the size in bytes. 256 = 2048-bit RSA key.
295   if (signature_algorithm_ == ct::DigitallySigned::SIG_ALGO_RSA &&
296       EVP_PKEY_size(public_key_.get()) < 256) {
297     return false;
298   }
299 
300   return true;
301 }
302 
VerifySignature(std::string_view data_to_sign,std::string_view signature) const303 bool CTLogVerifier::VerifySignature(std::string_view data_to_sign,
304                                     std::string_view signature) const {
305   crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
306 
307   const EVP_MD* hash_alg = GetEvpAlg(hash_algorithm_);
308   bssl::ScopedEVP_MD_CTX ctx;
309   return hash_alg &&
310          EVP_DigestVerifyInit(ctx.get(), nullptr, hash_alg, nullptr,
311                               public_key_.get()) &&
312          EVP_DigestVerifyUpdate(ctx.get(), data_to_sign.data(),
313                                 data_to_sign.size()) &&
314          EVP_DigestVerifyFinal(
315              ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()),
316              signature.size());
317 }
318 
319 }  // namespace net
320