client_cert_resolver.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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 "components/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 explicit 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 net::X509Certificate::OSCertHandle issuer_handle = 147 CERT_FindCertIssuer(cert.os_cert_handle(), PR_Now(), certUsageAnyCA); 148 if (!issuer_handle) { 149 LOG(ERROR) << "Couldn't find an issuer."; 150 continue; 151 } 152 scoped_refptr<net::X509Certificate> issuer = 153 net::X509Certificate::CreateFromHandle( 154 issuer_handle, 155 net::X509Certificate::OSCertHandles() /* no intermediate certs */); 156 if (!issuer) { 157 LOG(ERROR) << "Couldn't create issuer cert."; 158 continue; 159 } 160 std::string pem_encoded_issuer; 161 if (!net::X509Certificate::GetPEMEncoded(issuer->os_cert_handle(), 162 &pem_encoded_issuer)) { 163 LOG(ERROR) << "Couldn't PEM-encode certificate."; 164 continue; 165 } 166 client_certs.push_back(CertAndIssuer(*it, pem_encoded_issuer)); 167 } 168 169 std::sort(client_certs.begin(), client_certs.end(), &CompareCertExpiration); 170 171 for (std::vector<NetworkAndCertPattern>::const_iterator it = 172 networks->begin(); 173 it != networks->end(); ++it) { 174 std::vector<CertAndIssuer>::iterator cert_it = std::find_if( 175 client_certs.begin(), client_certs.end(), MatchCertWithPattern(*it)); 176 if (cert_it == client_certs.end()) { 177 LOG(WARNING) << "Couldn't find a matching client cert for network " 178 << it->service_path; 179 continue; 180 } 181 std::string pkcs11_id = CertLoader::GetPkcs11IdForCert(*cert_it->cert); 182 if (pkcs11_id.empty()) { 183 LOG(ERROR) << "Couldn't determine PKCS#11 ID."; 184 continue; 185 } 186 matches->push_back(ClientCertResolver::NetworkAndMatchingCert( 187 it->service_path, it->cert_config_type, pkcs11_id)); 188 } 189} 190 191// Determines the type of the CertificatePattern configuration, i.e. is it a 192// pattern within an EAP, IPsec or OpenVPN configuration. 193client_cert::ConfigType OncToClientCertConfigurationType( 194 const base::DictionaryValue& network_config) { 195 using namespace ::onc; 196 197 const base::DictionaryValue* wifi = NULL; 198 network_config.GetDictionaryWithoutPathExpansion(network_config::kWiFi, 199 &wifi); 200 if (wifi) { 201 const base::DictionaryValue* eap = NULL; 202 wifi->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); 203 if (!eap) 204 return client_cert::CONFIG_TYPE_NONE; 205 return client_cert::CONFIG_TYPE_EAP; 206 } 207 208 const base::DictionaryValue* vpn = NULL; 209 network_config.GetDictionaryWithoutPathExpansion(network_config::kVPN, &vpn); 210 if (vpn) { 211 const base::DictionaryValue* openvpn = NULL; 212 vpn->GetDictionaryWithoutPathExpansion(vpn::kOpenVPN, &openvpn); 213 if (openvpn) 214 return client_cert::CONFIG_TYPE_OPENVPN; 215 216 const base::DictionaryValue* ipsec = NULL; 217 vpn->GetDictionaryWithoutPathExpansion(vpn::kIPsec, &ipsec); 218 if (ipsec) 219 return client_cert::CONFIG_TYPE_IPSEC; 220 221 return client_cert::CONFIG_TYPE_NONE; 222 } 223 224 const base::DictionaryValue* ethernet = NULL; 225 network_config.GetDictionaryWithoutPathExpansion(network_config::kEthernet, 226 ðernet); 227 if (ethernet) { 228 const base::DictionaryValue* eap = NULL; 229 ethernet->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); 230 if (eap) 231 return client_cert::CONFIG_TYPE_EAP; 232 return client_cert::CONFIG_TYPE_NONE; 233 } 234 235 return client_cert::CONFIG_TYPE_NONE; 236} 237 238void LogError(const std::string& service_path, 239 const std::string& dbus_error_name, 240 const std::string& dbus_error_message) { 241 network_handler::ShillErrorCallbackFunction( 242 "ClientCertResolver.SetProperties failed", 243 service_path, 244 network_handler::ErrorCallback(), 245 dbus_error_name, 246 dbus_error_message); 247} 248 249bool ClientCertificatesLoaded() { 250 if (!CertLoader::Get()->certificates_loaded()) { 251 VLOG(1) << "Certificates not loaded yet."; 252 return false; 253 } 254 if (!CertLoader::Get()->IsHardwareBacked()) { 255 VLOG(1) << "TPM is not available."; 256 return false; 257 } 258 return true; 259} 260 261} // namespace 262 263ClientCertResolver::ClientCertResolver() 264 : network_state_handler_(NULL), 265 managed_network_config_handler_(NULL), 266 weak_ptr_factory_(this) { 267} 268 269ClientCertResolver::~ClientCertResolver() { 270 if (network_state_handler_) 271 network_state_handler_->RemoveObserver(this, FROM_HERE); 272 if (CertLoader::IsInitialized()) 273 CertLoader::Get()->RemoveObserver(this); 274 if (managed_network_config_handler_) 275 managed_network_config_handler_->RemoveObserver(this); 276} 277 278void ClientCertResolver::Init( 279 NetworkStateHandler* network_state_handler, 280 ManagedNetworkConfigurationHandler* managed_network_config_handler) { 281 DCHECK(network_state_handler); 282 network_state_handler_ = network_state_handler; 283 network_state_handler_->AddObserver(this, FROM_HERE); 284 285 DCHECK(managed_network_config_handler); 286 managed_network_config_handler_ = managed_network_config_handler; 287 managed_network_config_handler_->AddObserver(this); 288 289 CertLoader::Get()->AddObserver(this); 290} 291 292void ClientCertResolver::SetSlowTaskRunnerForTest( 293 const scoped_refptr<base::TaskRunner>& task_runner) { 294 slow_task_runner_for_test_ = task_runner; 295} 296 297void ClientCertResolver::NetworkListChanged() { 298 VLOG(2) << "NetworkListChanged."; 299 if (!ClientCertificatesLoaded()) 300 return; 301 // Configure only networks that were not configured before. 302 303 // We'll drop networks from |resolved_networks_|, which are not known anymore. 304 std::set<std::string> old_resolved_networks; 305 old_resolved_networks.swap(resolved_networks_); 306 307 FavoriteStateList networks; 308 network_state_handler_->GetFavoriteList(&networks); 309 310 FavoriteStateList networks_to_check; 311 for (FavoriteStateList::const_iterator it = networks.begin(); 312 it != networks.end(); ++it) { 313 const std::string& service_path = (*it)->path(); 314 if (ContainsKey(old_resolved_networks, service_path)) { 315 resolved_networks_.insert(service_path); 316 continue; 317 } 318 networks_to_check.push_back(*it); 319 } 320 321 ResolveNetworks(networks_to_check); 322} 323 324void ClientCertResolver::OnCertificatesLoaded( 325 const net::CertificateList& cert_list, 326 bool initial_load) { 327 VLOG(2) << "OnCertificatesLoaded."; 328 if (!ClientCertificatesLoaded()) 329 return; 330 // Compare all networks with all certificates. 331 FavoriteStateList networks; 332 network_state_handler_->GetFavoriteList(&networks); 333 ResolveNetworks(networks); 334} 335 336void ClientCertResolver::PolicyApplied(const std::string& service_path) { 337 VLOG(2) << "PolicyApplied " << service_path; 338 if (!ClientCertificatesLoaded()) 339 return; 340 // Compare this network with all certificates. 341 const FavoriteState* network = 342 network_state_handler_->GetFavoriteState(service_path); 343 if (!network) { 344 LOG(ERROR) << "service path '" << service_path << "' unknown."; 345 return; 346 } 347 FavoriteStateList networks; 348 networks.push_back(network); 349 ResolveNetworks(networks); 350} 351 352void ClientCertResolver::ResolveNetworks(const FavoriteStateList& networks) { 353 scoped_ptr<std::vector<NetworkAndCertPattern> > networks_with_pattern( 354 new std::vector<NetworkAndCertPattern>); 355 356 // Filter networks with ClientCertPattern. As ClientCertPatterns can only be 357 // set by policy, we check there. 358 for (FavoriteStateList::const_iterator it = networks.begin(); 359 it != networks.end(); ++it) { 360 const FavoriteState* network = *it; 361 362 // In any case, don't check this network again in NetworkListChanged. 363 resolved_networks_.insert(network->path()); 364 365 // If this network is not managed, it cannot have a ClientCertPattern. 366 if (network->guid().empty()) 367 continue; 368 369 if (network->profile_path().empty()) { 370 LOG(ERROR) << "Network " << network->path() 371 << " has a GUID but not profile path"; 372 continue; 373 } 374 const base::DictionaryValue* policy = 375 managed_network_config_handler_->FindPolicyByGuidAndProfile( 376 network->guid(), network->profile_path()); 377 378 if (!policy) { 379 VLOG(1) << "The policy for network " << network->path() << " with GUID " 380 << network->guid() << " is not available yet."; 381 // Skip this network for now. Once the policy is loaded, PolicyApplied() 382 // will retry. 383 continue; 384 } 385 386 VLOG(2) << "Inspecting network " << network->path(); 387 // TODO(pneubeck): Access the ClientCertPattern without relying on 388 // NetworkUIData, which also parses other things that we don't need here. 389 // The ONCSource is irrelevant here. 390 scoped_ptr<NetworkUIData> ui_data( 391 NetworkUIData::CreateFromONC(onc::ONC_SOURCE_NONE, *policy)); 392 393 // Skip networks that don't have a ClientCertPattern. 394 if (ui_data->certificate_type() != CLIENT_CERT_TYPE_PATTERN) 395 continue; 396 397 client_cert::ConfigType config_type = 398 OncToClientCertConfigurationType(*policy); 399 if (config_type == client_cert::CONFIG_TYPE_NONE) { 400 LOG(ERROR) << "UIData contains a CertificatePattern, but the policy " 401 << "doesn't. Network: " << network->path(); 402 continue; 403 } 404 405 networks_with_pattern->push_back(NetworkAndCertPattern( 406 network->path(), config_type, ui_data->certificate_pattern())); 407 } 408 if (networks_with_pattern->empty()) 409 return; 410 411 VLOG(2) << "Start task for resolving client cert patterns."; 412 base::TaskRunner* task_runner = slow_task_runner_for_test_.get(); 413 if (!task_runner) 414 task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */); 415 416 NetworkCertMatches* matches = new NetworkCertMatches; 417 task_runner->PostTaskAndReply( 418 FROM_HERE, 419 base::Bind(&FindCertificateMatches, 420 CertLoader::Get()->cert_list(), 421 base::Owned(networks_with_pattern.release()), 422 matches), 423 base::Bind(&ClientCertResolver::ConfigureCertificates, 424 weak_ptr_factory_.GetWeakPtr(), 425 base::Owned(matches))); 426} 427 428void ClientCertResolver::ConfigureCertificates(NetworkCertMatches* matches) { 429 for (NetworkCertMatches::const_iterator it = matches->begin(); 430 it != matches->end(); ++it) { 431 VLOG(1) << "Configuring certificate of network " << it->service_path; 432 CertLoader* cert_loader = CertLoader::Get(); 433 base::DictionaryValue shill_properties; 434 client_cert::SetShillProperties(it->cert_config_type, 435 cert_loader->tpm_token_slot(), 436 cert_loader->tpm_user_pin(), 437 &it->pkcs11_id, 438 &shill_properties); 439 DBusThreadManager::Get()->GetShillServiceClient()-> 440 SetProperties(dbus::ObjectPath(it->service_path), 441 shill_properties, 442 base::Bind(&base::DoNothing), 443 base::Bind(&LogError, it->service_path)); 444 network_state_handler_->RequestUpdateForNetwork(it->service_path); 445 } 446} 447 448} // namespace chromeos 449