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