| // 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 <stddef.h> | 
 |  | 
 | #include <list> | 
 |  | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/stringprintf.h" | 
 | #include "base/values.h" | 
 | #include "chromeos/network/network_event_log.h" | 
 | #include "components/onc/onc_constants.h" | 
 | #include "net/base/net_errors.h" | 
 | #include "net/cert/cert_database.h" | 
 | #include "net/cert/nss_cert_database.h" | 
 | #include "net/cert/scoped_nss_types.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 { | 
 |  | 
 | const char kDefaultTPMPin[] = "111111"; | 
 |  | 
 | std::string GetStringFromDictionary(const base::DictionaryValue& dict, | 
 |                                     const std::string& key) { | 
 |   std::string s; | 
 |   dict.GetStringWithoutPathExpansion(key, &s); | 
 |   return s; | 
 | } | 
 |  | 
 | void GetClientCertTypeAndPattern( | 
 |     const base::DictionaryValue& dict_with_client_cert, | 
 |     ClientCertConfig* cert_config) { | 
 |   dict_with_client_cert.GetStringWithoutPathExpansion( | 
 |       ::onc::eap::kIdentity, &cert_config->policy_identity); | 
 |  | 
 |   using namespace ::onc::client_cert; | 
 |   dict_with_client_cert.GetStringWithoutPathExpansion( | 
 |       kClientCertType, &cert_config->client_cert_type); | 
 |  | 
 |   if (cert_config->client_cert_type == kPattern) { | 
 |     const base::DictionaryValue* pattern = NULL; | 
 |     dict_with_client_cert.GetDictionaryWithoutPathExpansion(kClientCertPattern, | 
 |                                                             &pattern); | 
 |     if (pattern) { | 
 |       bool success = cert_config->pattern.ReadFromONCDictionary(*pattern); | 
 |       DCHECK(success); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | }  // 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; | 
 | } | 
 |  | 
 | std::string GetPkcs11AndSlotIdFromEapCertId(const std::string& cert_id, | 
 |                                             int* slot_id) { | 
 |   *slot_id = -1; | 
 |   if (cert_id.empty()) | 
 |     return std::string(); | 
 |  | 
 |   size_t delimiter_pos = cert_id.find(':'); | 
 |   if (delimiter_pos == std::string::npos) { | 
 |     // No delimiter found, so |cert_id| only contains the PKCS11 id. | 
 |     return cert_id; | 
 |   } | 
 |   if (delimiter_pos + 1 >= cert_id.size()) { | 
 |     LOG(ERROR) << "Empty PKCS11 id in cert id."; | 
 |     return std::string(); | 
 |   } | 
 |   int parsed_slot_id; | 
 |   if (base::StringToInt(cert_id.substr(0, delimiter_pos), &parsed_slot_id)) | 
 |     *slot_id = parsed_slot_id; | 
 |   else | 
 |     LOG(ERROR) << "Slot ID is not an integer. Cert ID is: " << cert_id << "."; | 
 |   return cert_id.substr(delimiter_pos + 1); | 
 | } | 
 |  | 
 | void GetClientCertFromShillProperties( | 
 |     const base::DictionaryValue& shill_properties, | 
 |     ConfigType* cert_config_type, | 
 |     int* tpm_slot, | 
 |     std::string* pkcs11_id) { | 
 |   *cert_config_type = CONFIG_TYPE_NONE; | 
 |   *tpm_slot = -1; | 
 |   pkcs11_id->clear(); | 
 |  | 
 |   // Look for VPN specific client certificate properties. | 
 |   // | 
 |   // VPN Provider values are read from the "Provider" dictionary, not the | 
 |   // "Provider.Type", etc keys (which are used only to set the values). | 
 |   const base::DictionaryValue* provider_properties = NULL; | 
 |   if (shill_properties.GetDictionaryWithoutPathExpansion( | 
 |           shill::kProviderProperty, &provider_properties)) { | 
 |     // Look for OpenVPN specific properties. | 
 |     if (provider_properties->GetStringWithoutPathExpansion( | 
 |             shill::kOpenVPNClientCertIdProperty, pkcs11_id)) { | 
 |       *cert_config_type = CONFIG_TYPE_OPENVPN; | 
 |       return; | 
 |     } | 
 |     // Look for L2TP-IPsec specific properties. | 
 |     if (provider_properties->GetStringWithoutPathExpansion( | 
 |                    shill::kL2tpIpsecClientCertIdProperty, pkcs11_id)) { | 
 |       std::string cert_slot; | 
 |       provider_properties->GetStringWithoutPathExpansion( | 
 |           shill::kL2tpIpsecClientCertSlotProperty, &cert_slot); | 
 |       if (!cert_slot.empty() && !base::StringToInt(cert_slot, tpm_slot)) { | 
 |         LOG(ERROR) << "Cert slot is not an integer: " << cert_slot << "."; | 
 |         return; | 
 |       } | 
 |  | 
 |       *cert_config_type = CONFIG_TYPE_IPSEC; | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   // Look for EAP specific client certificate properties, which can either be | 
 |   // part of a WiFi or EthernetEAP configuration. | 
 |   std::string cert_id; | 
 |   if (shill_properties.GetStringWithoutPathExpansion(shill::kEapCertIdProperty, | 
 |                                                      &cert_id)) { | 
 |     // Shill requires both CertID and KeyID for TLS connections, despite the | 
 |     // fact that by convention they are the same ID, because one identifies | 
 |     // the certificate and the other the private key. | 
 |     std::string key_id; | 
 |     shill_properties.GetStringWithoutPathExpansion(shill::kEapKeyIdProperty, | 
 |                                                    &key_id); | 
 |     // Assume the configuration to be invalid, if the two IDs are not identical. | 
 |     if (cert_id != key_id) { | 
 |       LOG(ERROR) << "EAP CertID differs from KeyID"; | 
 |       return; | 
 |     } | 
 |     *pkcs11_id = GetPkcs11AndSlotIdFromEapCertId(cert_id, tpm_slot); | 
 |     *cert_config_type = CONFIG_TYPE_EAP; | 
 |   } | 
 | } | 
 |  | 
 | void SetShillProperties(const ConfigType cert_config_type, | 
 |                         const int tpm_slot, | 
 |                         const std::string& pkcs11_id, | 
 |                         base::DictionaryValue* properties) { | 
 |   switch (cert_config_type) { | 
 |     case CONFIG_TYPE_NONE: { | 
 |       return; | 
 |     } | 
 |     case CONFIG_TYPE_OPENVPN: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kOpenVPNPinProperty, | 
 |                                                 kDefaultTPMPin); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kOpenVPNClientCertIdProperty, pkcs11_id); | 
 |       break; | 
 |     } | 
 |     case CONFIG_TYPE_IPSEC: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kL2tpIpsecPinProperty, | 
 |                                                 kDefaultTPMPin); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kL2tpIpsecClientCertSlotProperty, base::IntToString(tpm_slot)); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kL2tpIpsecClientCertIdProperty, pkcs11_id); | 
 |       break; | 
 |     } | 
 |     case CONFIG_TYPE_EAP: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapPinProperty, | 
 |                                                 kDefaultTPMPin); | 
 |       std::string key_id = | 
 |           base::StringPrintf("%i:%s", tpm_slot, pkcs11_id.c_str()); | 
 |  | 
 |       // Shill requires both CertID and KeyID for TLS connections, despite the | 
 |       // fact that by convention they are the same ID, because one identifies | 
 |       // the certificate and the other the private key. | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapCertIdProperty, | 
 |                                                 key_id); | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapKeyIdProperty, | 
 |                                                 key_id); | 
 |       break; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SetEmptyShillProperties(const ConfigType cert_config_type, | 
 |                              base::DictionaryValue* properties) { | 
 |   switch (cert_config_type) { | 
 |     case CONFIG_TYPE_NONE: { | 
 |       return; | 
 |     } | 
 |     case CONFIG_TYPE_OPENVPN: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kOpenVPNPinProperty, | 
 |                                                 std::string()); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kOpenVPNClientCertIdProperty, std::string()); | 
 |       break; | 
 |     } | 
 |     case CONFIG_TYPE_IPSEC: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kL2tpIpsecPinProperty, | 
 |                                                 std::string()); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kL2tpIpsecClientCertSlotProperty, std::string()); | 
 |       properties->SetStringWithoutPathExpansion( | 
 |           shill::kL2tpIpsecClientCertIdProperty, std::string()); | 
 |       break; | 
 |     } | 
 |     case CONFIG_TYPE_EAP: { | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapPinProperty, | 
 |                                                 std::string()); | 
 |       // Shill requires both CertID and KeyID for TLS connections, despite the | 
 |       // fact that by convention they are the same ID, because one identifies | 
 |       // the certificate and the other the private key. | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapCertIdProperty, | 
 |                                                 std::string()); | 
 |       properties->SetStringWithoutPathExpansion(shill::kEapKeyIdProperty, | 
 |                                                 std::string()); | 
 |       break; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | ClientCertConfig::ClientCertConfig() | 
 |     : location(CONFIG_TYPE_NONE), | 
 |       client_cert_type(onc::client_cert::kClientCertTypeNone) { | 
 | } | 
 |  | 
 | ClientCertConfig::ClientCertConfig(const ClientCertConfig& other) = default; | 
 |  | 
 | void OncToClientCertConfig(const base::DictionaryValue& network_config, | 
 |                            ClientCertConfig* cert_config) { | 
 |   using namespace ::onc; | 
 |  | 
 |   *cert_config = ClientCertConfig(); | 
 |  | 
 |   const base::DictionaryValue* dict_with_client_cert = NULL; | 
 |  | 
 |   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; | 
 |  | 
 |     dict_with_client_cert = eap; | 
 |     cert_config->location = 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); | 
 |     const base::DictionaryValue* ipsec = NULL; | 
 |     vpn->GetDictionaryWithoutPathExpansion(vpn::kIPsec, &ipsec); | 
 |     if (openvpn) { | 
 |       dict_with_client_cert = openvpn; | 
 |       cert_config->location = CONFIG_TYPE_OPENVPN; | 
 |     } else if (ipsec) { | 
 |       dict_with_client_cert = ipsec; | 
 |       cert_config->location = CONFIG_TYPE_IPSEC; | 
 |     } else { | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   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; | 
 |     dict_with_client_cert = eap; | 
 |     cert_config->location = CONFIG_TYPE_EAP; | 
 |   } | 
 |  | 
 |   if (dict_with_client_cert) | 
 |     GetClientCertTypeAndPattern(*dict_with_client_cert, cert_config); | 
 | } | 
 |  | 
 | bool IsCertificateConfigured(const 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 |