1// Copyright (c) 2012 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/onc/onc_utils.h" 6 7#include "base/base64.h" 8#include "base/json/json_reader.h" 9#include "base/logging.h" 10#include "base/metrics/histogram.h" 11#include "base/strings/string_util.h" 12#include "base/values.h" 13#include "chromeos/network/network_event_log.h" 14#include "chromeos/network/onc/onc_mapper.h" 15#include "chromeos/network/onc/onc_signature.h" 16#include "chromeos/network/onc/onc_utils.h" 17#include "chromeos/network/onc/onc_validator.h" 18#include "crypto/encryptor.h" 19#include "crypto/hmac.h" 20#include "crypto/symmetric_key.h" 21#include "net/cert/pem_tokenizer.h" 22#include "net/cert/x509_certificate.h" 23 24#define ONC_LOG_WARNING(message) NET_LOG_WARNING("ONC", message) 25#define ONC_LOG_ERROR(message) NET_LOG_ERROR("ONC", message) 26 27using namespace ::onc; 28 29namespace chromeos { 30namespace onc { 31 32namespace { 33 34const char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC"; 35const char kUnableToDecode[] = "Unable to decode encrypted ONC"; 36 37} // namespace 38 39const char kEmptyUnencryptedConfiguration[] = 40 "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[]," 41 "\"Certificates\":[]}"; 42 43scoped_ptr<base::DictionaryValue> ReadDictionaryFromJson( 44 const std::string& json) { 45 std::string error; 46 base::Value* root = base::JSONReader::ReadAndReturnError( 47 json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, &error); 48 49 base::DictionaryValue* dict_ptr = NULL; 50 if (!root || !root->GetAsDictionary(&dict_ptr)) { 51 ONC_LOG_ERROR("Invalid JSON Dictionary: " + error); 52 delete root; 53 } 54 55 return make_scoped_ptr(dict_ptr); 56} 57 58scoped_ptr<base::DictionaryValue> Decrypt(const std::string& passphrase, 59 const base::DictionaryValue& root) { 60 const int kKeySizeInBits = 256; 61 const int kMaxIterationCount = 500000; 62 std::string onc_type; 63 std::string initial_vector; 64 std::string salt; 65 std::string cipher; 66 std::string stretch_method; 67 std::string hmac_method; 68 std::string hmac; 69 int iterations; 70 std::string ciphertext; 71 72 if (!root.GetString(encrypted::kCiphertext, &ciphertext) || 73 !root.GetString(encrypted::kCipher, &cipher) || 74 !root.GetString(encrypted::kHMAC, &hmac) || 75 !root.GetString(encrypted::kHMACMethod, &hmac_method) || 76 !root.GetString(encrypted::kIV, &initial_vector) || 77 !root.GetInteger(encrypted::kIterations, &iterations) || 78 !root.GetString(encrypted::kSalt, &salt) || 79 !root.GetString(encrypted::kStretch, &stretch_method) || 80 !root.GetString(toplevel_config::kType, &onc_type) || 81 onc_type != toplevel_config::kEncryptedConfiguration) { 82 83 ONC_LOG_ERROR("Encrypted ONC malformed."); 84 return scoped_ptr<base::DictionaryValue>(); 85 } 86 87 if (hmac_method != encrypted::kSHA1 || 88 cipher != encrypted::kAES256 || 89 stretch_method != encrypted::kPBKDF2) { 90 ONC_LOG_ERROR("Encrypted ONC unsupported encryption scheme."); 91 return scoped_ptr<base::DictionaryValue>(); 92 } 93 94 // Make sure iterations != 0, since that's not valid. 95 if (iterations == 0) { 96 ONC_LOG_ERROR(kUnableToDecrypt); 97 return scoped_ptr<base::DictionaryValue>(); 98 } 99 100 // Simply a sanity check to make sure we can't lock up the machine 101 // for too long with a huge number (or a negative number). 102 if (iterations < 0 || iterations > kMaxIterationCount) { 103 ONC_LOG_ERROR("Too many iterations in encrypted ONC"); 104 return scoped_ptr<base::DictionaryValue>(); 105 } 106 107 if (!base::Base64Decode(salt, &salt)) { 108 ONC_LOG_ERROR(kUnableToDecode); 109 return scoped_ptr<base::DictionaryValue>(); 110 } 111 112 scoped_ptr<crypto::SymmetricKey> key( 113 crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES, 114 passphrase, 115 salt, 116 iterations, 117 kKeySizeInBits)); 118 119 if (!base::Base64Decode(initial_vector, &initial_vector)) { 120 ONC_LOG_ERROR(kUnableToDecode); 121 return scoped_ptr<base::DictionaryValue>(); 122 } 123 if (!base::Base64Decode(ciphertext, &ciphertext)) { 124 ONC_LOG_ERROR(kUnableToDecode); 125 return scoped_ptr<base::DictionaryValue>(); 126 } 127 if (!base::Base64Decode(hmac, &hmac)) { 128 ONC_LOG_ERROR(kUnableToDecode); 129 return scoped_ptr<base::DictionaryValue>(); 130 } 131 132 crypto::HMAC hmac_verifier(crypto::HMAC::SHA1); 133 if (!hmac_verifier.Init(key.get()) || 134 !hmac_verifier.Verify(ciphertext, hmac)) { 135 ONC_LOG_ERROR(kUnableToDecrypt); 136 return scoped_ptr<base::DictionaryValue>(); 137 } 138 139 crypto::Encryptor decryptor; 140 if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector)) { 141 ONC_LOG_ERROR(kUnableToDecrypt); 142 return scoped_ptr<base::DictionaryValue>(); 143 } 144 145 std::string plaintext; 146 if (!decryptor.Decrypt(ciphertext, &plaintext)) { 147 ONC_LOG_ERROR(kUnableToDecrypt); 148 return scoped_ptr<base::DictionaryValue>(); 149 } 150 151 scoped_ptr<base::DictionaryValue> new_root = 152 ReadDictionaryFromJson(plaintext); 153 if (new_root.get() == NULL) { 154 ONC_LOG_ERROR("Property dictionary malformed."); 155 return scoped_ptr<base::DictionaryValue>(); 156 } 157 158 return new_root.Pass(); 159} 160 161std::string GetSourceAsString(ONCSource source) { 162 switch (source) { 163 case ONC_SOURCE_DEVICE_POLICY: 164 return "device policy"; 165 case ONC_SOURCE_USER_POLICY: 166 return "user policy"; 167 case ONC_SOURCE_NONE: 168 return "none"; 169 case ONC_SOURCE_USER_IMPORT: 170 return "user import"; 171 } 172 NOTREACHED() << "unknown ONC source " << source; 173 return "unknown"; 174} 175 176void ExpandField(const std::string& fieldname, 177 const StringSubstitution& substitution, 178 base::DictionaryValue* onc_object) { 179 std::string user_string; 180 if (!onc_object->GetStringWithoutPathExpansion(fieldname, &user_string)) 181 return; 182 183 std::string login_id; 184 if (substitution.GetSubstitute(substitutes::kLoginIDField, &login_id)) { 185 ReplaceSubstringsAfterOffset(&user_string, 0, 186 substitutes::kLoginIDField, 187 login_id); 188 } 189 190 std::string email; 191 if (substitution.GetSubstitute(substitutes::kEmailField, &email)) { 192 ReplaceSubstringsAfterOffset(&user_string, 0, 193 substitutes::kEmailField, 194 email); 195 } 196 197 onc_object->SetStringWithoutPathExpansion(fieldname, user_string); 198} 199 200void ExpandStringsInOncObject( 201 const OncValueSignature& signature, 202 const StringSubstitution& substitution, 203 base::DictionaryValue* onc_object) { 204 if (&signature == &kEAPSignature) { 205 ExpandField(eap::kAnonymousIdentity, substitution, onc_object); 206 ExpandField(eap::kIdentity, substitution, onc_object); 207 } else if (&signature == &kL2TPSignature || 208 &signature == &kOpenVPNSignature) { 209 ExpandField(vpn::kUsername, substitution, onc_object); 210 } 211 212 // Recurse into nested objects. 213 for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd(); 214 it.Advance()) { 215 base::DictionaryValue* inner_object = NULL; 216 if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object)) 217 continue; 218 219 const OncFieldSignature* field_signature = 220 GetFieldSignature(signature, it.key()); 221 if (!field_signature) 222 continue; 223 224 ExpandStringsInOncObject(*field_signature->value_signature, 225 substitution, inner_object); 226 } 227} 228 229void ExpandStringsInNetworks(const StringSubstitution& substitution, 230 base::ListValue* network_configs) { 231 for (base::ListValue::iterator it = network_configs->begin(); 232 it != network_configs->end(); ++it) { 233 base::DictionaryValue* network = NULL; 234 (*it)->GetAsDictionary(&network); 235 DCHECK(network); 236 ExpandStringsInOncObject( 237 kNetworkConfigurationSignature, substitution, network); 238 } 239} 240 241namespace { 242 243class OncMaskValues : public Mapper { 244 public: 245 static scoped_ptr<base::DictionaryValue> Mask( 246 const OncValueSignature& signature, 247 const base::DictionaryValue& onc_object, 248 const std::string& mask) { 249 OncMaskValues masker(mask); 250 bool unused_error; 251 return masker.MapObject(signature, onc_object, &unused_error); 252 } 253 254 protected: 255 explicit OncMaskValues(const std::string& mask) 256 : mask_(mask) { 257 } 258 259 virtual scoped_ptr<base::Value> MapField( 260 const std::string& field_name, 261 const OncValueSignature& object_signature, 262 const base::Value& onc_value, 263 bool* found_unknown_field, 264 bool* error) OVERRIDE { 265 if (FieldIsCredential(object_signature, field_name)) { 266 return scoped_ptr<base::Value>(new base::StringValue(mask_)); 267 } else { 268 return Mapper::MapField(field_name, object_signature, onc_value, 269 found_unknown_field, error); 270 } 271 } 272 273 // Mask to insert in place of the sensitive values. 274 std::string mask_; 275}; 276 277} // namespace 278 279scoped_ptr<base::DictionaryValue> MaskCredentialsInOncObject( 280 const OncValueSignature& signature, 281 const base::DictionaryValue& onc_object, 282 const std::string& mask) { 283 return OncMaskValues::Mask(signature, onc_object, mask); 284} 285 286namespace { 287 288std::string DecodePEM(const std::string& pem_encoded) { 289 // The PEM block header used for DER certificates 290 const char kCertificateHeader[] = "CERTIFICATE"; 291 292 // This is an older PEM marker for DER certificates. 293 const char kX509CertificateHeader[] = "X509 CERTIFICATE"; 294 295 std::vector<std::string> pem_headers; 296 pem_headers.push_back(kCertificateHeader); 297 pem_headers.push_back(kX509CertificateHeader); 298 299 net::PEMTokenizer pem_tokenizer(pem_encoded, pem_headers); 300 std::string decoded; 301 if (pem_tokenizer.GetNext()) { 302 decoded = pem_tokenizer.data(); 303 } else { 304 // If we failed to read the data as a PEM file, then try plain base64 decode 305 // in case the PEM marker strings are missing. For this to work, there has 306 // to be no white space, and it has to only contain the base64-encoded data. 307 if (!base::Base64Decode(pem_encoded, &decoded)) { 308 LOG(ERROR) << "Unable to base64 decode X509 data: " << pem_encoded; 309 return std::string(); 310 } 311 } 312 return decoded; 313} 314 315CertPEMsByGUIDMap GetServerAndCACertsByGUID( 316 const base::ListValue& certificates) { 317 CertPEMsByGUIDMap certs_by_guid; 318 for (base::ListValue::const_iterator it = certificates.begin(); 319 it != certificates.end(); ++it) { 320 base::DictionaryValue* cert = NULL; 321 (*it)->GetAsDictionary(&cert); 322 323 std::string guid; 324 cert->GetStringWithoutPathExpansion(certificate::kGUID, &guid); 325 std::string cert_type; 326 cert->GetStringWithoutPathExpansion(certificate::kType, &cert_type); 327 if (cert_type != certificate::kServer && 328 cert_type != certificate::kAuthority) { 329 continue; 330 } 331 std::string x509_data; 332 cert->GetStringWithoutPathExpansion(certificate::kX509, &x509_data); 333 334 std::string der = DecodePEM(x509_data); 335 std::string pem; 336 if (der.empty() || !net::X509Certificate::GetPEMEncodedFromDER(der, &pem)) { 337 LOG(ERROR) << "Certificate with GUID " << guid 338 << " is not in PEM encoding."; 339 continue; 340 } 341 certs_by_guid[guid] = pem; 342 } 343 344 return certs_by_guid; 345} 346 347} // namespace 348 349bool ParseAndValidateOncForImport(const std::string& onc_blob, 350 ONCSource onc_source, 351 const std::string& passphrase, 352 base::ListValue* network_configs, 353 base::DictionaryValue* global_network_config, 354 base::ListValue* certificates) { 355 network_configs->Clear(); 356 global_network_config->Clear(); 357 certificates->Clear(); 358 if (onc_blob.empty()) 359 return true; 360 361 scoped_ptr<base::DictionaryValue> toplevel_onc = 362 ReadDictionaryFromJson(onc_blob); 363 if (toplevel_onc.get() == NULL) { 364 LOG(ERROR) << "ONC loaded from " << GetSourceAsString(onc_source) 365 << " is not a valid JSON dictionary."; 366 return false; 367 } 368 369 // Check and see if this is an encrypted ONC file. If so, decrypt it. 370 std::string onc_type; 371 toplevel_onc->GetStringWithoutPathExpansion(toplevel_config::kType, 372 &onc_type); 373 if (onc_type == toplevel_config::kEncryptedConfiguration) { 374 toplevel_onc = Decrypt(passphrase, *toplevel_onc); 375 if (toplevel_onc.get() == NULL) { 376 LOG(ERROR) << "Couldn't decrypt the ONC from " 377 << GetSourceAsString(onc_source); 378 return false; 379 } 380 } 381 382 bool from_policy = (onc_source == ONC_SOURCE_USER_POLICY || 383 onc_source == ONC_SOURCE_DEVICE_POLICY); 384 385 // Validate the ONC dictionary. We are liberal and ignore unknown field 386 // names and ignore invalid field names in kRecommended arrays. 387 Validator validator(false, // Ignore unknown fields. 388 false, // Ignore invalid recommended field names. 389 true, // Fail on missing fields. 390 from_policy); 391 validator.SetOncSource(onc_source); 392 393 Validator::Result validation_result; 394 toplevel_onc = validator.ValidateAndRepairObject( 395 &kToplevelConfigurationSignature, 396 *toplevel_onc, 397 &validation_result); 398 399 if (from_policy) { 400 UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation", 401 validation_result == Validator::VALID); 402 } 403 404 bool success = true; 405 if (validation_result == Validator::VALID_WITH_WARNINGS) { 406 LOG(WARNING) << "ONC from " << GetSourceAsString(onc_source) 407 << " produced warnings."; 408 success = false; 409 } else if (validation_result == Validator::INVALID || toplevel_onc == NULL) { 410 LOG(ERROR) << "ONC from " << GetSourceAsString(onc_source) 411 << " is invalid and couldn't be repaired."; 412 return false; 413 } 414 415 base::ListValue* validated_certs = NULL; 416 if (toplevel_onc->GetListWithoutPathExpansion(toplevel_config::kCertificates, 417 &validated_certs)) { 418 certificates->Swap(validated_certs); 419 } 420 421 base::ListValue* validated_networks = NULL; 422 if (toplevel_onc->GetListWithoutPathExpansion( 423 toplevel_config::kNetworkConfigurations, &validated_networks)) { 424 CertPEMsByGUIDMap server_and_ca_certs = 425 GetServerAndCACertsByGUID(*certificates); 426 427 if (!ResolveServerCertRefsInNetworks(server_and_ca_certs, 428 validated_networks)) { 429 LOG(ERROR) << "Some certificate references in the ONC policy for source " 430 << GetSourceAsString(onc_source) << " could not be resolved."; 431 success = false; 432 } 433 434 network_configs->Swap(validated_networks); 435 } 436 437 base::DictionaryValue* validated_global_config = NULL; 438 if (toplevel_onc->GetDictionaryWithoutPathExpansion( 439 toplevel_config::kGlobalNetworkConfiguration, 440 &validated_global_config)) { 441 global_network_config->Swap(validated_global_config); 442 } 443 444 return success; 445} 446 447scoped_refptr<net::X509Certificate> DecodePEMCertificate( 448 const std::string& pem_encoded) { 449 std::string decoded = DecodePEM(pem_encoded); 450 scoped_refptr<net::X509Certificate> cert = 451 net::X509Certificate::CreateFromBytes(decoded.data(), decoded.size()); 452 LOG_IF(ERROR, !cert.get()) << "Couldn't create certificate from X509 data: " 453 << decoded; 454 return cert; 455} 456 457namespace { 458 459bool GUIDRefToPEMEncoding(const CertPEMsByGUIDMap& certs_by_guid, 460 const std::string& guid_ref, 461 std::string* pem_encoded) { 462 CertPEMsByGUIDMap::const_iterator it = certs_by_guid.find(guid_ref); 463 if (it == certs_by_guid.end()) { 464 LOG(ERROR) << "Couldn't resolve certificate reference " << guid_ref; 465 return false; 466 } 467 *pem_encoded = it->second; 468 if (pem_encoded->empty()) { 469 LOG(ERROR) << "Couldn't PEM-encode certificate with GUID " << guid_ref; 470 return false; 471 } 472 return true; 473} 474 475bool ResolveSingleCertRef(const CertPEMsByGUIDMap& certs_by_guid, 476 const std::string& key_guid_ref, 477 const std::string& key_pem, 478 base::DictionaryValue* onc_object) { 479 std::string guid_ref; 480 if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref)) 481 return true; 482 483 std::string pem_encoded; 484 if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded)) 485 return false; 486 487 onc_object->RemoveWithoutPathExpansion(key_guid_ref, NULL); 488 onc_object->SetStringWithoutPathExpansion(key_pem, pem_encoded); 489 return true; 490} 491 492bool ResolveCertRefList(const CertPEMsByGUIDMap& certs_by_guid, 493 const std::string& key_guid_ref_list, 494 const std::string& key_pem_list, 495 base::DictionaryValue* onc_object) { 496 const base::ListValue* guid_ref_list = NULL; 497 if (!onc_object->GetListWithoutPathExpansion(key_guid_ref_list, 498 &guid_ref_list)) { 499 return true; 500 } 501 502 scoped_ptr<base::ListValue> pem_list(new base::ListValue); 503 for (base::ListValue::const_iterator it = guid_ref_list->begin(); 504 it != guid_ref_list->end(); ++it) { 505 std::string guid_ref; 506 (*it)->GetAsString(&guid_ref); 507 508 std::string pem_encoded; 509 if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded)) 510 return false; 511 512 pem_list->AppendString(pem_encoded); 513 } 514 515 onc_object->RemoveWithoutPathExpansion(key_guid_ref_list, NULL); 516 onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release()); 517 return true; 518} 519 520bool ResolveSingleCertRefToList(const CertPEMsByGUIDMap& certs_by_guid, 521 const std::string& key_guid_ref, 522 const std::string& key_pem_list, 523 base::DictionaryValue* onc_object) { 524 std::string guid_ref; 525 if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref)) 526 return true; 527 528 std::string pem_encoded; 529 if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded)) 530 return false; 531 532 scoped_ptr<base::ListValue> pem_list(new base::ListValue); 533 pem_list->AppendString(pem_encoded); 534 onc_object->RemoveWithoutPathExpansion(key_guid_ref, NULL); 535 onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release()); 536 return true; 537} 538 539// Resolves the reference list at |key_guid_refs| if present and otherwise the 540// single reference at |key_guid_ref|. Returns whether the respective resolving 541// was successful. 542bool ResolveCertRefsOrRefToList(const CertPEMsByGUIDMap& certs_by_guid, 543 const std::string& key_guid_refs, 544 const std::string& key_guid_ref, 545 const std::string& key_pem_list, 546 base::DictionaryValue* onc_object) { 547 if (onc_object->HasKey(key_guid_refs)) { 548 if (onc_object->HasKey(key_guid_ref)) { 549 LOG(ERROR) << "Found both " << key_guid_refs << " and " << key_guid_ref 550 << ". Ignoring and removing the latter."; 551 onc_object->RemoveWithoutPathExpansion(key_guid_ref, NULL); 552 } 553 return ResolveCertRefList( 554 certs_by_guid, key_guid_refs, key_pem_list, onc_object); 555 } 556 557 // Only resolve |key_guid_ref| if |key_guid_refs| isn't present. 558 return ResolveSingleCertRefToList( 559 certs_by_guid, key_guid_ref, key_pem_list, onc_object); 560} 561 562bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap& certs_by_guid, 563 const OncValueSignature& signature, 564 base::DictionaryValue* onc_object) { 565 if (&signature == &kCertificatePatternSignature) { 566 if (!ResolveCertRefList(certs_by_guid, certificate::kIssuerCARef, 567 certificate::kIssuerCAPEMs, onc_object)) { 568 return false; 569 } 570 } else if (&signature == &kEAPSignature) { 571 if (!ResolveCertRefsOrRefToList(certs_by_guid, 572 eap::kServerCARefs, 573 eap::kServerCARef, 574 eap::kServerCAPEMs, 575 onc_object)) { 576 return false; 577 } 578 } else if (&signature == &kIPsecSignature) { 579 if (!ResolveCertRefsOrRefToList(certs_by_guid, 580 ipsec::kServerCARefs, 581 ipsec::kServerCARef, 582 ipsec::kServerCAPEMs, 583 onc_object)) { 584 return false; 585 } 586 } else if (&signature == &kIPsecSignature || 587 &signature == &kOpenVPNSignature) { 588 if (!ResolveSingleCertRef(certs_by_guid, 589 openvpn::kServerCertRef, 590 openvpn::kServerCertPEM, 591 onc_object) || 592 !ResolveCertRefsOrRefToList(certs_by_guid, 593 openvpn::kServerCARefs, 594 openvpn::kServerCARef, 595 openvpn::kServerCAPEMs, 596 onc_object)) { 597 return false; 598 } 599 } 600 601 // Recurse into nested objects. 602 for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd(); 603 it.Advance()) { 604 base::DictionaryValue* inner_object = NULL; 605 if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object)) 606 continue; 607 608 const OncFieldSignature* field_signature = 609 GetFieldSignature(signature, it.key()); 610 if (!field_signature) 611 continue; 612 613 if (!ResolveServerCertRefsInObject(certs_by_guid, 614 *field_signature->value_signature, 615 inner_object)) { 616 return false; 617 } 618 } 619 return true; 620} 621 622} // namespace 623 624bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap& certs_by_guid, 625 base::ListValue* network_configs) { 626 bool success = true; 627 for (base::ListValue::iterator it = network_configs->begin(); 628 it != network_configs->end(); ) { 629 base::DictionaryValue* network = NULL; 630 (*it)->GetAsDictionary(&network); 631 if (!ResolveServerCertRefsInNetwork(certs_by_guid, network)) { 632 std::string guid; 633 network->GetStringWithoutPathExpansion(network_config::kGUID, &guid); 634 // This might happen even with correct validation, if the referenced 635 // certificate couldn't be imported. 636 LOG(ERROR) << "Couldn't resolve some certificate reference of network " 637 << guid; 638 it = network_configs->Erase(it, NULL); 639 success = false; 640 continue; 641 } 642 ++it; 643 } 644 return success; 645} 646 647bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap& certs_by_guid, 648 base::DictionaryValue* network_config) { 649 return ResolveServerCertRefsInObject(certs_by_guid, 650 kNetworkConfigurationSignature, 651 network_config); 652} 653 654} // namespace onc 655} // namespace chromeos 656