123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 |
- // Copyright 2013 The Chromium Authors. All rights reserved.
- // 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_store_mac.h"
- #include <CommonCrypto/CommonDigest.h>
- #include <CoreFoundation/CFArray.h>
- #include <CoreServices/CoreServices.h>
- #include <Security/SecBase.h>
- #include <Security/Security.h>
- #include <algorithm>
- #include <functional>
- #include <memory>
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback.h"
- #include "base/callback_helpers.h"
- #include "base/logging.h"
- #include "base/mac/mac_logging.h"
- #include "base/mac/scoped_cftyperef.h"
- #include "base/strings/sys_string_conversions.h"
- #include "base/synchronization/lock.h"
- #include "base/task/task_runner_util.h"
- #include "crypto/mac_security_services_lock.h"
- #include "net/base/host_port_pair.h"
- #include "net/cert/pki/extended_key_usage.h"
- #include "net/cert/pki/parse_certificate.h"
- #include "net/cert/x509_util.h"
- #include "net/cert/x509_util_apple.h"
- #include "net/ssl/client_cert_identity_mac.h"
- #include "net/ssl/ssl_platform_key_util.h"
- using base::ScopedCFTypeRef;
- namespace net {
- namespace {
- using ClientCertIdentityMacList =
- std::vector<std::unique_ptr<ClientCertIdentityMac>>;
- // Gets the issuer for a given cert, starting with the cert itself and
- // including the intermediate and finally root certificates (if any).
- // This function calls SecTrust but doesn't actually pay attention to the trust
- // result: it shouldn't be used to determine trust, just to traverse the chain.
- OSStatus CopyCertChain(SecCertificateRef cert_handle,
- base::ScopedCFTypeRef<CFArrayRef>* out_cert_chain) {
- DCHECK(cert_handle);
- DCHECK(out_cert_chain);
- // Create an SSL policy ref configured for client cert evaluation.
- ScopedCFTypeRef<SecPolicyRef> ssl_policy(
- SecPolicyCreateSSL(/*server=*/false, /*hostname=*/nullptr));
- if (!ssl_policy)
- return errSecNoPolicyModule;
- // Create a SecTrustRef.
- ScopedCFTypeRef<CFArrayRef> input_certs(CFArrayCreate(
- nullptr, const_cast<const void**>(reinterpret_cast<void**>(&cert_handle)),
- 1, &kCFTypeArrayCallBacks));
- OSStatus result;
- SecTrustRef trust_ref = nullptr;
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- result = SecTrustCreateWithCertificates(input_certs, ssl_policy,
- &trust_ref);
- }
- if (result)
- return result;
- ScopedCFTypeRef<SecTrustRef> trust(trust_ref);
- // Evaluate trust, which creates the cert chain.
- SecTrustResultType status;
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- result = SecTrustEvaluate(trust, &status);
- if (result)
- return result;
- *out_cert_chain = x509_util::CertificateChainFromSecTrust(trust);
- }
- return result;
- }
- // Returns true if |*identity| is issued by an authority in |valid_issuers|
- // according to Keychain Services, rather than using |identity|'s intermediate
- // certificates. If it is, |*identity| is updated to include the intermediates.
- bool IsIssuedByInKeychain(const std::vector<std::string>& valid_issuers,
- ClientCertIdentityMac* identity) {
- DCHECK(identity);
- DCHECK(identity->sec_identity_ref());
- ScopedCFTypeRef<SecCertificateRef> os_cert;
- int err = SecIdentityCopyCertificate(identity->sec_identity_ref(),
- os_cert.InitializeInto());
- if (err != noErr)
- return false;
- base::ScopedCFTypeRef<CFArrayRef> cert_chain;
- OSStatus result = CopyCertChain(os_cert.get(), &cert_chain);
- if (result) {
- OSSTATUS_LOG(ERROR, result) << "CopyCertChain error";
- return false;
- }
- if (!cert_chain)
- return false;
- std::vector<base::ScopedCFTypeRef<SecCertificateRef>> intermediates;
- for (CFIndex i = 1, chain_count = CFArrayGetCount(cert_chain);
- i < chain_count; ++i) {
- SecCertificateRef sec_cert = reinterpret_cast<SecCertificateRef>(
- const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i)));
- intermediates.emplace_back(sec_cert, base::scoped_policy::RETAIN);
- }
- // Allow UTF-8 inside PrintableStrings in client certificates. See
- // crbug.com/770323.
- X509Certificate::UnsafeCreateOptions options;
- options.printable_string_is_utf8 = true;
- scoped_refptr<X509Certificate> new_cert(
- x509_util::CreateX509CertificateFromSecCertificate(os_cert, intermediates,
- options));
- if (!new_cert || !new_cert->IsIssuedByEncoded(valid_issuers))
- return false;
- std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediate_buffers;
- intermediate_buffers.reserve(new_cert->intermediate_buffers().size());
- for (const auto& intermediate : new_cert->intermediate_buffers()) {
- intermediate_buffers.push_back(bssl::UpRef(intermediate.get()));
- }
- identity->SetIntermediates(std::move(intermediate_buffers));
- return true;
- }
- // Does |cert|'s usage allow SSL client authentication?
- bool SupportsSSLClientAuth(CRYPTO_BUFFER* cert) {
- DCHECK(cert);
- ParseCertificateOptions options;
- options.allow_invalid_serial_numbers = true;
- der::Input tbs_certificate_tlv;
- der::Input signature_algorithm_tlv;
- der::BitString signature_value;
- ParsedTbsCertificate tbs;
- if (!ParseCertificate(
- der::Input(CRYPTO_BUFFER_data(cert), CRYPTO_BUFFER_len(cert)),
- &tbs_certificate_tlv, &signature_algorithm_tlv, &signature_value,
- nullptr /* errors*/) ||
- !ParseTbsCertificate(tbs_certificate_tlv, options, &tbs,
- nullptr /*errors*/)) {
- return false;
- }
- if (!tbs.extensions_tlv)
- return true;
- std::map<der::Input, ParsedExtension> extensions;
- if (!ParseExtensions(tbs.extensions_tlv.value(), &extensions))
- return false;
- // RFC5280 says to take the intersection of the two extensions.
- //
- // We only support signature-based client certificates, so we need the
- // digitalSignature bit.
- //
- // In particular, if a key has the nonRepudiation bit and not the
- // digitalSignature one, we will not offer it to the user.
- if (auto it = extensions.find(der::Input(kKeyUsageOid));
- it != extensions.end()) {
- der::BitString key_usage;
- if (!ParseKeyUsage(it->second.value, &key_usage) ||
- !key_usage.AssertsBit(KEY_USAGE_BIT_DIGITAL_SIGNATURE)) {
- return false;
- }
- }
- if (auto it = extensions.find(der::Input(kExtKeyUsageOid));
- it != extensions.end()) {
- std::vector<der::Input> extended_key_usage;
- if (!ParseEKUExtension(it->second.value, &extended_key_usage))
- return false;
- bool found_acceptable_eku = false;
- for (const auto& oid : extended_key_usage) {
- if (oid == der::Input(kAnyEKU) || oid == der::Input(kClientAuth)) {
- found_acceptable_eku = true;
- break;
- }
- }
- if (!found_acceptable_eku)
- return false;
- }
- return true;
- }
- // Examines the certificates in |preferred_identity| and |regular_identities| to
- // find all certificates that match the client certificate request in |request|,
- // storing the matching certificates in |selected_identities|.
- // If |query_keychain| is true, Keychain Services will be queried to construct
- // full certificate chains. If it is false, only the the certificates and their
- // intermediates (available via X509Certificate::intermediate_buffers())
- // will be considered.
- void GetClientCertsImpl(
- std::unique_ptr<ClientCertIdentityMac> preferred_identity,
- ClientCertIdentityMacList regular_identities,
- const SSLCertRequestInfo& request,
- bool query_keychain,
- ClientCertIdentityList* selected_identities) {
- scoped_refptr<X509Certificate> preferred_cert_orig;
- ClientCertIdentityMacList preliminary_list = std::move(regular_identities);
- if (preferred_identity) {
- preferred_cert_orig = preferred_identity->certificate();
- preliminary_list.insert(preliminary_list.begin(),
- std::move(preferred_identity));
- }
- selected_identities->clear();
- for (size_t i = 0; i < preliminary_list.size(); ++i) {
- std::unique_ptr<ClientCertIdentityMac>& cert = preliminary_list[i];
- if (cert->certificate()->HasExpired() ||
- !SupportsSSLClientAuth(cert->certificate()->cert_buffer())) {
- continue;
- }
- // Skip duplicates (a cert may be in multiple keychains).
- auto cert_iter = std::find_if(
- selected_identities->begin(), selected_identities->end(),
- [&cert](
- const std::unique_ptr<ClientCertIdentity>& other_cert_identity) {
- return x509_util::CryptoBufferEqual(
- cert->certificate()->cert_buffer(),
- other_cert_identity->certificate()->cert_buffer());
- });
- if (cert_iter != selected_identities->end())
- continue;
- // Check if the certificate issuer is allowed by the server.
- if (request.cert_authorities.empty() ||
- cert->certificate()->IsIssuedByEncoded(request.cert_authorities) ||
- (query_keychain &&
- IsIssuedByInKeychain(request.cert_authorities, cert.get()))) {
- selected_identities->push_back(std::move(cert));
- }
- }
- // Preferred cert should appear first in the ui, so exclude it from the
- // sorting. Compare the cert_buffer since the X509Certificate object may
- // have changed if intermediates were added.
- ClientCertIdentityList::iterator sort_begin = selected_identities->begin();
- ClientCertIdentityList::iterator sort_end = selected_identities->end();
- if (preferred_cert_orig && sort_begin != sort_end &&
- x509_util::CryptoBufferEqual(
- sort_begin->get()->certificate()->cert_buffer(),
- preferred_cert_orig->cert_buffer())) {
- ++sort_begin;
- }
- sort(sort_begin, sort_end, ClientCertIdentitySorter());
- }
- // Given a |sec_identity|, identifies its corresponding certificate, and either
- // adds it to |regular_identities| or assigns it to |preferred_identity|, if the
- // |sec_identity| matches the |preferred_sec_identity|.
- void AddIdentity(ScopedCFTypeRef<SecIdentityRef> sec_identity,
- SecIdentityRef preferred_sec_identity,
- ClientCertIdentityMacList* regular_identities,
- std::unique_ptr<ClientCertIdentityMac>* preferred_identity) {
- OSStatus err;
- ScopedCFTypeRef<SecCertificateRef> cert_handle;
- err = SecIdentityCopyCertificate(sec_identity.get(),
- cert_handle.InitializeInto());
- if (err != noErr)
- return;
- // Allow UTF-8 inside PrintableStrings in client certificates. See
- // crbug.com/770323.
- X509Certificate::UnsafeCreateOptions options;
- options.printable_string_is_utf8 = true;
- scoped_refptr<X509Certificate> cert(
- x509_util::CreateX509CertificateFromSecCertificate(cert_handle, {},
- options));
- if (!cert)
- return;
- if (preferred_sec_identity &&
- CFEqual(preferred_sec_identity, sec_identity.get())) {
- *preferred_identity = std::make_unique<ClientCertIdentityMac>(
- std::move(cert), std::move(sec_identity));
- } else {
- regular_identities->push_back(std::make_unique<ClientCertIdentityMac>(
- std::move(cert), std::move(sec_identity)));
- }
- }
- ClientCertIdentityList GetClientCertsOnBackgroundThread(
- const SSLCertRequestInfo& request) {
- std::string server_domain = request.host_and_port.host();
- ScopedCFTypeRef<SecIdentityRef> preferred_sec_identity;
- if (!server_domain.empty()) {
- // See if there's an identity preference for this domain:
- ScopedCFTypeRef<CFStringRef> domain_str(
- base::SysUTF8ToCFStringRef("https://" + server_domain));
- // While SecIdentityCopyPreferred appears to take a list of CA issuers
- // to restrict the identity search to, within Security.framework the
- // argument is ignored and filtering unimplemented. See SecIdentity.cpp in
- // libsecurity_keychain, specifically
- // _SecIdentityCopyPreferenceMatchingName().
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- preferred_sec_identity.reset(
- SecIdentityCopyPreferred(domain_str, nullptr, nullptr));
- }
- }
- // Now enumerate the identities in the available keychains.
- std::unique_ptr<ClientCertIdentityMac> preferred_identity;
- ClientCertIdentityMacList regular_identities;
- // TODO(https://crbug.com/1348251): Is it still true, as claimed below, that
- // SecIdentitySearchCopyNext sometimes returns identities missed by
- // SecItemCopyMatching? Add some histograms to test this and, if none are
- // missing, remove this code.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- SecIdentitySearchRef search = nullptr;
- OSStatus err;
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- err = SecIdentitySearchCreate(nullptr, CSSM_KEYUSE_SIGN, &search);
- }
- if (err)
- return ClientCertIdentityList();
- ScopedCFTypeRef<SecIdentitySearchRef> scoped_search(search);
- while (!err) {
- ScopedCFTypeRef<SecIdentityRef> sec_identity;
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- err = SecIdentitySearchCopyNext(search, sec_identity.InitializeInto());
- }
- if (err)
- break;
- AddIdentity(std::move(sec_identity), preferred_sec_identity.get(),
- ®ular_identities, &preferred_identity);
- }
- if (err != errSecItemNotFound) {
- OSSTATUS_LOG(ERROR, err) << "SecIdentitySearch error";
- return ClientCertIdentityList();
- }
- #pragma clang diagnostic pop // "-Wdeprecated-declarations"
- // macOS provides two ways to search for identities. SecIdentitySearchCreate()
- // is deprecated, as it relies on CSSM_KEYUSE_SIGN (part of the deprecated
- // CDSM/CSSA implementation), but is necessary to return some certificates
- // that would otherwise not be returned by SecItemCopyMatching(), which is the
- // non-deprecated way. However, SecIdentitySearchCreate() will not return all
- // items, particularly smart-card based identities, so it's necessary to call
- // both functions.
- static const void* kKeys[] = {
- kSecClass, kSecMatchLimit, kSecReturnRef, kSecAttrCanSign,
- };
- static const void* kValues[] = {
- kSecClassIdentity, kSecMatchLimitAll, kCFBooleanTrue, kCFBooleanTrue,
- };
- ScopedCFTypeRef<CFDictionaryRef> query(CFDictionaryCreate(
- kCFAllocatorDefault, kKeys, kValues, std::size(kValues),
- &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
- ScopedCFTypeRef<CFArrayRef> result;
- {
- base::AutoLock lock(crypto::GetMacSecurityServicesLock());
- err = SecItemCopyMatching(
- query, reinterpret_cast<CFTypeRef*>(result.InitializeInto()));
- }
- if (!err) {
- for (CFIndex i = 0; i < CFArrayGetCount(result); i++) {
- SecIdentityRef item = reinterpret_cast<SecIdentityRef>(
- const_cast<void*>(CFArrayGetValueAtIndex(result, i)));
- AddIdentity(
- ScopedCFTypeRef<SecIdentityRef>(item, base::scoped_policy::RETAIN),
- preferred_sec_identity.get(), ®ular_identities,
- &preferred_identity);
- }
- }
- ClientCertIdentityList selected_identities;
- GetClientCertsImpl(std::move(preferred_identity),
- std::move(regular_identities), request, true,
- &selected_identities);
- return selected_identities;
- }
- } // namespace
- ClientCertStoreMac::ClientCertStoreMac() = default;
- ClientCertStoreMac::~ClientCertStoreMac() = default;
- void ClientCertStoreMac::GetClientCerts(const SSLCertRequestInfo& request,
- ClientCertListCallback callback) {
- base::PostTaskAndReplyWithResult(
- GetSSLPlatformKeyTaskRunner().get(), FROM_HERE,
- // Caller is responsible for keeping the |request| alive
- // until the callback is run, so std::cref is safe.
- base::BindOnce(&GetClientCertsOnBackgroundThread, std::cref(request)),
- std::move(callback));
- }
- bool ClientCertStoreMac::SelectClientCertsForTesting(
- ClientCertIdentityMacList input_identities,
- const SSLCertRequestInfo& request,
- ClientCertIdentityList* selected_identities) {
- GetClientCertsImpl(nullptr, std::move(input_identities), request, false,
- selected_identities);
- return true;
- }
- bool ClientCertStoreMac::SelectClientCertsGivenPreferredForTesting(
- std::unique_ptr<ClientCertIdentityMac> preferred_identity,
- ClientCertIdentityMacList regular_identities,
- const SSLCertRequestInfo& request,
- ClientCertIdentityList* selected_identities) {
- GetClientCertsImpl(std::move(preferred_identity),
- std::move(regular_identities), request, false,
- selected_identities);
- return true;
- }
- } // namespace net
|