client_cert_resolver.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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/client_cert_resolver.h" 6 7#include <cert.h> 8#include <certt.h> // for (SECCertUsageEnum) certUsageAnyCA 9#include <pk11pub.h> 10 11#include <algorithm> 12#include <string> 13 14#include "base/stl_util.h" 15#include "base/task_runner.h" 16#include "base/threading/worker_pool.h" 17#include "base/time/time.h" 18#include "chromeos/cert_loader.h" 19#include "chromeos/dbus/dbus_thread_manager.h" 20#include "chromeos/dbus/shill_service_client.h" 21#include "chromeos/network/certificate_pattern.h" 22#include "chromeos/network/client_cert_util.h" 23#include "chromeos/network/favorite_state.h" 24#include "chromeos/network/managed_network_configuration_handler.h" 25#include "chromeos/network/network_state_handler.h" 26#include "chromeos/network/network_ui_data.h" 27#include "chromeos/network/onc/onc_constants.h" 28#include "dbus/object_path.h" 29#include "net/cert/x509_certificate.h" 30 31namespace chromeos { 32 33// Describes a network |network_path| for which a matching certificate |cert_id| 34// was found. 35struct ClientCertResolver::NetworkAndMatchingCert { 36 NetworkAndMatchingCert(const std::string& network_path, 37 client_cert::ConfigType config_type, 38 const std::string& cert_id) 39 : service_path(network_path), 40 cert_config_type(config_type), 41 pkcs11_id(cert_id) {} 42 43 std::string service_path; 44 client_cert::ConfigType cert_config_type; 45 // The id of the matching certificate. 46 std::string pkcs11_id; 47}; 48 49typedef std::vector<ClientCertResolver::NetworkAndMatchingCert> 50 NetworkCertMatches; 51 52namespace { 53 54// Returns true if |vector| contains |value|. 55template <class T> 56bool ContainsValue(const std::vector<T>& vector, const T& value) { 57 return find(vector.begin(), vector.end(), value) != vector.end(); 58} 59 60// Returns true if a private key for certificate |cert| is installed. 61bool HasPrivateKey(const net::X509Certificate& cert) { 62 PK11SlotInfo* slot = PK11_KeyForCertExists(cert.os_cert_handle(), NULL, NULL); 63 if (!slot) 64 return false; 65 66 PK11_FreeSlot(slot); 67 return true; 68} 69 70// Describes a certificate which is issued by |issuer| (encoded as PEM). 71struct CertAndIssuer { 72 CertAndIssuer(const scoped_refptr<net::X509Certificate>& certificate, 73 const std::string& issuer) 74 : cert(certificate), 75 pem_encoded_issuer(issuer) {} 76 77 scoped_refptr<net::X509Certificate> cert; 78 std::string pem_encoded_issuer; 79}; 80 81bool CompareCertExpiration(const CertAndIssuer& a, 82 const CertAndIssuer& b) { 83 return (a.cert->valid_expiry() < b.cert->valid_expiry()); 84} 85 86// Describes a network that is configured with the certificate pattern 87// |client_cert_pattern|. 88struct NetworkAndCertPattern { 89 NetworkAndCertPattern(const std::string& network_path, 90 client_cert::ConfigType config_type, 91 const CertificatePattern& pattern) 92 : service_path(network_path), 93 cert_config_type(config_type), 94 client_cert_pattern(pattern) {} 95 std::string service_path; 96 client_cert::ConfigType cert_config_type; 97 CertificatePattern client_cert_pattern; 98}; 99 100// A unary predicate that returns true if the given CertAndIssuer matches the 101// certificate pattern of the NetworkAndCertPattern. 102struct MatchCertWithPattern { 103 MatchCertWithPattern(const NetworkAndCertPattern& pattern) 104 : net_and_pattern(pattern) {} 105 106 bool operator()(const CertAndIssuer& cert_and_issuer) { 107 const CertificatePattern& pattern = net_and_pattern.client_cert_pattern; 108 if (!pattern.issuer().Empty() && 109 !client_cert::CertPrincipalMatches(pattern.issuer(), 110 cert_and_issuer.cert->issuer())) { 111 return false; 112 } 113 if (!pattern.subject().Empty() && 114 !client_cert::CertPrincipalMatches(pattern.subject(), 115 cert_and_issuer.cert->subject())) { 116 return false; 117 } 118 119 const std::vector<std::string>& issuer_ca_pems = pattern.issuer_ca_pems(); 120 if (!issuer_ca_pems.empty() && 121 !ContainsValue(issuer_ca_pems, cert_and_issuer.pem_encoded_issuer)) { 122 return false; 123 } 124 return true; 125 } 126 127 NetworkAndCertPattern net_and_pattern; 128}; 129 130// Searches for matches between |networks| and |certs| and writes matches to 131// |matches|. Because this calls NSS functions and is potentially slow, it must 132// be run on a worker thread. 133void FindCertificateMatches(const net::CertificateList& certs, 134 std::vector<NetworkAndCertPattern>* networks, 135 NetworkCertMatches* matches) { 136 // Filter all client certs and determines each certificate's issuer, which is 137 // required for the pattern matching. 138 std::vector<CertAndIssuer> client_certs; 139 for (net::CertificateList::const_iterator it = certs.begin(); 140 it != certs.end(); ++it) { 141 const net::X509Certificate& cert = **it; 142 if (cert.valid_expiry().is_null() || cert.HasExpired() || 143 !HasPrivateKey(cert)) { 144 continue; 145 } 146 scoped_refptr<net::X509Certificate> issuer = 147 net::X509Certificate::CreateFromHandle( 148 CERT_FindCertIssuer( 149 cert.os_cert_handle(), PR_Now(), certUsageAnyCA), 150 net::X509Certificate::OSCertHandles()); 151 if (!issuer) { 152 LOG(ERROR) << "Couldn't find cert issuer."; 153 continue; 154 } 155 std::string pem_encoded_issuer; 156 if (!net::X509Certificate::GetPEMEncoded(issuer->os_cert_handle(), 157 &pem_encoded_issuer)) { 158 LOG(ERROR) << "Couldn't PEM-encode certificate."; 159 continue; 160 } 161 client_certs.push_back(CertAndIssuer(*it, pem_encoded_issuer)); 162 } 163 164 std::sort(client_certs.begin(), client_certs.end(), &CompareCertExpiration); 165 166 for (std::vector<NetworkAndCertPattern>::const_iterator it = 167 networks->begin(); 168 it != networks->end(); ++it) { 169 std::vector<CertAndIssuer>::iterator cert_it = std::find_if( 170 client_certs.begin(), client_certs.end(), MatchCertWithPattern(*it)); 171 if (cert_it == client_certs.end()) { 172 LOG(WARNING) << "Couldn't find a matching client cert for network " 173 << it->service_path; 174 continue; 175 } 176 std::string pkcs11_id = CertLoader::GetPkcs11IdForCert(*cert_it->cert); 177 if (pkcs11_id.empty()) { 178 LOG(ERROR) << "Couldn't determine PKCS#11 ID."; 179 continue; 180 } 181 matches->push_back(ClientCertResolver::NetworkAndMatchingCert( 182 it->service_path, it->cert_config_type, pkcs11_id)); 183 } 184} 185 186// Determines the type of the CertificatePattern configuration, i.e. is it a 187// pattern within an EAP, IPsec or OpenVPN configuration. 188client_cert::ConfigType OncToClientCertConfigurationType( 189 const base::DictionaryValue& network_config) { 190 using namespace ::chromeos::onc; 191 192 const base::DictionaryValue* wifi = NULL; 193 network_config.GetDictionaryWithoutPathExpansion(network_config::kWiFi, 194 &wifi); 195 if (wifi) { 196 const base::DictionaryValue* eap = NULL; 197 wifi->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); 198 if (!eap) 199 return client_cert::CONFIG_TYPE_NONE; 200 return client_cert::CONFIG_TYPE_EAP; 201 } 202 203 const base::DictionaryValue* vpn = NULL; 204 network_config.GetDictionaryWithoutPathExpansion(network_config::kVPN, &vpn); 205 if (vpn) { 206 const base::DictionaryValue* openvpn = NULL; 207 vpn->GetDictionaryWithoutPathExpansion(vpn::kOpenVPN, &openvpn); 208 if (openvpn) 209 return client_cert::CONFIG_TYPE_OPENVPN; 210 211 const base::DictionaryValue* ipsec = NULL; 212 vpn->GetDictionaryWithoutPathExpansion(vpn::kIPsec, &ipsec); 213 if (ipsec) 214 return client_cert::CONFIG_TYPE_IPSEC; 215 216 return client_cert::CONFIG_TYPE_NONE; 217 } 218 219 const base::DictionaryValue* ethernet = NULL; 220 network_config.GetDictionaryWithoutPathExpansion(network_config::kEthernet, 221 ðernet); 222 if (ethernet) { 223 const base::DictionaryValue* eap = NULL; 224 ethernet->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); 225 if (eap) 226 return client_cert::CONFIG_TYPE_EAP; 227 return client_cert::CONFIG_TYPE_NONE; 228 } 229 230 return client_cert::CONFIG_TYPE_NONE; 231} 232 233void LogError(const std::string& service_path, 234 const std::string& dbus_error_name, 235 const std::string& dbus_error_message) { 236 network_handler::ShillErrorCallbackFunction( 237 "ClientCertResolver.SetProperties failed", 238 service_path, 239 network_handler::ErrorCallback(), 240 dbus_error_name, 241 dbus_error_message); 242} 243 244bool ClientCertificatesLoaded() { 245 if(!CertLoader::Get()->certificates_loaded()) { 246 VLOG(1) << "Certificates not loaded yet."; 247 return false; 248 } 249 if (!CertLoader::Get()->IsHardwareBacked()) { 250 VLOG(1) << "TPM is not available."; 251 return false; 252 } 253 return true; 254} 255 256} // namespace 257 258ClientCertResolver::ClientCertResolver() 259 : network_state_handler_(NULL), 260 managed_network_config_handler_(NULL), 261 weak_ptr_factory_(this) { 262} 263 264ClientCertResolver::~ClientCertResolver() { 265 if (network_state_handler_) 266 network_state_handler_->RemoveObserver(this, FROM_HERE); 267 if (CertLoader::IsInitialized()) 268 CertLoader::Get()->RemoveObserver(this); 269 if (managed_network_config_handler_) 270 managed_network_config_handler_->RemoveObserver(this); 271} 272 273void ClientCertResolver::Init( 274 NetworkStateHandler* network_state_handler, 275 ManagedNetworkConfigurationHandler* managed_network_config_handler) { 276 DCHECK(network_state_handler); 277 network_state_handler_ = network_state_handler; 278 network_state_handler_->AddObserver(this, FROM_HERE); 279 280 DCHECK(managed_network_config_handler); 281 managed_network_config_handler_ = managed_network_config_handler; 282 managed_network_config_handler_->AddObserver(this); 283 284 CertLoader::Get()->AddObserver(this); 285} 286 287void ClientCertResolver::SetSlowTaskRunnerForTest( 288 const scoped_refptr<base::TaskRunner>& task_runner) { 289 slow_task_runner_for_test_ = task_runner; 290} 291 292void ClientCertResolver::NetworkListChanged() { 293 VLOG(2) << "NetworkListChanged."; 294 if (!ClientCertificatesLoaded()) 295 return; 296 // Configure only networks that were not configured before. 297 298 // We'll drop networks from |resolved_networks_|, which are not known anymore. 299 std::set<std::string> old_resolved_networks; 300 old_resolved_networks.swap(resolved_networks_); 301 302 FavoriteStateList networks; 303 network_state_handler_->GetFavoriteList(&networks); 304 305 FavoriteStateList networks_to_check; 306 for (FavoriteStateList::const_iterator it = networks.begin(); 307 it != networks.end(); ++it) { 308 const std::string& service_path = (*it)->path(); 309 if (ContainsKey(old_resolved_networks, service_path)) { 310 resolved_networks_.insert(service_path); 311 continue; 312 } 313 networks_to_check.push_back(*it); 314 } 315 316 ResolveNetworks(networks_to_check); 317} 318 319void ClientCertResolver::OnCertificatesLoaded( 320 const net::CertificateList& cert_list, 321 bool initial_load) { 322 VLOG(2) << "OnCertificatesLoaded."; 323 if (!ClientCertificatesLoaded()) 324 return; 325 // Compare all networks with all certificates. 326 FavoriteStateList networks; 327 network_state_handler_->GetFavoriteList(&networks); 328 ResolveNetworks(networks); 329} 330 331void ClientCertResolver::PolicyApplied(const std::string& service_path) { 332 VLOG(2) << "PolicyApplied " << service_path; 333 if (!ClientCertificatesLoaded()) 334 return; 335 // Compare this network with all certificates. 336 const FavoriteState* network = 337 network_state_handler_->GetFavoriteState(service_path); 338 if (!network) { 339 LOG(ERROR) << "service path '" << service_path << "' unknown."; 340 return; 341 } 342 FavoriteStateList networks; 343 networks.push_back(network); 344 ResolveNetworks(networks); 345} 346 347void ClientCertResolver::ResolveNetworks(const FavoriteStateList& networks) { 348 scoped_ptr<std::vector<NetworkAndCertPattern> > networks_with_pattern( 349 new std::vector<NetworkAndCertPattern>); 350 351 // Filter networks with ClientCertPattern. As ClientCertPatterns can only be 352 // set by policy, we check there. 353 for (FavoriteStateList::const_iterator it = networks.begin(); 354 it != networks.end(); ++it) { 355 const FavoriteState* network = *it; 356 357 // In any case, don't check this network again in NetworkListChanged. 358 resolved_networks_.insert(network->path()); 359 360 // If this network is not managed, it cannot have a ClientCertPattern. 361 if (network->guid().empty()) 362 continue; 363 364 if (network->profile_path().empty()) { 365 LOG(ERROR) << "Network " << network->path() 366 << " has a GUID but not profile path"; 367 continue; 368 } 369 const base::DictionaryValue* policy = 370 managed_network_config_handler_->FindPolicyByGuidAndProfile( 371 network->guid(), network->profile_path()); 372 373 if (!policy) { 374 VLOG(1) << "The policy for network " << network->path() << " with GUID " 375 << network->guid() << " is not available yet."; 376 // Skip this network for now. Once the policy is loaded, PolicyApplied() 377 // will retry. 378 continue; 379 } 380 381 VLOG(2) << "Inspecting network " << network->path(); 382 // TODO(pneubeck): Access the ClientCertPattern without relying on 383 // NetworkUIData, which also parses other things that we don't need here. 384 // The ONCSource is irrelevant here. 385 scoped_ptr<NetworkUIData> ui_data( 386 NetworkUIData::CreateFromONC(onc::ONC_SOURCE_NONE, *policy)); 387 388 // Skip networks that don't have a ClientCertPattern. 389 if (ui_data->certificate_type() != CLIENT_CERT_TYPE_PATTERN) 390 continue; 391 392 client_cert::ConfigType config_type = 393 OncToClientCertConfigurationType(*policy); 394 if (config_type == client_cert::CONFIG_TYPE_NONE) { 395 LOG(ERROR) << "UIData contains a CertificatePattern, but the policy " 396 << "doesn't. Network: " << network->path(); 397 continue; 398 } 399 400 networks_with_pattern->push_back(NetworkAndCertPattern( 401 network->path(), config_type, ui_data->certificate_pattern())); 402 } 403 if (networks_with_pattern->empty()) 404 return; 405 406 VLOG(2) << "Start task for resolving client cert patterns."; 407 base::TaskRunner* task_runner = slow_task_runner_for_test_.get(); 408 if (!task_runner) 409 task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */); 410 411 NetworkCertMatches* matches = new NetworkCertMatches; 412 task_runner->PostTaskAndReply( 413 FROM_HERE, 414 base::Bind(&FindCertificateMatches, 415 CertLoader::Get()->cert_list(), 416 base::Owned(networks_with_pattern.release()), 417 matches), 418 base::Bind(&ClientCertResolver::ConfigureCertificates, 419 weak_ptr_factory_.GetWeakPtr(), 420 base::Owned(matches))); 421} 422 423void ClientCertResolver::ConfigureCertificates(NetworkCertMatches* matches) { 424 for (NetworkCertMatches::const_iterator it = matches->begin(); 425 it != matches->end(); ++it) { 426 VLOG(1) << "Configuring certificate of network " << it->service_path; 427 CertLoader* cert_loader = CertLoader::Get(); 428 base::DictionaryValue shill_properties; 429 client_cert::SetShillProperties(it->cert_config_type, 430 cert_loader->tpm_token_slot(), 431 cert_loader->tpm_user_pin(), 432 &it->pkcs11_id, 433 &shill_properties); 434 DBusThreadManager::Get()->GetShillServiceClient()-> 435 SetProperties(dbus::ObjectPath(it->service_path), 436 shill_properties, 437 base::Bind(&base::DoNothing), 438 base::Bind(&LogError, it->service_path)); 439 network_state_handler_->RequestUpdateForNetwork(it->service_path); 440 } 441} 442 443} // namespace chromeos 444