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, ¤t_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