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 "chromeos/network/onc/onc_certificate_importer_impl.h"
6
7 #include <cert.h>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/notreached.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/test/test_simple_task_runner.h"
14 #include "base/threading/thread_task_runner_handle.h"
15 #include "base/values.h"
16 #include "chromeos/network/certificate_helper.h"
17 #include "chromeos/network/onc/onc_parsed_certificates.h"
18 #include "chromeos/network/onc/onc_test_utils.h"
19 #include "components/onc/onc_constants.h"
20 #include "crypto/scoped_test_nss_db.h"
21 #include "net/base/hash_value.h"
22 #include "net/cert/cert_type.h"
23 #include "net/cert/nss_cert_database_chromeos.h"
24 #include "net/cert/x509_util_nss.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 namespace chromeos {
28 namespace onc {
29
30 class ONCCertificateImporterImplTest : public testing::Test {
31 public:
32 ONCCertificateImporterImplTest() = default;
33 ~ONCCertificateImporterImplTest() override = default;
34
SetUp()35 void SetUp() override {
36 ASSERT_TRUE(public_nssdb_.is_open());
37 ASSERT_TRUE(private_nssdb_.is_open());
38
39 task_runner_ = new base::TestSimpleTaskRunner();
40 thread_task_runner_handle_.reset(
41 new base::ThreadTaskRunnerHandle(task_runner_));
42
43 test_nssdb_.reset(new net::NSSCertDatabaseChromeOS(
44 crypto::ScopedPK11Slot(PK11_ReferenceSlot(public_nssdb_.slot())),
45 crypto::ScopedPK11Slot(PK11_ReferenceSlot(private_nssdb_.slot()))));
46
47 // Test db should be empty at start of test.
48 EXPECT_TRUE(ListCertsInPublicSlot().empty());
49 EXPECT_TRUE(ListCertsInPrivateSlot().empty());
50 }
51
TearDown()52 void TearDown() override {
53 thread_task_runner_handle_.reset();
54 task_runner_.reset();
55 }
56
57 protected:
58 enum class ImportType { kClientCertificatesOnly, kAllCertificates };
59
OnImportCompleted(bool expected_import_success,bool success)60 void OnImportCompleted(bool expected_import_success, bool success) {
61 EXPECT_EQ(expected_import_success, success);
62 }
63
64 // Runs the import on the certificates specified in |filename|.
65 // |import_type| specifies if only client certificates should be imported, or
66 // if all certificates should be imported.
67 // |expected_parse_success| should be true if at least one certificate in
68 // |filename| is expected to have parse errors.
69 // |expected_import_success| is the expected result of importing the
70 // certificates which did not have parsing errors.
AddCertificatesFromFile(const std::string & filename,ImportType import_type,bool expected_parse_success,bool expected_import_success)71 void AddCertificatesFromFile(const std::string& filename,
72 ImportType import_type,
73 bool expected_parse_success,
74 bool expected_import_success) {
75 std::unique_ptr<base::DictionaryValue> onc =
76 test_utils::ReadTestDictionary(filename);
77 base::Optional<base::Value> certificates_value =
78 onc->ExtractKey(::onc::toplevel_config::kCertificates);
79 onc_certificates_ = std::move(*certificates_value);
80
81 CertificateImporterImpl importer(task_runner_, test_nssdb_.get());
82 auto onc_parsed_certificates =
83 std::make_unique<OncParsedCertificates>(onc_certificates_);
84 EXPECT_EQ(expected_parse_success, !onc_parsed_certificates->has_error());
85 switch (import_type) {
86 case ImportType::kClientCertificatesOnly:
87 importer.ImportClientCertificates(
88 onc_parsed_certificates->client_certificates(),
89 base::BindOnce(&ONCCertificateImporterImplTest::OnImportCompleted,
90 base::Unretained(this), expected_import_success));
91 break;
92 case ImportType::kAllCertificates:
93 importer.ImportAllCertificatesUserInitiated(
94 onc_parsed_certificates->server_or_authority_certificates(),
95 onc_parsed_certificates->client_certificates(),
96 base::BindOnce(&ONCCertificateImporterImplTest::OnImportCompleted,
97 base::Unretained(this), expected_import_success));
98 break;
99 default:
100 NOTREACHED();
101 }
102
103 task_runner_->RunUntilIdle();
104
105 public_list_ = ListCertsInPublicSlot();
106 private_list_ = ListCertsInPrivateSlot();
107 }
108
AddCertificateFromFile(const std::string & filename,ImportType import_type,net::CertType expected_type,std::string * guid)109 void AddCertificateFromFile(const std::string& filename,
110 ImportType import_type,
111 net::CertType expected_type,
112 std::string* guid) {
113 std::string guid_temporary;
114 if (!guid)
115 guid = &guid_temporary;
116
117 AddCertificatesFromFile(filename, import_type,
118 true /* expected_parse_success */,
119 true /* expected_import_success */);
120
121 if (expected_type == net::SERVER_CERT || expected_type == net::CA_CERT) {
122 ASSERT_EQ(1u, public_list_.size());
123 EXPECT_EQ(expected_type, certificate::GetCertType(public_list_[0].get()));
124 EXPECT_TRUE(private_list_.empty());
125 } else { // net::USER_CERT
126 EXPECT_TRUE(public_list_.empty());
127 ASSERT_EQ(1u, private_list_.size());
128 EXPECT_EQ(expected_type,
129 certificate::GetCertType(private_list_[0].get()));
130 }
131
132 const base::Value& certificate = onc_certificates_.GetList()[0];
133 const std::string* guid_value =
134 certificate.FindStringKey(::onc::certificate::kGUID);
135 *guid = *guid_value;
136 }
137
138 // Certificates and the NSSCertDatabase depend on these test DBs. Destroy them
139 // last.
140 crypto::ScopedTestNSSDB public_nssdb_;
141 crypto::ScopedTestNSSDB private_nssdb_;
142
143 scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
144 std::unique_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
145 std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nssdb_;
146 base::Value onc_certificates_;
147 // List of certs in the nssdb's public slot.
148 net::ScopedCERTCertificateList public_list_;
149 // List of certs in the nssdb's "private" slot.
150 net::ScopedCERTCertificateList private_list_;
151
152 private:
ListCertsInPublicSlot()153 net::ScopedCERTCertificateList ListCertsInPublicSlot() {
154 return ListCertsInSlot(public_nssdb_.slot());
155 }
156
ListCertsInPrivateSlot()157 net::ScopedCERTCertificateList ListCertsInPrivateSlot() {
158 return ListCertsInSlot(private_nssdb_.slot());
159 }
160
ListCertsInSlot(PK11SlotInfo * slot)161 net::ScopedCERTCertificateList ListCertsInSlot(PK11SlotInfo* slot) {
162 net::ScopedCERTCertificateList result;
163 CERTCertList* cert_list = PK11_ListCertsInSlot(slot);
164 if (!cert_list)
165 return result;
166 for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
167 !CERT_LIST_END(node, cert_list);
168 node = CERT_LIST_NEXT(node)) {
169 result.push_back(net::x509_util::DupCERTCertificate(node->cert));
170 }
171 CERT_DestroyCertList(cert_list);
172
173 std::sort(result.begin(), result.end(),
174 [](const net::ScopedCERTCertificate& lhs,
175 const net::ScopedCERTCertificate& rhs) {
176 return net::x509_util::CalculateFingerprint256(lhs.get()) <
177 net::x509_util::CalculateFingerprint256(rhs.get());
178 });
179 return result;
180 }
181 };
182
TEST_F(ONCCertificateImporterImplTest,MultipleCertificates)183 TEST_F(ONCCertificateImporterImplTest, MultipleCertificates) {
184 AddCertificatesFromFile("managed_toplevel2.onc", ImportType::kAllCertificates,
185 true /* expected_parse_success */,
186 true /* expected_import_success */);
187 EXPECT_EQ(onc_certificates_.GetList().size(), public_list_.size());
188 EXPECT_TRUE(private_list_.empty());
189 EXPECT_EQ(2ul, public_list_.size());
190 }
191
TEST_F(ONCCertificateImporterImplTest,OnlyClientCertificatesImpored)192 TEST_F(ONCCertificateImporterImplTest, OnlyClientCertificatesImpored) {
193 AddCertificatesFromFile(
194 "managed_toplevel2.onc", ImportType::kClientCertificatesOnly,
195 true /* expected_parse_success */, true /* expected_import_success */);
196 AddCertificatesFromFile(
197 "certificate-client.onc", ImportType::kClientCertificatesOnly,
198 true /* expected_parse_success */, true /* expected_import_success */);
199 EXPECT_EQ(0ul, public_list_.size());
200 EXPECT_EQ(1ul, private_list_.size());
201 }
202
TEST_F(ONCCertificateImporterImplTest,MultipleCertificatesWithFailures)203 TEST_F(ONCCertificateImporterImplTest, MultipleCertificatesWithFailures) {
204 AddCertificatesFromFile(
205 "toplevel_partially_invalid.onc", ImportType::kAllCertificates,
206 false /* expected_parse_success */, true /* expected_import_success */);
207 EXPECT_EQ(3ul, onc_certificates_.GetList().size());
208 EXPECT_EQ(1ul, private_list_.size());
209 EXPECT_TRUE(public_list_.empty());
210 }
211
TEST_F(ONCCertificateImporterImplTest,AddClientCertificate)212 TEST_F(ONCCertificateImporterImplTest, AddClientCertificate) {
213 std::string guid;
214 AddCertificateFromFile("certificate-client.onc", ImportType::kAllCertificates,
215 net::USER_CERT, &guid);
216 EXPECT_EQ(1ul, private_list_.size());
217 EXPECT_TRUE(public_list_.empty());
218
219 SECKEYPrivateKeyList* privkey_list =
220 PK11_ListPrivKeysInSlot(private_nssdb_.slot(), NULL, NULL);
221 EXPECT_TRUE(privkey_list);
222 if (privkey_list) {
223 SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privkey_list);
224 int count = 0;
225 while (!PRIVKEY_LIST_END(node, privkey_list)) {
226 char* name = PK11_GetPrivateKeyNickname(node->key);
227 EXPECT_STREQ(guid.c_str(), name);
228 PORT_Free(name);
229 count++;
230 node = PRIVKEY_LIST_NEXT(node);
231 }
232 EXPECT_EQ(1, count);
233 SECKEY_DestroyPrivateKeyList(privkey_list);
234 }
235
236 SECKEYPublicKeyList* pubkey_list =
237 PK11_ListPublicKeysInSlot(private_nssdb_.slot(), NULL);
238 EXPECT_TRUE(pubkey_list);
239 if (pubkey_list) {
240 SECKEYPublicKeyListNode* node = PUBKEY_LIST_HEAD(pubkey_list);
241 int count = 0;
242 while (!PUBKEY_LIST_END(node, pubkey_list)) {
243 count++;
244 node = PUBKEY_LIST_NEXT(node);
245 }
246 EXPECT_EQ(1, count);
247 SECKEY_DestroyPublicKeyList(pubkey_list);
248 }
249 }
250
TEST_F(ONCCertificateImporterImplTest,AddServerCertificateWithWebTrust)251 TEST_F(ONCCertificateImporterImplTest, AddServerCertificateWithWebTrust) {
252 AddCertificateFromFile("certificate-server.onc", ImportType::kAllCertificates,
253 net::SERVER_CERT, NULL);
254
255 SECKEYPrivateKeyList* privkey_list =
256 PK11_ListPrivKeysInSlot(private_nssdb_.slot(), NULL, NULL);
257 EXPECT_FALSE(privkey_list);
258
259 SECKEYPublicKeyList* pubkey_list =
260 PK11_ListPublicKeysInSlot(private_nssdb_.slot(), NULL);
261 EXPECT_FALSE(pubkey_list);
262
263 ASSERT_EQ(1u, public_list_.size());
264 EXPECT_TRUE(private_list_.empty());
265 }
266
TEST_F(ONCCertificateImporterImplTest,AddWebAuthorityCertificateWithWebTrust)267 TEST_F(ONCCertificateImporterImplTest, AddWebAuthorityCertificateWithWebTrust) {
268 AddCertificateFromFile("certificate-web-authority.onc",
269 ImportType::kAllCertificates, net::CA_CERT, NULL);
270
271 SECKEYPrivateKeyList* privkey_list =
272 PK11_ListPrivKeysInSlot(private_nssdb_.slot(), NULL, NULL);
273 EXPECT_FALSE(privkey_list);
274
275 SECKEYPublicKeyList* pubkey_list =
276 PK11_ListPublicKeysInSlot(private_nssdb_.slot(), NULL);
277 EXPECT_FALSE(pubkey_list);
278
279 ASSERT_EQ(1u, public_list_.size());
280 EXPECT_TRUE(private_list_.empty());
281 }
282
TEST_F(ONCCertificateImporterImplTest,AddAuthorityCertificateWithoutWebTrust)283 TEST_F(ONCCertificateImporterImplTest, AddAuthorityCertificateWithoutWebTrust) {
284 AddCertificateFromFile("certificate-authority.onc",
285 ImportType::kAllCertificates, net::CA_CERT, NULL);
286 SECKEYPrivateKeyList* privkey_list =
287 PK11_ListPrivKeysInSlot(private_nssdb_.slot(), NULL, NULL);
288 EXPECT_FALSE(privkey_list);
289
290 SECKEYPublicKeyList* pubkey_list =
291 PK11_ListPublicKeysInSlot(private_nssdb_.slot(), NULL);
292 EXPECT_FALSE(pubkey_list);
293 }
294
295 struct CertParam {
CertParamchromeos::onc::CertParam296 CertParam(net::CertType certificate_type,
297 const char* original_filename,
298 const char* update_filename)
299 : cert_type(certificate_type),
300 original_file(original_filename),
301 update_file(update_filename) {}
302
303 net::CertType cert_type;
304 const char* original_file;
305 const char* update_file;
306 };
307
308 class ONCCertificateImporterImplTestWithParam :
309 public ONCCertificateImporterImplTest,
310 public testing::WithParamInterface<CertParam> {
311 };
312
TEST_P(ONCCertificateImporterImplTestWithParam,UpdateCertificate)313 TEST_P(ONCCertificateImporterImplTestWithParam, UpdateCertificate) {
314 // First we import a certificate.
315 {
316 SCOPED_TRACE("Import original certificate");
317 AddCertificateFromFile(GetParam().original_file,
318 ImportType::kAllCertificates, GetParam().cert_type,
319 NULL);
320 }
321
322 // Now we import the same certificate with a different GUID. In case of a
323 // client cert, the cert should be retrievable via the new GUID.
324 {
325 SCOPED_TRACE("Import updated certificate");
326 AddCertificateFromFile(GetParam().update_file, ImportType::kAllCertificates,
327 GetParam().cert_type, NULL);
328 }
329 }
330
TEST_P(ONCCertificateImporterImplTestWithParam,ReimportCertificate)331 TEST_P(ONCCertificateImporterImplTestWithParam, ReimportCertificate) {
332 // Verify that reimporting a client certificate works.
333 for (int i = 0; i < 2; ++i) {
334 SCOPED_TRACE("Import certificate, iteration " + base::NumberToString(i));
335 AddCertificateFromFile(GetParam().original_file,
336 ImportType::kAllCertificates, GetParam().cert_type,
337 NULL);
338 }
339 }
340
341 INSTANTIATE_TEST_SUITE_P(
342 ONCCertificateImporterImplTestWithParam,
343 ONCCertificateImporterImplTestWithParam,
344 ::testing::Values(CertParam(net::USER_CERT,
345 "certificate-client.onc",
346 "certificate-client-update.onc"),
347 CertParam(net::SERVER_CERT,
348 "certificate-server.onc",
349 "certificate-server-update.onc"),
350 CertParam(net::CA_CERT,
351 "certificate-web-authority.onc",
352 "certificate-web-authority-update.onc")));
353
354 } // namespace onc
355 } // namespace chromeos
356