Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(269)

Side by Side Diff: components/cast_certificate/cast_crl.cc

Issue 2050983002: Cast device revocation checking. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: (Rebase only) Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
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 "components/cast_certificate/cast_crl.h"
6
7 #include <unordered_map>
8 #include <unordered_set>
9
10 #include "base/base64.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/memory/singleton.h"
13 #include "components/cast_certificate/proto/revocation.pb.h"
14 #include "crypto/sha2.h"
15 #include "net/cert/internal/parse_certificate.h"
16 #include "net/cert/internal/parsed_certificate.h"
17 #include "net/cert/internal/path_builder.h"
18 #include "net/cert/internal/signature_algorithm.h"
19 #include "net/cert/internal/signature_policy.h"
20 #include "net/cert/internal/trust_store.h"
21 #include "net/cert/internal/verify_certificate_chain.h"
22 #include "net/cert/internal/verify_signed_data.h"
23 #include "net/cert/x509_certificate.h"
24 #include "net/der/input.h"
25 #include "net/der/parser.h"
26 #include "net/der/parse_values.h"
27
28 namespace cast_certificate {
29 namespace {
30
31 enum CrlVersion {
32 // version 0: Spki Hash Algorithm = SHA-256
33 // Signature Algorithm = RSA-PKCS1 V1.5 with SHA-256
34 CRL_VERSION_0 = 0,
35 };
36
37 // -------------------------------------------------------------------------
38 // Cast CRL trust anchors.
39 // -------------------------------------------------------------------------
40
41 // There is one trusted root for Cast CRL certificate chains:
42 //
43 // (1) CN=Cast CRL Root CA (kCastCRLRootCaDer)
44 //
45 // These constants are defined by the file included next:
46
47 #include "components/cast_certificate/cast_crl_root_ca_cert_der-inc.h"
48
49 // Singleton for the Cast CRL trust store.
50 class CastCRLTrustStore {
51 public:
52 static CastCRLTrustStore* GetInstance() {
53 return base::Singleton<CastCRLTrustStore, base::LeakySingletonTraits<
54 CastCRLTrustStore>>::get();
55 }
56
57 static net::TrustStore& Get() { return GetInstance()->store_; }
58
59 private:
60 friend struct base::DefaultSingletonTraits<CastCRLTrustStore>;
61
62 CastCRLTrustStore() {
63 // Initialize the trust store with the root certificate.
64 // TODO (ryanchung): Add official Cast CRL Root here
eroman 2016/07/12 21:22:01 Why is this a TODO() rather than added here?
ryanchung 2016/07/14 16:15:25 The root certificate has not been generated yet.
65 // scoped_refptr<net::ParsedCertificate> root = net::ParsedCertificate::
66 // net::ParsedCertificate::CreateFromCertificateData(
67 // kCastCRLRootCaDer, sizeof(kCastCRLRootCaDer),
68 // net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE, {});
69 // CHECK(root);
70 // store_.AddTrustedCertificate(std::move(root));
71 }
72
73 net::TrustStore store_;
74 DISALLOW_COPY_AND_ASSIGN(CastCRLTrustStore);
75 };
76
77 // Converts a base::Time::Exploded to a net::der::GeneralizedTime.
eroman 2016/07/12 21:22:00 side-note: This seems like something we should ext
78 net::der::GeneralizedTime ConvertExplodedTime(
79 const base::Time::Exploded& exploded) {
80 // Add 500 milliseconds and strip the milliseconds.
81 // Effectively rounding the original time to the nearest second.
82 base::Time rounded_time = base::Time::FromUTCExploded(exploded);
eroman 2016/07/12 21:22:00 This isn't generally safe pattern, since FromUTCEx
ryanchung 2016/07/14 16:15:25 Removed the rounding and removed this. Thanks for
83 rounded_time += base::TimeDelta::FromMilliseconds(500);
eroman 2016/07/12 21:22:00 Why is this rounding desired? We don't for instanc
ryanchung 2016/07/14 16:15:25 Removed rounding. Originally, this was because the
84 base::Time::Exploded rounded_exploded_time;
85 rounded_time.UTCExplode(&rounded_exploded_time);
86
87 net::der::GeneralizedTime result;
88 result.year = rounded_exploded_time.year;
89 result.month = rounded_exploded_time.month;
90 result.day = rounded_exploded_time.day_of_month;
91 result.hours = rounded_exploded_time.hour;
92 result.minutes = rounded_exploded_time.minute;
93 result.seconds = rounded_exploded_time.second;
94 return result;
95 }
96
97 // Converts a ner::der::GeneralizedTime to base::Time::Exploded
98 base::Time::Exploded ConvertGeneralizedTime(
99 const net::der::GeneralizedTime generalized) {
100 base::Time::Exploded result = {0};
101 result.year = generalized.year;
102 result.month = generalized.month;
103 result.day_of_month = generalized.day;
104 result.hour = generalized.hours;
105 result.minute = generalized.minutes;
106 result.second = generalized.seconds;
107 return result;
108 }
109
110 // Specifies the signature verification policy.
111 std::unique_ptr<net::SignaturePolicy> CreateCastSignaturePolicy() {
112 return base::WrapUnique(new net::SimpleSignaturePolicy(2048));
eroman 2016/07/12 21:22:00 Can you add a comment explaining what algorithms a
ryanchung 2016/07/14 16:15:25 Done.
113 }
114
115 // Verifies the CRL is signed by a trusted CRL authority at the time the CRL
116 // was issued. Verifies the signature of |tbs_crl| is valid based on the
117 // certificate and signature in |crl|. The validity of |tbs_crl| is verified
118 // at |time|. The validity period of the CRL is adjusted to be the earliest
119 // of the issuer certificate chain's expiration and the CRL's expiration and
120 // the result is stored in |overall_validity_end|.
121 bool VerifyCRL(const Crl& crl,
122 const TbsCrl& tbs_crl,
123 const base::Time::Exploded& time,
124 base::Time* overall_validity_end) {
125 // Verify the trust of the CRL authority.
126 scoped_refptr<net::ParsedCertificate> parsed_cert =
127 net::ParsedCertificate::CreateFromCertificateData(
128 reinterpret_cast<const uint8_t*>(crl.signer_cert().data()),
129 crl.signer_cert().size(),
130 net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE, {});
131 if (parsed_cert == nullptr) {
132 VLOG(2) << "CRL - Issuer certificate parsing failed.";
133 return false;
134 }
135
136 // Parse the signature.
137 std::string signature = crl.signature();
eroman 2016/07/12 21:22:00 can this be a reference instead (to avoid copying
ryanchung 2016/07/14 16:15:25 Done.
138 net::der::BitString signature_value_bit_string =
139 net::der::BitString(net::der::Input(&signature), 0);
140
141 // Verify the signature.
142 std::unique_ptr<net::SignaturePolicy> policy =
143 base::WrapUnique(new net::SimpleSignaturePolicy(2048));
eroman 2016/07/12 21:22:00 Should this be using CreateCastSignaturePolicy() i
ryanchung 2016/07/14 16:15:25 Done.
144 std::unique_ptr<net::SignatureAlgorithm> signature_algorithm_type =
145 net::SignatureAlgorithm::CreateRsaPkcs1(net::DigestAlgorithm::Sha256);
146 if (!VerifySignedData(*signature_algorithm_type,
147 net::der::Input(&crl.tbs_crl()),
148 signature_value_bit_string, parsed_cert->tbs().spki_tlv,
149 policy.get())) {
150 VLOG(2) << "CRL - Signature verification failed.";
151 return false;
152 }
153
154 // Verify the issuer certificate.
155 auto signature_policy = CreateCastSignaturePolicy();
156 net::CertPathBuilder::Result result;
157 net::CertPathBuilder path_builder(
158 parsed_cert.get(), &CastCRLTrustStore::Get(), signature_policy.get(),
159 ConvertExplodedTime(time), &result);
160 net::CompletionStatus rv = path_builder.Run(base::Closure());
161 DCHECK_EQ(rv, net::CompletionStatus::SYNC);
162 if (!result.is_success() || result.paths.empty() ||
163 !result.paths[result.best_result_index]->is_success()) {
164 VLOG(2) << "CRL - Issuer certificate verification failed.";
165 return false;
166 }
167
168 // Verify the CRL is still valid.
169 base::Time utc_time = base::Time::FromUTCExploded(time);
170 base::Time validity_start =
171 base::Time::UnixEpoch() +
172 base::TimeDelta::FromMilliseconds(tbs_crl.issuance_time_millis());
173 base::Time validity_end =
174 base::Time::UnixEpoch() +
175 base::TimeDelta::FromMilliseconds(tbs_crl.issuance_time_millis() +
176 tbs_crl.validity_period_millis());
177 if ((utc_time < validity_start) || (utc_time >= validity_end)) {
178 VLOG(2) << "CRL - Not time-valid.";
179 return false;
180 }
181
182 // Set CRL expiry to the earliest of the cert chain expiry and CRL expiry.
183 *overall_validity_end = validity_end;
184 for (const auto cert : result.paths[result.best_result_index]->path) {
eroman 2016/07/12 21:22:00 const auto&
ryanchung 2016/07/14 16:15:25 Done.
185 base::Time::Exploded cert_expiry_exploded =
186 ConvertGeneralizedTime(cert->tbs().validity_not_after);
187 base::Time cert_time;
188 if (!base::Time::FromUTCExploded(cert_expiry_exploded, &cert_time)) {
189 VLOG(2) << "Unexpected error: Malformed certificate not-after time.";
190 return false;
191 }
192 // Certificate expiry time uses ASN.1 Generalized time accurate to the
193 // second and has a precision of +/- 500ms. Conservatively add 500ms when
194 // converted to base::Time.
195 cert_time += base::TimeDelta::FromMilliseconds(500);
eroman 2016/07/12 21:22:01 Same comment as earlier. I don't think there is re
ryanchung 2016/07/14 16:15:25 Removed the rounding. Updated all instances of tim
196 if (cert_time < *overall_validity_end)
197 *overall_validity_end = cert_time;
198 }
199
200 // Perform sanity check on serial numbers.
201 for (const auto& range : tbs_crl.revoked_serial_number_ranges()) {
202 uint64_t first_serial_number = range.first_serial_number();
203 uint64_t last_serial_number = range.last_serial_number();
204 if (last_serial_number < first_serial_number) {
205 VLOG(2) << "CRL - Malformed serial number range.";
206 return false;
207 }
208 }
209 return true;
210 }
211
212 class CastCRLImpl : public CastCRL {
213 public:
214 CastCRLImpl(const TbsCrl& tbs_crl, const base::Time& overall_validity_end);
215 ~CastCRLImpl() override;
216
217 bool CheckRevocation(
218 const std::vector<scoped_refptr<net::ParsedCertificate>>& trusted_chain,
219 const base::Time::Exploded& time) const override;
220
221 private:
222 base::Time validity_start;
223 base::Time validity_end;
224
225 // Hashes of all revoked public keys.
eroman 2016/07/12 21:22:00 document what the hash used is.
ryanchung 2016/07/14 16:15:25 Done.
226 std::set<std::string> revoked_hashes_;
227
228 // Revoked serial number ranges indexed by issuer public key hash.
229 std::unordered_map<std::string, std::set<std::pair<uint64_t, uint64_t>>>
230 revoked_serial_numbers_;
231 DISALLOW_COPY_AND_ASSIGN(CastCRLImpl);
232 };
233
234 CastCRLImpl::CastCRLImpl(const TbsCrl& tbs_crl,
235 const base::Time& overall_validity_end) {
236 // Parse the validity information.
237 validity_start =
238 base::Time::UnixEpoch() +
239 base::TimeDelta::FromMilliseconds(tbs_crl.issuance_time_millis());
eroman 2016/07/12 21:22:00 This is done elsewhere too, suggest extracting to
ryanchung 2016/07/14 16:15:25 Done.
240 validity_end =
241 base::Time::UnixEpoch() +
242 base::TimeDelta::FromMilliseconds(tbs_crl.issuance_time_millis() +
eroman 2016/07/12 21:22:00 Note that adding the two numbers in this fashion c
ryanchung 2016/07/14 16:15:25 Done. The CRL proto has been updated to use not_be
243 tbs_crl.validity_period_millis());
244 if (overall_validity_end < validity_end)
245 validity_end = overall_validity_end;
246
247 // Parse the revoked hashes.
248 for (const auto& hash : tbs_crl.revoked_public_key_hashes()) {
249 revoked_hashes_.insert(hash);
250 }
251
252 // Parse the revoked serial ranges.
253 for (const auto& range : tbs_crl.revoked_serial_number_ranges()) {
254 std::string issuer_hash = range.issuer_public_key_hash();
255
256 auto issuer_iter = revoked_serial_numbers_.find(issuer_hash);
257 if (issuer_iter == revoked_serial_numbers_.end()) {
258 std::set<std::pair<uint64_t, uint64_t>> serials;
259 std::pair<std::string, std::set<std::pair<uint64_t, uint64_t>>>
260 revocation_entry = std::make_pair(issuer_hash, serials);
261 issuer_iter = revoked_serial_numbers_.insert(revocation_entry).first;
262 }
263
264 uint64_t first_serial_number = range.first_serial_number();
265 uint64_t last_serial_number = range.last_serial_number();
266 std::pair<uint64_t, uint64_t> revocation_range(first_serial_number,
267 last_serial_number);
268 issuer_iter->second.insert(revocation_range);
269 }
270 }
271
272 CastCRLImpl::~CastCRLImpl() {}
273
274 // Verifies the revocation status of the certificate chain, at the specified
275 // time.
276 bool CastCRLImpl::CheckRevocation(
277 const std::vector<scoped_refptr<net::ParsedCertificate>>& trusted_chain,
278 const base::Time::Exploded& time) const {
279 if (trusted_chain.empty())
280 return false;
281
282 // Check the validity of the CRL at the specified time.
283 base::Time utc_time = base::Time::FromUTCExploded(time);
284 if ((utc_time < validity_start) || (utc_time >= validity_end)) {
eroman 2016/07/12 21:22:00 validity_end corresponds with notAfter, so shouldn
ryanchung 2016/07/14 16:15:25 Done.
285 VLOG(2) << "CRL not time-valid. Perform hard fail.";
286 return false;
287 }
288
289 // Check revocation.
290 std::string issuer_key_hash;
291 for (int i = trusted_chain.size() - 1; i >= 0; --i) {
292 // Check public key revocation.
293 scoped_refptr<net::ParsedCertificate> parsed_cert = trusted_chain[i];
eroman 2016/07/12 21:22:00 can you use a const reference instead?
ryanchung 2016/07/14 16:15:25 Done.
294 if (parsed_cert == nullptr) {
295 VLOG(2) << "Certificate chain not available.";
eroman 2016/07/12 21:22:00 This shouldn't be reachable.
ryanchung 2016/07/14 16:15:25 Done.
296 return false;
297 }
298 // Calculate the public key's hash.
299 std::string spki_hash =
300 crypto::SHA256HashString(parsed_cert->tbs().spki_tlv.AsString());
301 if (revoked_hashes_.find(spki_hash) != revoked_hashes_.end()) {
302 VLOG(2) << "Public key is revoked.";
303 return false;
304 }
305
306 // Check serial range revocation.
307 if (!issuer_key_hash.empty()) {
308 auto issuer_iter = revoked_serial_numbers_.find(issuer_key_hash);
309 if (issuer_iter != revoked_serial_numbers_.end()) {
310 uint64_t serial_number;
311 if (!net::der::ParseUint64(parsed_cert->tbs().serial_number,
eroman 2016/07/12 21:22:00 Why is this sufficient? Serial numbers can be 160
ryanchung 2016/07/14 16:15:25 For serial range revocation, we will only revoke G
eroman 2016/07/15 22:52:48 Thanks for the explanation, that sounds reasonable
312 &serial_number)) {
313 continue;
314 }
315 for (auto const& revoked_serial : issuer_iter->second) {
316 if (revoked_serial.first <= serial_number &&
317 revoked_serial.second >= serial_number) {
318 VLOG(2) << "Serial number is revoked";
319 return false;
320 }
321 }
322 }
323 }
324 issuer_key_hash = spki_hash;
325 }
326 return true;
327 }
328
329 } // namespace
330
331 std::unique_ptr<CastCRL> ParseAndVerifyCRL(const std::string& crl_proto,
332 const base::Time::Exploded& time) {
333 CrlBundle crl_bundle;
334 if (!crl_bundle.ParseFromString(crl_proto)) {
335 LOG(ERROR) << "CRL - Binary could not be parsed.";
336 return nullptr;
337 }
338 for (auto const& crl : crl_bundle.crls()) {
339 TbsCrl tbs_crl;
340 if (!tbs_crl.ParseFromString(crl.tbs_crl())) {
341 LOG(WARNING) << "Binary TBS CRL could not be parsed.";
342 continue;
343 }
344 if (tbs_crl.version() != CRL_VERSION_0) {
345 continue;
346 }
347 base::Time overall_validity_end;
348 if (!VerifyCRL(crl, tbs_crl, time, &overall_validity_end)) {
349 LOG(ERROR) << "CRL - Verification failed.";
350 return nullptr;
351 }
352 return base::WrapUnique(new CastCRLImpl(tbs_crl, overall_validity_end));
353 }
354 LOG(ERROR) << "No supported version of revocation data.";
355 return nullptr;
356 }
357
358 bool SetCRLTrustAnchorForTest(const uint8_t* data, size_t length) {
359 scoped_refptr<net::ParsedCertificate> anchor(
360 net::ParsedCertificate::CreateFromCertificateData(
361 data, length, net::ParsedCertificate::DataSource::EXTERNAL_REFERENCE,
362 {}));
363 if (!anchor)
364 return false;
365 CastCRLTrustStore::Get().Clear();
366 CastCRLTrustStore::Get().AddTrustedCertificate(std::move(anchor));
367 return true;
368 }
369
370 void ClearCRLTrustAnchorForTest() {
371 CastCRLTrustStore::Get().Clear();
372 }
373
374 } // namespace cast_certificate
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698