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(), ¤t_issuer, ¤t_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()), ¤t_issuer,
64 ¤t_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