| // 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_nss.h" |
| |
| #include <nss.h> |
| #include <ssl.h> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/strings/string_piece.h" |
| #include "base/threading/worker_pool.h" |
| #include "crypto/nss_crypto_module_delegate.h" |
| #include "net/cert/x509_util.h" |
| #include "net/ssl/ssl_cert_request_info.h" |
| |
| namespace net { |
| |
| ClientCertStoreNSS::ClientCertStoreNSS( |
| const PasswordDelegateFactory& password_delegate_factory) |
| : password_delegate_factory_(password_delegate_factory) {} |
| |
| ClientCertStoreNSS::~ClientCertStoreNSS() {} |
| |
| void ClientCertStoreNSS::GetClientCerts(const SSLCertRequestInfo& request, |
| CertificateList* selected_certs, |
| const base::Closure& callback) { |
| scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate> password_delegate; |
| if (!password_delegate_factory_.is_null()) { |
| password_delegate.reset( |
| password_delegate_factory_.Run(request.host_and_port)); |
| } |
| if (base::WorkerPool::PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&ClientCertStoreNSS::GetAndFilterCertsOnWorkerThread, |
| // Caller is responsible for keeping the ClientCertStore |
| // alive until the callback is run. |
| base::Unretained(this), base::Passed(&password_delegate), |
| &request, selected_certs), |
| callback, true)) { |
| return; |
| } |
| // If the task could not be posted, behave as if there were no certificates |
| // which requires to clear |selected_certs|. |
| selected_certs->clear(); |
| callback.Run(); |
| } |
| |
| // static |
| void ClientCertStoreNSS::FilterCertsOnWorkerThread( |
| const CertificateList& certs, |
| const SSLCertRequestInfo& request, |
| bool query_nssdb, |
| CertificateList* filtered_certs) { |
| DCHECK(filtered_certs); |
| |
| filtered_certs->clear(); |
| |
| // Create a "fake" CERTDistNames structure. No public API exists to create |
| // one from a list of issuers. |
| CERTDistNames ca_names; |
| ca_names.arena = NULL; |
| ca_names.nnames = 0; |
| ca_names.names = NULL; |
| ca_names.head = NULL; |
| |
| std::vector<SECItem> ca_names_items(request.cert_authorities.size()); |
| for (size_t i = 0; i < request.cert_authorities.size(); ++i) { |
| const std::string& authority = request.cert_authorities[i]; |
| ca_names_items[i].type = siBuffer; |
| ca_names_items[i].data = |
| reinterpret_cast<unsigned char*>(const_cast<char*>(authority.data())); |
| ca_names_items[i].len = static_cast<unsigned int>(authority.size()); |
| } |
| ca_names.nnames = static_cast<int>(ca_names_items.size()); |
| if (!ca_names_items.empty()) |
| ca_names.names = &ca_names_items[0]; |
| |
| size_t num_raw = 0; |
| for (const auto& cert : certs) { |
| ++num_raw; |
| X509Certificate::OSCertHandle handle = cert->os_cert_handle(); |
| |
| // Only offer unexpired certificates. |
| if (CERT_CheckCertValidTimes(handle, PR_Now(), PR_TRUE) != |
| secCertTimeValid) { |
| DVLOG(2) << "skipped expired cert: " |
| << base::StringPiece(handle->nickname); |
| continue; |
| } |
| |
| // Check if the certificate issuer is allowed by the server. |
| if (request.cert_authorities.empty() || |
| (!query_nssdb && cert->IsIssuedByEncoded(request.cert_authorities)) || |
| (query_nssdb && |
| NSS_CmpCertChainWCANames(handle, &ca_names) == SECSuccess)) { |
| DVLOG(2) << "matched cert: " << base::StringPiece(handle->nickname); |
| filtered_certs->push_back(cert); |
| } else { |
| DVLOG(2) << "skipped non-matching cert: " |
| << base::StringPiece(handle->nickname); |
| } |
| } |
| DVLOG(2) << "num_raw:" << num_raw |
| << " num_filtered:" << filtered_certs->size(); |
| |
| std::sort(filtered_certs->begin(), filtered_certs->end(), |
| x509_util::ClientCertSorter()); |
| } |
| |
| void ClientCertStoreNSS::GetAndFilterCertsOnWorkerThread( |
| scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate> password_delegate, |
| const SSLCertRequestInfo* request, |
| CertificateList* selected_certs) { |
| CertificateList platform_certs; |
| GetPlatformCertsOnWorkerThread(password_delegate.Pass(), &platform_certs); |
| FilterCertsOnWorkerThread(platform_certs, *request, true, selected_certs); |
| } |
| |
| // static |
| void ClientCertStoreNSS::GetPlatformCertsOnWorkerThread( |
| scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate> password_delegate, |
| net::CertificateList* certs) { |
| CERTCertList* found_certs = |
| CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient, |
| PR_FALSE, PR_FALSE, password_delegate.get()); |
| if (!found_certs) { |
| DVLOG(2) << "No client certs found."; |
| return; |
| } |
| for (CERTCertListNode* node = CERT_LIST_HEAD(found_certs); |
| !CERT_LIST_END(node, found_certs); node = CERT_LIST_NEXT(node)) { |
| certs->push_back(X509Certificate::CreateFromHandle( |
| node->cert, X509Certificate::OSCertHandles())); |
| } |
| CERT_DestroyCertList(found_certs); |
| } |
| |
| } // namespace net |