Index: net/ssl/client_cert_store_chromeos_unittest.cc |
diff --git a/net/ssl/client_cert_store_chromeos_unittest.cc b/net/ssl/client_cert_store_chromeos_unittest.cc |
index 65b668dbb25d21696337db71aeef49b0d5c58ded..4a6a6c35eca3bc88e00d444108688b2ab349d825 100644 |
--- a/net/ssl/client_cert_store_chromeos_unittest.cc |
+++ b/net/ssl/client_cert_store_chromeos_unittest.cc |
@@ -4,94 +4,191 @@ |
#include "net/ssl/client_cert_store_chromeos.h" |
+#include <string> |
+ |
#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 "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<X509Certificate>& 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<X509Certificate> ImportCertForUser( |
const std::string& username_hash, |
- const std::string& filename, |
- const std::string& password) { |
+ const std::string& cert_filename, |
+ const std::string& key_filename) { |
crypto::ScopedPK11Slot slot( |
crypto::GetPublicSlotForChromeOSUser(username_hash)); |
- EXPECT_TRUE(slot.get()); |
- if (!slot.get()) |
+ if (!slot) { |
+ LOG(ERROR) << "No slot for user " << username_hash; |
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); |
+ if (!ImportSensitiveKeyFromFile( |
+ GetTestCertsDirectory(), key_filename, slot.get())) { |
+ LOG(ERROR) << "Could not import private key for user " << username_hash; |
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); |
+ scoped_refptr<X509Certificate> cert( |
+ ImportCertFromFile(GetTestCertsDirectory(), cert_filename)); |
- EXPECT_EQ(0, rv); |
- EXPECT_EQ(1U, cert_list.size()); |
- if (rv || cert_list.size() != 1) |
+ if (!cert) { |
+ LOG(ERROR) << "Failed to parse cert from file " << cert_filename; |
return NULL; |
+ } |
- return cert_list[0]; |
+ 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; |
} |
}; |
-// 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) { |
+// 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<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; |
+ 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<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()); |
+ base::RunLoop run_loop; |
store.GetClientCerts( |
- *request_all, &request_all->client_certs, run_loop_all.QuitClosure()); |
+ *request_all, &request_all->client_certs, run_loop.QuitClosure()); |
- // Callbacks won't be run until nss_util init finishes for the user. |
+ { |
+ 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_1.Run(); |
- run_loop_all.Run(); |
+ run_loop.Run(); |
- ASSERT_EQ(0u, request_1->client_certs.size()); |
- ASSERT_EQ(2u, request_all->client_certs.size()); |
+ ASSERT_EQ(1u, request_all->client_certs.size()); |
} |
-TEST_F(ClientCertStoreChromeOSTest, NSSAlreadyInitialized) { |
+// 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(); |
@@ -99,70 +196,66 @@ TEST_F(ClientCertStoreChromeOSTest, NSSAlreadyInitialized) { |
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; |
+ ImportCertForUser(user.username_hash(), "client_1.pem", "client_1.pk8")); |
+ ASSERT_TRUE(cert_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()); |
+ base::RunLoop run_loop; |
store.GetClientCerts( |
- *request_all, &request_all->client_certs, run_loop_all.QuitClosure()); |
- |
- run_loop_1.Run(); |
- run_loop_all.Run(); |
+ *request_all, &request_all->client_certs, run_loop.QuitClosure()); |
+ run_loop.Run(); |
- ASSERT_EQ(0u, request_1->client_certs.size()); |
- ASSERT_EQ(2u, request_all->client_certs.size()); |
+ ASSERT_EQ(1u, request_all->client_certs.size()); |
} |
-TEST_F(ClientCertStoreChromeOSTest, TwoUsers) { |
+// 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<X509Certificate> cert_1( |
- ImportCertForUser(user1.username_hash(), "client.p12", "12345")); |
- scoped_refptr<X509Certificate> cert_2(ImportCertForUser( |
- user2.username_hash(), "websocket_client_cert.p12", "")); |
+ ImportCertForUser(user1.username_hash(), "client_1.pem", "client_1.pk8")); |
+ ASSERT_TRUE(cert_1); |
+ scoped_refptr<X509Certificate> cert_2( |
+ ImportCertForUser(user2.username_hash(), "client_2.pem", "client_2.pk8")); |
+ ASSERT_TRUE(cert_2); |
- scoped_refptr<SSLCertRequestInfo> request_1(new SSLCertRequestInfo()); |
- scoped_refptr<SSLCertRequestInfo> request_2(new SSLCertRequestInfo()); |
+ scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo()); |
base::RunLoop run_loop_1; |
base::RunLoop run_loop_2; |
+ |
+ CertificateList selected_certs1, selected_certs2; |
store1.GetClientCerts( |
- *request_1, &request_1->client_certs, run_loop_1.QuitClosure()); |
+ *request_all, &selected_certs1, 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(); |
+ *request_all, &selected_certs2, run_loop_2.QuitClosure()); |
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. |
+ // 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 |