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
22namespace chromeos {
23
24namespace {
25
26enum 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
34std::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.
65class 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
313NetworkCertMigrator::NetworkCertMigrator()
314    : network_state_handler_(NULL),
315      weak_ptr_factory_(this) {
316}
317
318NetworkCertMigrator::~NetworkCertMigrator() {
319  network_state_handler_->RemoveObserver(this, FROM_HERE);
320  if (CertLoader::IsInitialized())
321    CertLoader::Get()->RemoveObserver(this);
322}
323
324void 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
333void 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
353void 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