| // 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/cert/nss_cert_database_chromeos.h" | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/thread_task_runner_handle.h" | 
 | #include "crypto/nss_util_internal.h" | 
 | #include "crypto/scoped_test_nss_chromeos_user.h" | 
 | #include "crypto/scoped_test_nss_db.h" | 
 | #include "net/base/test_data_directory.h" | 
 | #include "net/cert/cert_database.h" | 
 | #include "net/test/cert_test_util.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | namespace { | 
 |  | 
 | bool IsCertInCertificateList(const X509Certificate* cert, | 
 |                              const CertificateList& cert_list) { | 
 |   for (CertificateList::const_iterator it = cert_list.begin(); | 
 |        it != cert_list.end(); | 
 |        ++it) { | 
 |     if (X509Certificate::IsSameOSCert((*it)->os_cert_handle(), | 
 |                                       cert->os_cert_handle())) | 
 |       return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | void SwapCertLists(CertificateList* destination, | 
 |                    scoped_ptr<CertificateList> source) { | 
 |   ASSERT_TRUE(destination); | 
 |   ASSERT_TRUE(source); | 
 |  | 
 |   destination->swap(*source); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | class NSSCertDatabaseChromeOSTest : public testing::Test, | 
 |                                     public CertDatabase::Observer { | 
 |  public: | 
 |   NSSCertDatabaseChromeOSTest() | 
 |       : observer_added_(false), user_1_("user1"), user_2_("user2") {} | 
 |  | 
 |   void SetUp() override { | 
 |     // Initialize nss_util slots. | 
 |     ASSERT_TRUE(user_1_.constructed_successfully()); | 
 |     ASSERT_TRUE(user_2_.constructed_successfully()); | 
 |     user_1_.FinishInit(); | 
 |     user_2_.FinishInit(); | 
 |  | 
 |     // Create NSSCertDatabaseChromeOS for each user. | 
 |     db_1_.reset(new NSSCertDatabaseChromeOS( | 
 |         crypto::GetPublicSlotForChromeOSUser(user_1_.username_hash()), | 
 |         crypto::GetPrivateSlotForChromeOSUser( | 
 |             user_1_.username_hash(), | 
 |             base::Callback<void(crypto::ScopedPK11Slot)>()))); | 
 |     db_1_->SetSlowTaskRunnerForTest(base::ThreadTaskRunnerHandle::Get()); | 
 |     db_1_->SetSystemSlot( | 
 |         crypto::ScopedPK11Slot(PK11_ReferenceSlot(system_db_.slot()))); | 
 |     db_2_.reset(new NSSCertDatabaseChromeOS( | 
 |         crypto::GetPublicSlotForChromeOSUser(user_2_.username_hash()), | 
 |         crypto::GetPrivateSlotForChromeOSUser( | 
 |             user_2_.username_hash(), | 
 |             base::Callback<void(crypto::ScopedPK11Slot)>()))); | 
 |     db_2_->SetSlowTaskRunnerForTest(base::ThreadTaskRunnerHandle::Get()); | 
 |  | 
 |     // Add observer to CertDatabase for checking that notifications from | 
 |     // NSSCertDatabaseChromeOS are proxied to the CertDatabase. | 
 |     CertDatabase::GetInstance()->AddObserver(this); | 
 |     observer_added_ = true; | 
 |   } | 
 |  | 
 |   void TearDown() override { | 
 |     if (observer_added_) | 
 |       CertDatabase::GetInstance()->RemoveObserver(this); | 
 |   } | 
 |  | 
 |   // CertDatabase::Observer: | 
 |   void OnCertAdded(const X509Certificate* cert) override { | 
 |     added_.push_back(cert ? cert->os_cert_handle() : NULL); | 
 |   } | 
 |  | 
 |   void OnCertRemoved(const X509Certificate* cert) override {} | 
 |  | 
 |   void OnCACertChanged(const X509Certificate* cert) override { | 
 |     added_ca_.push_back(cert ? cert->os_cert_handle() : NULL); | 
 |   } | 
 |  | 
 |  protected: | 
 |   bool observer_added_; | 
 |   // Certificates that were passed to the CertDatabase observers. | 
 |   std::vector<CERTCertificate*> added_ca_; | 
 |   std::vector<CERTCertificate*> added_; | 
 |  | 
 |   crypto::ScopedTestNSSChromeOSUser user_1_; | 
 |   crypto::ScopedTestNSSChromeOSUser user_2_; | 
 |   crypto::ScopedTestNSSDB system_db_; | 
 |   scoped_ptr<NSSCertDatabaseChromeOS> db_1_; | 
 |   scoped_ptr<NSSCertDatabaseChromeOS> db_2_; | 
 | }; | 
 |  | 
 | // Test that ListModules() on each user includes that user's NSS software slot, | 
 | // and does not include the software slot of the other user. (Does not check the | 
 | // private slot, since it is the same as the public slot in tests.) | 
 | TEST_F(NSSCertDatabaseChromeOSTest, ListModules) { | 
 |   CryptoModuleList modules_1; | 
 |   CryptoModuleList modules_2; | 
 |  | 
 |   db_1_->ListModules(&modules_1, false /* need_rw */); | 
 |   db_2_->ListModules(&modules_2, false /* need_rw */); | 
 |  | 
 |   bool found_1 = false; | 
 |   for (CryptoModuleList::iterator it = modules_1.begin(); it != modules_1.end(); | 
 |        ++it) { | 
 |     EXPECT_NE(db_2_->GetPublicSlot().get(), (*it)->os_module_handle()); | 
 |     if ((*it)->os_module_handle() == db_1_->GetPublicSlot().get()) | 
 |       found_1 = true; | 
 |   } | 
 |   EXPECT_TRUE(found_1); | 
 |  | 
 |   bool found_2 = false; | 
 |   for (CryptoModuleList::iterator it = modules_2.begin(); it != modules_2.end(); | 
 |        ++it) { | 
 |     EXPECT_NE(db_1_->GetPublicSlot().get(), (*it)->os_module_handle()); | 
 |     if ((*it)->os_module_handle() == db_2_->GetPublicSlot().get()) | 
 |       found_2 = true; | 
 |   } | 
 |   EXPECT_TRUE(found_2); | 
 | } | 
 |  | 
 | // Test that ImportCACerts imports the cert to the correct slot, and that | 
 | // ListCerts includes the added cert for the correct user, and does not include | 
 | // it for the other user. | 
 | TEST_F(NSSCertDatabaseChromeOSTest, ImportCACerts) { | 
 |   // Load test certs from disk. | 
 |   CertificateList certs_1 = | 
 |       CreateCertificateListFromFile(GetTestCertsDirectory(), | 
 |                                     "root_ca_cert.pem", | 
 |                                     X509Certificate::FORMAT_AUTO); | 
 |   ASSERT_EQ(1U, certs_1.size()); | 
 |  | 
 |   CertificateList certs_2 = | 
 |       CreateCertificateListFromFile(GetTestCertsDirectory(), | 
 |                                     "2048-rsa-root.pem", | 
 |                                     X509Certificate::FORMAT_AUTO); | 
 |   ASSERT_EQ(1U, certs_2.size()); | 
 |  | 
 |   // Import one cert for each user. | 
 |   NSSCertDatabase::ImportCertFailureList failed; | 
 |   EXPECT_TRUE( | 
 |       db_1_->ImportCACerts(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed)); | 
 |   EXPECT_EQ(0U, failed.size()); | 
 |   failed.clear(); | 
 |   EXPECT_TRUE( | 
 |       db_2_->ImportCACerts(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed)); | 
 |   EXPECT_EQ(0U, failed.size()); | 
 |  | 
 |   // Get cert list for each user. | 
 |   CertificateList user_1_certlist; | 
 |   CertificateList user_2_certlist; | 
 |   db_1_->ListCertsSync(&user_1_certlist); | 
 |   db_2_->ListCertsSync(&user_2_certlist); | 
 |  | 
 |   // Check that the imported certs only shows up in the list for the user that | 
 |   // imported them. | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist)); | 
 |   EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist)); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist)); | 
 |   EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist)); | 
 |  | 
 |   // Run the message loop so the observer notifications get processed. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |   // Should have gotten two OnCACertChanged notifications. | 
 |   ASSERT_EQ(2U, added_ca_.size()); | 
 |   // TODO(mattm): make NSSCertDatabase actually pass the cert to the callback, | 
 |   // and enable these checks: | 
 |   // EXPECT_EQ(certs_1[0]->os_cert_handle(), added_ca_[0]); | 
 |   // EXPECT_EQ(certs_2[0]->os_cert_handle(), added_ca_[1]); | 
 |   EXPECT_EQ(0U, added_.size()); | 
 |  | 
 |   // Tests that the new certs are loaded by async ListCerts method. | 
 |   CertificateList user_1_certlist_async; | 
 |   CertificateList user_2_certlist_async; | 
 |   db_1_->ListCerts( | 
 |       base::Bind(&SwapCertLists, base::Unretained(&user_1_certlist_async))); | 
 |   db_2_->ListCerts( | 
 |       base::Bind(&SwapCertLists, base::Unretained(&user_2_certlist_async))); | 
 |  | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist_async)); | 
 |   EXPECT_FALSE( | 
 |       IsCertInCertificateList(certs_1[0].get(), user_2_certlist_async)); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist_async)); | 
 |   EXPECT_FALSE( | 
 |       IsCertInCertificateList(certs_2[0].get(), user_1_certlist_async)); | 
 | } | 
 |  | 
 | // Test that ImportServerCerts imports the cert to the correct slot, and that | 
 | // ListCerts includes the added cert for the correct user, and does not include | 
 | // it for the other user. | 
 | TEST_F(NSSCertDatabaseChromeOSTest, ImportServerCert) { | 
 |   // Load test certs from disk. | 
 |   CertificateList certs_1 = CreateCertificateListFromFile( | 
 |       GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO); | 
 |   ASSERT_EQ(1U, certs_1.size()); | 
 |  | 
 |   CertificateList certs_2 = | 
 |       CreateCertificateListFromFile(GetTestCertsDirectory(), | 
 |                                     "2048-rsa-ee-by-2048-rsa-intermediate.pem", | 
 |                                     X509Certificate::FORMAT_AUTO); | 
 |   ASSERT_EQ(1U, certs_2.size()); | 
 |  | 
 |   // Import one cert for each user. | 
 |   NSSCertDatabase::ImportCertFailureList failed; | 
 |   EXPECT_TRUE( | 
 |       db_1_->ImportServerCert(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed)); | 
 |   EXPECT_EQ(0U, failed.size()); | 
 |   failed.clear(); | 
 |   EXPECT_TRUE( | 
 |       db_2_->ImportServerCert(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed)); | 
 |   EXPECT_EQ(0U, failed.size()); | 
 |  | 
 |   // Get cert list for each user. | 
 |   CertificateList user_1_certlist; | 
 |   CertificateList user_2_certlist; | 
 |   db_1_->ListCertsSync(&user_1_certlist); | 
 |   db_2_->ListCertsSync(&user_2_certlist); | 
 |  | 
 |   // Check that the imported certs only shows up in the list for the user that | 
 |   // imported them. | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist)); | 
 |   EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist)); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist)); | 
 |   EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist)); | 
 |  | 
 |   // Run the message loop so the observer notifications get processed. | 
 |   base::RunLoop().RunUntilIdle(); | 
 |   // TODO(mattm): ImportServerCert doesn't actually cause any observers to | 
 |   // fire. Is that correct? | 
 |   EXPECT_EQ(0U, added_ca_.size()); | 
 |   EXPECT_EQ(0U, added_.size()); | 
 |  | 
 |   // Tests that the new certs are loaded by async ListCerts method. | 
 |   CertificateList user_1_certlist_async; | 
 |   CertificateList user_2_certlist_async; | 
 |   db_1_->ListCerts( | 
 |       base::Bind(&SwapCertLists, base::Unretained(&user_1_certlist_async))); | 
 |   db_2_->ListCerts( | 
 |       base::Bind(&SwapCertLists, base::Unretained(&user_2_certlist_async))); | 
 |  | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist_async)); | 
 |   EXPECT_FALSE( | 
 |       IsCertInCertificateList(certs_1[0].get(), user_2_certlist_async)); | 
 |  | 
 |   EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist_async)); | 
 |   EXPECT_FALSE( | 
 |       IsCertInCertificateList(certs_2[0].get(), user_1_certlist_async)); | 
 | } | 
 |  | 
 | // Tests that There is no crash if the database is deleted while ListCerts | 
 | // is being processed on the worker pool. | 
 | TEST_F(NSSCertDatabaseChromeOSTest, NoCrashIfShutdownBeforeDoneOnWorkerPool) { | 
 |   CertificateList certlist; | 
 |   db_1_->ListCerts(base::Bind(&SwapCertLists, base::Unretained(&certlist))); | 
 |   EXPECT_EQ(0U, certlist.size()); | 
 |  | 
 |   db_1_.reset(); | 
 |  | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   EXPECT_LT(0U, certlist.size()); | 
 | } | 
 |  | 
 | TEST_F(NSSCertDatabaseChromeOSTest, ListCertsReadsSystemSlot) { | 
 |   scoped_refptr<X509Certificate> cert_1( | 
 |       ImportClientCertAndKeyFromFile(GetTestCertsDirectory(), | 
 |                                      "client_1.pem", | 
 |                                      "client_1.pk8", | 
 |                                      db_1_->GetPublicSlot().get())); | 
 |  | 
 |   scoped_refptr<X509Certificate> cert_2( | 
 |       ImportClientCertAndKeyFromFile(GetTestCertsDirectory(), | 
 |                                      "client_2.pem", | 
 |                                      "client_2.pk8", | 
 |                                      db_1_->GetSystemSlot().get())); | 
 |   CertificateList certs; | 
 |   db_1_->ListCertsSync(&certs); | 
 |   EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs)); | 
 |   EXPECT_TRUE(IsCertInCertificateList(cert_2.get(), certs)); | 
 | } | 
 |  | 
 | TEST_F(NSSCertDatabaseChromeOSTest, ListCertsDoesNotCrossReadSystemSlot) { | 
 |   scoped_refptr<X509Certificate> cert_1( | 
 |       ImportClientCertAndKeyFromFile(GetTestCertsDirectory(), | 
 |                                      "client_1.pem", | 
 |                                      "client_1.pk8", | 
 |                                      db_2_->GetPublicSlot().get())); | 
 |  | 
 |   scoped_refptr<X509Certificate> cert_2( | 
 |       ImportClientCertAndKeyFromFile(GetTestCertsDirectory(), | 
 |                                      "client_2.pem", | 
 |                                      "client_2.pk8", | 
 |                                      system_db_.slot())); | 
 |   CertificateList certs; | 
 |   db_2_->ListCertsSync(&certs); | 
 |   EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs)); | 
 |   EXPECT_FALSE(IsCertInCertificateList(cert_2.get(), certs)); | 
 | } | 
 |  | 
 | }  // namespace net |