xref: /aosp_15_r20/external/cronet/net/cert/crl_set.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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/crl_set.h"
6 
7 #include <algorithm>
8 #include <string_view>
9 
10 #include "base/base64.h"
11 #include "base/json/json_reader.h"
12 #include "base/time/time.h"
13 #include "base/values.h"
14 #include "crypto/sha2.h"
15 #include "net/base/trace_constants.h"
16 #include "net/base/tracing.h"
17 #include "third_party/boringssl/src/include/openssl/bytestring.h"
18 #include "third_party/boringssl/src/include/openssl/mem.h"
19 
20 namespace net {
21 
22 namespace {
23 
24 // CRLSet format:
25 //
26 // uint16le header_len
27 // byte[header_len] header_bytes
28 // repeated {
29 //   byte[32] parent_spki_sha256
30 //   uint32le num_serials
31 //   [num_serials] {
32 //     uint8_t serial_length;
33 //     byte[serial_length] serial;
34 //   }
35 //
36 // header_bytes consists of a JSON dictionary with the following keys:
37 //   Version (int): currently 0
38 //   ContentType (string): "CRLSet" (magic value)
39 //   Sequence (int32_t): the monotonic sequence number of this CRL set.
40 //   NotAfter (optional) (double/int64_t): The number of seconds since the
41 //     Unix epoch, after which, this CRLSet is expired.
42 //   BlockedSPKIs (array of string): An array of Base64 encoded, SHA-256 hashed
43 //     SubjectPublicKeyInfos that should be blocked.
44 //   LimitedSubjects (object/map of string -> array of string): A map between
45 //     the Base64-encoded SHA-256 hash of the DER-encoded Subject and the
46 //     Base64-encoded SHA-256 hashes of the SubjectPublicKeyInfos that are
47 //     allowed for that subject.
48 //   KnownInterceptionSPKIs (array of string): An array of Base64-encoded
49 //     SHA-256 hashed SubjectPublicKeyInfos known to be used for interception.
50 //   BlockedInterceptionSPKIs (array of string): An array of Base64-encoded
51 //     SHA-256 hashed SubjectPublicKeyInfos known to be used for interception
52 //     and that should be actively blocked.
53 //
54 // ReadHeader reads the header (including length prefix) from |data| and
55 // updates |data| to remove the header on return. Caller takes ownership of the
56 // returned pointer.
ReadHeader(std::string_view * data)57 std::optional<base::Value> ReadHeader(std::string_view* data) {
58   uint16_t header_len;
59   if (data->size() < sizeof(header_len)) {
60     return std::nullopt;
61   }
62   // Assumes little-endian.
63   memcpy(&header_len, data->data(), sizeof(header_len));
64   data->remove_prefix(sizeof(header_len));
65 
66   if (data->size() < header_len) {
67     return std::nullopt;
68   }
69 
70   const std::string_view header_bytes = data->substr(0, header_len);
71   data->remove_prefix(header_len);
72 
73   std::optional<base::Value> header =
74       base::JSONReader::Read(header_bytes, base::JSON_ALLOW_TRAILING_COMMAS);
75   if (!header || !header->is_dict()) {
76     return std::nullopt;
77   }
78 
79   return header;
80 }
81 
82 // kCurrentFileVersion is the version of the CRLSet file format that we
83 // currently implement.
84 static const int kCurrentFileVersion = 0;
85 
ReadCRL(std::string_view * data,std::string * out_parent_spki_hash,std::vector<std::string> * out_serials)86 bool ReadCRL(std::string_view* data,
87              std::string* out_parent_spki_hash,
88              std::vector<std::string>* out_serials) {
89   if (data->size() < crypto::kSHA256Length)
90     return false;
91   *out_parent_spki_hash = std::string(data->substr(0, crypto::kSHA256Length));
92   data->remove_prefix(crypto::kSHA256Length);
93 
94   uint32_t num_serials;
95   if (data->size() < sizeof(num_serials))
96     return false;
97   // Assumes little endian.
98   memcpy(&num_serials, data->data(), sizeof(num_serials));
99   data->remove_prefix(sizeof(num_serials));
100 
101   if (num_serials > 32 * 1024 * 1024)  // Sanity check.
102     return false;
103 
104   out_serials->reserve(num_serials);
105 
106   for (uint32_t i = 0; i < num_serials; ++i) {
107     if (data->size() < sizeof(uint8_t))
108       return false;
109 
110     uint8_t serial_length = (*data)[0];
111     data->remove_prefix(sizeof(uint8_t));
112 
113     if (data->size() < serial_length)
114       return false;
115 
116     out_serials->push_back(std::string());
117     out_serials->back() = std::string(data->substr(0, serial_length));
118     data->remove_prefix(serial_length);
119   }
120 
121   return true;
122 }
123 
124 // CopyHashListFromHeader parses a list of base64-encoded, SHA-256 hashes from
125 // the given |key| (without path expansion) in |header_dict| and sets |*out|
126 // to the decoded values. It's not an error if |key| is not found in
127 // |header_dict|.
CopyHashListFromHeader(const base::Value::Dict & header_dict,const char * key,std::vector<std::string> * out)128 bool CopyHashListFromHeader(const base::Value::Dict& header_dict,
129                             const char* key,
130                             std::vector<std::string>* out) {
131   const base::Value::List* list = header_dict.FindList(key);
132   if (!list) {
133     // Hash lists are optional so it's not an error if not present.
134     return true;
135   }
136 
137   out->clear();
138   out->reserve(list->size());
139 
140   std::string sha256_base64;
141 
142   for (const base::Value& i : *list) {
143     sha256_base64.clear();
144 
145     if (!i.is_string())
146       return false;
147     sha256_base64 = i.GetString();
148 
149     out->push_back(std::string());
150     if (!base::Base64Decode(sha256_base64, &out->back())) {
151       out->pop_back();
152       return false;
153     }
154   }
155 
156   return true;
157 }
158 
159 // CopyHashToHashesMapFromHeader parse a map from base64-encoded, SHA-256
160 // hashes to lists of the same, from the given |key| in |header_dict|. It
161 // copies the map data into |out| (after base64-decoding).
CopyHashToHashesMapFromHeader(const base::Value::Dict & header_dict,const char * key,std::unordered_map<std::string,std::vector<std::string>> * out)162 bool CopyHashToHashesMapFromHeader(
163     const base::Value::Dict& header_dict,
164     const char* key,
165     std::unordered_map<std::string, std::vector<std::string>>* out) {
166   out->clear();
167 
168   const base::Value::Dict* dict = header_dict.FindDict(key);
169   if (dict == nullptr) {
170     // Maps are optional so it's not an error if not present.
171     return true;
172   }
173 
174   for (auto i : *dict) {
175     if (!i.second.is_list()) {
176       return false;
177     }
178 
179     std::vector<std::string> allowed_spkis;
180     for (const auto& j : i.second.GetList()) {
181       allowed_spkis.emplace_back();
182       if (!j.is_string() ||
183           !base::Base64Decode(j.GetString(), &allowed_spkis.back())) {
184         return false;
185       }
186     }
187 
188     std::string subject_hash;
189     if (!base::Base64Decode(i.first, &subject_hash)) {
190       return false;
191     }
192 
193     (*out)[subject_hash] = allowed_spkis;
194   }
195 
196   return true;
197 }
198 
199 }  // namespace
200 
201 CRLSet::CRLSet() = default;
202 
203 CRLSet::~CRLSet() = default;
204 
205 // static
Parse(std::string_view data,scoped_refptr<CRLSet> * out_crl_set)206 bool CRLSet::Parse(std::string_view data, scoped_refptr<CRLSet>* out_crl_set) {
207   TRACE_EVENT0(NetTracingCategory(), "CRLSet::Parse");
208 // Other parts of Chrome assume that we're little endian, so we don't lose
209 // anything by doing this.
210 #if defined(__BYTE_ORDER)
211   // Linux check
212   static_assert(__BYTE_ORDER == __LITTLE_ENDIAN, "assumes little endian");
213 #elif defined(__BIG_ENDIAN__)
214 // Mac check
215 #error assumes little endian
216 #endif
217 
218   std::optional<base::Value> header_value = ReadHeader(&data);
219   if (!header_value) {
220     return false;
221   }
222 
223   const base::Value::Dict& header_dict = header_value->GetDict();
224 
225   const std::string* contents = header_dict.FindString("ContentType");
226   if (!contents || (*contents != "CRLSet"))
227     return false;
228 
229   if (header_dict.FindInt("Version") != kCurrentFileVersion)
230     return false;
231 
232   std::optional<int> sequence = header_dict.FindInt("Sequence");
233   if (!sequence)
234     return false;
235 
236   // NotAfter is optional for now.
237   double not_after = header_dict.FindDouble("NotAfter").value_or(0);
238   if (not_after < 0)
239     return false;
240 
241   auto crl_set = base::WrapRefCounted(new CRLSet());
242   crl_set->sequence_ = static_cast<uint32_t>(*sequence);
243   crl_set->not_after_ = static_cast<uint64_t>(not_after);
244   crl_set->crls_.reserve(64);  // Value observed experimentally.
245 
246   while (!data.empty()) {
247     std::string spki_hash;
248     std::vector<std::string> blocked_serials;
249 
250     if (!ReadCRL(&data, &spki_hash, &blocked_serials)) {
251       return false;
252     }
253     crl_set->crls_[std::move(spki_hash)] = std::move(blocked_serials);
254   }
255 
256   std::vector<std::string> blocked_interception_spkis;
257   if (!CopyHashListFromHeader(header_dict, "BlockedSPKIs",
258                               &crl_set->blocked_spkis_) ||
259       !CopyHashToHashesMapFromHeader(header_dict, "LimitedSubjects",
260                                      &crl_set->limited_subjects_) ||
261       !CopyHashListFromHeader(header_dict, "KnownInterceptionSPKIs",
262                               &crl_set->known_interception_spkis_) ||
263       !CopyHashListFromHeader(header_dict, "BlockedInterceptionSPKIs",
264                               &blocked_interception_spkis)) {
265     return false;
266   }
267 
268   // Add the BlockedInterceptionSPKIs to both lists; these are provided as
269   // a separate list to allow less data to be sent over the wire, even though
270   // they are duplicated in-memory.
271   crl_set->blocked_spkis_.insert(crl_set->blocked_spkis_.end(),
272                                  blocked_interception_spkis.begin(),
273                                  blocked_interception_spkis.end());
274   crl_set->known_interception_spkis_.insert(
275       crl_set->known_interception_spkis_.end(),
276       blocked_interception_spkis.begin(), blocked_interception_spkis.end());
277 
278   // Defines kSPKIBlockList and kKnownInterceptionList
279 #include "net/cert/cert_verify_proc_blocklist.inc"
280   for (const auto& hash : kSPKIBlockList) {
281     crl_set->blocked_spkis_.emplace_back(reinterpret_cast<const char*>(hash),
282                                          crypto::kSHA256Length);
283   }
284 
285   for (const auto& hash : kKnownInterceptionList) {
286     crl_set->known_interception_spkis_.emplace_back(
287         reinterpret_cast<const char*>(hash), crypto::kSHA256Length);
288   }
289 
290   // Sort, as these will be std::binary_search()'d.
291   std::sort(crl_set->blocked_spkis_.begin(), crl_set->blocked_spkis_.end());
292   std::sort(crl_set->known_interception_spkis_.begin(),
293             crl_set->known_interception_spkis_.end());
294 
295   *out_crl_set = std::move(crl_set);
296   return true;
297 }
298 
CheckSPKI(std::string_view spki_hash) const299 CRLSet::Result CRLSet::CheckSPKI(std::string_view spki_hash) const {
300   if (std::binary_search(blocked_spkis_.begin(), blocked_spkis_.end(),
301                          spki_hash))
302     return REVOKED;
303   return GOOD;
304 }
305 
CheckSubject(std::string_view encoded_subject,std::string_view spki_hash) const306 CRLSet::Result CRLSet::CheckSubject(std::string_view encoded_subject,
307                                     std::string_view spki_hash) const {
308   const std::string digest(crypto::SHA256HashString(encoded_subject));
309   const auto i = limited_subjects_.find(digest);
310   if (i == limited_subjects_.end()) {
311     return GOOD;
312   }
313 
314   for (const auto& j : i->second) {
315     if (spki_hash == j) {
316       return GOOD;
317     }
318   }
319 
320   return REVOKED;
321 }
322 
CheckSerial(std::string_view serial_number,std::string_view issuer_spki_hash) const323 CRLSet::Result CRLSet::CheckSerial(std::string_view serial_number,
324                                    std::string_view issuer_spki_hash) const {
325   std::string_view serial(serial_number);
326 
327   if (!serial.empty() && (serial[0] & 0x80) != 0) {
328     // This serial number is negative but the process which generates CRL sets
329     // will reject any certificates with negative serial numbers as invalid.
330     return UNKNOWN;
331   }
332 
333   // Remove any leading zero bytes.
334   while (serial.size() > 1 && serial[0] == 0x00)
335     serial.remove_prefix(1);
336 
337   auto it = crls_.find(std::string(issuer_spki_hash));
338   if (it == crls_.end())
339     return UNKNOWN;
340 
341   for (const auto& issuer_serial : it->second) {
342     if (issuer_serial == serial)
343       return REVOKED;
344   }
345 
346   return GOOD;
347 }
348 
IsKnownInterceptionKey(std::string_view spki_hash) const349 bool CRLSet::IsKnownInterceptionKey(std::string_view spki_hash) const {
350   return std::binary_search(known_interception_spkis_.begin(),
351                             known_interception_spkis_.end(), spki_hash);
352 }
353 
IsExpired() const354 bool CRLSet::IsExpired() const {
355   if (not_after_ == 0)
356     return false;
357 
358   uint64_t now = base::Time::Now().ToTimeT();
359   return now > not_after_;
360 }
361 
sequence() const362 uint32_t CRLSet::sequence() const {
363   return sequence_;
364 }
365 
CrlsForTesting() const366 const CRLSet::CRLList& CRLSet::CrlsForTesting() const {
367   return crls_;
368 }
369 
370 // static
BuiltinCRLSet()371 scoped_refptr<CRLSet> CRLSet::BuiltinCRLSet() {
372   constexpr char kCRLSet[] =
373       "\x31\x00{\"ContentType\":\"CRLSet\",\"Sequence\":0,\"Version\":0}";
374   scoped_refptr<CRLSet> ret;
375   bool parsed = CRLSet::Parse({kCRLSet, sizeof(kCRLSet) - 1}, &ret);
376   DCHECK(parsed);
377   return ret;
378 }
379 
380 // static
EmptyCRLSetForTesting()381 scoped_refptr<CRLSet> CRLSet::EmptyCRLSetForTesting() {
382   return ForTesting(false, nullptr, "", "", {});
383 }
384 
385 // static
ExpiredCRLSetForTesting()386 scoped_refptr<CRLSet> CRLSet::ExpiredCRLSetForTesting() {
387   return ForTesting(true, nullptr, "", "", {});
388 }
389 
390 // static
ForTesting(bool is_expired,const SHA256HashValue * issuer_spki,std::string_view serial_number,std::string_view utf8_common_name,const std::vector<std::string> & acceptable_spki_hashes_for_cn)391 scoped_refptr<CRLSet> CRLSet::ForTesting(
392     bool is_expired,
393     const SHA256HashValue* issuer_spki,
394     std::string_view serial_number,
395     std::string_view utf8_common_name,
396     const std::vector<std::string>& acceptable_spki_hashes_for_cn) {
397   std::string subject_hash;
398   if (!utf8_common_name.empty()) {
399     CBB cbb, top_level, set, inner_seq, oid, cn;
400     uint8_t* x501_data;
401     size_t x501_len;
402     static const uint8_t kCommonNameOID[] = {0x55, 0x04, 0x03};  // 2.5.4.3
403 
404     CBB_zero(&cbb);
405 
406     if (!CBB_init(&cbb, 32) ||
407         !CBB_add_asn1(&cbb, &top_level, CBS_ASN1_SEQUENCE) ||
408         !CBB_add_asn1(&top_level, &set, CBS_ASN1_SET) ||
409         !CBB_add_asn1(&set, &inner_seq, CBS_ASN1_SEQUENCE) ||
410         !CBB_add_asn1(&inner_seq, &oid, CBS_ASN1_OBJECT) ||
411         !CBB_add_bytes(&oid, kCommonNameOID, sizeof(kCommonNameOID)) ||
412         !CBB_add_asn1(&inner_seq, &cn, CBS_ASN1_UTF8STRING) ||
413         !CBB_add_bytes(
414             &cn, reinterpret_cast<const uint8_t*>(utf8_common_name.data()),
415             utf8_common_name.size()) ||
416         !CBB_finish(&cbb, &x501_data, &x501_len)) {
417       CBB_cleanup(&cbb);
418       return nullptr;
419     }
420 
421     subject_hash.assign(crypto::SHA256HashString(
422         std::string_view(reinterpret_cast<char*>(x501_data), x501_len)));
423     OPENSSL_free(x501_data);
424   }
425 
426   auto crl_set = base::WrapRefCounted(new CRLSet());
427   crl_set->sequence_ = 0;
428   if (is_expired)
429     crl_set->not_after_ = 1;
430 
431   if (issuer_spki) {
432     const std::string spki(reinterpret_cast<const char*>(issuer_spki->data),
433                            sizeof(issuer_spki->data));
434     std::vector<std::string> serials;
435     if (!serial_number.empty()) {
436       serials.push_back(std::string(serial_number));
437       // |serial_number| is in DER-encoded form, which means it may have a
438       // leading 0x00 to indicate it is a positive INTEGER. CRLSets are stored
439       // without these leading 0x00, as handled in CheckSerial(), so remove
440       // that here. As DER-encoding means that any sequences of leading zeroes
441       // should be omitted, except to indicate sign, there should only ever
442       // be one, and the next byte should have the high bit set.
443       DCHECK_EQ(serials[0][0] & 0x80, 0);  // Negative serials are not allowed.
444       if (serials[0][0] == 0x00) {
445         serials[0].erase(0, 1);
446         // If there was a leading 0x00, then the high-bit of the next byte
447         // should have been set.
448         DCHECK(!serials[0].empty() && serials[0][0] & 0x80);
449       }
450     }
451 
452     crl_set->crls_.emplace(std::move(spki), std::move(serials));
453   }
454 
455   if (!subject_hash.empty())
456     crl_set->limited_subjects_[subject_hash] = acceptable_spki_hashes_for_cn;
457 
458   return crl_set;
459 }
460 
461 }  // namespace net
462