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