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