network_cert_migrator.cc revision 68043e1e95eeb07d5cae7aca370b26518b0867d6
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/location.h"
11#include "base/metrics/histogram.h"
12#include "chromeos/dbus/dbus_thread_manager.h"
13#include "chromeos/dbus/shill_service_client.h"
14#include "chromeos/network/network_handler_callbacks.h"
15#include "chromeos/network/network_state.h"
16#include "chromeos/network/network_state_handler.h"
17#include "dbus/object_path.h"
18#include "third_party/cros_system_api/dbus/service_constants.h"
19
20namespace chromeos {
21
22namespace {
23
24enum UMANetworkType {
25  UMA_NETWORK_TYPE_EAP,
26  UMA_NETWORK_TYPE_OPENVPN,
27  UMA_NETWORK_TYPE_IPSEC,
28  UMA_NETWORK_TYPE_SIZE,
29};
30
31// Copied from x509_certificate_model_nss.cc
32std::string GetNickname(const net::X509Certificate& cert) {
33  if (!cert.os_cert_handle()->nickname)
34    return std::string();
35  std::string name = cert.os_cert_handle()->nickname;
36  // Hack copied from mozilla: Cut off text before first :, which seems to
37  // just be the token name.
38  size_t colon_pos = name.find(':');
39  if (colon_pos != std::string::npos)
40    name = name.substr(colon_pos + 1);
41  return name;
42}
43
44}  // namespace
45
46// Checks which of the given |networks| has one of the deprecated
47// CaCertNssProperties set. If such a network already has a CaCertPEM property,
48// then the NssProperty is cleared. Otherwise, the NssProperty is compared with
49// the nickname of each certificate of |certs|. If a match is found, then the
50// CaCertPemProperty is set and the NssProperty is cleared. Otherwise, the
51// network is not modified.
52class NetworkCertMigrator::MigrationTask
53    : public base::RefCounted<MigrationTask> {
54 public:
55  MigrationTask(const net::CertificateList& certs,
56                const base::WeakPtr<NetworkCertMigrator>& cert_migrator)
57      : certs_(certs),
58        cert_migrator_(cert_migrator) {
59  }
60
61  void Run(const NetworkStateHandler::NetworkStateList& networks) {
62    // Request properties for each network that has a CaCertNssProperty set
63    // according to the NetworkStateHandler.
64    for (NetworkStateHandler::NetworkStateList::const_iterator it =
65             networks.begin(); it != networks.end(); ++it) {
66      if (!(*it)->HasCACertNSS())
67        continue;
68      const std::string& service_path = (*it)->path();
69      DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
70          dbus::ObjectPath(service_path),
71          base::Bind(&network_handler::GetPropertiesCallback,
72                     base::Bind(&MigrationTask::MigrateNetwork, this),
73                     network_handler::ErrorCallback(),
74                     service_path));
75    }
76  }
77
78  void MigrateNetwork(const std::string& service_path,
79                      const base::DictionaryValue& properties) {
80    if (!cert_migrator_) {
81      VLOG(2) << "NetworkCertMigrator already destroyed. Aborting migration.";
82      return;
83    }
84
85    std::string nss_key, pem_key, nickname;
86    const base::ListValue* pem_property = NULL;
87    UMANetworkType uma_type = UMA_NETWORK_TYPE_SIZE;
88
89    GetNssAndPemProperties(
90        properties, &nss_key, &pem_key, &pem_property, &nickname, &uma_type);
91    if (nickname.empty())
92      return;  // Didn't find any nickname.
93
94    VLOG(2) << "Found NSS nickname to migrate. Property: " << nss_key
95            << ", network: " << service_path;
96    UMA_HISTOGRAM_ENUMERATION(
97        "Network.MigrationNssToPem", uma_type, UMA_NETWORK_TYPE_SIZE);
98
99    if (pem_property && !pem_property->empty()) {
100      VLOG(2) << "PEM already exists, clearing NSS property.";
101      ClearNssProperty(service_path, nss_key);
102      return;
103    }
104
105    scoped_refptr<net::X509Certificate> cert =
106        FindCertificateWithNickname(nickname);
107    if (!cert) {
108      VLOG(2) << "No matching cert found.";
109      return;
110    }
111
112    std::string pem_encoded;
113    if (!net::X509Certificate::GetPEMEncoded(cert->os_cert_handle(),
114                                             &pem_encoded)) {
115      LOG(ERROR) << "PEM encoding failed.";
116      return;
117    }
118
119    SetNssAndPemProperties(service_path, nss_key, pem_key, pem_encoded);
120  }
121
122  void GetNssAndPemProperties(const base::DictionaryValue& shill_properties,
123                              std::string* nss_key,
124                              std::string* pem_key,
125                              const base::ListValue** pem_property,
126                              std::string* nickname,
127                              UMANetworkType* uma_type) {
128    struct NssPem {
129      const char* read_prefix;
130      const char* nss_key;
131      const char* pem_key;
132      UMANetworkType uma_type;
133    } const kNssPemMap[] = {
134        { NULL, shill::kEapCaCertNssProperty, shill::kEapCaCertPemProperty,
135         UMA_NETWORK_TYPE_EAP },
136        { shill::kProviderProperty, shill::kL2tpIpsecCaCertNssProperty,
137         shill::kL2tpIpsecCaCertPemProperty, UMA_NETWORK_TYPE_IPSEC },
138        { shill::kProviderProperty, shill::kOpenVPNCaCertNSSProperty,
139         shill::kOpenVPNCaCertPemProperty, UMA_NETWORK_TYPE_OPENVPN },
140    };
141
142    for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kNssPemMap); ++i) {
143      const base::DictionaryValue* dict = &shill_properties;
144      if (kNssPemMap[i].read_prefix) {
145        shill_properties.GetDictionaryWithoutPathExpansion(
146            kNssPemMap[i].read_prefix, &dict);
147        if (!dict)
148          continue;
149      }
150      dict->GetStringWithoutPathExpansion(kNssPemMap[i].nss_key, nickname);
151      if (!nickname->empty()) {
152        *nss_key = kNssPemMap[i].nss_key;
153        *pem_key = kNssPemMap[i].pem_key;
154        *uma_type = kNssPemMap[i].uma_type;
155        dict->GetListWithoutPathExpansion(kNssPemMap[i].pem_key, pem_property);
156        return;
157      }
158    }
159  }
160
161  void ClearNssProperty(const std::string& service_path,
162                        const std::string& nss_key) {
163    DBusThreadManager::Get()->GetShillServiceClient()->SetProperty(
164        dbus::ObjectPath(service_path),
165        nss_key,
166        base::StringValue(std::string()),
167        base::Bind(
168            &MigrationTask::NotifyNetworkStateHandler, this, service_path),
169        base::Bind(&network_handler::ShillErrorCallbackFunction,
170                   "MigrationTask.SetProperty failed",
171                   service_path,
172                   network_handler::ErrorCallback()));
173  }
174
175  scoped_refptr<net::X509Certificate> FindCertificateWithNickname(
176      const std::string& nickname) {
177    for (net::CertificateList::iterator it = certs_.begin(); it != certs_.end();
178         ++it) {
179      if (nickname == GetNickname(**it))
180        return *it;
181    }
182    return NULL;
183  }
184
185  void SetNssAndPemProperties(const std::string& service_path,
186                              const std::string& nss_key,
187                              const std::string& pem_key,
188                              const std::string& pem_encoded_cert) {
189    base::DictionaryValue new_properties;
190    new_properties.SetStringWithoutPathExpansion(nss_key, std::string());
191    scoped_ptr<base::ListValue> ca_cert_pems(new base::ListValue);
192    ca_cert_pems->AppendString(pem_encoded_cert);
193    new_properties.SetWithoutPathExpansion(pem_key, ca_cert_pems.release());
194
195    DBusThreadManager::Get()->GetShillServiceClient()->SetProperties(
196        dbus::ObjectPath(service_path),
197        new_properties,
198        base::Bind(
199            &MigrationTask::NotifyNetworkStateHandler, this, service_path),
200        base::Bind(&MigrationTask::LogErrorAndNotifyNetworkStateHandler,
201                   this,
202                   service_path));
203  }
204
205  void LogErrorAndNotifyNetworkStateHandler(const std::string& service_path,
206                                            const std::string& error_name,
207                                            const std::string& error_message) {
208    network_handler::ShillErrorCallbackFunction(
209        "MigrationTask.SetProperties failed",
210        service_path,
211        network_handler::ErrorCallback(),
212        error_name,
213        error_message);
214    NotifyNetworkStateHandler(service_path);
215  }
216
217  void NotifyNetworkStateHandler(const std::string& service_path) {
218    if (!cert_migrator_) {
219      VLOG(2) << "NetworkCertMigrator already destroyed. Aborting migration.";
220      return;
221    }
222    cert_migrator_->network_state_handler_->RequestUpdateForNetwork(
223        service_path);
224  }
225
226 private:
227  friend class base::RefCounted<MigrationTask>;
228  virtual ~MigrationTask() {
229  }
230
231  net::CertificateList certs_;
232  base::WeakPtr<NetworkCertMigrator> cert_migrator_;
233};
234
235NetworkCertMigrator::NetworkCertMigrator()
236    : network_state_handler_(NULL),
237      weak_ptr_factory_(this) {
238}
239
240NetworkCertMigrator::~NetworkCertMigrator() {
241  network_state_handler_->RemoveObserver(this, FROM_HERE);
242  if (CertLoader::IsInitialized())
243    CertLoader::Get()->RemoveObserver(this);
244}
245
246void NetworkCertMigrator::Init(NetworkStateHandler* network_state_handler) {
247  DCHECK(network_state_handler);
248  network_state_handler_ = network_state_handler;
249  network_state_handler_->AddObserver(this, FROM_HERE);
250
251  DCHECK(CertLoader::IsInitialized());
252  CertLoader::Get()->AddObserver(this);
253}
254
255void NetworkCertMigrator::NetworkListChanged() {
256  if (!CertLoader::Get()->certificates_loaded()) {
257    VLOG(2) << "Certs not loaded yet.";
258    return;
259  }
260  // Run the migration process from deprecated CaCertNssProperties to CaCertPem.
261  VLOG(2) << "Start NSS nickname to PEM migration.";
262  scoped_refptr<MigrationTask> helper(new MigrationTask(
263      CertLoader::Get()->cert_list(), weak_ptr_factory_.GetWeakPtr()));
264  NetworkStateHandler::NetworkStateList networks;
265  network_state_handler_->GetNetworkList(&networks);
266  helper->Run(networks);
267}
268
269void NetworkCertMigrator::OnCertificatesLoaded(
270    const net::CertificateList& cert_list,
271    bool initial_load) {
272  // Maybe there are networks referring to certs (by NSS nickname) that were not
273  // loaded before but are now.
274  NetworkListChanged();
275}
276
277}  // namespace chromeos
278