• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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/ssl/client_cert_matcher.h"
6 
7 #include <algorithm>
8 
9 #include "base/containers/span.h"
10 #include "base/logging.h"
11 #include "net/cert/asn1_util.h"
12 #include "net/cert/x509_certificate.h"
13 #include "net/cert/x509_util.h"
14 #include "third_party/boringssl/src/include/openssl/pool.h"
15 
16 namespace net {
17 
18 namespace {
19 
MatchClientCertificateIssuers(X509Certificate * cert,const std::vector<std::string> & cert_authorities,const ClientCertIssuerSourceCollection & sources,std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> * intermediates)20 bool MatchClientCertificateIssuers(
21     X509Certificate* cert,
22     const std::vector<std::string>& cert_authorities,
23     const ClientCertIssuerSourceCollection& sources,
24     std::vector<bssl::UniquePtr<CRYPTO_BUFFER>>* intermediates) {
25   constexpr size_t kMaxDepth = 20;
26   intermediates->clear();
27 
28   // If the request didn't supply `cert_authorities`, all client certs are
29   // returned.
30   if (cert_authorities.empty()) {
31     return true;
32   }
33 
34   base::span<const uint8_t> current_issuer;
35   base::span<const uint8_t> current_subject;
36   if (!asn1::ExtractIssuerAndSubjectFromDERCert(
37           cert->cert_span(), &current_issuer, &current_subject)) {
38     return false;
39   }
40 
41   while (intermediates->size() < kMaxDepth) {
42     // If the current cert in the chain is issued by one of the names in
43     // `cert_authorities`, this chain matches the request.
44     for (const std::string& authority : cert_authorities) {
45       if (base::as_byte_span(authority) == current_issuer) {
46         return true;
47       }
48     }
49 
50     // If the chain reached a self-issued cert before matching the requested
51     // `cert_authorities`, give up.
52     if (current_issuer == current_subject) {
53       return false;
54     }
55 
56     // Look for an issuer of the current cert.
57     bool found_issuer = false;
58     for (const auto& source : sources) {
59       std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> issuers =
60           source->GetCertsByName(current_issuer);
61       for (auto& issuer : issuers) {
62         if (asn1::ExtractIssuerAndSubjectFromDERCert(
63                 x509_util::CryptoBufferAsSpan(issuer.get()), &current_issuer,
64                 &current_subject)) {
65           // The first issuer found at each step is used. This algorithm doesn't
66           // do a full graph exploration.
67           found_issuer = true;
68           intermediates->push_back(std::move(issuer));
69           break;
70         }
71       }
72     }
73 
74     if (!found_issuer) {
75       // No issuers were found, give up.
76       return false;
77     }
78   }
79 
80   return false;
81 }
82 
83 }  // namespace
84 
FilterMatchingClientCertIdentities(ClientCertIdentityList * identities,const SSLCertRequestInfo & request,const ClientCertIssuerSourceCollection & sources)85 void FilterMatchingClientCertIdentities(
86     ClientCertIdentityList* identities,
87     const SSLCertRequestInfo& request,
88     const ClientCertIssuerSourceCollection& sources) {
89   size_t num_raw = 0;
90 
91   auto keep_iter = identities->begin();
92 
93   base::Time now = base::Time::Now();
94 
95   for (auto examine_iter = identities->begin();
96        examine_iter != identities->end(); ++examine_iter) {
97     ++num_raw;
98 
99     X509Certificate* cert = (*examine_iter)->certificate();
100 
101     // Only offer unexpired certificates.
102     // TODO(https://crbug.com/379943126): If the client system time is
103     // incorrect this may prune certificates that the server would have
104     // accepted (and we may still successfully validate the server certificate
105     // by using secure time). Consider removing.
106     if (now < cert->valid_start()) {
107       DVLOG(2) << "is not yet valid: " << cert->subject().GetDisplayName();
108       continue;
109     }
110     if (now > cert->valid_expiry()) {
111       DVLOG(2) << "is expired: " << cert->subject().GetDisplayName();
112       continue;
113     }
114 
115     std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates;
116     if (!MatchClientCertificateIssuers(cert, request.cert_authorities, sources,
117                                        &intermediates)) {
118       DVLOG(2) << "doesn't match: " << cert->subject().GetDisplayName();
119       continue;
120     } else {
121       DVLOG(2) << "found a match: " << cert->subject().GetDisplayName();
122     }
123 
124     // Retain a copy of the intermediates. Some deployments expect the client to
125     // supply intermediates out of the local store. See
126     // https://crbug.com/548631.
127     (*examine_iter)->SetIntermediates(std::move(intermediates));
128 
129     if (examine_iter == keep_iter) {
130       ++keep_iter;
131     } else {
132       *keep_iter++ = std::move(*examine_iter);
133     }
134   }
135   identities->erase(keep_iter, identities->end());
136 
137   DVLOG(2) << "num_raw:" << num_raw << " num_filtered:" << identities->size();
138 
139   std::ranges::sort(*identities, ClientCertIdentitySorter());
140 }
141 
142 }  // namespace net
143