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