network_cert_migrator.cc revision 3240926e260ce088908e02ac07a6cf7b0c0cbf44
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, flimflam::kEapCaCertNssProperty, shill::kEapCaCertPemProperty,
135         UMA_NETWORK_TYPE_EAP },
136        { flimflam::kProviderProperty, flimflam::kL2tpIpsecCaCertNssProperty,
137         shill::kL2tpIpsecCaCertPemProperty, UMA_NETWORK_TYPE_IPSEC },
138        { flimflam::kProviderProperty, flimflam::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()
164        ->SetProperty(dbus::ObjectPath(service_path),
165                      nss_key,
166                      base::StringValue(std::string()),
167                      base::Bind(&base::DoNothing),
168                      base::Bind(&network_handler::ShillErrorCallbackFunction,
169                                 "MigrationTask.SetProperty failed",
170                                 service_path,
171                                 network_handler::ErrorCallback()));
172    cert_migrator_->network_state_handler_
173        ->RequestUpdateForNetwork(service_path);
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()
197        ->SetProperties(dbus::ObjectPath(service_path),
198                        new_properties,
199                        base::Bind(&base::DoNothing),
200                        base::Bind(&network_handler::ShillErrorCallbackFunction,
201                                   "MigrationTask.SetProperties failed",
202                                   service_path,
203                                   network_handler::ErrorCallback()));
204    cert_migrator_->network_state_handler_
205        ->RequestUpdateForNetwork(service_path);
206  }
207
208 private:
209  friend class base::RefCounted<MigrationTask>;
210  virtual ~MigrationTask() {
211  }
212
213  net::CertificateList certs_;
214  base::WeakPtr<NetworkCertMigrator> cert_migrator_;
215};
216
217NetworkCertMigrator::NetworkCertMigrator()
218    : network_state_handler_(NULL),
219      weak_ptr_factory_(this) {
220}
221
222NetworkCertMigrator::~NetworkCertMigrator() {
223  network_state_handler_->RemoveObserver(this, FROM_HERE);
224  if (CertLoader::IsInitialized())
225    CertLoader::Get()->RemoveObserver(this);
226}
227
228void NetworkCertMigrator::Init(NetworkStateHandler* network_state_handler) {
229  DCHECK(network_state_handler);
230  network_state_handler_ = network_state_handler;
231  network_state_handler_->AddObserver(this, FROM_HERE);
232
233  DCHECK(CertLoader::IsInitialized());
234  CertLoader::Get()->AddObserver(this);
235}
236
237void NetworkCertMigrator::NetworkListChanged() {
238  if (!CertLoader::Get()->certificates_loaded()) {
239    VLOG(2) << "Certs not loaded yet.";
240    return;
241  }
242  // Run the migration process from deprecated CaCertNssProperties to CaCertPem.
243  VLOG(2) << "Start NSS nickname to PEM migration.";
244  scoped_refptr<MigrationTask> helper(new MigrationTask(
245      CertLoader::Get()->cert_list(), weak_ptr_factory_.GetWeakPtr()));
246  NetworkStateHandler::NetworkStateList networks;
247  network_state_handler_->GetNetworkList(&networks);
248  helper->Run(networks);
249}
250
251void NetworkCertMigrator::OnCertificatesLoaded(
252    const net::CertificateList& cert_list,
253    bool initial_load) {
254  // Maybe there are networks referring to certs (by NSS nickname) that were not
255  // loaded before but are now.
256  NetworkListChanged();
257}
258
259}  // namespace chromeos
260