[email protected] | 72b3a7e | 2013-08-13 15:30:04 | [diff] [blame^] | 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 | #include "chromeos/network/client_cert_resolver.h" |
| 5 | |
| 6 | #include <cert.h> |
| 7 | #include <pk11pub.h> |
| 8 | |
| 9 | #include "base/file_util.h" |
| 10 | #include "base/files/file_path.h" |
| 11 | #include "base/json/json_reader.h" |
| 12 | #include "base/run_loop.h" |
| 13 | #include "base/strings/stringprintf.h" |
| 14 | #include "chromeos/dbus/dbus_thread_manager.h" |
| 15 | #include "chromeos/dbus/shill_profile_client.h" |
| 16 | #include "chromeos/dbus/shill_service_client.h" |
| 17 | #include "chromeos/login/login_state.h" |
| 18 | #include "chromeos/network/managed_network_configuration_handler.h" |
| 19 | #include "chromeos/network/network_configuration_handler.h" |
| 20 | #include "chromeos/network/network_profile_handler.h" |
| 21 | #include "chromeos/network/network_state_handler.h" |
| 22 | #include "crypto/nss_util.h" |
| 23 | #include "net/base/crypto_module.h" |
| 24 | #include "net/base/net_errors.h" |
| 25 | #include "net/base/test_data_directory.h" |
| 26 | #include "net/cert/nss_cert_database.h" |
| 27 | #include "net/cert/x509_certificate.h" |
| 28 | #include "net/test/cert_test_util.h" |
| 29 | #include "testing/gtest/include/gtest/gtest.h" |
| 30 | #include "third_party/cros_system_api/dbus/service_constants.h" |
| 31 | |
| 32 | namespace chromeos { |
| 33 | |
| 34 | namespace { |
| 35 | |
| 36 | const char* kWifiStub = "wifi_stub"; |
| 37 | const char* kWifiSSID = "wifi_ssid"; |
| 38 | const char* kUserProfilePath = "user_profile"; |
| 39 | const char* kUserHash = "user_hash"; |
| 40 | |
| 41 | } // namespace |
| 42 | |
| 43 | class ClientCertResolverTest : public testing::Test { |
| 44 | public: |
| 45 | ClientCertResolverTest() {} |
| 46 | virtual ~ClientCertResolverTest() {} |
| 47 | |
| 48 | virtual void SetUp() OVERRIDE { |
| 49 | ASSERT_TRUE(test_nssdb_.is_open()); |
| 50 | slot_ = net::NSSCertDatabase::GetInstance()->GetPublicModule(); |
| 51 | ASSERT_TRUE(slot_->os_module_handle()); |
| 52 | |
| 53 | LoginState::Initialize(); |
| 54 | |
| 55 | DBusThreadManager::InitializeWithStub(); |
| 56 | service_test_ = |
| 57 | DBusThreadManager::Get()->GetShillServiceClient()->GetTestInterface(); |
| 58 | profile_test_ = |
| 59 | DBusThreadManager::Get()->GetShillProfileClient()->GetTestInterface(); |
| 60 | message_loop_.RunUntilIdle(); |
| 61 | service_test_->ClearServices(); |
| 62 | message_loop_.RunUntilIdle(); |
| 63 | |
| 64 | CertLoader::Initialize(); |
| 65 | CertLoader* cert_loader = CertLoader::Get(); |
| 66 | cert_loader->InitializeTPMForTest(); |
| 67 | cert_loader->SetSlowTaskRunnerForTest(message_loop_.message_loop_proxy()); |
| 68 | cert_loader->SetCryptoTaskRunner(message_loop_.message_loop_proxy()); |
| 69 | } |
| 70 | |
| 71 | virtual void TearDown() OVERRIDE { |
| 72 | client_cert_resolver_.reset(); |
| 73 | managed_config_handler_.reset(); |
| 74 | network_config_handler_.reset(); |
| 75 | network_profile_handler_.reset(); |
| 76 | network_state_handler_.reset(); |
| 77 | CertLoader::Shutdown(); |
| 78 | DBusThreadManager::Shutdown(); |
| 79 | LoginState::Shutdown(); |
| 80 | CleanupSlotContents(); |
| 81 | } |
| 82 | |
| 83 | protected: |
| 84 | // Imports a CA cert (stored as PEM in test_ca_cert_pem_) and a client |
| 85 | // certificate signed by that CA. Its PKCS#11 ID is stored in |
| 86 | // |test_pkcs11_id_|. |
| 87 | void SetupTestCerts() { |
| 88 | // Import a CA cert. |
| 89 | net::NSSCertDatabase* cert_db = net::NSSCertDatabase::GetInstance(); |
| 90 | net::CertificateList ca_cert_list = |
| 91 | net::CreateCertificateListFromFile(net::GetTestCertsDirectory(), |
| 92 | "websocket_cacert.pem", |
| 93 | net::X509Certificate::FORMAT_AUTO); |
| 94 | ASSERT_TRUE(!ca_cert_list.empty()); |
| 95 | net::NSSCertDatabase::ImportCertFailureList failures; |
| 96 | EXPECT_TRUE(cert_db->ImportCACerts( |
| 97 | ca_cert_list, net::NSSCertDatabase::TRUST_DEFAULT, &failures)); |
| 98 | ASSERT_TRUE(failures.empty()) << net::ErrorToString(failures[0].net_error); |
| 99 | |
| 100 | net::X509Certificate::GetPEMEncoded(ca_cert_list[0]->os_cert_handle(), |
| 101 | &test_ca_cert_pem_); |
| 102 | ASSERT_TRUE(!test_ca_cert_pem_.empty()); |
| 103 | |
| 104 | // Import a client cert signed by that CA. |
| 105 | scoped_refptr<net::CryptoModule> crypt_module = cert_db->GetPrivateModule(); |
| 106 | std::string pkcs12_data; |
| 107 | ASSERT_TRUE(file_util::ReadFileToString( |
| 108 | net::GetTestCertsDirectory().Append("websocket_client_cert.p12"), |
| 109 | &pkcs12_data)); |
| 110 | |
| 111 | net::CertificateList client_cert_list; |
| 112 | ASSERT_EQ(net::OK, |
| 113 | cert_db->ImportFromPKCS12(crypt_module.get(), |
| 114 | pkcs12_data, |
| 115 | string16(), |
| 116 | false, |
| 117 | &client_cert_list)); |
| 118 | ASSERT_TRUE(!client_cert_list.empty()); |
| 119 | test_pkcs11_id_ = CertLoader::GetPkcs11IdForCert(*client_cert_list[0]); |
| 120 | ASSERT_TRUE(!test_pkcs11_id_.empty()); |
| 121 | } |
| 122 | |
| 123 | void SetupNetworkHandlers() { |
| 124 | network_state_handler_.reset(NetworkStateHandler::InitializeForTest()); |
| 125 | network_profile_handler_.reset(new NetworkProfileHandler()); |
| 126 | network_config_handler_.reset(new NetworkConfigurationHandler()); |
| 127 | managed_config_handler_.reset(new ManagedNetworkConfigurationHandler()); |
| 128 | client_cert_resolver_.reset(new ClientCertResolver()); |
| 129 | |
| 130 | network_profile_handler_->Init(network_state_handler_.get()); |
| 131 | network_config_handler_->Init(network_state_handler_.get()); |
| 132 | managed_config_handler_->Init(network_state_handler_.get(), |
| 133 | network_profile_handler_.get(), |
| 134 | network_config_handler_.get()); |
| 135 | client_cert_resolver_->Init(network_state_handler_.get(), |
| 136 | managed_config_handler_.get()); |
| 137 | client_cert_resolver_->SetSlowTaskRunnerForTest( |
| 138 | message_loop_.message_loop_proxy()); |
| 139 | |
| 140 | profile_test_->AddProfile(kUserProfilePath, kUserHash); |
| 141 | } |
| 142 | |
| 143 | void SetupWifi() { |
| 144 | const bool add_to_visible = true; |
| 145 | const bool add_to_watchlist = true; |
| 146 | service_test_->AddService(kWifiStub, |
| 147 | kWifiSSID, |
| 148 | flimflam::kTypeWifi, |
| 149 | flimflam::kStateOnline, |
| 150 | add_to_visible, |
| 151 | add_to_watchlist); |
| 152 | service_test_->SetServiceProperty( |
| 153 | kWifiStub, flimflam::kGuidProperty, base::StringValue(kWifiStub)); |
| 154 | |
| 155 | profile_test_->AddService(kUserProfilePath, kWifiStub); |
| 156 | } |
| 157 | |
| 158 | // Setup a policy with a certificate pattern that matches any client cert that |
| 159 | // is signed by the test CA cert (stored in |test_ca_cert_pem_|). In |
| 160 | // particular it will match the test client cert. |
| 161 | void SetupPolicy() { |
| 162 | const char* kTestPolicyTemplate = |
| 163 | "[ { \"GUID\": \"wifi_stub\"," |
| 164 | " \"Name\": \"wifi_stub\"," |
| 165 | " \"Type\": \"WiFi\"," |
| 166 | " \"WiFi\": {" |
| 167 | " \"Security\": \"WPA-EAP\"," |
| 168 | " \"SSID\": \"wifi_ssid\"," |
| 169 | " \"EAP\": {" |
| 170 | " \"Outer\": \"EAP-TLS\"," |
| 171 | " \"ClientCertType\": \"Pattern\"," |
| 172 | " \"ClientCertPattern\": {" |
| 173 | " \"IssuerCAPEMs\": [ \"%s\" ]" |
| 174 | " }" |
| 175 | " }" |
| 176 | " }" |
| 177 | "} ]"; |
| 178 | std::string policy_json = |
| 179 | base::StringPrintf(kTestPolicyTemplate, test_ca_cert_pem_.c_str()); |
| 180 | |
| 181 | std::string error; |
| 182 | scoped_ptr<base::Value> policy_value(base::JSONReader::ReadAndReturnError( |
| 183 | policy_json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, &error)); |
| 184 | ASSERT_TRUE(policy_value) << error; |
| 185 | |
| 186 | base::ListValue* policy = NULL; |
| 187 | ASSERT_TRUE(policy_value->GetAsList(&policy)); |
| 188 | |
| 189 | managed_config_handler_->SetPolicy( |
| 190 | onc::ONC_SOURCE_USER_POLICY, kUserHash, *policy); |
| 191 | } |
| 192 | |
| 193 | void GetClientCertProperties(std::string* pkcs11_id) { |
| 194 | pkcs11_id->clear(); |
| 195 | const base::DictionaryValue* properties = |
| 196 | service_test_->GetServiceProperties(kWifiStub); |
| 197 | if (!properties) |
| 198 | return; |
| 199 | properties->GetStringWithoutPathExpansion(flimflam::kEapCertIdProperty, |
| 200 | pkcs11_id); |
| 201 | } |
| 202 | |
| 203 | ShillServiceClient::TestInterface* service_test_; |
| 204 | ShillProfileClient::TestInterface* profile_test_; |
| 205 | std::string test_pkcs11_id_; |
| 206 | scoped_refptr<net::X509Certificate> test_ca_cert_; |
| 207 | std::string test_ca_cert_pem_; |
| 208 | base::MessageLoop message_loop_; |
| 209 | |
| 210 | private: |
| 211 | void CleanupSlotContents() { |
| 212 | CERTCertList* cert_list = PK11_ListCertsInSlot(slot_->os_module_handle()); |
| 213 | for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); |
| 214 | !CERT_LIST_END(node, cert_list); |
| 215 | node = CERT_LIST_NEXT(node)) { |
| 216 | scoped_refptr<net::X509Certificate> cert( |
| 217 | net::X509Certificate::CreateFromHandle( |
| 218 | node->cert, net::X509Certificate::OSCertHandles())); |
| 219 | net::NSSCertDatabase::GetInstance()->DeleteCertAndKey(cert.get()); |
| 220 | } |
| 221 | CERT_DestroyCertList(cert_list); |
| 222 | } |
| 223 | |
| 224 | scoped_ptr<NetworkStateHandler> network_state_handler_; |
| 225 | scoped_ptr<NetworkProfileHandler> network_profile_handler_; |
| 226 | scoped_ptr<NetworkConfigurationHandler> network_config_handler_; |
| 227 | scoped_ptr<ManagedNetworkConfigurationHandler> managed_config_handler_; |
| 228 | scoped_ptr<ClientCertResolver> client_cert_resolver_; |
| 229 | scoped_refptr<net::CryptoModule> slot_; |
| 230 | crypto::ScopedTestNSSDB test_nssdb_; |
| 231 | |
| 232 | DISALLOW_COPY_AND_ASSIGN(ClientCertResolverTest); |
| 233 | }; |
| 234 | |
| 235 | TEST_F(ClientCertResolverTest, NoMatchingCertificates) { |
| 236 | SetupNetworkHandlers(); |
| 237 | SetupPolicy(); |
| 238 | message_loop_.RunUntilIdle(); |
| 239 | |
| 240 | SetupWifi(); |
| 241 | message_loop_.RunUntilIdle(); |
| 242 | |
| 243 | // Verify that no client certificate was configured. |
| 244 | std::string pkcs11_id; |
| 245 | GetClientCertProperties(&pkcs11_id); |
| 246 | EXPECT_TRUE(pkcs11_id.empty()); |
| 247 | } |
| 248 | |
| 249 | TEST_F(ClientCertResolverTest, ResolveOnInitialization) { |
| 250 | SetupTestCerts(); |
| 251 | SetupNetworkHandlers(); |
| 252 | SetupPolicy(); |
| 253 | message_loop_.RunUntilIdle(); |
| 254 | |
| 255 | SetupWifi(); |
| 256 | message_loop_.RunUntilIdle(); |
| 257 | |
| 258 | // Verify that the resolver positively matched the pattern in the policy with |
| 259 | // the test client cert and configured the network. |
| 260 | std::string pkcs11_id; |
| 261 | GetClientCertProperties(&pkcs11_id); |
| 262 | EXPECT_EQ(test_pkcs11_id_, pkcs11_id); |
| 263 | } |
| 264 | |
| 265 | TEST_F(ClientCertResolverTest, ResolveAfterPolicyApplication) { |
| 266 | SetupTestCerts(); |
| 267 | SetupNetworkHandlers(); |
| 268 | message_loop_.RunUntilIdle(); |
| 269 | |
| 270 | // The policy will trigger the creation of a new wifi service. |
| 271 | SetupPolicy(); |
| 272 | message_loop_.RunUntilIdle(); |
| 273 | |
| 274 | // Verify that the resolver positively matched the pattern in the policy with |
| 275 | // the test client cert and configured the network. |
| 276 | std::string pkcs11_id; |
| 277 | GetClientCertProperties(&pkcs11_id); |
| 278 | EXPECT_EQ(test_pkcs11_id_, pkcs11_id); |
| 279 | } |
| 280 | |
| 281 | } // namespace chromeos |