Home | History | Annotate | Download | only in network
      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/network_cert_migrator.h"
      6 
      7 #include <cert.h>
      8 #include <string>
      9 
     10 #include "base/bind.h"
     11 #include "base/location.h"
     12 #include "base/metrics/histogram.h"
     13 #include "chromeos/dbus/dbus_thread_manager.h"
     14 #include "chromeos/dbus/shill_service_client.h"
     15 #include "chromeos/network/client_cert_util.h"
     16 #include "chromeos/network/network_handler_callbacks.h"
     17 #include "chromeos/network/network_state.h"
     18 #include "chromeos/network/network_state_handler.h"
     19 #include "dbus/object_path.h"
     20 #include "third_party/cros_system_api/dbus/service_constants.h"
     21 
     22 namespace chromeos {
     23 
     24 namespace {
     25 
     26 enum UMANetworkType {
     27   UMA_NETWORK_TYPE_EAP,
     28   UMA_NETWORK_TYPE_OPENVPN,
     29   UMA_NETWORK_TYPE_IPSEC,
     30   UMA_NETWORK_TYPE_SIZE,
     31 };
     32 
     33 // Copied from x509_certificate_model_nss.cc
     34 std::string GetNickname(const net::X509Certificate& cert) {
     35   if (!cert.os_cert_handle()->nickname)
     36     return std::string();
     37   std::string name = cert.os_cert_handle()->nickname;
     38   // Hack copied from mozilla: Cut off text before first :, which seems to
     39   // just be the token name.
     40   size_t colon_pos = name.find(':');
     41   if (colon_pos != std::string::npos)
     42     name = name.substr(colon_pos + 1);
     43   return name;
     44 }
     45 
     46 }  // namespace
     47 
     48 // Migrates each network of |networks| with a deprecated CaCertNss property to
     49 // the respective CaCertPEM property and fixes an invalid or missing slot ID of
     50 // a client certificate configuration.
     51 //
     52 // If a network already has a CaCertPEM property, then the NssProperty is
     53 // cleared. Otherwise, the NssProperty is compared with
     54 // the nickname of each certificate of |certs|. If a match is found, the
     55 // CaCertPemProperty is set and the NssProperty is cleared.
     56 //
     57 // If a network with a client certificate configuration (i.e. a PKCS11 ID) is
     58 // found, the configured client certificate is looked up.
     59 // If the certificate is found, the currently configured slot ID (if any) is
     60 // compared with the actual slot ID of the certificate and if required updated.
     61 // If the certificate is not found, the client certificate configuration is
     62 // removed.
     63 //
     64 // Only if necessary, a network will be notified.
     65 class NetworkCertMigrator::MigrationTask
     66     : public base::RefCounted<MigrationTask> {
     67  public:
     68   MigrationTask(const net::CertificateList& certs,
     69                 const base::WeakPtr<NetworkCertMigrator>& cert_migrator)
     70       : certs_(certs),
     71         cert_migrator_(cert_migrator) {
     72   }
     73 
     74   void Run(const NetworkStateHandler::NetworkStateList& networks) {
     75     // Request properties for each network that has a CaCertNssProperty set
     76     // or which could be configured with a client certificate.
     77     for (NetworkStateHandler::NetworkStateList::const_iterator it =
     78              networks.begin(); it != networks.end(); ++it) {
     79       if (!(*it)->HasCACertNSS() &&
     80           (*it)->security() != shill::kSecurity8021x &&
     81           (*it)->type() != shill::kTypeVPN &&
     82           (*it)->type() != shill::kTypeEthernetEap) {
     83         continue;
     84       }
     85       const std::string& service_path = (*it)->path();
     86       DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
     87           dbus::ObjectPath(service_path),
     88           base::Bind(&network_handler::GetPropertiesCallback,
     89                      base::Bind(&MigrationTask::MigrateNetwork, this),
     90                      network_handler::ErrorCallback(),
     91                      service_path));
     92     }
     93   }
     94 
     95   void MigrateNetwork(const std::string& service_path,
     96                       const base::DictionaryValue& properties) {
     97     if (!cert_migrator_) {
     98       VLOG(2) << "NetworkCertMigrator already destroyed. Aborting migration.";
     99       return;
    100     }
    101 
    102     base::DictionaryValue new_properties;
    103     MigrateClientCertProperties(service_path, properties, &new_properties);
    104     MigrateNssProperties(service_path, properties, &new_properties);
    105 
    106     if (new_properties.empty())
    107       return;
    108     SendPropertiesToShill(service_path, new_properties);
    109   }
    110 
    111   void MigrateClientCertProperties(const std::string& service_path,
    112                                    const base::DictionaryValue& properties,
    113                                    base::DictionaryValue* new_properties) {
    114     int configured_slot_id = -1;
    115     std::string pkcs11_id;
    116     chromeos::client_cert::ConfigType config_type =
    117         chromeos::client_cert::CONFIG_TYPE_NONE;
    118     chromeos::client_cert::GetClientCertFromShillProperties(
    119         properties, &config_type, &configured_slot_id, &pkcs11_id);
    120     if (config_type == chromeos::client_cert::CONFIG_TYPE_NONE ||
    121         pkcs11_id.empty()) {
    122       return;
    123     }
    124 
    125     // OpenVPN configuration doesn't have a slot id to migrate.
    126     if (config_type == chromeos::client_cert::CONFIG_TYPE_OPENVPN)
    127       return;
    128 
    129     int real_slot_id = -1;
    130     scoped_refptr<net::X509Certificate> cert =
    131         FindCertificateWithPkcs11Id(pkcs11_id, &real_slot_id);
    132     if (!cert.get()) {
    133       LOG(WARNING) << "No matching cert found, removing the certificate "
    134                       "configuration from network " << service_path;
    135       chromeos::client_cert::SetEmptyShillProperties(config_type,
    136                                                      new_properties);
    137       return;
    138     }
    139     if (real_slot_id == -1) {
    140       LOG(WARNING) << "Found a certificate without slot id.";
    141       return;
    142     }
    143 
    144     if (cert.get() && real_slot_id != configured_slot_id) {
    145       VLOG(1) << "Network " << service_path
    146               << " is configured with no or an incorrect slot id.";
    147       chromeos::client_cert::SetShillProperties(
    148           config_type, real_slot_id, pkcs11_id, new_properties);
    149     }
    150   }
    151 
    152   void MigrateNssProperties(const std::string& service_path,
    153                             const base::DictionaryValue& properties,
    154                             base::DictionaryValue* new_properties) {
    155     std::string nss_key, pem_key, nickname;
    156     const base::ListValue* pem_property = NULL;
    157     UMANetworkType uma_type = UMA_NETWORK_TYPE_SIZE;
    158 
    159     GetNssAndPemProperties(
    160         properties, &nss_key, &pem_key, &pem_property, &nickname, &uma_type);
    161     if (nickname.empty())
    162       return;  // Didn't find any nickname.
    163 
    164     VLOG(2) << "Found NSS nickname to migrate. Property: " << nss_key
    165             << ", network: " << service_path;
    166     UMA_HISTOGRAM_ENUMERATION(
    167         "Network.MigrationNssToPem", uma_type, UMA_NETWORK_TYPE_SIZE);
    168 
    169     if (pem_property && !pem_property->empty()) {
    170       VLOG(2) << "PEM already exists, clearing NSS property.";
    171       ClearNssProperty(nss_key, new_properties);
    172       return;
    173     }
    174 
    175     scoped_refptr<net::X509Certificate> cert =
    176         FindCertificateWithNickname(nickname);
    177     if (!cert.get()) {
    178       VLOG(2) << "No matching cert found.";
    179       return;
    180     }
    181 
    182     std::string pem_encoded;
    183     if (!net::X509Certificate::GetPEMEncoded(cert->os_cert_handle(),
    184                                              &pem_encoded)) {
    185       LOG(ERROR) << "PEM encoding failed.";
    186       return;
    187     }
    188 
    189     ClearNssProperty(nss_key, new_properties);
    190     SetPemProperty(pem_key, pem_encoded, new_properties);
    191   }
    192 
    193   void GetNssAndPemProperties(const base::DictionaryValue& shill_properties,
    194                               std::string* nss_key,
    195                               std::string* pem_key,
    196                               const base::ListValue** pem_property,
    197                               std::string* nickname,
    198                               UMANetworkType* uma_type) {
    199     struct NssPem {
    200       const char* read_prefix;
    201       const char* nss_key;
    202       const char* pem_key;
    203       UMANetworkType uma_type;
    204     } const kNssPemMap[] = {
    205         { NULL, shill::kEapCaCertNssProperty, shill::kEapCaCertPemProperty,
    206          UMA_NETWORK_TYPE_EAP },
    207         { shill::kProviderProperty, shill::kL2tpIpsecCaCertNssProperty,
    208          shill::kL2tpIpsecCaCertPemProperty, UMA_NETWORK_TYPE_IPSEC },
    209         { shill::kProviderProperty, shill::kOpenVPNCaCertNSSProperty,
    210          shill::kOpenVPNCaCertPemProperty, UMA_NETWORK_TYPE_OPENVPN },
    211     };
    212 
    213     for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kNssPemMap); ++i) {
    214       const base::DictionaryValue* dict = &shill_properties;
    215       if (kNssPemMap[i].read_prefix) {
    216         shill_properties.GetDictionaryWithoutPathExpansion(
    217             kNssPemMap[i].read_prefix, &dict);
    218         if (!dict)
    219           continue;
    220       }
    221       dict->GetStringWithoutPathExpansion(kNssPemMap[i].nss_key, nickname);
    222       if (!nickname->empty()) {
    223         *nss_key = kNssPemMap[i].nss_key;
    224         *pem_key = kNssPemMap[i].pem_key;
    225         *uma_type = kNssPemMap[i].uma_type;
    226         dict->GetListWithoutPathExpansion(kNssPemMap[i].pem_key, pem_property);
    227         return;
    228       }
    229     }
    230   }
    231 
    232   void ClearNssProperty(const std::string& nss_key,
    233                         base::DictionaryValue* new_properties) {
    234     new_properties->SetStringWithoutPathExpansion(nss_key, std::string());
    235   }
    236 
    237   scoped_refptr<net::X509Certificate> FindCertificateWithPkcs11Id(
    238       const std::string& pkcs11_id, int* slot_id) {
    239     *slot_id = -1;
    240     for (net::CertificateList::iterator it = certs_.begin(); it != certs_.end();
    241          ++it) {
    242       int current_slot_id = -1;
    243       std::string current_pkcs11_id =
    244           CertLoader::GetPkcs11IdAndSlotForCert(**it, &current_slot_id);
    245       if (current_pkcs11_id == pkcs11_id) {
    246         *slot_id = current_slot_id;
    247         return *it;
    248       }
    249     }
    250     return NULL;
    251   }
    252 
    253   scoped_refptr<net::X509Certificate> FindCertificateWithNickname(
    254       const std::string& nickname) {
    255     for (net::CertificateList::iterator it = certs_.begin(); it != certs_.end();
    256          ++it) {
    257       if (nickname == GetNickname(**it))
    258         return *it;
    259     }
    260     return NULL;
    261   }
    262 
    263   void SetPemProperty(const std::string& pem_key,
    264                       const std::string& pem_encoded_cert,
    265                       base::DictionaryValue* new_properties) {
    266     scoped_ptr<base::ListValue> ca_cert_pems(new base::ListValue);
    267     ca_cert_pems->AppendString(pem_encoded_cert);
    268     new_properties->SetWithoutPathExpansion(pem_key, ca_cert_pems.release());
    269   }
    270 
    271   void SendPropertiesToShill(const std::string& service_path,
    272                              const base::DictionaryValue& properties) {
    273     DBusThreadManager::Get()->GetShillServiceClient()->SetProperties(
    274         dbus::ObjectPath(service_path),
    275         properties,
    276         base::Bind(
    277             &MigrationTask::NotifyNetworkStateHandler, this, service_path),
    278         base::Bind(&MigrationTask::LogErrorAndNotifyNetworkStateHandler,
    279                    this,
    280                    service_path));
    281   }
    282 
    283   void LogErrorAndNotifyNetworkStateHandler(const std::string& service_path,
    284                                             const std::string& error_name,
    285                                             const std::string& error_message) {
    286     network_handler::ShillErrorCallbackFunction(
    287         "MigrationTask.SetProperties failed",
    288         service_path,
    289         network_handler::ErrorCallback(),
    290         error_name,
    291         error_message);
    292     NotifyNetworkStateHandler(service_path);
    293   }
    294 
    295   void NotifyNetworkStateHandler(const std::string& service_path) {
    296     if (!cert_migrator_) {
    297       VLOG(2) << "NetworkCertMigrator already destroyed. Aborting migration.";
    298       return;
    299     }
    300     cert_migrator_->network_state_handler_->RequestUpdateForNetwork(
    301         service_path);
    302   }
    303 
    304  private:
    305   friend class base::RefCounted<MigrationTask>;
    306   virtual ~MigrationTask() {
    307   }
    308 
    309   net::CertificateList certs_;
    310   base::WeakPtr<NetworkCertMigrator> cert_migrator_;
    311 };
    312 
    313 NetworkCertMigrator::NetworkCertMigrator()
    314     : network_state_handler_(NULL),
    315       weak_ptr_factory_(this) {
    316 }
    317 
    318 NetworkCertMigrator::~NetworkCertMigrator() {
    319   network_state_handler_->RemoveObserver(this, FROM_HERE);
    320   if (CertLoader::IsInitialized())
    321     CertLoader::Get()->RemoveObserver(this);
    322 }
    323 
    324 void NetworkCertMigrator::Init(NetworkStateHandler* network_state_handler) {
    325   DCHECK(network_state_handler);
    326   network_state_handler_ = network_state_handler;
    327   network_state_handler_->AddObserver(this, FROM_HERE);
    328 
    329   DCHECK(CertLoader::IsInitialized());
    330   CertLoader::Get()->AddObserver(this);
    331 }
    332 
    333 void NetworkCertMigrator::NetworkListChanged() {
    334   if (!CertLoader::Get()->certificates_loaded()) {
    335     VLOG(2) << "Certs not loaded yet.";
    336     return;
    337   }
    338   // Run the migration process from deprecated CaCertNssProperties to CaCertPem
    339   // and to fix missing or incorrect slot ids of client certificates.
    340   VLOG(2) << "Start certificate migration of network configurations.";
    341   scoped_refptr<MigrationTask> helper(new MigrationTask(
    342       CertLoader::Get()->cert_list(), weak_ptr_factory_.GetWeakPtr()));
    343   NetworkStateHandler::NetworkStateList networks;
    344   network_state_handler_->GetNetworkListByType(
    345       NetworkTypePattern::Default(),
    346       true,  // only configured networks
    347       false, // visible and not visible networks
    348       0,     // no count limit
    349       &networks);
    350   helper->Run(networks);
    351 }
    352 
    353 void NetworkCertMigrator::OnCertificatesLoaded(
    354     const net::CertificateList& cert_list,
    355     bool initial_load) {
    356   // Maybe there are networks referring to certs that were not loaded before but
    357   // are now.
    358   NetworkListChanged();
    359 }
    360 
    361 }  // namespace chromeos
    362