// Copyright (c) 2012 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_util.h" #include <cert.h> #include <pk11pub.h> #include <list> #include <string> #include <vector> #include "base/values.h" #include "chromeos/network/certificate_pattern.h" #include "net/base/net_errors.h" #include "net/cert/cert_database.h" #include "net/cert/nss_cert_database.h" #include "net/cert/x509_cert_types.h" #include "net/cert/x509_certificate.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { namespace client_cert { namespace { // Functor to filter out non-matching issuers. class IssuerFilter { public: explicit IssuerFilter(const IssuerSubjectPattern& issuer) : issuer_(issuer) {} bool operator()(const scoped_refptr<net::X509Certificate>& cert) const { return !CertPrincipalMatches(issuer_, cert.get()->issuer()); } private: const IssuerSubjectPattern& issuer_; }; // Functor to filter out non-matching subjects. class SubjectFilter { public: explicit SubjectFilter(const IssuerSubjectPattern& subject) : subject_(subject) {} bool operator()(const scoped_refptr<net::X509Certificate>& cert) const { return !CertPrincipalMatches(subject_, cert.get()->subject()); } private: const IssuerSubjectPattern& subject_; }; // Functor to filter out certs that don't have private keys, or are invalid. class PrivateKeyFilter { public: explicit PrivateKeyFilter(net::CertDatabase* cert_db) : cert_db_(cert_db) {} bool operator()(const scoped_refptr<net::X509Certificate>& cert) const { return cert_db_->CheckUserCert(cert.get()) != net::OK; } private: net::CertDatabase* cert_db_; }; // Functor to filter out certs that don't have an issuer in the associated // IssuerCAPEMs list. class IssuerCaFilter { public: explicit IssuerCaFilter(const std::vector<std::string>& issuer_ca_pems) : issuer_ca_pems_(issuer_ca_pems) {} bool operator()(const scoped_refptr<net::X509Certificate>& cert) const { // Find the certificate issuer for each certificate. // TODO(gspencer): this functionality should be available from // X509Certificate or NSSCertDatabase. CERTCertificate* issuer_cert = CERT_FindCertIssuer( cert.get()->os_cert_handle(), PR_Now(), certUsageAnyCA); if (!issuer_cert) return true; std::string pem_encoded; if (!net::X509Certificate::GetPEMEncoded(issuer_cert, &pem_encoded)) { LOG(ERROR) << "Couldn't PEM-encode certificate."; return true; } return (std::find(issuer_ca_pems_.begin(), issuer_ca_pems_.end(), pem_encoded) == issuer_ca_pems_.end()); } private: const std::vector<std::string>& issuer_ca_pems_; }; std::string GetStringFromDictionary(const base::DictionaryValue& dict, const std::string& key) { std::string s; dict.GetStringWithoutPathExpansion(key, &s); return s; } } // namespace // Returns true only if any fields set in this pattern match exactly with // similar fields in the principal. If organization_ or organizational_unit_ // are set, then at least one of the organizations or units in the principal // must match. bool CertPrincipalMatches(const IssuerSubjectPattern& pattern, const net::CertPrincipal& principal) { if (!pattern.common_name().empty() && pattern.common_name() != principal.common_name) { return false; } if (!pattern.locality().empty() && pattern.locality() != principal.locality_name) { return false; } if (!pattern.organization().empty()) { if (std::find(principal.organization_names.begin(), principal.organization_names.end(), pattern.organization()) == principal.organization_names.end()) { return false; } } if (!pattern.organizational_unit().empty()) { if (std::find(principal.organization_unit_names.begin(), principal.organization_unit_names.end(), pattern.organizational_unit()) == principal.organization_unit_names.end()) { return false; } } return true; } scoped_refptr<net::X509Certificate> GetCertificateMatch( const CertificatePattern& pattern) { typedef std::list<scoped_refptr<net::X509Certificate> > CertificateStlList; // Start with all the certs, and narrow it down from there. net::CertificateList all_certs; CertificateStlList matching_certs; net::NSSCertDatabase::GetInstance()->ListCerts(&all_certs); if (all_certs.empty()) return NULL; for (net::CertificateList::iterator iter = all_certs.begin(); iter != all_certs.end(); ++iter) { matching_certs.push_back(*iter); } // Strip off any certs that don't have the right issuer and/or subject. if (!pattern.issuer().Empty()) { matching_certs.remove_if(IssuerFilter(pattern.issuer())); if (matching_certs.empty()) return NULL; } if (!pattern.subject().Empty()) { matching_certs.remove_if(SubjectFilter(pattern.subject())); if (matching_certs.empty()) return NULL; } if (!pattern.issuer_ca_pems().empty()) { matching_certs.remove_if(IssuerCaFilter(pattern.issuer_ca_pems())); if (matching_certs.empty()) return NULL; } // Eliminate any certs that don't have private keys associated with // them. The CheckUserCert call in the filter is a little slow (because of // underlying PKCS11 calls), so we do this last to reduce the number of times // we have to call it. PrivateKeyFilter private_filter(net::CertDatabase::GetInstance()); matching_certs.remove_if(private_filter); if (matching_certs.empty()) return NULL; // We now have a list of certificates that match the pattern we're // looking for. Now we find the one with the latest start date. scoped_refptr<net::X509Certificate> latest(NULL); // Iterate over the rest looking for the one that was issued latest. for (CertificateStlList::iterator iter = matching_certs.begin(); iter != matching_certs.end(); ++iter) { if (!latest.get() || (*iter)->valid_start() > latest->valid_start()) latest = *iter; } return latest; } void SetShillProperties(const client_cert::ConfigType cert_config_type, const std::string& tpm_slot, const std::string& tpm_pin, const std::string* pkcs11_id, base::DictionaryValue* properties) { const char* tpm_pin_property = NULL; switch (cert_config_type) { case CONFIG_TYPE_NONE: { return; } case CONFIG_TYPE_OPENVPN: { tpm_pin_property = shill::kOpenVPNPinProperty; if (pkcs11_id) { properties->SetStringWithoutPathExpansion( shill::kOpenVPNClientCertIdProperty, *pkcs11_id); } break; } case CONFIG_TYPE_IPSEC: { tpm_pin_property = shill::kL2tpIpsecPinProperty; if (!tpm_slot.empty()) { properties->SetStringWithoutPathExpansion( shill::kL2tpIpsecClientCertSlotProperty, tpm_slot); } if (pkcs11_id) { properties->SetStringWithoutPathExpansion( shill::kL2tpIpsecClientCertIdProperty, *pkcs11_id); } break; } case CONFIG_TYPE_EAP: { tpm_pin_property = shill::kEapPinProperty; if (pkcs11_id) { // Shill requires both CertID and KeyID for TLS connections, despite the // fact that by convention they are the same ID. properties->SetStringWithoutPathExpansion(shill::kEapCertIdProperty, *pkcs11_id); properties->SetStringWithoutPathExpansion(shill::kEapKeyIdProperty, *pkcs11_id); } break; } } DCHECK(tpm_pin_property); if (!tpm_pin.empty()) properties->SetStringWithoutPathExpansion(tpm_pin_property, tpm_pin); } bool IsCertificateConfigured(const client_cert::ConfigType cert_config_type, const base::DictionaryValue& service_properties) { // VPN certificate properties are read from the Provider dictionary. const base::DictionaryValue* provider_properties = NULL; service_properties.GetDictionaryWithoutPathExpansion( shill::kProviderProperty, &provider_properties); switch (cert_config_type) { case CONFIG_TYPE_NONE: return true; case CONFIG_TYPE_OPENVPN: // OpenVPN generally requires a passphrase and we don't know whether or // not one is required, so always return false here. return false; case CONFIG_TYPE_IPSEC: { if (!provider_properties) return false; std::string client_cert_id; provider_properties->GetStringWithoutPathExpansion( shill::kL2tpIpsecClientCertIdProperty, &client_cert_id); return !client_cert_id.empty(); } case CONFIG_TYPE_EAP: { std::string cert_id = GetStringFromDictionary( service_properties, shill::kEapCertIdProperty); std::string key_id = GetStringFromDictionary( service_properties, shill::kEapKeyIdProperty); std::string identity = GetStringFromDictionary( service_properties, shill::kEapIdentityProperty); return !cert_id.empty() && !key_id.empty() && !identity.empty(); } } NOTREACHED(); return false; } } // namespace client_cert } // namespace chromeos