1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/ssl/client_cert_store_chromeos.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/callback.h"
11#include "base/files/file_util.h"
12#include "base/run_loop.h"
13#include "crypto/nss_util_internal.h"
14#include "crypto/rsa_private_key.h"
15#include "crypto/scoped_test_nss_chromeos_user.h"
16#include "crypto/scoped_test_system_nss_key_slot.h"
17#include "net/base/test_data_directory.h"
18#include "net/cert/x509_certificate.h"
19#include "net/ssl/client_cert_store_unittest-inl.h"
20#include "net/test/cert_test_util.h"
21
22namespace net {
23
24namespace {
25
26enum ReadFromSlot {
27  READ_FROM_SLOT_USER,
28  READ_FROM_SLOT_SYSTEM
29};
30
31enum SystemSlotAvailability {
32  SYSTEM_SLOT_AVAILABILITY_ENABLED,
33  SYSTEM_SLOT_AVAILABILITY_DISABLED
34};
35
36}  // namespace
37
38// Define a delegate to be used for instantiating the parameterized test set
39// ClientCertStoreTest.
40template <ReadFromSlot read_from,
41          SystemSlotAvailability system_slot_availability>
42class ClientCertStoreChromeOSTestDelegate {
43 public:
44  ClientCertStoreChromeOSTestDelegate()
45      : user_("scopeduser"),
46        store_(system_slot_availability == SYSTEM_SLOT_AVAILABILITY_ENABLED,
47               user_.username_hash(),
48               ClientCertStoreChromeOS::PasswordDelegateFactory()) {
49    // Defer futher initialization and checks to SelectClientCerts, because the
50    // constructor doesn't allow us to return an initialization result. Could be
51    // cleaned up by adding an Init() function.
52  }
53
54  // Called by the ClientCertStoreTest tests.
55  // |inpurt_certs| contains certificates to select from. Because
56  // ClientCertStoreChromeOS filters also for the right slot, we have to import
57  // the certs at first.
58  // Since the certs are imported, the store can be tested by using its public
59  // interface (GetClientCerts), which will read the certs from NSS.
60  bool SelectClientCerts(const CertificateList& input_certs,
61                         const SSLCertRequestInfo& cert_request_info,
62                         CertificateList* selected_certs) {
63    if (!user_.constructed_successfully()) {
64      LOG(ERROR) << "Scoped test user DB could not be constructed.";
65      return false;
66    }
67    user_.FinishInit();
68
69    crypto::ScopedPK11Slot slot;
70    switch (read_from) {
71      case READ_FROM_SLOT_USER:
72        slot = crypto::GetPublicSlotForChromeOSUser(user_.username_hash());
73        break;
74      case READ_FROM_SLOT_SYSTEM:
75        slot.reset(PK11_ReferenceSlot(system_db_.slot()));
76        break;
77      default:
78        CHECK(false);
79    }
80    if (!slot) {
81      LOG(ERROR) << "Could not get the NSS key slot";
82      return false;
83    }
84
85    // Only user certs are considered for the cert request, which means that the
86    // private key must be known to NSS. Import all private keys for certs that
87    // are used througout the test.
88    if (!ImportSensitiveKeyFromFile(
89            GetTestCertsDirectory(), "client_1.pk8", slot.get()) ||
90        !ImportSensitiveKeyFromFile(
91            GetTestCertsDirectory(), "client_2.pk8", slot.get())) {
92      return false;
93    }
94
95    for (CertificateList::const_iterator it = input_certs.begin();
96         it != input_certs.end();
97         ++it) {
98      if (!ImportClientCertToSlot(*it, slot.get()))
99        return false;
100    }
101    base::RunLoop run_loop;
102    store_.GetClientCerts(
103        cert_request_info, selected_certs, run_loop.QuitClosure());
104    run_loop.Run();
105    return true;
106  }
107
108 private:
109  crypto::ScopedTestNSSChromeOSUser user_;
110  crypto::ScopedTestSystemNSSKeySlot system_db_;
111  ClientCertStoreChromeOS store_;
112};
113
114// ClientCertStoreChromeOS derives from ClientCertStoreNSS and delegates the
115// filtering by issuer to that base class.
116// To verify that this delegation is functional, run the same filtering tests as
117// for the other implementations. These tests are defined in
118// client_cert_store_unittest-inl.h and are instantiated for each platform.
119
120// In this case, all requested certs are read from the user's slot and the
121// system slot is not enabled in the store.
122typedef ClientCertStoreChromeOSTestDelegate<READ_FROM_SLOT_USER,
123                                            SYSTEM_SLOT_AVAILABILITY_DISABLED>
124    DelegateReadUserDisableSystem;
125INSTANTIATE_TYPED_TEST_CASE_P(ChromeOS_ReadUserDisableSystem,
126                              ClientCertStoreTest,
127                              DelegateReadUserDisableSystem);
128
129// In this case, all requested certs are read from the user's slot and the
130// system slot is enabled in the store.
131typedef ClientCertStoreChromeOSTestDelegate<READ_FROM_SLOT_USER,
132                                            SYSTEM_SLOT_AVAILABILITY_ENABLED>
133    DelegateReadUserEnableSystem;
134INSTANTIATE_TYPED_TEST_CASE_P(ChromeOS_ReadUserEnableSystem,
135                              ClientCertStoreTest,
136                              DelegateReadUserEnableSystem);
137
138// In this case, all requested certs are read from the system slot, therefore
139// the system slot is enabled in the store.
140typedef ClientCertStoreChromeOSTestDelegate<READ_FROM_SLOT_SYSTEM,
141                                            SYSTEM_SLOT_AVAILABILITY_ENABLED>
142    DelegateReadSystem;
143INSTANTIATE_TYPED_TEST_CASE_P(ChromeOS_ReadSystem,
144                              ClientCertStoreTest,
145                              DelegateReadSystem);
146
147class ClientCertStoreChromeOSTest : public ::testing::Test {
148 public:
149  scoped_refptr<X509Certificate> ImportCertForUser(
150      const std::string& username_hash,
151      const std::string& cert_filename,
152      const std::string& key_filename) {
153    crypto::ScopedPK11Slot slot(
154        crypto::GetPublicSlotForChromeOSUser(username_hash));
155    if (!slot) {
156      LOG(ERROR) << "No slot for user " << username_hash;
157      return NULL;
158    }
159
160    return ImportClientCertAndKeyFromFile(
161        GetTestCertsDirectory(), cert_filename, key_filename, slot.get());
162  }
163
164};
165
166// Ensure that cert requests, that are started before the user's NSS DB is
167// initialized, will wait for the initialization and succeed afterwards.
168TEST_F(ClientCertStoreChromeOSTest, RequestWaitsForNSSInitAndSucceeds) {
169  crypto::ScopedTestNSSChromeOSUser user("scopeduser");
170  ASSERT_TRUE(user.constructed_successfully());
171
172  crypto::ScopedTestSystemNSSKeySlot system_slot;
173
174  ClientCertStoreChromeOS store(
175      true /* use system slot */,
176      user.username_hash(),
177      ClientCertStoreChromeOS::PasswordDelegateFactory());
178  scoped_refptr<X509Certificate> cert_1(
179      ImportCertForUser(user.username_hash(), "client_1.pem", "client_1.pk8"));
180  ASSERT_TRUE(cert_1.get());
181
182  // Request any client certificate, which is expected to match client_1.
183  scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo());
184
185  base::RunLoop run_loop;
186  store.GetClientCerts(
187      *request_all, &request_all->client_certs, run_loop.QuitClosure());
188
189  {
190    base::RunLoop run_loop_inner;
191    run_loop_inner.RunUntilIdle();
192    // GetClientCerts should wait for the initialization of the user's DB to
193    // finish.
194    ASSERT_EQ(0u, request_all->client_certs.size());
195  }
196  // This should trigger the GetClientCerts operation to finish and to call
197  // back.
198  user.FinishInit();
199
200  run_loop.Run();
201
202  ASSERT_EQ(1u, request_all->client_certs.size());
203}
204
205// Ensure that cert requests, that are started after the user's NSS DB was
206// initialized, will succeed.
207TEST_F(ClientCertStoreChromeOSTest, RequestsAfterNSSInitSucceed) {
208  crypto::ScopedTestNSSChromeOSUser user("scopeduser");
209  ASSERT_TRUE(user.constructed_successfully());
210  user.FinishInit();
211
212  crypto::ScopedTestSystemNSSKeySlot system_slot;
213
214  ClientCertStoreChromeOS store(
215      true /* use system slot */,
216      user.username_hash(),
217      ClientCertStoreChromeOS::PasswordDelegateFactory());
218  scoped_refptr<X509Certificate> cert_1(
219      ImportCertForUser(user.username_hash(), "client_1.pem", "client_1.pk8"));
220  ASSERT_TRUE(cert_1.get());
221
222  scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo());
223
224  base::RunLoop run_loop;
225  store.GetClientCerts(
226      *request_all, &request_all->client_certs, run_loop.QuitClosure());
227  run_loop.Run();
228
229  ASSERT_EQ(1u, request_all->client_certs.size());
230}
231
232// This verifies that a request in the context of User1 doesn't see certificates
233// of User2, and the other way round. We check both directions, to ensure that
234// the behavior doesn't depend on initialization order of the DBs, for example.
235TEST_F(ClientCertStoreChromeOSTest, RequestDoesCrossReadOtherUserDB) {
236  crypto::ScopedTestNSSChromeOSUser user1("scopeduser1");
237  ASSERT_TRUE(user1.constructed_successfully());
238  crypto::ScopedTestNSSChromeOSUser user2("scopeduser2");
239  ASSERT_TRUE(user2.constructed_successfully());
240
241  user1.FinishInit();
242  user2.FinishInit();
243
244  crypto::ScopedTestSystemNSSKeySlot system_slot;
245
246  ClientCertStoreChromeOS store1(
247      true /* use system slot */,
248      user1.username_hash(),
249      ClientCertStoreChromeOS::PasswordDelegateFactory());
250  ClientCertStoreChromeOS store2(
251      true /* use system slot */,
252      user2.username_hash(),
253      ClientCertStoreChromeOS::PasswordDelegateFactory());
254
255  scoped_refptr<X509Certificate> cert_1(
256      ImportCertForUser(user1.username_hash(), "client_1.pem", "client_1.pk8"));
257  ASSERT_TRUE(cert_1.get());
258  scoped_refptr<X509Certificate> cert_2(
259      ImportCertForUser(user2.username_hash(), "client_2.pem", "client_2.pk8"));
260  ASSERT_TRUE(cert_2.get());
261
262  scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo());
263
264  base::RunLoop run_loop_1;
265  base::RunLoop run_loop_2;
266
267  CertificateList selected_certs1, selected_certs2;
268  store1.GetClientCerts(
269      *request_all, &selected_certs1, run_loop_1.QuitClosure());
270  store2.GetClientCerts(
271      *request_all, &selected_certs2, run_loop_2.QuitClosure());
272
273  run_loop_1.Run();
274  run_loop_2.Run();
275
276  // store1 should only return certs of user1, namely cert_1.
277  ASSERT_EQ(1u, selected_certs1.size());
278  EXPECT_TRUE(cert_1->Equals(selected_certs1[0].get()));
279
280  // store2 should only return certs of user2, namely cert_2.
281  ASSERT_EQ(1u, selected_certs2.size());
282  EXPECT_TRUE(cert_2->Equals(selected_certs2[0].get()));
283}
284
285// This verifies that a request in the context of User1 doesn't see certificates
286// of the system store if the system store is disabled.
287TEST_F(ClientCertStoreChromeOSTest, RequestDoesCrossReadSystemDB) {
288  crypto::ScopedTestNSSChromeOSUser user1("scopeduser1");
289  ASSERT_TRUE(user1.constructed_successfully());
290
291  user1.FinishInit();
292
293  crypto::ScopedTestSystemNSSKeySlot system_slot;
294
295  ClientCertStoreChromeOS store(
296      false /* do not use system slot */,
297      user1.username_hash(),
298      ClientCertStoreChromeOS::PasswordDelegateFactory());
299
300  scoped_refptr<X509Certificate> cert_1(
301      ImportCertForUser(user1.username_hash(), "client_1.pem", "client_1.pk8"));
302  ASSERT_TRUE(cert_1.get());
303  scoped_refptr<X509Certificate> cert_2(
304      ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
305                                     "client_2.pem",
306                                     "client_2.pk8",
307                                     system_slot.slot()));
308  ASSERT_TRUE(cert_2.get());
309
310  scoped_refptr<SSLCertRequestInfo> request_all(new SSLCertRequestInfo());
311
312  base::RunLoop run_loop;
313
314  CertificateList selected_certs;
315  store.GetClientCerts(*request_all, &selected_certs, run_loop.QuitClosure());
316
317  run_loop.Run();
318
319  // store should only return certs of the user, namely cert_1.
320  ASSERT_EQ(1u, selected_certs.size());
321  EXPECT_TRUE(cert_1->Equals(selected_certs[0].get()));
322}
323
324}  // namespace net
325