// 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 #include "base/bind.h" #include "base/callback.h" #include "base/file_util.h" #include "base/run_loop.h" #include "crypto/nss_util.h" #include "crypto/nss_util_internal.h" #include "crypto/rsa_private_key.h" #include "net/base/test_data_directory.h" #include "net/cert/cert_type.h" #include "net/cert/x509_certificate.h" #include "net/ssl/client_cert_store_unittest-inl.h" #include "net/test/cert_test_util.h" namespace net { namespace { bool ImportClientCertToSlot(const scoped_refptr& cert, PK11SlotInfo* slot) { std::string nickname = cert->GetDefaultNickname(USER_CERT); { crypto::AutoNSSWriteLock lock; SECStatus rv = PK11_ImportCert(slot, cert->os_cert_handle(), CK_INVALID_HANDLE, nickname.c_str(), PR_FALSE); if (rv != SECSuccess) { LOG(ERROR) << "Could not import cert"; return false; } } return true; } } // namespace // Define a delegate to be used for instantiating the parameterized test set // ClientCertStoreTest. class ClientCertStoreChromeOSTestDelegate { public: ClientCertStoreChromeOSTestDelegate() : user_("scopeduser"), store_(user_.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()) { // Defer futher initialization and checks to SelectClientCerts, because the // constructor doesn't allow us to return an initialization result. Could be // cleaned up by adding an Init() function. } // Called by the ClientCertStoreTest tests. // |inpurt_certs| contains certificates to select from. Because // ClientCertStoreChromeOS filters also for the right slot, we have to import // the certs at first. // Since the certs are imported, the store can be tested by using its public // interface (GetClientCerts), which will read the certs from NSS. bool SelectClientCerts(const CertificateList& input_certs, const SSLCertRequestInfo& cert_request_info, CertificateList* selected_certs) { if (!user_.constructed_successfully()) { LOG(ERROR) << "Scoped test user DB could not be constructed."; return false; } user_.FinishInit(); crypto::ScopedPK11Slot slot( crypto::GetPublicSlotForChromeOSUser(user_.username_hash())); if (!slot) { LOG(ERROR) << "Could not get the user's public slot"; return false; } // Only user certs are considered for the cert request, which means that the // private key must be known to NSS. Import all private keys for certs that // are used througout the test. if (!ImportSensitiveKeyFromFile( GetTestCertsDirectory(), "client_1.pk8", slot.get()) || !ImportSensitiveKeyFromFile( GetTestCertsDirectory(), "client_2.pk8", slot.get())) { return false; } for (CertificateList::const_iterator it = input_certs.begin(); it != input_certs.end(); ++it) { if (!ImportClientCertToSlot(*it, slot.get())) return false; } base::RunLoop run_loop; store_.GetClientCerts( cert_request_info, selected_certs, run_loop.QuitClosure()); run_loop.Run(); return true; } private: crypto::ScopedTestNSSChromeOSUser user_; ClientCertStoreChromeOS store_; }; // ClientCertStoreChromeOS derives from ClientCertStoreNSS and delegates the // filtering by issuer to that base class. // To verify that this delegation is functional, run the same filtering tests as // for the other implementations. These tests are defined in // client_cert_store_unittest-inl.h and are instantiated for each platform. INSTANTIATE_TYPED_TEST_CASE_P(ChromeOS, ClientCertStoreTest, ClientCertStoreChromeOSTestDelegate); class ClientCertStoreChromeOSTest : public ::testing::Test { public: scoped_refptr ImportCertForUser( const std::string& username_hash, const std::string& cert_filename, const std::string& key_filename) { crypto::ScopedPK11Slot slot( crypto::GetPublicSlotForChromeOSUser(username_hash)); if (!slot) { LOG(ERROR) << "No slot for user " << username_hash; return NULL; } if (!ImportSensitiveKeyFromFile( GetTestCertsDirectory(), key_filename, slot.get())) { LOG(ERROR) << "Could not import private key for user " << username_hash; return NULL; } scoped_refptr cert( ImportCertFromFile(GetTestCertsDirectory(), cert_filename)); if (!cert) { LOG(ERROR) << "Failed to parse cert from file " << cert_filename; return NULL; } if (!ImportClientCertToSlot(cert, slot.get())) return NULL; // |cert| continues to point to the original X509Certificate before the // import to |slot|. However this should not make a difference for this // test. return cert; } }; // Ensure that cert requests, that are started before the user's NSS DB is // initialized, will wait for the initialization and succeed afterwards. TEST_F(ClientCertStoreChromeOSTest, RequestWaitsForNSSInitAndSucceeds) { crypto::ScopedTestNSSChromeOSUser user("scopeduser"); ASSERT_TRUE(user.constructed_successfully()); ClientCertStoreChromeOS store( user.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); scoped_refptr cert_1( ImportCertForUser(user.username_hash(), "client_1.pem", "client_1.pk8")); ASSERT_TRUE(cert_1); // Request any client certificate, which is expected to match client_1. scoped_refptr request_all(new SSLCertRequestInfo()); base::RunLoop run_loop; store.GetClientCerts( *request_all, &request_all->client_certs, run_loop.QuitClosure()); { base::RunLoop run_loop_inner; run_loop_inner.RunUntilIdle(); // GetClientCerts should wait for the initialization of the user's DB to // finish. ASSERT_EQ(0u, request_all->client_certs.size()); } // This should trigger the GetClientCerts operation to finish and to call // back. user.FinishInit(); run_loop.Run(); ASSERT_EQ(1u, request_all->client_certs.size()); } // Ensure that cert requests, that are started after the user's NSS DB was // initialized, will succeed. TEST_F(ClientCertStoreChromeOSTest, RequestsAfterNSSInitSucceed) { crypto::ScopedTestNSSChromeOSUser user("scopeduser"); ASSERT_TRUE(user.constructed_successfully()); user.FinishInit(); ClientCertStoreChromeOS store( user.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); scoped_refptr cert_1( ImportCertForUser(user.username_hash(), "client_1.pem", "client_1.pk8")); ASSERT_TRUE(cert_1); scoped_refptr request_all(new SSLCertRequestInfo()); base::RunLoop run_loop; store.GetClientCerts( *request_all, &request_all->client_certs, run_loop.QuitClosure()); run_loop.Run(); ASSERT_EQ(1u, request_all->client_certs.size()); } // This verifies that a request in the context of User1 doesn't see certificates // of User2, and the other way round. We check both directions, to ensure that // the behavior doesn't depend on initialization order of the DBs, for example. TEST_F(ClientCertStoreChromeOSTest, RequestDoesCrossReadSecondDB) { crypto::ScopedTestNSSChromeOSUser user1("scopeduser1"); ASSERT_TRUE(user1.constructed_successfully()); crypto::ScopedTestNSSChromeOSUser user2("scopeduser2"); ASSERT_TRUE(user2.constructed_successfully()); user1.FinishInit(); user2.FinishInit(); ClientCertStoreChromeOS store1( user1.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); ClientCertStoreChromeOS store2( user2.username_hash(), ClientCertStoreChromeOS::PasswordDelegateFactory()); scoped_refptr cert_1( ImportCertForUser(user1.username_hash(), "client_1.pem", "client_1.pk8")); ASSERT_TRUE(cert_1); scoped_refptr cert_2( ImportCertForUser(user2.username_hash(), "client_2.pem", "client_2.pk8")); ASSERT_TRUE(cert_2); scoped_refptr request_all(new SSLCertRequestInfo()); base::RunLoop run_loop_1; base::RunLoop run_loop_2; CertificateList selected_certs1, selected_certs2; store1.GetClientCerts( *request_all, &selected_certs1, run_loop_1.QuitClosure()); store2.GetClientCerts( *request_all, &selected_certs2, run_loop_2.QuitClosure()); run_loop_1.Run(); run_loop_2.Run(); // store1 should only return certs of user1, namely cert_1. ASSERT_EQ(1u, selected_certs1.size()); EXPECT_TRUE(cert_1->Equals(selected_certs1[0])); // store2 should only return certs of user2, namely cert_2. ASSERT_EQ(1u, selected_certs2.size()); EXPECT_TRUE(cert_2->Equals(selected_certs2[0])); } } // namespace net