diff options
author | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 00:09:00 +0000 |
---|---|---|
committer | mattm@chromium.org <mattm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 00:09:00 +0000 |
commit | e53c02329933022491988cb58f58423f7a18ba61 (patch) | |
tree | f87bb5be9cabe9d03a370496ab71419e3a43fe88 /net/ssl | |
parent | 61e2b8591c6940552fdac1bed5c8787e99f30d97 (diff) | |
download | chromium_src-e53c02329933022491988cb58f58423f7a18ba61.zip chromium_src-e53c02329933022491988cb58f58423f7a18ba61.tar.gz chromium_src-e53c02329933022491988cb58f58423f7a18ba61.tar.bz2 |
Add ClientCertStoreChromeOS which only returns the certs for a given user.
BUG=302125
Review URL: https://codereview.chromium.org/112533002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241080 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ssl')
-rw-r--r-- | net/ssl/client_cert_store.h | 3 | ||||
-rw-r--r-- | net/ssl/client_cert_store_chromeos.cc | 94 | ||||
-rw-r--r-- | net/ssl/client_cert_store_chromeos.h | 66 | ||||
-rw-r--r-- | net/ssl/client_cert_store_chromeos_unittest.cc | 193 | ||||
-rw-r--r-- | net/ssl/client_cert_store_nss.cc | 88 | ||||
-rw-r--r-- | net/ssl/client_cert_store_nss.h | 19 |
6 files changed, 421 insertions, 42 deletions
diff --git a/net/ssl/client_cert_store.h b/net/ssl/client_cert_store.h index 394d774..fe050e5 100644 --- a/net/ssl/client_cert_store.h +++ b/net/ssl/client_cert_store.h @@ -23,7 +23,8 @@ class NET_EXPORT ClientCertStore { // Get client certs matching the |cert_request_info|. On completion, the // results will be stored in |selected_certs| and the |callback| will be run. // The |callback| may be called sychronously. The caller must ensure the - // |selected_certs| object remains alive until the callback has been run. + // ClientCertStore and the |selected_certs| object remain alive until the + // callback has been run. virtual void GetClientCerts(const SSLCertRequestInfo& cert_request_info, CertificateList* selected_certs, const base::Closure& callback) = 0; diff --git a/net/ssl/client_cert_store_chromeos.cc b/net/ssl/client_cert_store_chromeos.cc new file mode 100644 index 0000000..f357797 --- /dev/null +++ b/net/ssl/client_cert_store_chromeos.cc @@ -0,0 +1,94 @@ +// 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_chromeos.h" + +#include <cert.h> + +#include "base/bind.h" +#include "crypto/nss_crypto_module_delegate.h" +#include "crypto/nss_util_internal.h" + +namespace net { + +ClientCertStoreChromeOS::ClientCertStoreChromeOS( + const std::string& username_hash, + const PasswordDelegateFactory& password_delegate_factory) + : ClientCertStoreNSS(password_delegate_factory), + username_hash_(username_hash) {} + +ClientCertStoreChromeOS::~ClientCertStoreChromeOS() {} + +void ClientCertStoreChromeOS::GetClientCerts( + const SSLCertRequestInfo& cert_request_info, + CertificateList* selected_certs, + const base::Closure& callback) { + crypto::ScopedPK11Slot private_slot(crypto::GetPrivateSlotForChromeOSUser( + username_hash_, + base::Bind(&ClientCertStoreChromeOS::DidGetPrivateSlot, + // Caller is responsible for keeping the ClientCertStore alive + // until the callback is run. + base::Unretained(this), + &cert_request_info, + selected_certs, + callback))); + if (private_slot) + DidGetPrivateSlot( + &cert_request_info, selected_certs, callback, private_slot.Pass()); +} + +void ClientCertStoreChromeOS::GetClientCertsImpl(CERTCertList* cert_list, + const SSLCertRequestInfo& request, + bool query_nssdb, + CertificateList* selected_certs) { + ClientCertStoreNSS::GetClientCertsImpl( + cert_list, request, query_nssdb, selected_certs); + + size_t pre_size = selected_certs->size(); + selected_certs->erase( + std::remove_if( + selected_certs->begin(), + selected_certs->end(), + NSSProfileFilterChromeOS::CertNotAllowedForProfilePredicate( + profile_filter_)), + selected_certs->end()); + DVLOG(1) << "filtered " << pre_size - selected_certs->size() << " of " + << pre_size << " certs"; +} + +void ClientCertStoreChromeOS::DidGetPrivateSlot( + const SSLCertRequestInfo* request, + CertificateList* selected_certs, + const base::Closure& callback, + crypto::ScopedPK11Slot private_slot) { + profile_filter_.Init(crypto::GetPublicSlotForChromeOSUser(username_hash_), + private_slot.Pass()); + ClientCertStoreNSS::GetClientCerts(*request, selected_certs, callback); +} + +void ClientCertStoreChromeOS::InitForTesting( + crypto::ScopedPK11Slot public_slot, + crypto::ScopedPK11Slot private_slot) { + profile_filter_.Init(public_slot.Pass(), private_slot.Pass()); +} + +bool ClientCertStoreChromeOS::SelectClientCertsForTesting( + const CertificateList& input_certs, + const SSLCertRequestInfo& request, + CertificateList* selected_certs) { + CERTCertList* cert_list = CERT_NewCertList(); + if (!cert_list) + return false; + for (size_t i = 0; i < input_certs.size(); ++i) { + CERT_AddCertToListTail( + cert_list, CERT_DupCertificate(input_certs[i]->os_cert_handle())); + } + + GetClientCertsImpl(cert_list, request, false, selected_certs); + CERT_DestroyCertList(cert_list); + return true; +} + + +} // namespace net diff --git a/net/ssl/client_cert_store_chromeos.h b/net/ssl/client_cert_store_chromeos.h new file mode 100644 index 0000000..41339f1 --- /dev/null +++ b/net/ssl/client_cert_store_chromeos.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef NET_SSL_CLIENT_CERT_STORE_CHROMEOS_H_ +#define NET_SSL_CLIENT_CERT_STORE_CHROMEOS_H_ + +#include <string> + +#include "crypto/scoped_nss_types.h" +#include "net/cert/nss_profile_filter_chromeos.h" +#include "net/ssl/client_cert_store_nss.h" + +namespace net { + +class NET_EXPORT ClientCertStoreChromeOS : public ClientCertStoreNSS { + public: + ClientCertStoreChromeOS( + const std::string& username_hash, + const PasswordDelegateFactory& password_delegate_factory); + virtual ~ClientCertStoreChromeOS(); + + // ClientCertStoreNSS: + virtual void GetClientCerts(const SSLCertRequestInfo& cert_request_info, + CertificateList* selected_certs, + const base::Closure& callback) OVERRIDE; + + protected: + // ClientCertStoreNSS: + virtual void GetClientCertsImpl(CERTCertList* cert_list, + const SSLCertRequestInfo& request, + bool query_nssdb, + CertificateList* selected_certs) OVERRIDE; + + private: + friend class ClientCertStoreChromeOSTestDelegate; + + void DidGetPrivateSlot(const SSLCertRequestInfo* request, + CertificateList* selected_certs, + const base::Closure& callback, + crypto::ScopedPK11Slot private_slot); + + // Allows tests to initialize the cert store with the given slots. + // Must be called before SelectClientCertsForTesting. + void InitForTesting(crypto::ScopedPK11Slot public_slot, + crypto::ScopedPK11Slot private_slot); + + // A hook for testing. Filters |input_certs| using the logic being used to + // filter the system store when GetClientCerts() is called. + // Implemented by creating a list of certificates that otherwise would be + // extracted from the system store and filtering it using the common logic + // (less adequate than the approach used on Windows). + bool SelectClientCertsForTesting(const CertificateList& input_certs, + const SSLCertRequestInfo& cert_request_info, + CertificateList* selected_certs); + + + std::string username_hash_; + NSSProfileFilterChromeOS profile_filter_; + + DISALLOW_COPY_AND_ASSIGN(ClientCertStoreChromeOS); +}; + +} // namespace net + +#endif // NET_SSL_CLIENT_CERT_STORE_CHROMEOS_H_ diff --git a/net/ssl/client_cert_store_chromeos_unittest.cc b/net/ssl/client_cert_store_chromeos_unittest.cc new file mode 100644 index 0000000..73e0326 --- /dev/null +++ b/net/ssl/client_cert_store_chromeos_unittest.cc @@ -0,0 +1,193 @@ +// 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_chromeos.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "net/cert/nss_cert_database.h" +#include "net/ssl/client_cert_store_unittest-inl.h" + +namespace net { + +class ClientCertStoreChromeOSTestDelegate { + public: + ClientCertStoreChromeOSTestDelegate() + : store_("usernamehash", + ClientCertStoreChromeOS::PasswordDelegateFactory()) { + store_.InitForTesting( + crypto::ScopedPK11Slot(crypto::GetPublicNSSKeySlot()), + crypto::ScopedPK11Slot(crypto::GetPrivateNSSKeySlot())); + } + + bool SelectClientCerts(const CertificateList& input_certs, + const SSLCertRequestInfo& cert_request_info, + CertificateList* selected_certs) { + return store_.SelectClientCertsForTesting( + input_certs, cert_request_info, selected_certs); + } + + private: + ClientCertStoreChromeOS store_; +}; + +INSTANTIATE_TYPED_TEST_CASE_P(ChromeOS, + ClientCertStoreTest, + ClientCertStoreChromeOSTestDelegate); + +class ClientCertStoreChromeOSTest : public ::testing::Test { + public: + scoped_refptr<X509Certificate> ImportCertForUser( + const std::string& username_hash, + const std::string& filename, + const std::string& password) { + crypto::ScopedPK11Slot slot( + crypto::GetPublicSlotForChromeOSUser(username_hash)); + EXPECT_TRUE(slot.get()); + if (!slot.get()) + return NULL; + + net::CertificateList cert_list; + + base::FilePath p12_path = GetTestCertsDirectory().AppendASCII(filename); + std::string p12_data; + if (!base::ReadFileToString(p12_path, &p12_data)) { + EXPECT_TRUE(false); + return NULL; + } + + scoped_refptr<net::CryptoModule> module( + net::CryptoModule::CreateFromHandle(slot.get())); + int rv = NSSCertDatabase::GetInstance()->ImportFromPKCS12( + module.get(), p12_data, base::UTF8ToUTF16(password), false, &cert_list); + + EXPECT_EQ(0, rv); + EXPECT_EQ(1U, cert_list.size()); + if (rv || cert_list.size() != 1) + return NULL; + + return cert_list[0]; + } +}; + +// TODO(mattm): Do better testing of cert_authorities matching below. Update +// net/data/ssl/scripts/generate-client-certificates.sh so that it actually +// saves the .p12 files, and regenerate them. + +TEST_F(ClientCertStoreChromeOSTest, WaitForNSSInit) { + crypto::ScopedTestNSSChromeOSUser user("scopeduser"); + ASSERT_TRUE(user.constructed_successfully()); + ClientCertStoreChromeOS store( + user.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); + scoped_refptr<X509Certificate> cert_1( + ImportCertForUser(user.username_hash(), "client.p12", "12345")); + scoped_refptr<X509Certificate> cert_2( + ImportCertForUser(user.username_hash(), "websocket_client_cert.p12", "")); + + std::vector<std::string> authority_1( + 1, + std::string(reinterpret_cast<const char*>(kAuthority1DN), + sizeof(kAuthority1DN))); + scoped_refptr<SSLCertRequestInfo> request_1(new SSLCertRequestInfo()); + request_1->cert_authorities = authority_1; + + scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo()); + + base::RunLoop run_loop_1; + base::RunLoop run_loop_all; + store.GetClientCerts( + *request_1, &request_1->client_certs, run_loop_1.QuitClosure()); + store.GetClientCerts( + *request_all, &request_all->client_certs, run_loop_all.QuitClosure()); + + // Callbacks won't be run until nss_util init finishes for the user. + user.FinishInit(); + + run_loop_1.Run(); + run_loop_all.Run(); + + ASSERT_EQ(0u, request_1->client_certs.size()); + ASSERT_EQ(2u, request_all->client_certs.size()); +} + +TEST_F(ClientCertStoreChromeOSTest, NSSAlreadyInitialized) { + crypto::ScopedTestNSSChromeOSUser user("scopeduser"); + ASSERT_TRUE(user.constructed_successfully()); + user.FinishInit(); + + ClientCertStoreChromeOS store( + user.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); + scoped_refptr<X509Certificate> cert_1( + ImportCertForUser(user.username_hash(), "client.p12", "12345")); + scoped_refptr<X509Certificate> cert_2( + ImportCertForUser(user.username_hash(), "websocket_client_cert.p12", "")); + + std::vector<std::string> authority_1( + 1, + std::string(reinterpret_cast<const char*>(kAuthority1DN), + sizeof(kAuthority1DN))); + scoped_refptr<SSLCertRequestInfo> request_1(new SSLCertRequestInfo()); + request_1->cert_authorities = authority_1; + + scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo()); + + base::RunLoop run_loop_1; + base::RunLoop run_loop_all; + store.GetClientCerts( + *request_1, &request_1->client_certs, run_loop_1.QuitClosure()); + store.GetClientCerts( + *request_all, &request_all->client_certs, run_loop_all.QuitClosure()); + + run_loop_1.Run(); + run_loop_all.Run(); + + ASSERT_EQ(0u, request_1->client_certs.size()); + ASSERT_EQ(2u, request_all->client_certs.size()); +} + +TEST_F(ClientCertStoreChromeOSTest, TwoUsers) { + crypto::ScopedTestNSSChromeOSUser user1("scopeduser1"); + ASSERT_TRUE(user1.constructed_successfully()); + crypto::ScopedTestNSSChromeOSUser user2("scopeduser2"); + ASSERT_TRUE(user2.constructed_successfully()); + ClientCertStoreChromeOS store1( + user1.username_hash(), + ClientCertStoreChromeOS::PasswordDelegateFactory()); + ClientCertStoreChromeOS store2( + user2.username_hash(), + ClientCertStoreChromeOS::PasswordDelegateFactory()); + scoped_refptr<X509Certificate> cert_1( + ImportCertForUser(user1.username_hash(), "client.p12", "12345")); + scoped_refptr<X509Certificate> cert_2(ImportCertForUser( + user2.username_hash(), "websocket_client_cert.p12", "")); + + scoped_refptr<SSLCertRequestInfo> request_1(new SSLCertRequestInfo()); + scoped_refptr<SSLCertRequestInfo> request_2(new SSLCertRequestInfo()); + + base::RunLoop run_loop_1; + base::RunLoop run_loop_2; + store1.GetClientCerts( + *request_1, &request_1->client_certs, run_loop_1.QuitClosure()); + store2.GetClientCerts( + *request_2, &request_2->client_certs, run_loop_2.QuitClosure()); + + // Callbacks won't be run until nss_util init finishes for the user. + user1.FinishInit(); + user2.FinishInit(); + + run_loop_1.Run(); + run_loop_2.Run(); + + ASSERT_EQ(1u, request_1->client_certs.size()); + EXPECT_TRUE(cert_1->Equals(request_1->client_certs[0])); + // TODO(mattm): Request for second user will have zero results due to + // crbug.com/315285. Update the test once that is fixed. +} + +} // namespace net diff --git a/net/ssl/client_cert_store_nss.cc b/net/ssl/client_cert_store_nss.cc index 9df5612..1b44bce 100644 --- a/net/ssl/client_cert_store_nss.cc +++ b/net/ssl/client_cert_store_nss.cc @@ -11,23 +11,47 @@ #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" namespace net { -namespace { - -// Examines the certificates in |cert_list| to find all certificates that match -// the client certificate request in |request|, storing the matching -// certificates in |selected_certs|. -// If |query_nssdb| is true, NSS will be queried to construct full certificate -// chains. If it is false, only the certificate will be considered. -void GetClientCertsImpl(CERTCertList* cert_list, - const SSLCertRequestInfo& request, - bool query_nssdb, - CertificateList* selected_certs) { +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::GetClientCertsOnWorkerThread, + // 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; + selected_certs->clear(); + callback.Run(); +} + +void ClientCertStoreNSS::GetClientCertsImpl(CERTCertList* cert_list, + const SSLCertRequestInfo& request, + bool query_nssdb, + CertificateList* selected_certs) { DCHECK(cert_list); DCHECK(selected_certs); @@ -53,12 +77,16 @@ void GetClientCertsImpl(CERTCertList* cert_list, if (!ca_names_items.empty()) ca_names.names = &ca_names_items[0]; + size_t num_raw = 0; for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { + ++num_raw; // Only offer unexpired certificates. if (CERT_CheckCertValidTimes(node->cert, PR_Now(), PR_TRUE) != secCertTimeValid) { + DVLOG(2) << "skipped expired cert: " + << base::StringPiece(node->cert->nickname); continue; } @@ -71,15 +99,21 @@ void GetClientCertsImpl(CERTCertList* cert_list, cert->IsIssuedByEncoded(request.cert_authorities)) || (query_nssdb && NSS_CmpCertChainWCANames(node->cert, &ca_names) == SECSuccess)) { + DVLOG(2) << "matched cert: " << base::StringPiece(node->cert->nickname); selected_certs->push_back(cert); } + else + DVLOG(2) << "skipped non-matching cert: " + << base::StringPiece(node->cert->nickname); } + DVLOG(2) << "num_raw:" << num_raw + << " num_selected:" << selected_certs->size(); std::sort(selected_certs->begin(), selected_certs->end(), x509_util::ClientCertSorter()); } -void GetClientCertsOnWorkerThread( +void ClientCertStoreNSS::GetClientCertsOnWorkerThread( scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate> password_delegate, const SSLCertRequestInfo* request, CertificateList* selected_certs) { @@ -91,6 +125,7 @@ void GetClientCertsOnWorkerThread( password_delegate.get()); // It is ok for a user not to have any client certs. if (!client_certs) { + DVLOG(2) << "No client certs found."; selected_certs->clear(); return; } @@ -99,35 +134,6 @@ void GetClientCertsOnWorkerThread( CERT_DestroyCertList(client_certs); } -} // namespace - -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(&GetClientCertsOnWorkerThread, - base::Passed(&password_delegate), - &request, - selected_certs), - callback, - true)) { - selected_certs->clear(); - callback.Run(); - } -} - bool ClientCertStoreNSS::SelectClientCertsForTesting( const CertificateList& input_certs, const SSLCertRequestInfo& request, diff --git a/net/ssl/client_cert_store_nss.h b/net/ssl/client_cert_store_nss.h index 53b7c60..c2a9611 100644 --- a/net/ssl/client_cert_store_nss.h +++ b/net/ssl/client_cert_store_nss.h @@ -12,6 +12,8 @@ #include "net/ssl/client_cert_store.h" #include "net/ssl/ssl_cert_request_info.h" +typedef struct CERTCertListStr CERTCertList; + namespace crypto { class CryptoModuleBlockingPasswordDelegate; } @@ -32,9 +34,26 @@ class NET_EXPORT ClientCertStoreNSS : public ClientCertStore { CertificateList* selected_certs, const base::Closure& callback) OVERRIDE; + protected: + // Examines the certificates in |cert_list| to find all certificates that + // match the client certificate request in |request|, storing the matching + // certificates in |selected_certs|. + // If |query_nssdb| is true, NSS will be queried to construct full certificate + // chains. If it is false, only the certificate will be considered. + virtual void GetClientCertsImpl(CERTCertList* cert_list, + const SSLCertRequestInfo& request, + bool query_nssdb, + CertificateList* selected_certs); + private: friend class ClientCertStoreNSSTestDelegate; + void GetClientCertsOnWorkerThread( + scoped_ptr<crypto::CryptoModuleBlockingPasswordDelegate> + password_delegate, + const SSLCertRequestInfo* request, + CertificateList* selected_certs); + // A hook for testing. Filters |input_certs| using the logic being used to // filter the system store when GetClientCerts() is called. // Implemented by creating a list of certificates that otherwise would be |