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