1 // Copyright 2013 The Chromium Authors
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 <memory>
8
9 #include "base/functional/bind.h"
10 #include "base/functional/callback.h"
11 #include "base/run_loop.h"
12 #include "crypto/nss_util_internal.h"
13 #include "crypto/scoped_test_nss_chromeos_user.h"
14 #include "crypto/scoped_test_nss_db.h"
15 #include "net/cert/cert_database.h"
16 #include "net/cert/x509_util_nss.h"
17 #include "net/test/cert_builder.h"
18 #include "net/test/cert_test_util.h"
19 #include "net/test/test_data_directory.h"
20 #include "net/test/test_with_task_environment.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 namespace net {
24
25 namespace {
26
IsCertInCertificateList(const X509Certificate * cert,const ScopedCERTCertificateList & cert_list)27 bool IsCertInCertificateList(const X509Certificate* cert,
28 const ScopedCERTCertificateList& cert_list) {
29 for (const auto& other : cert_list) {
30 if (x509_util::IsSameCertificate(other.get(), cert))
31 return true;
32 }
33 return false;
34 }
35
IsCertInCertificateList(CERTCertificate * cert,const ScopedCERTCertificateList & cert_list)36 bool IsCertInCertificateList(CERTCertificate* cert,
37 const ScopedCERTCertificateList& cert_list) {
38 for (const auto& other : cert_list) {
39 if (x509_util::IsSameCertificate(other.get(), cert))
40 return true;
41 }
42 return false;
43 }
44
SwapCertLists(ScopedCERTCertificateList * destination,ScopedCERTCertificateList source)45 void SwapCertLists(ScopedCERTCertificateList* destination,
46 ScopedCERTCertificateList source) {
47 ASSERT_TRUE(destination);
48
49 destination->swap(source);
50 }
51
52 } // namespace
53
54 class NSSCertDatabaseChromeOSTest : public TestWithTaskEnvironment,
55 public CertDatabase::Observer {
56 public:
NSSCertDatabaseChromeOSTest()57 NSSCertDatabaseChromeOSTest() : user_1_("user1"), user_2_("user2") {}
58
SetUp()59 void SetUp() override {
60 // Initialize nss_util slots.
61 ASSERT_TRUE(user_1_.constructed_successfully());
62 ASSERT_TRUE(user_2_.constructed_successfully());
63 user_1_.FinishInit();
64 user_2_.FinishInit();
65
66 // Create NSSCertDatabaseChromeOS for each user.
67 db_1_ = std::make_unique<NSSCertDatabaseChromeOS>(
68 crypto::GetPublicSlotForChromeOSUser(user_1_.username_hash()),
69 crypto::GetPrivateSlotForChromeOSUser(
70 user_1_.username_hash(),
71 base::OnceCallback<void(crypto::ScopedPK11Slot)>()));
72 db_1_->SetSystemSlot(
73 crypto::ScopedPK11Slot(PK11_ReferenceSlot(system_db_.slot())));
74 db_2_ = std::make_unique<NSSCertDatabaseChromeOS>(
75 crypto::GetPublicSlotForChromeOSUser(user_2_.username_hash()),
76 crypto::GetPrivateSlotForChromeOSUser(
77 user_2_.username_hash(),
78 base::OnceCallback<void(crypto::ScopedPK11Slot)>()));
79
80 // Add observer to CertDatabase for checking that notifications from
81 // NSSCertDatabaseChromeOS are proxied to the CertDatabase.
82 CertDatabase::GetInstance()->AddObserver(this);
83 observer_added_ = true;
84 }
85
TearDown()86 void TearDown() override {
87 if (observer_added_)
88 CertDatabase::GetInstance()->RemoveObserver(this);
89 }
90
91 // CertDatabase::Observer:
OnTrustStoreChanged()92 void OnTrustStoreChanged() override { trust_store_changed_count_++; }
OnClientCertStoreChanged()93 void OnClientCertStoreChanged() override { client_cert_changed_count_++; }
94
95 protected:
96 bool observer_added_ = false;
97 int trust_store_changed_count_ = 0;
98 int client_cert_changed_count_ = 0;
99
100 crypto::ScopedTestNSSChromeOSUser user_1_;
101 crypto::ScopedTestNSSChromeOSUser user_2_;
102 crypto::ScopedTestNSSDB system_db_;
103 std::unique_ptr<NSSCertDatabaseChromeOS> db_1_;
104 std::unique_ptr<NSSCertDatabaseChromeOS> db_2_;
105 };
106
107 // Test that ListModules() on each user includes that user's NSS software slot,
108 // and does not include the software slot of the other user. (Does not check the
109 // private slot, since it is the same as the public slot in tests.)
TEST_F(NSSCertDatabaseChromeOSTest,ListModules)110 TEST_F(NSSCertDatabaseChromeOSTest, ListModules) {
111 std::vector<crypto::ScopedPK11Slot> modules_1;
112 std::vector<crypto::ScopedPK11Slot> modules_2;
113
114 db_1_->ListModules(&modules_1, false /* need_rw */);
115 db_2_->ListModules(&modules_2, false /* need_rw */);
116
117 bool found_1 = false;
118 for (std::vector<crypto::ScopedPK11Slot>::iterator it = modules_1.begin();
119 it != modules_1.end(); ++it) {
120 EXPECT_NE(db_2_->GetPublicSlot().get(), (*it).get());
121 if ((*it).get() == db_1_->GetPublicSlot().get())
122 found_1 = true;
123 }
124 EXPECT_TRUE(found_1);
125
126 bool found_2 = false;
127 for (std::vector<crypto::ScopedPK11Slot>::iterator it = modules_2.begin();
128 it != modules_2.end(); ++it) {
129 EXPECT_NE(db_1_->GetPublicSlot().get(), (*it).get());
130 if ((*it).get() == db_2_->GetPublicSlot().get())
131 found_2 = true;
132 }
133 EXPECT_TRUE(found_2);
134 }
135
136 // Test that ImportCACerts imports the cert to the correct slot, and that
137 // ListCerts includes the added cert for the correct user, and does not include
138 // it for the other user.
TEST_F(NSSCertDatabaseChromeOSTest,ImportCACerts)139 TEST_F(NSSCertDatabaseChromeOSTest, ImportCACerts) {
140 // Load test certs from disk.
141 ScopedCERTCertificateList certs_1 = CreateCERTCertificateListFromFile(
142 GetTestCertsDirectory(), "root_ca_cert.pem",
143 X509Certificate::FORMAT_AUTO);
144 ASSERT_EQ(1U, certs_1.size());
145
146 auto [leaf2, root2] = CertBuilder::CreateSimpleChain2();
147 ScopedCERTCertificateList certs_2 =
148 x509_util::CreateCERTCertificateListFromX509Certificate(
149 root2->GetX509Certificate().get());
150 ASSERT_EQ(1U, certs_2.size());
151
152 // Import one cert for each user.
153 NSSCertDatabase::ImportCertFailureList failed;
154 EXPECT_TRUE(
155 db_1_->ImportCACerts(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed));
156 EXPECT_EQ(0U, failed.size());
157 failed.clear();
158 EXPECT_TRUE(
159 db_2_->ImportCACerts(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed));
160 EXPECT_EQ(0U, failed.size());
161
162 // Get cert list for each user.
163 ScopedCERTCertificateList user_1_certlist;
164 ScopedCERTCertificateList user_2_certlist;
165 db_1_->ListCerts(
166 base::BindOnce(&SwapCertLists, base::Unretained(&user_1_certlist)));
167 db_2_->ListCerts(
168 base::BindOnce(&SwapCertLists, base::Unretained(&user_2_certlist)));
169
170 // Run the message loop so the observer notifications get processed and
171 // lookups are completed.
172 RunUntilIdle();
173 // Should have gotten two OnTrustStoreChanged notifications.
174 EXPECT_EQ(2, trust_store_changed_count_);
175 EXPECT_EQ(0, client_cert_changed_count_);
176
177 EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist));
178 EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist));
179
180 EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist));
181 EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist));
182 }
183
184 // Test that ImportServerCerts imports the cert to the correct slot, and that
185 // ListCerts includes the added cert for the correct user, and does not include
186 // it for the other user.
TEST_F(NSSCertDatabaseChromeOSTest,ImportServerCert)187 TEST_F(NSSCertDatabaseChromeOSTest, ImportServerCert) {
188 // Load test certs from disk.
189 ScopedCERTCertificateList certs_1 = CreateCERTCertificateListFromFile(
190 GetTestCertsDirectory(), "ok_cert.pem", X509Certificate::FORMAT_AUTO);
191 ASSERT_EQ(1U, certs_1.size());
192
193 auto [leaf2, root2] = CertBuilder::CreateSimpleChain2();
194 ScopedCERTCertificateList certs_2 =
195 x509_util::CreateCERTCertificateListFromX509Certificate(
196 leaf2->GetX509Certificate().get());
197 ASSERT_EQ(1U, certs_2.size());
198
199 // Import one cert for each user.
200 NSSCertDatabase::ImportCertFailureList failed;
201 EXPECT_TRUE(
202 db_1_->ImportServerCert(certs_1, NSSCertDatabase::TRUSTED_SSL, &failed));
203 EXPECT_EQ(0U, failed.size());
204 failed.clear();
205 EXPECT_TRUE(
206 db_2_->ImportServerCert(certs_2, NSSCertDatabase::TRUSTED_SSL, &failed));
207 EXPECT_EQ(0U, failed.size());
208
209 // Get cert list for each user.
210 ScopedCERTCertificateList user_1_certlist;
211 ScopedCERTCertificateList user_2_certlist;
212 db_1_->ListCerts(
213 base::BindOnce(&SwapCertLists, base::Unretained(&user_1_certlist)));
214 db_2_->ListCerts(
215 base::BindOnce(&SwapCertLists, base::Unretained(&user_2_certlist)));
216
217 // Run the message loop so the observer notifications get processed and
218 // lookups are completed.
219 RunUntilIdle();
220 // TODO(mattm): this should be 2, but ImportServerCert doesn't currently
221 // generate notifications.
222 EXPECT_EQ(0, trust_store_changed_count_);
223 EXPECT_EQ(0, client_cert_changed_count_);
224
225 EXPECT_TRUE(IsCertInCertificateList(certs_1[0].get(), user_1_certlist));
226 EXPECT_FALSE(IsCertInCertificateList(certs_1[0].get(), user_2_certlist));
227
228 EXPECT_TRUE(IsCertInCertificateList(certs_2[0].get(), user_2_certlist));
229 EXPECT_FALSE(IsCertInCertificateList(certs_2[0].get(), user_1_certlist));
230 }
231
232 // Tests that There is no crash if the database is deleted while ListCerts
233 // is being processed on the worker pool.
TEST_F(NSSCertDatabaseChromeOSTest,NoCrashIfShutdownBeforeDoneOnWorkerPool)234 TEST_F(NSSCertDatabaseChromeOSTest, NoCrashIfShutdownBeforeDoneOnWorkerPool) {
235 ScopedCERTCertificateList certlist;
236 db_1_->ListCerts(base::BindOnce(&SwapCertLists, base::Unretained(&certlist)));
237 EXPECT_EQ(0U, certlist.size());
238
239 db_1_.reset();
240
241 RunUntilIdle();
242
243 EXPECT_LT(0U, certlist.size());
244 }
245
TEST_F(NSSCertDatabaseChromeOSTest,ListCertsReadsSystemSlot)246 TEST_F(NSSCertDatabaseChromeOSTest, ListCertsReadsSystemSlot) {
247 scoped_refptr<X509Certificate> cert_1(
248 ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
249 "client_1.pem",
250 "client_1.pk8",
251 db_1_->GetPublicSlot().get()));
252
253 scoped_refptr<X509Certificate> cert_2(
254 ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
255 "client_2.pem",
256 "client_2.pk8",
257 db_1_->GetSystemSlot().get()));
258
259 ScopedCERTCertificateList certs;
260 db_1_->ListCerts(base::BindOnce(&SwapCertLists, base::Unretained(&certs)));
261 RunUntilIdle();
262 EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs));
263 EXPECT_TRUE(IsCertInCertificateList(cert_2.get(), certs));
264 }
265
TEST_F(NSSCertDatabaseChromeOSTest,ListCertsDoesNotCrossReadSystemSlot)266 TEST_F(NSSCertDatabaseChromeOSTest, ListCertsDoesNotCrossReadSystemSlot) {
267 scoped_refptr<X509Certificate> cert_1(
268 ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
269 "client_1.pem",
270 "client_1.pk8",
271 db_2_->GetPublicSlot().get()));
272
273 scoped_refptr<X509Certificate> cert_2(
274 ImportClientCertAndKeyFromFile(GetTestCertsDirectory(),
275 "client_2.pem",
276 "client_2.pk8",
277 system_db_.slot()));
278 ScopedCERTCertificateList certs;
279 db_2_->ListCerts(base::BindOnce(&SwapCertLists, base::Unretained(&certs)));
280 RunUntilIdle();
281 EXPECT_TRUE(IsCertInCertificateList(cert_1.get(), certs));
282 EXPECT_FALSE(IsCertInCertificateList(cert_2.get(), certs));
283 }
284
TEST_F(NSSCertDatabaseChromeOSTest,SetCertTrustCertIsAlreadyOnPublicSlot)285 TEST_F(NSSCertDatabaseChromeOSTest, SetCertTrustCertIsAlreadyOnPublicSlot) {
286 // Import a certificate onto the public slot (and safety check that it ended
287 // up there).
288 ScopedCERTCertificateList certs = CreateCERTCertificateListFromFile(
289 GetTestCertsDirectory(), "root_ca_cert.pem",
290 X509Certificate::FORMAT_AUTO);
291 ASSERT_EQ(1U, certs.size());
292
293 NSSCertDatabase::ImportCertFailureList failed;
294 EXPECT_TRUE(
295 db_1_->ImportCACerts(certs, NSSCertDatabase::TRUST_DEFAULT, &failed));
296 EXPECT_EQ(0U, failed.size());
297
298 ASSERT_TRUE(NSSCertDatabase::IsCertificateOnSlot(
299 certs[0].get(), db_1_->GetPublicSlot().get()));
300
301 // Check that trust settings modification works.
302 EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
303 db_1_->GetCertTrust(certs[0].get(), CA_CERT));
304
305 EXPECT_TRUE(db_1_->SetCertTrust(certs[0].get(), CA_CERT,
306 NSSCertDatabase::TRUSTED_SSL));
307
308 EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL,
309 db_1_->GetCertTrust(certs[0].get(), CA_CERT));
310 }
311
TEST_F(NSSCertDatabaseChromeOSTest,SetCertTrustCertIsOnlyOnOtherSlot)312 TEST_F(NSSCertDatabaseChromeOSTest, SetCertTrustCertIsOnlyOnOtherSlot) {
313 crypto::ScopedTestNSSDB other_slot;
314
315 // Import a certificate onto a slot known by NSS which is not the
316 // NSSCertDatabase's public slot.
317 ScopedCERTCertificateList certs = CreateCERTCertificateListFromFile(
318 GetTestCertsDirectory(), "root_ca_cert.pem",
319 X509Certificate::FORMAT_AUTO);
320 ASSERT_EQ(1U, certs.size());
321 ASSERT_EQ(SECSuccess, PK11_ImportCert(other_slot.slot(), certs[0].get(),
322 CK_INVALID_HANDLE, "cert0",
323 PR_FALSE /* includeTrust (unused) */));
324 ASSERT_FALSE(NSSCertDatabase::IsCertificateOnSlot(
325 certs[0].get(), db_1_->GetPublicSlot().get()));
326
327 // Check that trust settings modification works.
328 EXPECT_EQ(NSSCertDatabase::TRUST_DEFAULT,
329 db_1_->GetCertTrust(certs[0].get(), CA_CERT));
330
331 EXPECT_TRUE(db_1_->SetCertTrust(certs[0].get(), CA_CERT,
332 NSSCertDatabase::TRUSTED_SSL));
333
334 EXPECT_EQ(NSSCertDatabase::TRUSTED_SSL,
335 db_1_->GetCertTrust(certs[0].get(), CA_CERT));
336
337 // Check that the certificate has been put onto the public slot as a side
338 // effect of changing trust.
339 EXPECT_TRUE(NSSCertDatabase::IsCertificateOnSlot(
340 certs[0].get(), db_1_->GetPublicSlot().get()));
341 }
342
TEST_F(NSSCertDatabaseChromeOSTest,SetCertTrustPublicSlotIsSystemSlot)343 TEST_F(NSSCertDatabaseChromeOSTest, SetCertTrustPublicSlotIsSystemSlot) {
344 // Create a NSSCertDatabase with |public_slot|==|system_slot|.
345 NSSCertDatabaseChromeOS test_db_for_system_slot(
346 /*public_slot=*/crypto::ScopedPK11Slot(
347 PK11_ReferenceSlot(system_db_.slot())),
348 /*private_slot=*/{});
349 test_db_for_system_slot.SetSystemSlot(
350 crypto::ScopedPK11Slot(PK11_ReferenceSlot(system_db_.slot())));
351
352 // Import a certificate onto a slot known by NSS which is not the
353 // NSSCertDatabase's public slot.
354 crypto::ScopedTestNSSDB other_slot;
355 ScopedCERTCertificateList certs = CreateCERTCertificateListFromFile(
356 GetTestCertsDirectory(), "root_ca_cert.pem",
357 X509Certificate::FORMAT_AUTO);
358 ASSERT_EQ(1U, certs.size());
359 ASSERT_EQ(SECSuccess, PK11_ImportCert(other_slot.slot(), certs[0].get(),
360 CK_INVALID_HANDLE, "cert0",
361 PR_FALSE /* includeTrust (unused) */));
362 ASSERT_FALSE(NSSCertDatabase::IsCertificateOnSlot(
363 certs[0].get(), test_db_for_system_slot.GetPublicSlot().get()));
364
365 // Changing trust through |test_db_for_system_slot| should fail and not do
366 // anything, because the database is not allowed to put the certificate onto
367 // its public slot (because it is also the system slot).
368 EXPECT_FALSE(test_db_for_system_slot.SetCertTrust(
369 certs[0].get(), CA_CERT, NSSCertDatabase::TRUSTED_SSL));
370 EXPECT_FALSE(NSSCertDatabase::IsCertificateOnSlot(
371 certs[0].get(), test_db_for_system_slot.GetPublicSlot().get()));
372 }
373
374 } // namespace net
375