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