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/cert/nss_cert_database_chromeos.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/message_loop/message_loop_proxy.h"
10#include "base/run_loop.h"
11#include "crypto/nss_util_internal.h"
12#include "crypto/scoped_test_nss_chromeos_user.h"
13#include "crypto/scoped_test_nss_db.h"
14#include "net/base/test_data_directory.h"
15#include "net/cert/cert_database.h"
16#include "net/test/cert_test_util.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace net {
20
21namespace {
22
23bool IsCertInCertificateList(const X509Certificate* cert,
24                             const CertificateList& cert_list) {
25  for (CertificateList::const_iterator it = cert_list.begin();
26       it != cert_list.end();
27       ++it) {
28    if (X509Certificate::IsSameOSCert((*it)->os_cert_handle(),
29                                      cert->os_cert_handle()))
30      return true;
31  }
32  return false;
33}
34
35void SwapCertLists(CertificateList* destination,
36                   scoped_ptr<CertificateList> source) {
37  ASSERT_TRUE(destination);
38  ASSERT_TRUE(source);
39
40  destination->swap(*source);
41}
42
43}  // namespace
44
45class NSSCertDatabaseChromeOSTest : public testing::Test,
46                                    public CertDatabase::Observer {
47 public:
48  NSSCertDatabaseChromeOSTest()
49      : observer_added_(false), user_1_("user1"), user_2_("user2") {}
50
51  virtual void SetUp() OVERRIDE {
52    // Initialize nss_util slots.
53    ASSERT_TRUE(user_1_.constructed_successfully());
54    ASSERT_TRUE(user_2_.constructed_successfully());
55    user_1_.FinishInit();
56    user_2_.FinishInit();
57
58    // Create NSSCertDatabaseChromeOS for each user.
59    db_1_.reset(new NSSCertDatabaseChromeOS(
60        crypto::GetPublicSlotForChromeOSUser(user_1_.username_hash()),
61        crypto::GetPrivateSlotForChromeOSUser(
62            user_1_.username_hash(),
63            base::Callback<void(crypto::ScopedPK11Slot)>())));
64    db_1_->SetSlowTaskRunnerForTest(base::MessageLoopProxy::current());
65    db_1_->SetSystemSlot(
66        crypto::ScopedPK11Slot(PK11_ReferenceSlot(system_db_.slot())));
67    db_2_.reset(new NSSCertDatabaseChromeOS(
68        crypto::GetPublicSlotForChromeOSUser(user_2_.username_hash()),
69        crypto::GetPrivateSlotForChromeOSUser(
70            user_2_.username_hash(),
71            base::Callback<void(crypto::ScopedPK11Slot)>())));
72    db_2_->SetSlowTaskRunnerForTest(base::MessageLoopProxy::current());
73
74    // Add observer to CertDatabase for checking that notifications from
75    // NSSCertDatabaseChromeOS are proxied to the CertDatabase.
76    CertDatabase::GetInstance()->AddObserver(this);
77    observer_added_ = true;
78  }
79
80  virtual void TearDown() OVERRIDE {
81    if (observer_added_)
82      CertDatabase::GetInstance()->RemoveObserver(this);
83  }
84
85  // CertDatabase::Observer:
86  virtual void OnCertAdded(const X509Certificate* cert) OVERRIDE {
87    added_.push_back(cert ? cert->os_cert_handle() : NULL);
88  }
89
90  virtual void OnCertRemoved(const X509Certificate* cert) OVERRIDE {}
91
92  virtual void OnCACertChanged(const X509Certificate* cert) OVERRIDE {
93    added_ca_.push_back(cert ? cert->os_cert_handle() : NULL);
94  }
95
96 protected:
97  bool observer_added_;
98  // Certificates that were passed to the CertDatabase observers.
99  std::vector<CERTCertificate*> added_ca_;
100  std::vector<CERTCertificate*> added_;
101
102  crypto::ScopedTestNSSChromeOSUser user_1_;
103  crypto::ScopedTestNSSChromeOSUser user_2_;
104  crypto::ScopedTestNSSDB system_db_;
105  scoped_ptr<NSSCertDatabaseChromeOS> db_1_;
106  scoped_ptr<NSSCertDatabaseChromeOS> db_2_;
107};
108
109// Test that ListModules() on each user includes that user's NSS software slot,
110// and does not include the software slot of the other user. (Does not check the
111// private slot, since it is the same as the public slot in tests.)
112TEST_F(NSSCertDatabaseChromeOSTest, ListModules) {
113  CryptoModuleList modules_1;
114  CryptoModuleList modules_2;
115
116  db_1_->ListModules(&modules_1, false /* need_rw */);
117  db_2_->ListModules(&modules_2, false /* need_rw */);
118
119  bool found_1 = false;
120  for (CryptoModuleList::iterator it = modules_1.begin(); it != modules_1.end();
121       ++it) {
122    EXPECT_NE(db_2_->GetPublicSlot().get(), (*it)->os_module_handle());
123    if ((*it)->os_module_handle() == db_1_->GetPublicSlot().get())
124      found_1 = true;
125  }
126  EXPECT_TRUE(found_1);
127
128  bool found_2 = false;
129  for (CryptoModuleList::iterator it = modules_2.begin(); it != modules_2.end();
130       ++it) {
131    EXPECT_NE(db_1_->GetPublicSlot().get(), (*it)->os_module_handle());
132    if ((*it)->os_module_handle() == db_2_->GetPublicSlot().get())
133      found_2 = true;
134  }
135  EXPECT_TRUE(found_2);
136}
137
138// Test that ImportCACerts imports the cert to the correct slot, and that
139// ListCerts includes the added cert for the correct user, and does not include
140// it for the other user.
141TEST_F(NSSCertDatabaseChromeOSTest, ImportCACerts) {
142  // Load test certs from disk.
143  CertificateList certs_1 =
144      CreateCertificateListFromFile(GetTestCertsDirectory(),
145                                    "root_ca_cert.pem",
146                                    X509Certificate::FORMAT_AUTO);
147  ASSERT_EQ(1U, certs_1.size());
148
149  CertificateList certs_2 =
150      CreateCertificateListFromFile(GetTestCertsDirectory(),
151                                    "2048-rsa-root.pem",
152                                    X509Certificate::FORMAT_AUTO);
153  ASSERT_EQ(1U, certs_2.size());
154
155  // Import one cert for each user.
156  NSSCertDatabase::ImportCertFailureList failed;
157  EXPECT_TRUE(
158      db_1_->ImportCACerts(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed));
159  EXPECT_EQ(0U, failed.size());
160  failed.clear();
161  EXPECT_TRUE(
162      db_2_->ImportCACerts(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed));
163  EXPECT_EQ(0U, failed.size());
164
165  // Get cert list for each user.
166  CertificateList user_1_certlist;
167  CertificateList user_2_certlist;
168  db_1_->ListCertsSync(&user_1_certlist);
169  db_2_->ListCertsSync(&user_2_certlist);
170
171  // Check that the imported certs only shows up in the list for the user that
172  // imported them.
173  EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist));
174  EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist));
175
176  EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist));
177  EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist));
178
179  // Run the message loop so the observer notifications get processed.
180  base::RunLoop().RunUntilIdle();
181  // Should have gotten two OnCACertChanged notifications.
182  ASSERT_EQ(2U, added_ca_.size());
183  // TODO(mattm): make NSSCertDatabase actually pass the cert to the callback,
184  // and enable these checks:
185  // EXPECT_EQ(certs_1[0]->os_cert_handle(), added_ca_[0]);
186  // EXPECT_EQ(certs_2[0]->os_cert_handle(), added_ca_[1]);
187  EXPECT_EQ(0U, added_.size());
188
189  // Tests that the new certs are loaded by async ListCerts method.
190  CertificateList user_1_certlist_async;
191  CertificateList user_2_certlist_async;
192  db_1_->ListCerts(
193      base::Bind(&SwapCertLists, base::Unretained(&user_1_certlist_async)));
194  db_2_->ListCerts(
195      base::Bind(&SwapCertLists, base::Unretained(&user_2_certlist_async)));
196
197  base::RunLoop().RunUntilIdle();
198
199  EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist_async));
200  EXPECT_FALSE(
201      IsCertInCertificateList(certs_1[0].get(), user_2_certlist_async));
202
203  EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist_async));
204  EXPECT_FALSE(
205      IsCertInCertificateList(certs_2[0].get(), user_1_certlist_async));
206}
207
208// Test that ImportServerCerts imports the cert to the correct slot, and that
209// ListCerts includes the added cert for the correct user, and does not include
210// it for the other user.
211TEST_F(NSSCertDatabaseChromeOSTest, ImportServerCert) {
212  // Load test certs from disk.
213  CertificateList certs_1 = CreateCertificateListFromFile(
214      GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO);
215  ASSERT_EQ(1U, certs_1.size());
216
217  CertificateList certs_2 =
218      CreateCertificateListFromFile(GetTestCertsDirectory(),
219                                    "2048-rsa-ee-by-2048-rsa-intermediate.pem",
220                                    X509Certificate::FORMAT_AUTO);
221  ASSERT_EQ(1U, certs_2.size());
222
223  // Import one cert for each user.
224  NSSCertDatabase::ImportCertFailureList failed;
225  EXPECT_TRUE(
226      db_1_->ImportServerCert(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed));
227  EXPECT_EQ(0U, failed.size());
228  failed.clear();
229  EXPECT_TRUE(
230      db_2_->ImportServerCert(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed));
231  EXPECT_EQ(0U, failed.size());
232
233  // Get cert list for each user.
234  CertificateList user_1_certlist;
235  CertificateList user_2_certlist;
236  db_1_->ListCertsSync(&user_1_certlist);
237  db_2_->ListCertsSync(&user_2_certlist);
238
239  // Check that the imported certs only shows up in the list for the user that
240  // imported them.
241  EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist));
242  EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist));
243
244  EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist));
245  EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist));
246
247  // Run the message loop so the observer notifications get processed.
248  base::RunLoop().RunUntilIdle();
249  // TODO(mattm): ImportServerCert doesn't actually cause any observers to
250  // fire. Is that correct?
251  EXPECT_EQ(0U, added_ca_.size());
252  EXPECT_EQ(0U, added_.size());
253
254  // Tests that the new certs are loaded by async ListCerts method.
255  CertificateList user_1_certlist_async;
256  CertificateList user_2_certlist_async;
257  db_1_->ListCerts(
258      base::Bind(&SwapCertLists, base::Unretained(&user_1_certlist_async)));
259  db_2_->ListCerts(
260      base::Bind(&SwapCertLists, base::Unretained(&user_2_certlist_async)));
261
262  base::RunLoop().RunUntilIdle();
263
264  EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist_async));
265  EXPECT_FALSE(
266      IsCertInCertificateList(certs_1[0].get(), user_2_certlist_async));
267
268  EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist_async));
269  EXPECT_FALSE(
270      IsCertInCertificateList(certs_2[0].get(), user_1_certlist_async));
271}
272
273// Tests that There is no crash if the database is deleted while ListCerts
274// is being processed on the worker pool.
275TEST_F(NSSCertDatabaseChromeOSTest, NoCrashIfShutdownBeforeDoneOnWorkerPool) {
276  CertificateList certlist;
277  db_1_->ListCerts(base::Bind(&SwapCertLists, base::Unretained(&certlist)));
278  EXPECT_EQ(0U, certlist.size());
279
280  db_1_.reset();
281
282  base::RunLoop().RunUntilIdle();
283
284  EXPECT_LT(0U, certlist.size());
285}
286
287TEST_F(NSSCertDatabaseChromeOSTest, ListCertsReadsSystemSlot) {
288  scoped_refptr<X509Certificate> cert_1(
289      ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
290                                     "client_1.pem",
291                                     "client_1.pk8",
292                                     db_1_->GetPublicSlot().get()));
293
294  scoped_refptr<X509Certificate> cert_2(
295      ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
296                                     "client_2.pem",
297                                     "client_2.pk8",
298                                     db_1_->GetSystemSlot().get()));
299  CertificateList certs;
300  db_1_->ListCertsSync(&certs);
301  EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs));
302  EXPECT_TRUE(IsCertInCertificateList(cert_2.get(), certs));
303}
304
305TEST_F(NSSCertDatabaseChromeOSTest, ListCertsDoesNotCrossReadSystemSlot) {
306  scoped_refptr<X509Certificate> cert_1(
307      ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
308                                     "client_1.pem",
309                                     "client_1.pk8",
310                                     db_2_->GetPublicSlot().get()));
311
312  scoped_refptr<X509Certificate> cert_2(
313      ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
314                                     "client_2.pem",
315                                     "client_2.pk8",
316                                     system_db_.slot()));
317  CertificateList certs;
318  db_2_->ListCertsSync(&certs);
319  EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs));
320  EXPECT_FALSE(IsCertInCertificateList(cert_2.get(), certs));
321}
322
323}  // namespace net
324