// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/ssl/client_cert_matcher.h" #include #include "base/containers/span.h" #include "base/logging.h" #include "net/cert/asn1_util.h" #include "net/cert/x509_certificate.h" #include "net/cert/x509_util.h" #include "third_party/boringssl/src/include/openssl/pool.h" namespace net { namespace { bool MatchClientCertificateIssuers( X509Certificate* cert, const std::vector& cert_authorities, const ClientCertIssuerSourceCollection& sources, std::vector>* intermediates) { constexpr size_t kMaxDepth = 20; intermediates->clear(); // If the request didn't supply `cert_authorities`, all client certs are // returned. if (cert_authorities.empty()) { return true; } base::span current_issuer; base::span current_subject; if (!asn1::ExtractIssuerAndSubjectFromDERCert( cert->cert_span(), ¤t_issuer, ¤t_subject)) { return false; } while (intermediates->size() < kMaxDepth) { // If the current cert in the chain is issued by one of the names in // `cert_authorities`, this chain matches the request. for (const std::string& authority : cert_authorities) { if (base::as_byte_span(authority) == current_issuer) { return true; } } // If the chain reached a self-issued cert before matching the requested // `cert_authorities`, give up. if (current_issuer == current_subject) { return false; } // Look for an issuer of the current cert. bool found_issuer = false; for (const auto& source : sources) { std::vector> issuers = source->GetCertsByName(current_issuer); for (auto& issuer : issuers) { if (asn1::ExtractIssuerAndSubjectFromDERCert( x509_util::CryptoBufferAsSpan(issuer.get()), ¤t_issuer, ¤t_subject)) { // The first issuer found at each step is used. This algorithm doesn't // do a full graph exploration. found_issuer = true; intermediates->push_back(std::move(issuer)); break; } } } if (!found_issuer) { // No issuers were found, give up. return false; } } return false; } } // namespace void FilterMatchingClientCertIdentities( ClientCertIdentityList* identities, const SSLCertRequestInfo& request, const ClientCertIssuerSourceCollection& sources) { size_t num_raw = 0; auto keep_iter = identities->begin(); base::Time now = base::Time::Now(); for (auto examine_iter = identities->begin(); examine_iter != identities->end(); ++examine_iter) { ++num_raw; X509Certificate* cert = (*examine_iter)->certificate(); // Only offer unexpired certificates. // TODO(https://crbug.com/379943126): If the client system time is // incorrect this may prune certificates that the server would have // accepted (and we may still successfully validate the server certificate // by using secure time). Consider removing. if (now < cert->valid_start()) { DVLOG(2) << "is not yet valid: " << cert->subject().GetDisplayName(); continue; } if (now > cert->valid_expiry()) { DVLOG(2) << "is expired: " << cert->subject().GetDisplayName(); continue; } std::vector> intermediates; if (!MatchClientCertificateIssuers(cert, request.cert_authorities, sources, &intermediates)) { DVLOG(2) << "doesn't match: " << cert->subject().GetDisplayName(); continue; } else { DVLOG(2) << "found a match: " << cert->subject().GetDisplayName(); } // Retain a copy of the intermediates. Some deployments expect the client to // supply intermediates out of the local store. See // https://crbug.com/548631. (*examine_iter)->SetIntermediates(std::move(intermediates)); if (examine_iter == keep_iter) { ++keep_iter; } else { *keep_iter++ = std::move(*examine_iter); } } identities->erase(keep_iter, identities->end()); DVLOG(2) << "num_raw:" << num_raw << " num_filtered:" << identities->size(); std::ranges::sort(*identities, ClientCertIdentitySorter()); } } // namespace net