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