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