blob: 4a6a6c35eca3bc88e00d444108688b2ab349d825 [file] [log] [blame]
// 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 <string>
#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<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& 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<X509Certificate> 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<X509Certificate> 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<SSLCertRequestInfo> 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<X509Certificate> cert_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;
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<X509Certificate> cert_1(
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_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