xref: /aosp_15_r20/external/cronet/net/cert/internal/revocation_checker.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2017 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/internal/revocation_checker.h"
6 
7 #include <optional>
8 #include <string>
9 #include <string_view>
10 
11 #include "base/logging.h"
12 #include "crypto/sha2.h"
13 #include "net/cert/cert_net_fetcher.h"
14 #include "third_party/boringssl/src/pki/common_cert_errors.h"
15 #include "third_party/boringssl/src/pki/crl.h"
16 #include "third_party/boringssl/src/pki/ocsp.h"
17 #include "third_party/boringssl/src/pki/ocsp_verify_result.h"
18 #include "third_party/boringssl/src/pki/parsed_certificate.h"
19 #include "third_party/boringssl/src/pki/trust_store.h"
20 #include "url/gurl.h"
21 
22 namespace net {
23 
24 namespace {
25 
MarkCertificateRevoked(bssl::CertErrors * errors)26 void MarkCertificateRevoked(bssl::CertErrors* errors) {
27   // TODO(eroman): Add a parameter to the error indicating which mechanism
28   // caused the revocation (i.e. CRLSet, OCSP, stapled OCSP, etc).
29   errors->AddError(bssl::cert_errors::kCertificateRevoked);
30 }
31 
32 // Checks the revocation status of |certs[target_cert_index]| according to
33 // |policy|. If the checks failed, returns false and adds errors to
34 // |cert_errors|.
35 //
36 // TODO(eroman): Make the verification time an input.
CheckCertRevocation(const bssl::ParsedCertificateList & certs,size_t target_cert_index,const RevocationPolicy & policy,base::TimeTicks deadline,std::string_view stapled_ocsp_response,std::optional<int64_t> max_age_seconds,CertNetFetcher * net_fetcher,bssl::CertErrors * cert_errors,bssl::OCSPVerifyResult * stapled_ocsp_verify_result)37 bool CheckCertRevocation(const bssl::ParsedCertificateList& certs,
38                          size_t target_cert_index,
39                          const RevocationPolicy& policy,
40                          base::TimeTicks deadline,
41                          std::string_view stapled_ocsp_response,
42                          std::optional<int64_t> max_age_seconds,
43                          CertNetFetcher* net_fetcher,
44                          bssl::CertErrors* cert_errors,
45                          bssl::OCSPVerifyResult* stapled_ocsp_verify_result) {
46   DCHECK_LT(target_cert_index, certs.size());
47   const bssl::ParsedCertificate* cert = certs[target_cert_index].get();
48   const bssl::ParsedCertificate* issuer_cert =
49       target_cert_index + 1 < certs.size() ? certs[target_cert_index + 1].get()
50                                            : nullptr;
51 
52   // Check using stapled OCSP, if available.
53   if (!stapled_ocsp_response.empty() && issuer_cert) {
54     bssl::OCSPVerifyResult::ResponseStatus response_details;
55     bssl::OCSPRevocationStatus ocsp_status = bssl::CheckOCSP(
56         stapled_ocsp_response, cert, issuer_cert, base::Time::Now().ToTimeT(),
57         max_age_seconds, &response_details);
58     if (stapled_ocsp_verify_result) {
59       stapled_ocsp_verify_result->response_status = response_details;
60       stapled_ocsp_verify_result->revocation_status = ocsp_status;
61     }
62 
63     // TODO(eroman): Save the stapled OCSP response to cache.
64     switch (ocsp_status) {
65       case bssl::OCSPRevocationStatus::REVOKED:
66         MarkCertificateRevoked(cert_errors);
67         return false;
68       case bssl::OCSPRevocationStatus::GOOD:
69         return true;
70       case bssl::OCSPRevocationStatus::UNKNOWN:
71         // TODO(eroman): If the OCSP response was invalid, should we keep
72         //               looking or fail?
73         break;
74     }
75   }
76 
77   if (!policy.check_revocation) {
78     // TODO(eroman): Should still check CRL/OCSP caches.
79     return true;
80   }
81 
82   bool found_revocation_info = false;
83 
84   // Check OCSP.
85   if (cert->has_authority_info_access()) {
86     // Try each of the OCSP URIs
87     for (const auto& ocsp_uri : cert->ocsp_uris()) {
88       // Only consider http:// URLs (https:// could create a circular
89       // dependency).
90       GURL parsed_ocsp_url(ocsp_uri);
91       if (!parsed_ocsp_url.is_valid() ||
92           !parsed_ocsp_url.SchemeIs(url::kHttpScheme)) {
93         continue;
94       }
95 
96       found_revocation_info = true;
97 
98       // Check the deadline after setting found_revocation_info, to not give a
99       // misleading kNoRevocationMechanism failure.
100       if (!deadline.is_null() && base::TimeTicks::Now() > deadline)
101         break;
102 
103       if (!policy.networking_allowed)
104         continue;
105 
106       if (!net_fetcher) {
107         LOG(ERROR) << "Cannot fetch OCSP as didn't specify a |net_fetcher|";
108         continue;
109       }
110 
111       // TODO(eroman): Duplication of work if there are multiple URLs to try.
112       // TODO(eroman): Are there cases where we would need to POST instead?
113       std::optional<std::string> get_url_str =
114           CreateOCSPGetURL(cert, issuer_cert, ocsp_uri);
115       if (!get_url_str.has_value()) {
116         // An unexpected failure from BoringSSL, or the input was too large to
117         // base64-encode.
118         continue;
119       }
120       GURL get_url(get_url_str.value());
121       if (!get_url.is_valid()) {
122         // Invalid URL.
123         continue;
124       }
125 
126       // Fetch it over network.
127       //
128       // TODO(eroman): Issue POST instead of GET if request is larger than 255
129       //               bytes?
130       // TODO(eroman): Improve interplay with HTTP cache.
131       std::unique_ptr<CertNetFetcher::Request> net_ocsp_request =
132           net_fetcher->FetchOcsp(get_url, CertNetFetcher::DEFAULT,
133                                  CertNetFetcher::DEFAULT);
134 
135       Error net_error;
136       std::vector<uint8_t> ocsp_response_bytes;
137       net_ocsp_request->WaitForResult(&net_error, &ocsp_response_bytes);
138 
139       if (net_error != OK)
140         continue;
141 
142       bssl::OCSPVerifyResult::ResponseStatus response_details;
143 
144       bssl::OCSPRevocationStatus ocsp_status = bssl::CheckOCSP(
145           std::string_view(
146               reinterpret_cast<const char*>(ocsp_response_bytes.data()),
147               ocsp_response_bytes.size()),
148           cert, issuer_cert, base::Time::Now().ToTimeT(), max_age_seconds,
149           &response_details);
150 
151       switch (ocsp_status) {
152         case bssl::OCSPRevocationStatus::REVOKED:
153           MarkCertificateRevoked(cert_errors);
154           return false;
155         case bssl::OCSPRevocationStatus::GOOD:
156           return true;
157         case bssl::OCSPRevocationStatus::UNKNOWN:
158           break;
159       }
160     }
161   }
162 
163   // Check CRLs.
164   bssl::ParsedExtension crl_dp_extension;
165   if (policy.crl_allowed &&
166       cert->GetExtension(bssl::der::Input(bssl::kCrlDistributionPointsOid),
167                          &crl_dp_extension)) {
168     std::vector<bssl::ParsedDistributionPoint> distribution_points;
169     if (ParseCrlDistributionPoints(crl_dp_extension.value,
170                                    &distribution_points)) {
171       for (const auto& distribution_point : distribution_points) {
172         if (distribution_point.crl_issuer) {
173           // Ignore indirect CRLs (CRL where CRLissuer != cert issuer), which
174           // are optional according to RFC 5280's profile.
175           continue;
176         }
177 
178         if (distribution_point.reasons) {
179           // Ignore CRLs that only contain some reasons. RFC 5280's profile
180           // requires that conforming CAs "MUST include at least one
181           // DistributionPoint that points to a CRL that covers the certificate
182           // for all reasons".
183           continue;
184         }
185 
186         if (!distribution_point.distribution_point_fullname) {
187           // Only distributionPoints with a fullName containing URIs are
188           // supported.
189           continue;
190         }
191 
192         for (const auto& crl_uri :
193              distribution_point.distribution_point_fullname
194                  ->uniform_resource_identifiers) {
195           // Only consider http:// URLs (https:// could create a circular
196           // dependency).
197           GURL parsed_crl_url(crl_uri);
198           if (!parsed_crl_url.is_valid() ||
199               !parsed_crl_url.SchemeIs(url::kHttpScheme)) {
200             continue;
201           }
202 
203           found_revocation_info = true;
204 
205           // Check the deadline after setting found_revocation_info, to not give
206           // a misleading kNoRevocationMechanism failure.
207           if (!deadline.is_null() && base::TimeTicks::Now() > deadline)
208             break;
209 
210           if (!policy.networking_allowed)
211             continue;
212 
213           if (!net_fetcher) {
214             LOG(ERROR) << "Cannot fetch CRL as didn't specify a |net_fetcher|";
215             continue;
216           }
217 
218           // Fetch it over network.
219           //
220           // Note that no attempt is made to refetch without cache if a cached
221           // CRL is too old, nor is there a separate CRL cache. It is assumed
222           // the CRL server will send reasonable HTTP caching headers.
223           std::unique_ptr<CertNetFetcher::Request> net_crl_request =
224               net_fetcher->FetchCrl(parsed_crl_url, CertNetFetcher::DEFAULT,
225                                     CertNetFetcher::DEFAULT);
226 
227           Error net_error;
228           std::vector<uint8_t> crl_response_bytes;
229           net_crl_request->WaitForResult(&net_error, &crl_response_bytes);
230 
231           if (net_error != OK)
232             continue;
233 
234           bssl::CRLRevocationStatus crl_status = CheckCRL(
235               std::string_view(
236                   reinterpret_cast<const char*>(crl_response_bytes.data()),
237                   crl_response_bytes.size()),
238               certs, target_cert_index, distribution_point,
239               base::Time::Now().ToTimeT(), max_age_seconds);
240 
241           switch (crl_status) {
242             case bssl::CRLRevocationStatus::REVOKED:
243               MarkCertificateRevoked(cert_errors);
244               return false;
245             case bssl::CRLRevocationStatus::GOOD:
246               return true;
247             case bssl::CRLRevocationStatus::UNKNOWN:
248               break;
249           }
250         }
251       }
252     }
253   }
254 
255   // Reaching here means that revocation checking was inconclusive. Determine
256   // whether failure to complete revocation checking constitutes an error.
257 
258   if (!found_revocation_info) {
259     if (policy.allow_missing_info) {
260       // If the certificate lacked any (recognized) revocation mechanisms, and
261       // the policy permits it, consider revocation checking a success.
262       return true;
263     } else {
264       // If the certificate lacked any (recognized) revocation mechanisms, and
265       // the policy forbids it, fail revocation checking.
266       cert_errors->AddError(bssl::cert_errors::kNoRevocationMechanism);
267       return false;
268     }
269   }
270 
271   // In soft-fail mode permit other failures.
272   // TODO(eroman): Add a warning to |cert_errors| indicating the failure.
273   if (policy.allow_unable_to_check)
274     return true;
275 
276   // Otherwise the policy doesn't allow revocation checking to fail.
277   cert_errors->AddError(bssl::cert_errors::kUnableToCheckRevocation);
278   return false;
279 }
280 
281 }  // namespace
282 
CheckValidatedChainRevocation(const bssl::ParsedCertificateList & certs,const RevocationPolicy & policy,base::TimeTicks deadline,std::string_view stapled_leaf_ocsp_response,CertNetFetcher * net_fetcher,bssl::CertPathErrors * errors,bssl::OCSPVerifyResult * stapled_ocsp_verify_result)283 void CheckValidatedChainRevocation(
284     const bssl::ParsedCertificateList& certs,
285     const RevocationPolicy& policy,
286     base::TimeTicks deadline,
287     std::string_view stapled_leaf_ocsp_response,
288     CertNetFetcher* net_fetcher,
289     bssl::CertPathErrors* errors,
290     bssl::OCSPVerifyResult* stapled_ocsp_verify_result) {
291   if (stapled_ocsp_verify_result)
292     *stapled_ocsp_verify_result = bssl::OCSPVerifyResult();
293 
294   // Check each certificate for revocation using OCSP/CRL. Checks proceed
295   // from the root certificate towards the leaf certificate. Revocation errors
296   // are added to |errors|.
297   for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) {
298     size_t i = certs.size() - reverse_i - 1;
299 
300     // Trust anchors bypass OCSP/CRL revocation checks. (The only way to revoke
301     // trust anchors is via CRLSet or the built-in SPKI block list). Since
302     // |certs| must be a validated chain, the final cert must be a trust
303     // anchor.
304     if (reverse_i == 0)
305       continue;
306 
307     // TODO(eroman): Plumb stapled OCSP for non-leaf certificates from TLS?
308     std::string_view stapled_ocsp =
309         (i == 0) ? stapled_leaf_ocsp_response : std::string_view();
310 
311     std::optional<int64_t> max_age_seconds;
312     if (policy.enforce_baseline_requirements) {
313       max_age_seconds = ((i == 0) ? kMaxRevocationLeafUpdateAge
314                                   : kMaxRevocationIntermediateUpdateAge)
315                             .InSeconds();
316     }
317 
318     // Check whether this certificate's revocation status complies with the
319     // policy.
320     bool cert_ok = CheckCertRevocation(
321         certs, i, policy, deadline, stapled_ocsp, max_age_seconds, net_fetcher,
322         errors->GetErrorsForCert(i),
323         (i == 0) ? stapled_ocsp_verify_result : nullptr);
324 
325     if (!cert_ok) {
326       // If any certificate in the chain fails revocation checks, the chain is
327       // revoked and no need to check revocation status for the remaining
328       // certificates.
329       DCHECK(errors->GetErrorsForCert(i)->ContainsAnyErrorWithSeverity(
330           bssl::CertError::SEVERITY_HIGH));
331       break;
332     }
333   }
334 }
335 
CheckChainRevocationUsingCRLSet(const CRLSet * crl_set,const bssl::ParsedCertificateList & certs,bssl::CertPathErrors * errors)336 CRLSet::Result CheckChainRevocationUsingCRLSet(
337     const CRLSet* crl_set,
338     const bssl::ParsedCertificateList& certs,
339     bssl::CertPathErrors* errors) {
340   // Iterate from the root certificate towards the leaf (the root certificate is
341   // also checked for revocation by CRLSet).
342   std::string issuer_spki_hash;
343   for (size_t reverse_i = 0; reverse_i < certs.size(); ++reverse_i) {
344     size_t i = certs.size() - reverse_i - 1;
345     const bssl::ParsedCertificate* cert = certs[i].get();
346 
347     // True if |cert| is the root of the chain.
348     const bool is_root = reverse_i == 0;
349     // True if |cert| is the leaf certificate of the chain.
350     const bool is_target = i == 0;
351 
352     // Check for revocation using the certificate's SPKI.
353     std::string spki_hash =
354         crypto::SHA256HashString(cert->tbs().spki_tlv.AsStringView());
355     CRLSet::Result result = crl_set->CheckSPKI(spki_hash);
356 
357     // Check for revocation using the certificate's Subject.
358     if (result != CRLSet::REVOKED) {
359       result = crl_set->CheckSubject(cert->tbs().subject_tlv.AsStringView(),
360                                      spki_hash);
361     }
362 
363     // Check for revocation using the certificate's serial number and issuer's
364     // SPKI.
365     if (result != CRLSet::REVOKED && !is_root) {
366       result = crl_set->CheckSerial(cert->tbs().serial_number.AsStringView(),
367                                     issuer_spki_hash);
368     }
369 
370     // Prepare for the next iteration.
371     issuer_spki_hash = std::move(spki_hash);
372 
373     switch (result) {
374       case CRLSet::REVOKED:
375         MarkCertificateRevoked(errors->GetErrorsForCert(i));
376         return CRLSet::Result::REVOKED;
377       case CRLSet::UNKNOWN:
378         // If the status is unknown, advance to the subordinate certificate.
379         break;
380       case CRLSet::GOOD:
381         if (is_target && !crl_set->IsExpired()) {
382           // If the target is covered by the CRLSet and known good, consider
383           // the entire chain to be valid (even though the revocation status
384           // of the intermediates may have been UNKNOWN).
385           //
386           // Only the leaf certificate is considered for coverage because some
387           // intermediates have CRLs with no revocations (after filtering) and
388           // those CRLs are pruned from the CRLSet at generation time.
389           return CRLSet::Result::GOOD;
390         }
391         break;
392     }
393   }
394 
395   // If no certificate was revoked, and the target was not known good, then
396   // the revocation status is still unknown.
397   return CRLSet::Result::UNKNOWN;
398 }
399 
400 }  // namespace net
401