From 72b3a7eea94e94507e8ae7398082e9ffe1cd620f Mon Sep 17 00:00:00 2001 From: "pneubeck@chromium.org" Date: Tue, 13 Aug 2013 15:30:04 +0000 Subject: Automatically resolve ClientCertificatePatterns. This adds a new ClientCertResolver to chromeos/network, which automatically resolves ClientCertificatePatterns and writes the cert id of the matching certificate to Shill. This should fix several issues like updating client certs and auto-connect immediately after installing EAP networks from policy. It's required for Ethernet EAP policies where the current pattern matching on each manual connect isn't sufficient. BUG=234983, 126870 Review URL: https://chromiumcodereview.appspot.com/22327005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217257 0039d316-1c4b-4281-b951-d872f2087c98 --- chromeos/network/client_cert_resolver.cc | 450 +++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 chromeos/network/client_cert_resolver.cc (limited to 'chromeos/network/client_cert_resolver.cc') diff --git a/chromeos/network/client_cert_resolver.cc b/chromeos/network/client_cert_resolver.cc new file mode 100644 index 0000000..adbb9de --- /dev/null +++ b/chromeos/network/client_cert_resolver.cc @@ -0,0 +1,450 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chromeos/network/client_cert_resolver.h" + +#include +#include // for (SECCertUsageEnum) certUsageAnyCA +#include + +#include +#include + +#include "base/stl_util.h" +#include "base/task_runner.h" +#include "base/threading/worker_pool.h" +#include "base/time/time.h" +#include "chromeos/cert_loader.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/shill_service_client.h" +#include "chromeos/network/certificate_pattern.h" +#include "chromeos/network/client_cert_util.h" +#include "chromeos/network/managed_network_configuration_handler.h" +#include "chromeos/network/network_state.h" +#include "chromeos/network/network_state_handler.h" +#include "chromeos/network/network_ui_data.h" +#include "chromeos/network/onc/onc_constants.h" +#include "dbus/object_path.h" +#include "net/cert/x509_certificate.h" + +namespace chromeos { + +// Describes a network |network_path| for which a matching certificate |cert_id| +// was found. +struct ClientCertResolver::NetworkAndMatchingCert { + NetworkAndMatchingCert(const std::string& network_path, + client_cert::ConfigType config_type, + const std::string& cert_id) + : service_path(network_path), + cert_config_type(config_type), + pkcs11_id(cert_id) {} + + std::string service_path; + client_cert::ConfigType cert_config_type; + // The id of the matching certificate. + std::string pkcs11_id; +}; + +typedef std::vector + NetworkCertMatches; + +namespace { + +// Returns true if |vector| contains |value|. +template +bool ContainsValue(const std::vector& vector, const T& value) { + return find(vector.begin(), vector.end(), value) != vector.end(); +} + +// Returns true if a private key for certificate |cert| is installed. +bool HasPrivateKey(const net::X509Certificate& cert) { + PK11SlotInfo* slot = PK11_KeyForCertExists(cert.os_cert_handle(), NULL, NULL); + if (!slot) + return false; + + PK11_FreeSlot(slot); + return true; +} + +// Describes a certificate which is issued by |issuer| (encoded as PEM). +struct CertAndIssuer { + CertAndIssuer(const scoped_refptr& certificate, + const std::string& issuer) + : cert(certificate), + pem_encoded_issuer(issuer) {} + + scoped_refptr cert; + std::string pem_encoded_issuer; +}; + +bool CompareCertExpiration(const CertAndIssuer& a, + const CertAndIssuer& b) { + return (a.cert->valid_expiry() < b.cert->valid_expiry()); +} + +// Describes a network that is configured with the certificate pattern +// |client_cert_pattern|. +struct NetworkAndCertPattern { + NetworkAndCertPattern(const std::string& network_path, + client_cert::ConfigType config_type, + const CertificatePattern& pattern) + : service_path(network_path), + cert_config_type(config_type), + client_cert_pattern(pattern) {} + std::string service_path; + client_cert::ConfigType cert_config_type; + CertificatePattern client_cert_pattern; +}; + +// A unary predicate that returns true if the given CertAndIssuer matches the +// certificate pattern of the NetworkAndCertPattern. +struct MatchCertWithPattern { + MatchCertWithPattern(const NetworkAndCertPattern& pattern) + : net_and_pattern(pattern) {} + + bool operator()(const CertAndIssuer& cert_and_issuer) { + const CertificatePattern& pattern = net_and_pattern.client_cert_pattern; + if (!pattern.issuer().Empty() && + !client_cert::CertPrincipalMatches(pattern.issuer(), + cert_and_issuer.cert->issuer())) { + return false; + } + if (!pattern.subject().Empty() && + !client_cert::CertPrincipalMatches(pattern.subject(), + cert_and_issuer.cert->subject())) { + return false; + } + + const std::vector& issuer_ca_pems = pattern.issuer_ca_pems(); + if (!issuer_ca_pems.empty() && + !ContainsValue(issuer_ca_pems, cert_and_issuer.pem_encoded_issuer)) { + return false; + } + return true; + } + + NetworkAndCertPattern net_and_pattern; +}; + +// Searches for matches between |networks| and |certs| and writes matches to +// |matches|. Because this calls NSS functions and is potentially slow, it must +// be run on a worker thread. +void FindCertificateMatches(const net::CertificateList& certs, + std::vector* networks, + NetworkCertMatches* matches) { + // Filter all client certs and determines each certificate's issuer, which is + // required for the pattern matching. + std::vector client_certs; + for (net::CertificateList::const_iterator it = certs.begin(); + it != certs.end(); ++it) { + const net::X509Certificate& cert = **it; + if (cert.valid_expiry().is_null() || cert.HasExpired() || + !HasPrivateKey(cert)) { + continue; + } + scoped_refptr issuer = + net::X509Certificate::CreateFromHandle( + CERT_FindCertIssuer( + cert.os_cert_handle(), PR_Now(), certUsageAnyCA), + net::X509Certificate::OSCertHandles()); + if (!issuer) { + LOG(ERROR) << "Couldn't find cert issuer."; + continue; + } + std::string pem_encoded_issuer; + if (!net::X509Certificate::GetPEMEncoded(issuer->os_cert_handle(), + &pem_encoded_issuer)) { + LOG(ERROR) << "Couldn't PEM-encode certificate."; + continue; + } + client_certs.push_back(CertAndIssuer(*it, pem_encoded_issuer)); + } + + std::sort(client_certs.begin(), client_certs.end(), &CompareCertExpiration); + + for (std::vector::const_iterator it = + networks->begin(); + it != networks->end(); ++it) { + std::vector::iterator cert_it = std::find_if( + client_certs.begin(), client_certs.end(), MatchCertWithPattern(*it)); + if (cert_it == client_certs.end()) { + LOG(WARNING) << "Couldn't find a matching client cert for network " + << it->service_path; + continue; + } + std::string pkcs11_id = CertLoader::GetPkcs11IdForCert(*cert_it->cert); + if (pkcs11_id.empty()) { + LOG(ERROR) << "Couldn't determine PKCS#11 ID."; + continue; + } + matches->push_back(ClientCertResolver::NetworkAndMatchingCert( + it->service_path, it->cert_config_type, pkcs11_id)); + } +} + +// Determines the type of the CertificatePattern configuration, i.e. is it a +// pattern within an EAP, IPsec or OpenVPN configuration. +client_cert::ConfigType OncToClientCertConfigurationType( + const base::DictionaryValue& network_config) { + using namespace ::chromeos::onc; + + const base::DictionaryValue* wifi = NULL; + network_config.GetDictionaryWithoutPathExpansion(network_config::kWiFi, + &wifi); + if (wifi) { + const base::DictionaryValue* eap = NULL; + wifi->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); + if (!eap) + return client_cert::CONFIG_TYPE_NONE; + return client_cert::CONFIG_TYPE_EAP; + } + + const base::DictionaryValue* vpn = NULL; + network_config.GetDictionaryWithoutPathExpansion(network_config::kVPN, &vpn); + if (vpn) { + const base::DictionaryValue* openvpn = NULL; + vpn->GetDictionaryWithoutPathExpansion(vpn::kOpenVPN, &openvpn); + if (openvpn) + return client_cert::CONFIG_TYPE_OPENVPN; + + const base::DictionaryValue* ipsec = NULL; + vpn->GetDictionaryWithoutPathExpansion(vpn::kIPsec, &ipsec); + if (ipsec) + return client_cert::CONFIG_TYPE_IPSEC; + + return client_cert::CONFIG_TYPE_NONE; + } + + const base::DictionaryValue* ethernet = NULL; + network_config.GetDictionaryWithoutPathExpansion(network_config::kEthernet, + ðernet); + if (ethernet) { + const base::DictionaryValue* eap = NULL; + ethernet->GetDictionaryWithoutPathExpansion(wifi::kEAP, &eap); + if (eap) + return client_cert::CONFIG_TYPE_EAP; + return client_cert::CONFIG_TYPE_NONE; + } + + return client_cert::CONFIG_TYPE_NONE; +} + +void LogError(const std::string& service_path, + const std::string& dbus_error_name, + const std::string& dbus_error_message) { + network_handler::ShillErrorCallbackFunction( + "ClientCertResolver.SetProperties failed", + service_path, + network_handler::ErrorCallback(), + dbus_error_name, + dbus_error_message); +} + +bool ClientCertificatesLoaded() { + if(!CertLoader::Get()->certificates_loaded()) { + VLOG(1) << "Certificates not loaded yet."; + return false; + } + if (!CertLoader::Get()->IsHardwareBacked()) { + VLOG(1) << "TPM is not available."; + return false; + } + return true; +} + +} // namespace + +ClientCertResolver::ClientCertResolver() + : network_state_handler_(NULL), + managed_network_config_handler_(NULL), + weak_ptr_factory_(this) { +} + +ClientCertResolver::~ClientCertResolver() { + if (network_state_handler_) + network_state_handler_->RemoveObserver(this, FROM_HERE); + if (CertLoader::IsInitialized()) + CertLoader::Get()->RemoveObserver(this); + if (managed_network_config_handler_) + managed_network_config_handler_->RemoveObserver(this); +} + +void ClientCertResolver::Init( + NetworkStateHandler* network_state_handler, + ManagedNetworkConfigurationHandler* managed_network_config_handler) { + DCHECK(network_state_handler); + network_state_handler_ = network_state_handler; + network_state_handler_->AddObserver(this, FROM_HERE); + + DCHECK(managed_network_config_handler); + managed_network_config_handler_ = managed_network_config_handler; + managed_network_config_handler_->AddObserver(this); + + CertLoader::Get()->AddObserver(this); +} + +void ClientCertResolver::SetSlowTaskRunnerForTest( + const scoped_refptr& task_runner) { + slow_task_runner_for_test_ = task_runner; +} + +void ClientCertResolver::NetworkListChanged() { + VLOG(2) << "NetworkListChanged."; + if (!ClientCertificatesLoaded()) + return; + // Configure only networks that were not configured before. + + // We'll drop networks from |resolved_networks_|, which are not known anymore. + std::set old_resolved_networks; + old_resolved_networks.swap(resolved_networks_); + + NetworkStateList networks; + network_state_handler_->GetNetworkList(&networks); + + NetworkStateList networks_to_check; + for (NetworkStateList::const_iterator it = networks.begin(); + it != networks.end(); ++it) { + // If this network is not managed, it cannot have a ClientCertPattern. + // We do this check here additionally to ResolveNetworks because it's cheap + // and prevents |resolved_networks_| from becoming too large. + if ((*it)->guid().empty()) + continue; + + const std::string& service_path = (*it)->path(); + if (ContainsKey(old_resolved_networks, service_path)) { + resolved_networks_.insert(service_path); + continue; + } + networks_to_check.push_back(*it); + } + + ResolveNetworks(networks_to_check); +} + +void ClientCertResolver::OnCertificatesLoaded( + const net::CertificateList& cert_list, + bool initial_load) { + VLOG(2) << "OnCertificatesLoaded."; + if (!ClientCertificatesLoaded()) + return; + // Compare all networks with all certificates. + NetworkStateList networks; + network_state_handler_->GetNetworkList(&networks); + ResolveNetworks(networks); +} + +void ClientCertResolver::PolicyApplied(const std::string& service_path) { + VLOG(2) << "PolicyApplied " << service_path; + if (!ClientCertificatesLoaded()) + return; + // Compare this network with all certificates. + const NetworkState* network = + network_state_handler_->GetNetworkState(service_path); + if (!network) { + LOG(ERROR) << "service path '" << service_path << "' unknown."; + return; + } + NetworkStateList networks; + networks.push_back(network); + ResolveNetworks(networks); +} + +void ClientCertResolver::ResolveNetworks( + const NetworkStateList& networks) { + scoped_ptr > networks_with_pattern( + new std::vector); + + // Filter networks with ClientCertPattern. As ClientCertPatterns can only be + // set by policy, we check there. + for (NetworkStateList::const_iterator it = networks.begin(); + it != networks.end(); ++it) { + const NetworkState* network = *it; + + // In any case, don't check this network again in NetworkListChanged. + resolved_networks_.insert(network->path()); + + // If this network is not managed, it cannot have a ClientCertPattern. + if (network->guid().empty()) + continue; + + if (network->profile_path().empty()) { + LOG(ERROR) << "Network " << network->path() + << " has a GUID but not profile path"; + continue; + } + const base::DictionaryValue* policy = + managed_network_config_handler_->FindPolicyByGuidAndProfile( + network->guid(), network->profile_path()); + + if (!policy) { + VLOG(1) << "The policy for network " << network->path() << " with GUID " + << network->guid() << " is not available yet."; + // Skip this network for now. Once the policy is loaded, PolicyApplied() + // will retry. + continue; + } + + VLOG(2) << "Inspecting network " << network->path(); + // TODO(pneubeck): Access the ClientCertPattern without relying on + // NetworkUIData, which also parses other things that we don't need here. + // The ONCSource is irrelevant here. + scoped_ptr ui_data( + NetworkUIData::CreateFromONC(onc::ONC_SOURCE_NONE, *policy)); + + // Skip networks that don't have a ClientCertPattern. + if (ui_data->certificate_type() != CLIENT_CERT_TYPE_PATTERN) + continue; + + client_cert::ConfigType config_type = + OncToClientCertConfigurationType(*policy); + if (config_type == client_cert::CONFIG_TYPE_NONE) { + LOG(ERROR) << "UIData contains a CertificatePattern, but the policy " + << "doesn't. Network: " << network->path(); + continue; + } + + networks_with_pattern->push_back(NetworkAndCertPattern( + network->path(), config_type, ui_data->certificate_pattern())); + } + if (networks_with_pattern->empty()) + return; + + VLOG(2) << "Start task for resolving client cert patterns."; + base::TaskRunner* task_runner = slow_task_runner_for_test_.get(); + if (!task_runner) + task_runner = base::WorkerPool::GetTaskRunner(true /* task is slow */); + + NetworkCertMatches* matches = new NetworkCertMatches; + task_runner->PostTaskAndReply( + FROM_HERE, + base::Bind(&FindCertificateMatches, + CertLoader::Get()->cert_list(), + base::Owned(networks_with_pattern.release()), + matches), + base::Bind(&ClientCertResolver::ConfigureCertificates, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(matches))); +} + +void ClientCertResolver::ConfigureCertificates(NetworkCertMatches* matches) { + for (NetworkCertMatches::const_iterator it = matches->begin(); + it != matches->end(); ++it) { + VLOG(1) << "Configuring certificate of network " << it->service_path; + CertLoader* cert_loader = CertLoader::Get(); + base::DictionaryValue shill_properties; + client_cert::SetShillProperties(it->cert_config_type, + cert_loader->tpm_token_slot(), + cert_loader->tpm_user_pin(), + &it->pkcs11_id, + &shill_properties); + DBusThreadManager::Get()->GetShillServiceClient()-> + SetProperties(dbus::ObjectPath(it->service_path), + shill_properties, + base::Bind(&base::DoNothing), + base::Bind(&LogError, it->service_path)); + network_state_handler_->RequestUpdateForNetwork(it->service_path); + } +} + +} // namespace chromeos -- cgit v1.1