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