summaryrefslogtreecommitdiffstats
path: root/chromeos
diff options
context:
space:
mode:
authorgspencer@chromium.org <gspencer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-11 23:02:54 +0000
committergspencer@chromium.org <gspencer@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-12-11 23:02:54 +0000
commit01c988df2e091c8c1c7b2d0fe652124d8e2d0101 (patch)
treea4c2c68a29d0500d6422f72dc9a709919dbf38a3 /chromeos
parent6ba4d65ab5e3dd39bb7098a4a19265cb7593f482 (diff)
downloadchromium_src-01c988df2e091c8c1c7b2d0fe652124d8e2d0101.zip
chromium_src-01c988df2e091c8c1c7b2d0fe652124d8e2d0101.tar.gz
chromium_src-01c988df2e091c8c1c7b2d0fe652124d8e2d0101.tar.bz2
This moves the ONC parsing code into chromeos/network/onc
so that it can be used there without violating dependency rules. (Using the "refactoring" OWNERS TBR rule...) BUG=none TEST=ran unit tests TBR=jhawkins@chromium.org Review URL: https://chromiumcodereview.appspot.com/11299236 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@172446 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos')
-rw-r--r--chromeos/DEPS1
-rw-r--r--chromeos/chromeos.gyp35
-rw-r--r--chromeos/chromeos_test_utils.cc32
-rw-r--r--chromeos/chromeos_test_utils.h23
-rw-r--r--chromeos/network/network_event_log.cc2
-rw-r--r--chromeos/network/network_event_log.h14
-rw-r--r--chromeos/network/network_event_log_unittest.cc20
-rw-r--r--chromeos/network/onc/onc_certificate_importer.cc368
-rw-r--r--chromeos/network/onc/onc_certificate_importer.h91
-rw-r--r--chromeos/network/onc/onc_certificate_importer_unittest.cc261
-rw-r--r--chromeos/network/onc/onc_constants.cc204
-rw-r--r--chromeos/network/onc/onc_constants.h220
-rw-r--r--chromeos/network/onc/onc_mapper.cc140
-rw-r--r--chromeos/network/onc/onc_mapper.h104
-rw-r--r--chromeos/network/onc/onc_merger.cc174
-rw-r--r--chromeos/network/onc/onc_merger.h36
-rw-r--r--chromeos/network/onc/onc_merger_unittest.cc146
-rw-r--r--chromeos/network/onc/onc_normalizer.cc104
-rw-r--r--chromeos/network/onc/onc_normalizer.h50
-rw-r--r--chromeos/network/onc/onc_normalizer_unittest.cc33
-rw-r--r--chromeos/network/onc/onc_signature.cc313
-rw-r--r--chromeos/network/onc/onc_signature.h55
-rw-r--r--chromeos/network/onc/onc_test_utils.cc68
-rw-r--r--chromeos/network/onc/onc_test_utils.h36
-rw-r--r--chromeos/network/onc/onc_translation_tables.cc30
-rw-r--r--chromeos/network/onc/onc_translation_tables.h24
-rw-r--r--chromeos/network/onc/onc_translator.h48
-rw-r--r--chromeos/network/onc/onc_translator_onc_to_shill.cc210
-rw-r--r--chromeos/network/onc/onc_translator_shill_to_onc.cc231
-rw-r--r--chromeos/network/onc/onc_translator_unittest.cc86
-rw-r--r--chromeos/network/onc/onc_utils.cc164
-rw-r--r--chromeos/network/onc/onc_utils.h40
-rw-r--r--chromeos/network/onc/onc_utils_unittest.cc53
-rw-r--r--chromeos/network/onc/onc_validator.cc562
-rw-r--r--chromeos/network/onc/onc_validator.h153
-rw-r--r--chromeos/network/onc/onc_validator_unittest.cc143
-rw-r--r--chromeos/test/data/network/broken-encrypted-iterations.onc11
-rw-r--r--chromeos/test/data/network/broken-encrypted-zero-iterations.onc11
-rw-r--r--chromeos/test/data/network/certificate-client-update.onc9
-rw-r--r--chromeos/test/data/network/certificate-client.onc9
-rw-r--r--chromeos/test/data/network/certificate-server-update.onc14
-rw-r--r--chromeos/test/data/network/certificate-server.onc14
-rw-r--r--chromeos/test/data/network/certificate-web-authority-update.onc14
-rw-r--r--chromeos/test/data/network/certificate-web-authority.onc14
-rw-r--r--chromeos/test/data/network/decrypted.onc18
-rw-r--r--chromeos/test/data/network/device_policy.onc26
-rw-r--r--chromeos/test/data/network/encrypted.onc11
-rw-r--r--chromeos/test/data/network/invalid_settings_with_repairs.json92
-rw-r--r--chromeos/test/data/network/policy.onc29
-rw-r--r--chromeos/test/data/network/policy_without_recommended.onc26
-rw-r--r--chromeos/test/data/network/settings_with_normalization.json33
-rw-r--r--chromeos/test/data/network/shill_ethernet.json4
-rw-r--r--chromeos/test/data/network/shill_l2tpipsec.json10
-rw-r--r--chromeos/test/data/network/shill_openvpn.json25
-rw-r--r--chromeos/test/data/network/user.onc21
-rw-r--r--chromeos/test/data/network/valid.onc7
-rw-r--r--chromeos/test/data/network/valid_l2tpipsec.onc18
-rw-r--r--chromeos/test/data/network/valid_openvpn.onc31
58 files changed, 4710 insertions, 11 deletions
diff --git a/chromeos/DEPS b/chromeos/DEPS
index bc7030d2..a88fc06 100644
--- a/chromeos/DEPS
+++ b/chromeos/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+crypto",
"+net",
"+third_party/cros_system_api",
"+third_party/libxml",
diff --git a/chromeos/chromeos.gyp b/chromeos/chromeos.gyp
index d8ce661..fbe4497 100644
--- a/chromeos/chromeos.gyp
+++ b/chromeos/chromeos.gyp
@@ -13,6 +13,7 @@
'dependencies': [
'../base/base.gyp:base',
'../build/linux/system.gyp:dbus',
+ '../build/linux/system.gyp:ssl',
'../dbus/dbus.gyp:dbus',
'../net/net.gyp:net',
'../third_party/libxml/libxml.gyp:libxml',
@@ -142,6 +143,27 @@
'network/network_state_handler.h',
'network/network_state_handler_observer.cc',
'network/network_state_handler_observer.h',
+ 'network/onc/onc_certificate_importer.cc',
+ 'network/onc/onc_certificate_importer.h',
+ 'network/onc/onc_constants.cc',
+ 'network/onc/onc_constants.h',
+ 'network/onc/onc_mapper.cc',
+ 'network/onc/onc_mapper.h',
+ 'network/onc/onc_merger.cc',
+ 'network/onc/onc_merger.h',
+ 'network/onc/onc_normalizer.cc',
+ 'network/onc/onc_normalizer.h',
+ 'network/onc/onc_signature.cc',
+ 'network/onc/onc_signature.h',
+ 'network/onc/onc_translation_tables.cc',
+ 'network/onc/onc_translation_tables.h',
+ 'network/onc/onc_translator.h',
+ 'network/onc/onc_translator_onc_to_shill.cc',
+ 'network/onc/onc_translator_shill_to_onc.cc',
+ 'network/onc/onc_utils.cc',
+ 'network/onc/onc_utils.h',
+ 'network/onc/onc_validator.cc',
+ 'network/onc/onc_validator.h',
'network/shill_property_handler.cc',
'network/shill_property_handler.h',
'network/shill_service_observer.cc',
@@ -166,6 +188,8 @@
'chromeos',
],
'sources': [
+ 'chromeos_test_utils.cc',
+ 'chromeos_test_utils.h',
'cryptohome/mock_async_method_caller.cc',
'cryptohome/mock_async_method_caller.h',
'dbus/mock_bluetooth_adapter_client.cc',
@@ -263,7 +287,10 @@
'../base/base.gyp:run_all_unittests',
'../base/base.gyp:test_support_base',
'../build/linux/system.gyp:dbus',
+ '../build/linux/system.gyp:ssl',
+ '../crypto/crypto.gyp:crypto',
'../dbus/dbus.gyp:dbus_test_support',
+ '../net/net.gyp:net',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
'chromeos_test_support',
@@ -298,6 +325,14 @@
'network/network_event_log_unittest.cc',
'network/network_sms_handler_unittest.cc',
'network/network_state_handler_unittest.cc',
+ 'network/onc/onc_certificate_importer_unittest.cc',
+ 'network/onc/onc_merger_unittest.cc',
+ 'network/onc/onc_normalizer_unittest.cc',
+ 'network/onc/onc_test_utils.cc',
+ 'network/onc/onc_test_utils.h',
+ 'network/onc/onc_translator_unittest.cc',
+ 'network/onc/onc_utils_unittest.cc',
+ 'network/onc/onc_validator_unittest.cc',
'network/shill_property_handler_unittest.cc',
'power/power_state_override_unittest.cc',
],
diff --git a/chromeos/chromeos_test_utils.cc b/chromeos/chromeos_test_utils.cc
new file mode 100644
index 0000000..3455644
--- /dev/null
+++ b/chromeos/chromeos_test_utils.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/chromeos_test_utils.h"
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/path_service.h"
+
+namespace chromeos {
+namespace test_utils {
+
+bool GetTestDataPath(const std::string& component,
+ const std::string& filename,
+ FilePath* data_dir) {
+ FilePath path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &path))
+ return false;
+ path = path.Append(FILE_PATH_LITERAL("chromeos"));
+ path = path.Append(FILE_PATH_LITERAL("test"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ if (!file_util::PathExists(path)) // We don't want to create this.
+ return false;
+ DCHECK(data_dir);
+ path = path.Append(component);
+ *data_dir = path.Append(filename);
+ return true;
+}
+
+} // namespace test_utils
+} // namespace chromeos
diff --git a/chromeos/chromeos_test_utils.h b/chromeos/chromeos_test_utils.h
new file mode 100644
index 0000000..b424580
--- /dev/null
+++ b/chromeos/chromeos_test_utils.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_CHROMEOS_TEST_UTILS_H_
+#define CHROMEOS_CHROMEOS_TEST_UTILS_H_
+
+#include <string>
+
+class FilePath;
+
+namespace chromeos {
+namespace test_utils {
+
+// Returns the path to the given test data file for this library.
+bool GetTestDataPath(const std::string& component,
+ const std::string& filename,
+ FilePath* data_dir);
+
+} // namespace test_utils
+} // namespace chromeos
+
+#endif // CHROMEOS_CHROMEOS_TEST_UTILS_H_
diff --git a/chromeos/network/network_event_log.cc b/chromeos/network/network_event_log.cc
index bf1669d..3ed5144 100644
--- a/chromeos/network/network_event_log.cc
+++ b/chromeos/network/network_event_log.cc
@@ -43,7 +43,7 @@ LogEntry::LogEntry(const std::string& module,
std::string LogEntry::ToString() const {
std::string line;
line += "[" + UTF16ToUTF8(base::TimeFormatShortDateAndTime(time)) + "]";
- line += " " + module + "." + event;
+ line += " " + module + ":" + event;
if (!description.empty())
line += ": " + description;
if (count > 1)
diff --git a/chromeos/network/network_event_log.h b/chromeos/network/network_event_log.h
index be5a2e4..293144c 100644
--- a/chromeos/network/network_event_log.h
+++ b/chromeos/network/network_event_log.h
@@ -9,6 +9,7 @@
#include <string>
#include "base/basictypes.h"
+#include "base/stringprintf.h"
#include "base/time.h"
#include "chromeos/chromeos_export.h"
@@ -45,6 +46,19 @@ CHROMEOS_EXPORT void AddEntry(const std::string& module,
// output the events. If |max_events| > 0, limits how many events are output.
CHROMEOS_EXPORT std::string GetAsString(StringOrder order, size_t max_events);
+// Macros to make logging format more consistent.
+#define NET_LOG(module, message) \
+ ::chromeos::network_event_log::AddEntry( \
+ module, \
+ std::string(__FILE__) + ":" + ::base::StringPrintf("%d",__LINE__) + \
+ " (" + std::string(__func__) + ")", \
+ message)
+
+#define NET_LOG_WARNING(module, message) \
+ NET_LOG(module, std::string("WARNING:") + message)
+#define NET_LOG_ERROR(module, message) \
+ NET_LOG(module, std::string("ERROR:") + message)
+
} // namespace network_event_log
} // namespace chromeos
diff --git a/chromeos/network/network_event_log_unittest.cc b/chromeos/network/network_event_log_unittest.cc
index 3d44f6c..ed575bb 100644
--- a/chromeos/network/network_event_log_unittest.cc
+++ b/chromeos/network/network_event_log_unittest.cc
@@ -60,34 +60,34 @@ TEST_F(NetworkEventLogTest, TestNetworkEvents) {
network_event_log::AddEntry("module3", "event3", "description3");
const std::string expected_output_oldest_first(
- "module1.event1: description1\n"
- "module2.event2: description2\n"
- "module3.event3: description3 (2)\n");
+ "module1:event1: description1\n"
+ "module2:event2: description2\n"
+ "module3:event3: description3 (2)\n");
std::string output_oldest_first = network_event_log::GetAsString(
network_event_log::OLDEST_FIRST, 0);
output_oldest_first = SkipTime(output_oldest_first);
EXPECT_EQ(expected_output_oldest_first, output_oldest_first);
const std::string expected_output_oldest_first_short(
- "module2.event2: description2\n"
- "module3.event3: description3 (2)\n");
+ "module2:event2: description2\n"
+ "module3:event3: description3 (2)\n");
std::string output_oldest_first_short = network_event_log::GetAsString(
network_event_log::OLDEST_FIRST, 2);
output_oldest_first_short = SkipTime(output_oldest_first_short);
EXPECT_EQ(expected_output_oldest_first_short, output_oldest_first_short);
const std::string expected_output_newest_first(
- "module3.event3: description3 (2)\n"
- "module2.event2: description2\n"
- "module1.event1: description1\n");
+ "module3:event3: description3 (2)\n"
+ "module2:event2: description2\n"
+ "module1:event1: description1\n");
std::string output_newest_first = network_event_log::GetAsString(
network_event_log::NEWEST_FIRST, 0);
output_newest_first = SkipTime(output_newest_first);
EXPECT_EQ(expected_output_newest_first, output_newest_first);
const std::string expected_output_newest_first_short(
- "module3.event3: description3 (2)\n"
- "module2.event2: description2\n");
+ "module3:event3: description3 (2)\n"
+ "module2:event2: description2\n");
std::string output_newest_first_short = network_event_log::GetAsString(
network_event_log::NEWEST_FIRST, 2);
output_newest_first_short = SkipTime(output_newest_first_short);
diff --git a/chromeos/network/onc/onc_certificate_importer.cc b/chromeos/network/onc/onc_certificate_importer.cc
new file mode 100644
index 0000000..b8d4711
--- /dev/null
+++ b/chromeos/network/onc/onc_certificate_importer.cc
@@ -0,0 +1,368 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_certificate_importer.h"
+
+#include <cert.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+
+#include "base/base64.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "net/base/crypto_module.h"
+#include "net/base/net_errors.h"
+#include "net/base/nss_cert_database.h"
+#include "net/base/pem_tokenizer.h"
+#include "net/base/x509_certificate.h"
+
+#define ONC_LOG_WARNING(message) NET_LOG_WARNING("ONC", message)
+#define ONC_LOG_ERROR(message) NET_LOG_ERROR("ONC", message)
+
+namespace {
+
+// The PEM block header used for DER certificates
+const char kCertificateHeader[] = "CERTIFICATE";
+// This is an older PEM marker for DER certificates.
+const char kX509CertificateHeader[] = "X509 CERTIFICATE";
+
+} // namespace
+
+namespace chromeos {
+namespace onc {
+
+CertificateImporter::CertificateImporter(
+ ONCSource onc_source,
+ bool allow_web_trust_from_policy)
+ : onc_source_(onc_source),
+ allow_web_trust_from_policy_(allow_web_trust_from_policy) {
+}
+
+CertificateImporter::ParseResult CertificateImporter::ParseAndStoreCertificates(
+ const base::ListValue& certificates) {
+ for (size_t i = 0; i < certificates.GetSize(); ++i) {
+ const base::DictionaryValue* certificate = NULL;
+ if (!certificates.GetDictionary(i, &certificate)) {
+ ONC_LOG_ERROR("Certificate data malformed");
+ return i > 0 ? IMPORT_INCOMPLETE : IMPORT_FAILED;
+ }
+
+ if (VLOG_IS_ON(2))
+ VLOG(2) << "Parsing certificate at index " << i << ": " << *certificate;
+
+ if (!ParseAndStoreCertificate(*certificate)) {
+ ONC_LOG_ERROR(
+ base::StringPrintf("Cannot parse certificate at index %zu", i));
+ return i > 0 ? IMPORT_INCOMPLETE : IMPORT_FAILED;
+ }
+
+ VLOG(2) << "Successfully imported certificate at index " << i;
+ }
+ return IMPORT_OK;
+}
+
+bool CertificateImporter::ParseAndStoreCertificate(
+ const base::DictionaryValue& certificate) {
+ // Get out the attributes of the given certificate.
+ std::string guid;
+ if (!certificate.GetString(kGUID, &guid) || guid.empty()) {
+ ONC_LOG_ERROR("Certificate missing GUID identifier");
+ return false;
+ }
+
+ bool remove = false;
+ if (certificate.GetBoolean(kRemove, &remove) && remove) {
+ if (!DeleteCertAndKeyByNickname(guid)) {
+ ONC_LOG_WARNING("Unable to delete certificate");
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ // Not removing, so let's get the data we need to add this certificate.
+ std::string cert_type;
+ certificate.GetString(certificate::kType, &cert_type);
+ if (cert_type == certificate::kServer || cert_type == certificate::kAuthority)
+ return ParseServerOrCaCertificate(cert_type, guid, certificate);
+
+ if (cert_type == certificate::kClient)
+ return ParseClientCertificate(guid, certificate);
+
+ ONC_LOG_ERROR("Certificate of unknown type: " + cert_type);
+ return false;
+}
+
+// static
+void CertificateImporter::ListCertsWithNickname(const std::string& label,
+ net::CertificateList* result) {
+ net::CertificateList all_certs;
+ net::NSSCertDatabase::GetInstance()->ListCerts(&all_certs);
+ result->clear();
+ for (net::CertificateList::iterator iter = all_certs.begin();
+ iter != all_certs.end(); ++iter) {
+ if (iter->get()->os_cert_handle()->nickname) {
+ // Separate the nickname stored in the certificate at the colon, since
+ // NSS likes to store it as token:nickname.
+ const char* delimiter =
+ ::strchr(iter->get()->os_cert_handle()->nickname, ':');
+ if (delimiter) {
+ ++delimiter; // move past the colon.
+ if (strcmp(delimiter, label.c_str()) == 0) {
+ result->push_back(*iter);
+ continue;
+ }
+ }
+ }
+ // Now we find the private key for this certificate and see if it has a
+ // nickname that matches. If there is a private key, and it matches,
+ // then this is a client cert that we are looking for.
+ SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
+ iter->get()->os_cert_handle()->slot,
+ iter->get()->os_cert_handle(),
+ NULL); // wincx
+ if (private_key) {
+ char* private_key_nickname = PK11_GetPrivateKeyNickname(private_key);
+ if (private_key_nickname && std::string(label) == private_key_nickname)
+ result->push_back(*iter);
+ PORT_Free(private_key_nickname);
+ SECKEY_DestroyPrivateKey(private_key);
+ }
+ }
+}
+
+// static
+bool CertificateImporter::DeleteCertAndKeyByNickname(const std::string& label) {
+ net::CertificateList cert_list;
+ ListCertsWithNickname(label, &cert_list);
+ bool result = true;
+ for (net::CertificateList::iterator iter = cert_list.begin();
+ iter != cert_list.end(); ++iter) {
+ // If we fail, we try and delete the rest still.
+ // TODO(gspencer): this isn't very "transactional". If we fail on some, but
+ // not all, then it's possible to leave things in a weird state.
+ // Luckily there should only be one cert with a particular
+ // label, and the cert not being found is one of the few reasons the
+ // delete could fail, but still... The other choice is to return
+ // failure immediately, but that doesn't seem to do what is intended.
+ if (!net::NSSCertDatabase::GetInstance()->DeleteCertAndKey(iter->get()))
+ result = false;
+ }
+ return result;
+}
+
+bool CertificateImporter::ParseServerOrCaCertificate(
+ const std::string& cert_type,
+ const std::string& guid,
+ const base::DictionaryValue& certificate) {
+ // Device policy can't import certificates.
+ if (onc_source_ == ONC_SOURCE_DEVICE_POLICY) {
+ // This isn't a parsing error.
+ ONC_LOG_WARNING("Refusing to import certificate from device policy.");
+ return true;
+ }
+
+ bool web_trust = false;
+ const base::ListValue* trust_list = NULL;
+ if (certificate.GetList(certificate::kTrust, &trust_list)) {
+ for (size_t i = 0; i < trust_list->GetSize(); ++i) {
+ std::string trust_type;
+ if (!trust_list->GetString(i, &trust_type)) {
+ ONC_LOG_ERROR("Certificate trust is invalid");
+ return false;
+ }
+ if (trust_type == certificate::kWeb) {
+ // "Web" implies that the certificate is to be trusted for SSL
+ // identification.
+ web_trust = true;
+ } else {
+ ONC_LOG_ERROR("Certificate contains unknown trust type " + trust_type);
+ return false;
+ }
+ }
+ }
+
+ // Web trust is only granted to certificates imported for a managed user
+ // on a managed device.
+ if (onc_source_ == ONC_SOURCE_USER_POLICY &&
+ web_trust && !allow_web_trust_from_policy_) {
+ LOG(WARNING) << "Web trust not granted for certificate: " << guid;
+ web_trust = false;
+ }
+
+ std::string x509_data;
+ if (!certificate.GetString(certificate::kX509, &x509_data) ||
+ x509_data.empty()) {
+ ONC_LOG_ERROR(
+ "Certificate missing appropriate certificate data for type: " +
+ cert_type);
+ return false;
+ }
+
+ // Parse PEM certificate, and get the decoded data for use in creating
+ // certificate below.
+ std::vector<std::string> pem_headers;
+ pem_headers.push_back(kCertificateHeader);
+ pem_headers.push_back(kX509CertificateHeader);
+
+ net::PEMTokenizer pem_tokenizer(x509_data, pem_headers);
+ std::string decoded_x509;
+ if (!pem_tokenizer.GetNext()) {
+ // If we failed to read the data as a PEM file, then let's just try plain
+ // base64 decode: some versions of Spigots didn't apply the PEM marker
+ // strings. For this to work, there has to be no white space, and it has to
+ // only contain the base64-encoded data.
+ if (!base::Base64Decode(x509_data, &decoded_x509)) {
+ ONC_LOG_ERROR("Unable to base64 decode X509 data: " + x509_data);
+ return false;
+ }
+ } else {
+ decoded_x509 = pem_tokenizer.data();
+ }
+
+ scoped_refptr<net::X509Certificate> x509_cert =
+ net::X509Certificate::CreateFromBytesWithNickname(
+ decoded_x509.data(),
+ decoded_x509.size(),
+ guid.c_str());
+ if (!x509_cert.get()) {
+ ONC_LOG_ERROR("Unable to create X509 certificate from bytes.");
+ return false;
+ }
+
+ // Due to a mismatch regarding cert identity between NSS (cert identity is
+ // determined by the raw bytes) and ONC (cert identity is determined by
+ // GUIDs), we have to special-case a number of situations here:
+ //
+ // a) The cert bits we're trying to insert are already present in the NSS cert
+ // store. This is indicated by the isperm bit in CERTCertificateStr. Since
+ // we might have to update the nick name, we just delete the existing cert
+ // and reimport the cert bits.
+ // b) NSS gives us an actual temporary certificate. In this case, there is no
+ // identical certificate known to NSS, so we can safely import the
+ // certificate. The GUID being imported may still be on a different
+ // certificate, and we could jump through hoops to reimport the existing
+ // certificate with a different nickname. However, that would mean lots of
+ // effort for a case that's pretty much illegal (reusing GUIDs contradicts
+ // the intention of GUIDs), so we just report an error.
+ //
+ // TODO(mnissler, gspencer): We should probably switch to a mode where we
+ // keep our own database for mapping GUIDs to certs in order to enable several
+ // GUIDs to map to the same cert. See http://crosbug.com/26073.
+ net::NSSCertDatabase* cert_database = net::NSSCertDatabase::GetInstance();
+ if (x509_cert->os_cert_handle()->isperm) {
+ if (!cert_database->DeleteCertAndKey(x509_cert.get())) {
+ ONC_LOG_ERROR("Unable to delete X509 certificate.");
+ return false;
+ }
+
+ // Reload the cert here to get an actual temporary cert instance.
+ x509_cert =
+ net::X509Certificate::CreateFromBytesWithNickname(
+ decoded_x509.data(),
+ decoded_x509.size(),
+ guid.c_str());
+ if (!x509_cert.get()) {
+ ONC_LOG_ERROR("Unable to create X509 certificate from bytes.");
+ return false;
+ }
+ DCHECK(!x509_cert->os_cert_handle()->isperm);
+ DCHECK(x509_cert->os_cert_handle()->istemp);
+ }
+
+ // Make sure the GUID is not already taken. Note that for the reimport case we
+ // have removed the existing cert above.
+ net::CertificateList certs;
+ ListCertsWithNickname(guid, &certs);
+ if (!certs.empty()) {
+ ONC_LOG_ERROR("Certificate GUID is already in use: " + guid);
+ return false;
+ }
+
+ net::CertificateList cert_list;
+ cert_list.push_back(x509_cert);
+ net::NSSCertDatabase::ImportCertFailureList failures;
+ bool success = false;
+ net::NSSCertDatabase::TrustBits trust = web_trust ?
+ net::NSSCertDatabase::TRUSTED_SSL :
+ net::NSSCertDatabase::TRUST_DEFAULT;
+ if (cert_type == certificate::kServer)
+ success = cert_database->ImportServerCert(cert_list, trust, &failures);
+ else // Authority cert
+ success = cert_database->ImportCACerts(cert_list, trust, &failures);
+
+ if (!failures.empty()) {
+ ONC_LOG_ERROR("Error (" + net::ErrorToString(failures[0].net_error) +
+ ") importing " + cert_type + " certificate");
+ return false;
+ }
+ if (!success) {
+ ONC_LOG_ERROR("Unknown error importing " + cert_type + " certificate.");
+ return false;
+ }
+
+ return true;
+}
+
+bool CertificateImporter::ParseClientCertificate(
+ const std::string& guid,
+ const base::DictionaryValue& certificate) {
+ std::string pkcs12_data;
+ if (!certificate.GetString(certificate::kPKCS12, &pkcs12_data) ||
+ pkcs12_data.empty()) {
+ ONC_LOG_ERROR("PKCS12 data is missing for client certificate.");
+ return false;
+ }
+
+ std::string decoded_pkcs12;
+ if (!base::Base64Decode(pkcs12_data, &decoded_pkcs12)) {
+ ONC_LOG_ERROR(
+ "Unable to base64 decode PKCS#12 data: \"" + pkcs12_data + "\".");
+ return false;
+ }
+
+ // Since this has a private key, always use the private module.
+ net::NSSCertDatabase* cert_database = net::NSSCertDatabase::GetInstance();
+ scoped_refptr<net::CryptoModule> module(cert_database->GetPrivateModule());
+ net::CertificateList imported_certs;
+
+ int import_result = cert_database->ImportFromPKCS12(
+ module.get(), decoded_pkcs12, string16(), false, &imported_certs);
+ if (import_result != net::OK) {
+ ONC_LOG_ERROR("Unable to import client certificate (error " +
+ net::ErrorToString(import_result) + ").");
+ return false;
+ }
+
+ if (imported_certs.size() == 0) {
+ ONC_LOG_WARNING("PKCS12 data contains no importable certificates.");
+ return true;
+ }
+
+ if (imported_certs.size() != 1) {
+ ONC_LOG_WARNING("ONC File: PKCS12 data contains more than one certificate. "
+ "Only the first one will be imported.");
+ }
+
+ scoped_refptr<net::X509Certificate> cert_result = imported_certs[0];
+
+ // Find the private key associated with this certificate, and set the
+ // nickname on it.
+ SECKEYPrivateKey* private_key = PK11_FindPrivateKeyFromCert(
+ cert_result->os_cert_handle()->slot,
+ cert_result->os_cert_handle(),
+ NULL); // wincx
+ if (private_key) {
+ PK11_SetPrivateKeyNickname(private_key, const_cast<char*>(guid.c_str()));
+ SECKEY_DestroyPrivateKey(private_key);
+ } else {
+ ONC_LOG_WARNING("Unable to find private key for certificate.");
+ }
+ return true;
+}
+
+} // chromeos
+} // onc
diff --git a/chromeos/network/onc/onc_certificate_importer.h b/chromeos/network/onc/onc_certificate_importer.h
new file mode 100644
index 0000000..bb64f81
--- /dev/null
+++ b/chromeos/network/onc/onc_certificate_importer.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_CERTIFICATE_IMPORTER_H_
+#define CHROMEOS_NETWORK_ONC_ONC_CERTIFICATE_IMPORTER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+#include "chromeos/network/onc/onc_constants.h"
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+namespace net {
+class X509Certificate;
+typedef std::vector<scoped_refptr<X509Certificate> > CertificateList;
+}
+
+namespace chromeos {
+namespace onc {
+
+// This class handles certificate imports from ONC (both policy and user
+// imports) into the certificate store. In particular, the GUID of certificates
+// is stored together with the certificate as Nickname.
+class CHROMEOS_EXPORT CertificateImporter {
+ public:
+ enum ParseResult {
+ IMPORT_OK,
+ IMPORT_INCOMPLETE,
+ IMPORT_FAILED,
+ };
+
+ // Certificates pushed from a policy source with Web trust are only imported
+ // with ParseCertificate() if the |allow_web_trust_from_policy| permission is
+ // granted.
+ CertificateImporter(ONCSource onc_source,
+ bool allow_web_trust_from_policy);
+
+ // Parses and stores the certificates in |onc_certificates| into the
+ // certificate store. If the "Remove" field of a certificate is enabled, then
+ // removes the certificate from the store instead of importing. Returns the
+ // result of the parse operation. In case of IMPORT_INCOMPLETE, some of the
+ // certificates may be stored/removed successfully while others had errors.
+ // If no error occurred, returns IMPORT_OK.
+ ParseResult ParseAndStoreCertificates(
+ const base::ListValue& onc_certificates);
+
+ // Parses and stores/removes |certificate| in/from the certificate
+ // store. Returns true if the operation succeeded.
+ bool ParseAndStoreCertificate(const base::DictionaryValue& certificate);
+
+ // Lists the certificates that have the string |label| as their certificate
+ // nickname (exact match).
+ static void ListCertsWithNickname(const std::string& label,
+ net::CertificateList* result);
+
+ protected:
+ // Deletes any certificate that has the string |label| as its nickname (exact
+ // match).
+ static bool DeleteCertAndKeyByNickname(const std::string& label);
+
+ private:
+ bool ParseServerOrCaCertificate(const std::string& cert_type,
+ const std::string& guid,
+ const base::DictionaryValue& certificate);
+
+ bool ParseClientCertificate(const std::string& guid,
+ const base::DictionaryValue& certificate);
+
+ // Where the ONC blob comes from.
+ ONCSource onc_source_;
+
+ // Whether certificates with Web trust should be stored when pushed from a
+ // policy source.
+ bool allow_web_trust_from_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(CertificateImporter);
+};
+
+} // chromeos
+} // onc
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_CERTIFICATE_IMPORTER_H_
diff --git a/chromeos/network/onc/onc_certificate_importer_unittest.cc b/chromeos/network/onc/onc_certificate_importer_unittest.cc
new file mode 100644
index 0000000..736c3a9
--- /dev/null
+++ b/chromeos/network/onc/onc_certificate_importer_unittest.cc
@@ -0,0 +1,261 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_certificate_importer.h"
+
+#include <cert.h>
+#include <certdb.h>
+#include <keyhi.h>
+#include <pk11pub.h>
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "crypto/nss_util.h"
+#include "net/base/cert_type.h"
+#include "net/base/crypto_module.h"
+#include "net/base/nss_cert_database.h"
+#include "net/base/x509_certificate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+
+#if defined(USE_NSS)
+// In NSS 3.13, CERTDB_VALID_PEER was renamed CERTDB_TERMINAL_RECORD. So we use
+// the new name of the macro.
+#if !defined(CERTDB_TERMINAL_RECORD)
+#define CERTDB_TERMINAL_RECORD CERTDB_VALID_PEER
+#endif
+
+net::CertType GetCertType(net::X509Certificate::OSCertHandle cert) {
+ CERTCertTrust trust = {0};
+ CERT_GetCertTrust(cert, &trust);
+
+ unsigned all_flags = trust.sslFlags | trust.emailFlags |
+ trust.objectSigningFlags;
+
+ if (cert->nickname && (all_flags & CERTDB_USER))
+ return net::USER_CERT;
+ if ((all_flags & CERTDB_VALID_CA) || CERT_IsCACert(cert, NULL))
+ return net::CA_CERT;
+ // TODO(mattm): http://crbug.com/128633.
+ if (trust.sslFlags & CERTDB_TERMINAL_RECORD)
+ return net::SERVER_CERT;
+ return net::UNKNOWN_CERT;
+}
+#else
+net::CertType GetCertType(net::X509Certificate::OSCertHandle cert) {
+ NOTIMPLEMENTED();
+ return net::UNKNOWN_CERT;
+}
+#endif // USE_NSS
+
+class ONCCertificateImporterTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ ASSERT_TRUE(test_nssdb_.is_open());
+
+ slot_ = net::NSSCertDatabase::GetInstance()->GetPublicModule();
+
+ // Don't run the test if the setup failed.
+ ASSERT_TRUE(slot_->os_module_handle());
+
+ // Test db should be empty at start of test.
+ EXPECT_EQ(0ul, ListCertsInSlot(slot_->os_module_handle()).size());
+ }
+
+ virtual void TearDown() {
+ EXPECT_TRUE(CleanupSlotContents(slot_->os_module_handle()));
+ EXPECT_EQ(0ul, ListCertsInSlot(slot_->os_module_handle()).size());
+ }
+
+ virtual ~ONCCertificateImporterTest() {}
+
+ protected:
+ void AddCertificateFromFile(std::string filename,
+ net::CertType expected_type,
+ std::string* guid) {
+ scoped_ptr<base::DictionaryValue> onc =
+ test_utils::ReadTestDictionary(filename);
+ base::ListValue* certificates;
+ onc->GetListWithoutPathExpansion(kCertificates, &certificates);
+
+ base::DictionaryValue* certificate;
+ certificates->GetDictionary(0, &certificate);
+ certificate->GetStringWithoutPathExpansion(kGUID, guid);
+
+ CertificateImporter importer(ONC_SOURCE_USER_IMPORT,
+ false /* don't allow web trust */);
+ EXPECT_EQ(CertificateImporter::IMPORT_OK,
+ importer.ParseAndStoreCertificates(*certificates));
+
+ net::CertificateList result_list;
+ CertificateImporter::ListCertsWithNickname(*guid, &result_list);
+ ASSERT_EQ(1ul, result_list.size());
+ EXPECT_EQ(expected_type, GetCertType(result_list[0]->os_cert_handle()));
+ }
+
+ scoped_refptr<net::CryptoModule> slot_;
+
+ private:
+ net::CertificateList ListCertsInSlot(PK11SlotInfo* slot) {
+ net::CertificateList result;
+ CERTCertList* cert_list = PK11_ListCertsInSlot(slot);
+ for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+ !CERT_LIST_END(node, cert_list);
+ node = CERT_LIST_NEXT(node)) {
+ result.push_back(net::X509Certificate::CreateFromHandle(
+ node->cert, net::X509Certificate::OSCertHandles()));
+ }
+ CERT_DestroyCertList(cert_list);
+
+ // Sort the result so that test comparisons can be deterministic.
+ std::sort(result.begin(), result.end(), net::X509Certificate::LessThan());
+ return result;
+ }
+
+ bool CleanupSlotContents(PK11SlotInfo* slot) {
+ bool ok = true;
+ net::CertificateList certs = ListCertsInSlot(slot);
+ for (size_t i = 0; i < certs.size(); ++i) {
+ if (!net::NSSCertDatabase::GetInstance()->DeleteCertAndKey(certs[i]))
+ ok = false;
+ }
+ return ok;
+ }
+
+ crypto::ScopedTestNSSDB test_nssdb_;
+};
+
+TEST_F(ONCCertificateImporterTest, AddClientCertificate) {
+ std::string guid;
+ AddCertificateFromFile("certificate-client.onc", net::USER_CERT, &guid);
+
+ SECKEYPrivateKeyList* privkey_list =
+ PK11_ListPrivKeysInSlot(slot_->os_module_handle(), NULL, NULL);
+ EXPECT_TRUE(privkey_list);
+ if (privkey_list) {
+ SECKEYPrivateKeyListNode* node = PRIVKEY_LIST_HEAD(privkey_list);
+ int count = 0;
+ while (!PRIVKEY_LIST_END(node, privkey_list)) {
+ char* name = PK11_GetPrivateKeyNickname(node->key);
+ EXPECT_STREQ(guid.c_str(), name);
+ PORT_Free(name);
+ count++;
+ node = PRIVKEY_LIST_NEXT(node);
+ }
+ EXPECT_EQ(1, count);
+ SECKEY_DestroyPrivateKeyList(privkey_list);
+ }
+
+ SECKEYPublicKeyList* pubkey_list =
+ PK11_ListPublicKeysInSlot(slot_->os_module_handle(), NULL);
+ EXPECT_TRUE(pubkey_list);
+ if (pubkey_list) {
+ SECKEYPublicKeyListNode* node = PUBKEY_LIST_HEAD(pubkey_list);
+ int count = 0;
+ while (!PUBKEY_LIST_END(node, pubkey_list)) {
+ count++;
+ node = PUBKEY_LIST_NEXT(node);
+ }
+ EXPECT_EQ(1, count);
+ SECKEY_DestroyPublicKeyList(pubkey_list);
+ }
+}
+
+TEST_F(ONCCertificateImporterTest, AddServerCertificate) {
+ std::string guid;
+ AddCertificateFromFile("certificate-server.onc", net::SERVER_CERT, &guid);
+
+ SECKEYPrivateKeyList* privkey_list =
+ PK11_ListPrivKeysInSlot(slot_->os_module_handle(), NULL, NULL);
+ EXPECT_FALSE(privkey_list);
+
+ SECKEYPublicKeyList* pubkey_list =
+ PK11_ListPublicKeysInSlot(slot_->os_module_handle(), NULL);
+ EXPECT_FALSE(pubkey_list);
+}
+
+TEST_F(ONCCertificateImporterTest, AddWebAuthorityCertificate) {
+ std::string guid;
+ AddCertificateFromFile("certificate-web-authority.onc", net::CA_CERT, &guid);
+
+ SECKEYPrivateKeyList* privkey_list =
+ PK11_ListPrivKeysInSlot(slot_->os_module_handle(), NULL, NULL);
+ EXPECT_FALSE(privkey_list);
+
+ SECKEYPublicKeyList* pubkey_list =
+ PK11_ListPublicKeysInSlot(slot_->os_module_handle(), NULL);
+ EXPECT_FALSE(pubkey_list);
+}
+
+class ONCCertificateImporterTestWithParam :
+ public ONCCertificateImporterTest,
+ public testing::WithParamInterface<
+ std::pair<net::CertType, std::pair<const char*, const char*> > > {
+ protected:
+ net::CertType GetCertTypeParam() {
+ return GetParam().first;
+ }
+
+ std::string GetOriginalFilename() {
+ return GetParam().second.first;
+ }
+
+ std::string GetUpdatedFilename() {
+ return GetParam().second.second;
+ }
+};
+
+TEST_P(ONCCertificateImporterTestWithParam, UpdateCertificate) {
+ // First we import a certificate.
+ {
+ SCOPED_TRACE("Import original certificate");
+ std::string guid_original;
+ AddCertificateFromFile(GetOriginalFilename(), GetCertTypeParam(),
+ &guid_original);
+ }
+
+ // Now we import the same certificate with a different GUID. The cert should
+ // be retrievable via the new GUID.
+ {
+ SCOPED_TRACE("Import updated certificate");
+ std::string guid_updated;
+ AddCertificateFromFile(GetUpdatedFilename(), GetCertTypeParam(),
+ &guid_updated);
+ }
+}
+
+TEST_P(ONCCertificateImporterTestWithParam, ReimportCertificate) {
+ // Verify that reimporting a client certificate works.
+ for (int i = 0; i < 2; ++i) {
+ SCOPED_TRACE("Import certificate, iteration " + base::IntToString(i));
+
+ std::string guid_original;
+ AddCertificateFromFile(GetOriginalFilename(), GetCertTypeParam(),
+ &guid_original);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ONCCertificateImporterTestWithParam,
+ ONCCertificateImporterTestWithParam,
+ ::testing::Values(
+ std::make_pair(net::USER_CERT,
+ std::make_pair("certificate-client.onc",
+ "certificate-client-update.onc")),
+ std::make_pair(net::SERVER_CERT,
+ std::make_pair("certificate-server.onc",
+ "certificate-server-update.onc")),
+ std::make_pair(
+ net::CA_CERT,
+ std::make_pair("certificate-web-authority.onc",
+ "certificate-web-authority-update.onc"))));
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_constants.cc b/chromeos/network/onc/onc_constants.cc
new file mode 100644
index 0000000..f77e991
--- /dev/null
+++ b/chromeos/network/onc/onc_constants.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_constants.h"
+
+namespace chromeos {
+
+// Constants for ONC properties.
+namespace onc {
+
+// Top Level ONC
+const char kCertificates[] = "Certificates";
+const char kEncryptedConfiguration[] = "EncryptedConfiguration";
+const char kNetworkConfigurations[] = "NetworkConfigurations";
+const char kUnencryptedConfiguration[] = "UnencryptedConfiguration";
+const char kNetworkConfiguration[] = "NetworkConfiguration";
+
+// Common keys/values.
+const char kRecommended[] = "Recommended";
+const char kRemove[] = "Remove";
+
+// Network Configuration
+const char kCellular[] = "Cellular";
+const char kEthernet[] = "Ethernet";
+const char kGUID[] = "GUID";
+const char kIPConfigs[] = "IPConfigs";
+const char kName[] = "Name";
+const char kNameServers[] = "NameServers";
+const char kProxySettings[] = "ProxySettings";
+const char kSearchDomains[] = "SearchDomains";
+const char kType[] = "Type";
+const char kVPN[] = "VPN";
+const char kWiFi[] = "WiFi";
+
+namespace ethernet {
+const char kAuthentication[] = "Authentication";
+const char kEAP[] = "EAP";
+const char kNone[] = "None";
+const char k8021X[] = "8021X";
+} // namespace ethernet
+
+namespace ipconfig {
+const char kGateway[] = "Gateway";
+const char kIPAddress[] = "IPAddress";
+const char kIPv4[] = "IPv4";
+const char kIPv6[] = "IPv6";
+const char kRoutingPrefix[] = "RoutingPrefix";
+const char kType[] = "Type";
+} // namespace ipconfig
+
+namespace wifi {
+const char kAutoConnect[] = "AutoConnect";
+const char kEAP[] = "EAP";
+const char kHiddenSSID[] = "HiddenSSID";
+const char kNone[] = "None";
+const char kPassphrase[] = "Passphrase";
+const char kProxyURL[] = "ProxyURL";
+const char kSSID[] = "SSID";
+const char kSecurity[] = "Security";
+const char kWEP_PSK[] = "WEP-PSK";
+const char kWEP_8021X[] = "WEP-8021X";
+const char kWPA_PSK[] = "WPA-PSK";
+const char kWPA_EAP[] = "WPA-EAP";
+} // namespace wifi
+
+namespace certificate {
+const char kAuthority[] = "Authority";
+const char kClient[] = "Client";
+const char kCommonName[] = "CommonName";
+const char kEmailAddress[] = "EmailAddress";
+const char kEnrollmentURI[] = "EnrollmentURI";
+const char kIssuerCARef[] = "IssuerCARef";
+const char kIssuer[] = "Issuer";
+const char kLocality[] = "Locality";
+const char kNone[] = "None";
+const char kOrganization[] = "Organization";
+const char kOrganizationalUnit[] = "OrganizationalUnit";
+const char kPKCS12[] = "PKCS12";
+const char kPattern[] = "Pattern";
+const char kRef[] = "Ref";
+const char kServer[] = "Server";
+const char kSubject[] = "Subject";
+const char kTrust[] = "Trust";
+const char kType[] = "Type";
+const char kWeb[] = "Web";
+const char kX509[] = "X509";
+} // namespace certificate
+
+namespace encrypted {
+const char kAES256[] = "AES256";
+const char kCipher[] = "Cipher";
+const char kCiphertext[] = "Ciphertext";
+const char kHMACMethod[] = "HMACMethod";
+const char kHMAC[] = "HMAC";
+const char kIV[] = "IV";
+const char kIterations[] = "Iterations";
+const char kPBKDF2[] = "PBKDF2";
+const char kSHA1[] = "SHA1";
+const char kSalt[] = "Salt";
+const char kStretch[] = "Stretch";
+const char kType[] = "Type";
+} // namespace encrypted
+
+namespace eap {
+const char kAnonymousIdentity[] = "AnonymousIdentity";
+const char kAutomatic[] = "Automatic";
+const char kClientCertPattern[] = "ClientCertPattern";
+const char kClientCertRef[] = "ClientCertRef";
+const char kClientCertType[] = "ClientCertType";
+const char kEAP_AKA[] = "EAP-AKA";
+const char kEAP_FAST[] = "EAP-FAST";
+const char kEAP_SIM[] = "EAP-SIM";
+const char kEAP_TLS[] = "EAP-TLS";
+const char kEAP_TTLS[] = "EAP-TTLS";
+const char kIdentity[] = "Identity";
+const char kInner[] = "Inner";
+const char kLEAP[] = "LEAP";
+const char kMD5[] = "MD5";
+const char kMSCHAPv2[] = "MSCHAPv2";
+const char kOuter[] = "Outer";
+const char kPAP[] = "PAP";
+const char kPEAP[] = "PEAP";
+const char kPassword[] = "Password";
+const char kSaveCredentials[] = "SaveCredentials";
+const char kServerCARef[] = "ServerCARef";
+const char kUseSystemCAs[] = "UseSystemCAs";
+} // namespace eap
+
+namespace vpn {
+const char kAuthNoCache[] = "AuthNoCache";
+const char kAuthRetry[] = "AuthRetry";
+const char kAuth[] = "Auth";
+const char kAuthenticationType[] = "AuthenticationType";
+const char kCert[] = "Cert";
+const char kCipher[] = "Cipher";
+const char kClientCertPattern[] = "ClientCertPattern";
+const char kClientCertRef[] = "ClientCertRef";
+const char kClientCertType[] = "ClientCertType";
+const char kCompLZO[] = "CompLZO";
+const char kCompNoAdapt[] = "CompNoAdapt";
+const char kEAP[] = "EAP";
+const char kGroup[] = "Group";
+const char kHost[] = "Host";
+const char kIKEVersion[] = "IKEVersion";
+const char kIPsec[] = "IPsec";
+const char kKeyDirection[] = "KeyDirection";
+const char kL2TP[] = "L2TP";
+const char kNsCertType[] = "NsCertType";
+const char kOpenVPN[] = "OpenVPN";
+const char kPSK[] = "PSK";
+const char kPassword[] = "Password";
+const char kPort[] = "Port";
+const char kProto[] = "Proto";
+const char kPushPeerInfo[] = "PushPeerInfo";
+const char kRemoteCertEKU[] = "RemoteCertEKU";
+const char kRemoteCertKU[] = "RemoteCertKU";
+const char kRemoteCertTLS[] = "RemoteCertTLS";
+const char kRenegSec[] = "RenegSec";
+const char kSaveCredentials[] = "SaveCredentials";
+const char kServerCARef[] = "ServerCARef";
+const char kServerCertRef[] = "ServerCertRef";
+const char kServerPollTimeout[] = "ServerPollTimeout";
+const char kShaper[] = "Shaper";
+const char kStaticChallenge[] = "StaticChallenge";
+const char kTLSAuthContents[] = "TLSAuthContents";
+const char kTLSRemote[] = "TLSRemote";
+const char kTypeL2TP_IPsec[] = "L2TP-IPsec";
+const char kType[] = "Type";
+const char kUsername[] = "Username";
+const char kVerb[] = "Verb";
+const char kXAUTH[] = "XAUTH";
+} // namespace vpn
+
+namespace openvpn {
+const char kNone[] = "none";
+const char kInteract[] = "interact";
+const char kNoInteract[] = "nointeract";
+const char kServer[] = "server";
+} // namespace openvpn
+
+namespace proxy {
+const char kDirect[] = "Direct";
+const char kExcludeDomains[] = "ExcludeDomains";
+const char kFtp[] = "FTPProxy";
+const char kHost[] = "Host";
+const char kHttp[] = "HTTPProxy";
+const char kHttps[] = "SecureHTTPProxy";
+const char kManual[] = "Manual";
+const char kPAC[] = "PAC";
+const char kPort[] = "Port";
+const char kSocks[] = "SOCKS";
+const char kType[] = "Type";
+const char kWPAD[] = "WPAD";
+} // namespace proxy
+
+namespace substitutes {
+const char kLoginIDField[] = "${LOGIN_ID}";
+const char kEmailField[] = "${LOGIN_EMAIL}";
+} // namespace substitutes
+
+} // namespace onc
+
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_constants.h b/chromeos/network/onc/onc_constants.h
new file mode 100644
index 0000000..579049c
--- /dev/null
+++ b/chromeos/network/onc/onc_constants.h
@@ -0,0 +1,220 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef CHROMEOS_NETWORK_ONC_ONC_CONSTANTS_H_
+#define CHROMEOS_NETWORK_ONC_ONC_CONSTANTS_H_
+
+#include "chromeos/chromeos_export.h"
+
+namespace chromeos {
+
+// Constants for ONC properties.
+namespace onc {
+
+// Indicates from which source an ONC blob comes from.
+enum ONCSource {
+ ONC_SOURCE_NONE,
+ ONC_SOURCE_USER_IMPORT,
+ ONC_SOURCE_DEVICE_POLICY,
+ ONC_SOURCE_USER_POLICY,
+};
+
+// Top Level ONC.
+CHROMEOS_EXPORT extern const char kCertificates[];
+CHROMEOS_EXPORT extern const char kEncryptedConfiguration[];
+CHROMEOS_EXPORT extern const char kNetworkConfigurations[];
+CHROMEOS_EXPORT extern const char kUnencryptedConfiguration[];
+
+// This is no ONC key or value but used for logging only.
+// TODO(pneubeck): Remove.
+CHROMEOS_EXPORT extern const char kNetworkConfiguration[];
+
+// Common keys/values.
+CHROMEOS_EXPORT extern const char kRecommended[];
+CHROMEOS_EXPORT extern const char kRemove[];
+
+// NetworkConfiguration.
+// TODO(pneubeck): Put into namespace.
+CHROMEOS_EXPORT extern const char kCellular[];
+CHROMEOS_EXPORT extern const char kEthernet[];
+CHROMEOS_EXPORT extern const char kGUID[];
+CHROMEOS_EXPORT extern const char kIPConfigs[];
+CHROMEOS_EXPORT extern const char kName[];
+CHROMEOS_EXPORT extern const char kNameServers[];
+CHROMEOS_EXPORT extern const char kProxySettings[];
+CHROMEOS_EXPORT extern const char kSearchDomains[];
+CHROMEOS_EXPORT extern const char kType[];
+CHROMEOS_EXPORT extern const char kVPN[];
+CHROMEOS_EXPORT extern const char kWiFi[];
+
+namespace ipconfig {
+CHROMEOS_EXPORT extern const char kGateway[];
+CHROMEOS_EXPORT extern const char kIPAddress[];
+CHROMEOS_EXPORT extern const char kIPv4[];
+CHROMEOS_EXPORT extern const char kIPv6[];
+CHROMEOS_EXPORT extern const char kRoutingPrefix[];
+CHROMEOS_EXPORT extern const char kType[];
+} // namespace ipconfig
+
+namespace ethernet {
+CHROMEOS_EXPORT extern const char kAuthentication[];
+CHROMEOS_EXPORT extern const char kEAP[];
+CHROMEOS_EXPORT extern const char kNone[];
+CHROMEOS_EXPORT extern const char k8021X[];
+} // namespace ethernet
+
+namespace wifi {
+CHROMEOS_EXPORT extern const char kAutoConnect[];
+CHROMEOS_EXPORT extern const char kEAP[];
+CHROMEOS_EXPORT extern const char kHiddenSSID[];
+CHROMEOS_EXPORT extern const char kNone[];
+CHROMEOS_EXPORT extern const char kPassphrase[];
+CHROMEOS_EXPORT extern const char kProxyURL[];
+CHROMEOS_EXPORT extern const char kSecurity[];
+CHROMEOS_EXPORT extern const char kSSID[];
+CHROMEOS_EXPORT extern const char kWEP_PSK[];
+CHROMEOS_EXPORT extern const char kWEP_8021X[];
+CHROMEOS_EXPORT extern const char kWPA_PSK[];
+CHROMEOS_EXPORT extern const char kWPA_EAP[];
+} // namespace wifi
+
+namespace certificate {
+CHROMEOS_EXPORT extern const char kAuthority[];
+CHROMEOS_EXPORT extern const char kClient[];
+CHROMEOS_EXPORT extern const char kCommonName[];
+CHROMEOS_EXPORT extern const char kEmailAddress[];
+CHROMEOS_EXPORT extern const char kEnrollmentURI[];
+CHROMEOS_EXPORT extern const char kIssuerCARef[];
+CHROMEOS_EXPORT extern const char kIssuer[];
+CHROMEOS_EXPORT extern const char kLocality[];
+CHROMEOS_EXPORT extern const char kNone[];
+CHROMEOS_EXPORT extern const char kOrganization[];
+CHROMEOS_EXPORT extern const char kOrganizationalUnit[];
+CHROMEOS_EXPORT extern const char kPKCS12[];
+CHROMEOS_EXPORT extern const char kPattern[];
+CHROMEOS_EXPORT extern const char kRef[];
+CHROMEOS_EXPORT extern const char kServer[];
+CHROMEOS_EXPORT extern const char kSubject[];
+CHROMEOS_EXPORT extern const char kTrust[];
+CHROMEOS_EXPORT extern const char kType[];
+CHROMEOS_EXPORT extern const char kWeb[];
+CHROMEOS_EXPORT extern const char kX509[];
+} // namespace certificate
+
+namespace encrypted {
+CHROMEOS_EXPORT extern const char kAES256[];
+CHROMEOS_EXPORT extern const char kCipher[];
+CHROMEOS_EXPORT extern const char kCiphertext[];
+CHROMEOS_EXPORT extern const char kHMACMethod[];
+CHROMEOS_EXPORT extern const char kHMAC[];
+CHROMEOS_EXPORT extern const char kIV[];
+CHROMEOS_EXPORT extern const char kIterations[];
+CHROMEOS_EXPORT extern const char kPBKDF2[];
+CHROMEOS_EXPORT extern const char kSHA1[];
+CHROMEOS_EXPORT extern const char kSalt[];
+CHROMEOS_EXPORT extern const char kStretch[];
+CHROMEOS_EXPORT extern const char kType[];
+} // namespace encrypted
+
+namespace eap {
+CHROMEOS_EXPORT extern const char kAnonymousIdentity[];
+CHROMEOS_EXPORT extern const char kAutomatic[];
+CHROMEOS_EXPORT extern const char kClientCertPattern[];
+CHROMEOS_EXPORT extern const char kClientCertRef[];
+CHROMEOS_EXPORT extern const char kClientCertType[];
+CHROMEOS_EXPORT extern const char kEAP_AKA[];
+CHROMEOS_EXPORT extern const char kEAP_FAST[];
+CHROMEOS_EXPORT extern const char kEAP_SIM[];
+CHROMEOS_EXPORT extern const char kEAP_TLS[];
+CHROMEOS_EXPORT extern const char kEAP_TTLS[];
+CHROMEOS_EXPORT extern const char kIdentity[];
+CHROMEOS_EXPORT extern const char kInner[];
+CHROMEOS_EXPORT extern const char kLEAP[];
+CHROMEOS_EXPORT extern const char kMD5[];
+CHROMEOS_EXPORT extern const char kMSCHAPv2[];
+CHROMEOS_EXPORT extern const char kOuter[];
+CHROMEOS_EXPORT extern const char kPAP[];
+CHROMEOS_EXPORT extern const char kPEAP[];
+CHROMEOS_EXPORT extern const char kPassword[];
+CHROMEOS_EXPORT extern const char kSaveCredentials[];
+CHROMEOS_EXPORT extern const char kServerCARef[];
+CHROMEOS_EXPORT extern const char kUseSystemCAs[];
+} // namespace eap
+
+namespace vpn {
+CHROMEOS_EXPORT extern const char kAuthNoCache[];
+CHROMEOS_EXPORT extern const char kAuthRetry[];
+CHROMEOS_EXPORT extern const char kAuth[];
+CHROMEOS_EXPORT extern const char kAuthenticationType[];
+CHROMEOS_EXPORT extern const char kCert[];
+CHROMEOS_EXPORT extern const char kCipher[];
+CHROMEOS_EXPORT extern const char kClientCertPattern[];
+CHROMEOS_EXPORT extern const char kClientCertRef[];
+CHROMEOS_EXPORT extern const char kClientCertType[];
+CHROMEOS_EXPORT extern const char kCompLZO[];
+CHROMEOS_EXPORT extern const char kCompNoAdapt[];
+CHROMEOS_EXPORT extern const char kEAP[];
+CHROMEOS_EXPORT extern const char kGroup[];
+CHROMEOS_EXPORT extern const char kHost[];
+CHROMEOS_EXPORT extern const char kIKEVersion[];
+CHROMEOS_EXPORT extern const char kIPsec[];
+CHROMEOS_EXPORT extern const char kKeyDirection[];
+CHROMEOS_EXPORT extern const char kL2TP[];
+CHROMEOS_EXPORT extern const char kNsCertType[];
+CHROMEOS_EXPORT extern const char kOpenVPN[];
+CHROMEOS_EXPORT extern const char kPSK[];
+CHROMEOS_EXPORT extern const char kPassword[];
+CHROMEOS_EXPORT extern const char kPort[];
+CHROMEOS_EXPORT extern const char kProto[];
+CHROMEOS_EXPORT extern const char kPushPeerInfo[];
+CHROMEOS_EXPORT extern const char kRemoteCertEKU[];
+CHROMEOS_EXPORT extern const char kRemoteCertKU[];
+CHROMEOS_EXPORT extern const char kRemoteCertTLS[];
+CHROMEOS_EXPORT extern const char kRenegSec[];
+CHROMEOS_EXPORT extern const char kSaveCredentials[];
+CHROMEOS_EXPORT extern const char kServerCARef[];
+CHROMEOS_EXPORT extern const char kServerCertRef[];
+CHROMEOS_EXPORT extern const char kServerPollTimeout[];
+CHROMEOS_EXPORT extern const char kShaper[];
+CHROMEOS_EXPORT extern const char kStaticChallenge[];
+CHROMEOS_EXPORT extern const char kTLSAuthContents[];
+CHROMEOS_EXPORT extern const char kTLSRemote[];
+CHROMEOS_EXPORT extern const char kTypeL2TP_IPsec[];
+CHROMEOS_EXPORT extern const char kType[];
+CHROMEOS_EXPORT extern const char kUsername[];
+CHROMEOS_EXPORT extern const char kVerb[];
+CHROMEOS_EXPORT extern const char kXAUTH[];
+} // namespace vpn
+
+namespace openvpn {
+CHROMEOS_EXPORT extern const char kNone[];
+CHROMEOS_EXPORT extern const char kInteract[];
+CHROMEOS_EXPORT extern const char kNoInteract[];
+CHROMEOS_EXPORT extern const char kServer[];
+} // namespace openvpn
+
+namespace substitutes {
+CHROMEOS_EXPORT extern const char kEmailField[];
+CHROMEOS_EXPORT extern const char kLoginIDField[];
+} // namespace substitutes
+
+namespace proxy {
+CHROMEOS_EXPORT extern const char kDirect[];
+CHROMEOS_EXPORT extern const char kExcludeDomains[];
+CHROMEOS_EXPORT extern const char kFtp[];
+CHROMEOS_EXPORT extern const char kHost[];
+CHROMEOS_EXPORT extern const char kHttp[];
+CHROMEOS_EXPORT extern const char kHttps[];
+CHROMEOS_EXPORT extern const char kManual[];
+CHROMEOS_EXPORT extern const char kPAC[];
+CHROMEOS_EXPORT extern const char kPort[];
+CHROMEOS_EXPORT extern const char kSocks[];
+CHROMEOS_EXPORT extern const char kType[];
+CHROMEOS_EXPORT extern const char kWPAD[];
+} // namespace proxy
+
+} // namespace onc
+
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_CONSTANTS_H_
diff --git a/chromeos/network/onc/onc_mapper.cc b/chromeos/network/onc/onc_mapper.cc
new file mode 100644
index 0000000..8d5d723
--- /dev/null
+++ b/chromeos/network/onc/onc_mapper.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_mapper.h"
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_signature.h"
+
+namespace chromeos {
+namespace onc {
+
+Mapper::Mapper() {
+}
+
+Mapper::~Mapper() {
+}
+
+scoped_ptr<base::Value> Mapper::MapValue(
+ const OncValueSignature& signature,
+ const base::Value& onc_value) {
+ scoped_ptr<base::Value> result_value;
+ switch (onc_value.GetType()) {
+ case base::Value::TYPE_DICTIONARY: {
+ const base::DictionaryValue* dict = NULL;
+ onc_value.GetAsDictionary(&dict);
+ result_value = MapObject(signature, *dict);
+ break;
+ }
+ case base::Value::TYPE_LIST: {
+ const base::ListValue* list = NULL;
+ onc_value.GetAsList(&list);
+ bool nested_error_occured = false;
+ result_value = MapArray(signature, *list, &nested_error_occured);
+ if (nested_error_occured)
+ result_value.reset();
+ break;
+ }
+ default: {
+ result_value = MapPrimitive(signature, onc_value);
+ break;
+ }
+ }
+
+ return result_value.Pass();
+}
+
+scoped_ptr<base::DictionaryValue> Mapper::MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object) {
+ scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
+
+ bool found_unknown_field = false;
+ bool nested_error_occured = false;
+ MapFields(signature, onc_object, &found_unknown_field, &nested_error_occured,
+ result.get());
+ if (!nested_error_occured && !found_unknown_field)
+ return result.Pass();
+ else
+ return scoped_ptr<base::DictionaryValue>();
+}
+
+scoped_ptr<base::Value> Mapper::MapPrimitive(
+ const OncValueSignature& signature,
+ const base::Value& onc_primitive) {
+ return make_scoped_ptr(onc_primitive.DeepCopy());
+}
+
+void Mapper::MapFields(
+ const OncValueSignature& object_signature,
+ const base::DictionaryValue& onc_object,
+ bool* found_unknown_field,
+ bool* nested_error_occured,
+ base::DictionaryValue* result) {
+
+ for (base::DictionaryValue::Iterator it(onc_object); it.HasNext();
+ it.Advance()) {
+ bool current_field_unknown = false;
+ scoped_ptr<base::Value> result_value = MapField(
+ it.key(), object_signature, it.value(), &current_field_unknown);
+
+ if (current_field_unknown)
+ *found_unknown_field = true;
+ else if (result_value.get() != NULL)
+ result->SetWithoutPathExpansion(it.key(), result_value.release());
+ else
+ *nested_error_occured = true;
+ }
+}
+
+scoped_ptr<base::Value> Mapper::MapField(
+ const std::string& field_name,
+ const OncValueSignature& object_signature,
+ const base::Value& onc_value,
+ bool* found_unknown_field) {
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(object_signature, field_name);
+
+ if (field_signature != NULL) {
+ if (field_signature->value_signature == NULL) {
+ NOTREACHED() << "Found missing value signature at field '"
+ << field_name << "'.";
+ return scoped_ptr<base::Value>();
+ }
+
+ return MapValue(*field_signature->value_signature, onc_value);
+ } else {
+ DVLOG(1) << "Found unknown field name: '" << field_name << "'";
+ *found_unknown_field = true;
+ return scoped_ptr<base::Value>();
+ }
+}
+
+scoped_ptr<base::ListValue> Mapper::MapArray(
+ const OncValueSignature& array_signature,
+ const base::ListValue& onc_array,
+ bool* nested_error_occured) {
+ if (array_signature.onc_array_entry_signature == NULL) {
+ NOTREACHED() << "Found missing onc_array_entry_signature.";
+ return scoped_ptr<base::ListValue>();
+ }
+
+ scoped_ptr<base::ListValue> result_array(new base::ListValue);
+ for (base::ListValue::const_iterator it = onc_array.begin();
+ it != onc_array.end(); ++it) {
+ const base::Value* entry = *it;
+
+ scoped_ptr<base::Value> result_entry;
+ result_entry = MapValue(*array_signature.onc_array_entry_signature, *entry);
+ if (result_entry.get() != NULL)
+ result_array->Append(result_entry.release());
+ else
+ *nested_error_occured = true;
+ }
+ return result_array.Pass();
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_mapper.h b/chromeos/network/onc/onc_mapper.h
new file mode 100644
index 0000000..e8d42a9
--- /dev/null
+++ b/chromeos/network/onc/onc_mapper.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_MAPPER_H_
+#define CHROMEOS_NETWORK_ONC_ONC_MAPPER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+class ListValue;
+}
+
+namespace chromeos {
+namespace onc {
+
+struct OncValueSignature;
+
+// This class implements a DeepCopy of base::Values for ONC objects that
+// iterates over both the ONC signature and the object hierarchy. DCHECKs if a
+// field signature without value signature or an array signature without entry
+// signature is reached.
+//
+// The general term "map" is used here, as this class is meant as base class and
+// the copy behavior can be adapted by overriding the methods. By comparing the
+// address of a signature object to the list of signatures in "onc_signature.h",
+// accurate signature-specific translations or validations can be applied in the
+// overriding methods.
+//
+// The ONC validator and normalizer derive from this class and adapt the default
+// copy behavior.
+class Mapper {
+ public:
+ Mapper();
+ virtual ~Mapper();
+
+ protected:
+ // Calls |MapObject|, |MapArray| and |MapPrimitive| according to |onc_value|'s
+ // type. By default aborts on nested errors in arrays. Result of the mapping
+ // is returned. On error returns NULL.
+ virtual scoped_ptr<base::Value> MapValue(
+ const OncValueSignature& signature,
+ const base::Value& onc_value);
+
+ // Maps objects/dictionaries. By default calls |MapFields|, which recurses
+ // into each field of |onc_object|, and aborts on unknown fields. Result of
+ // the mapping is returned. On error returns NULL.
+ virtual scoped_ptr<base::DictionaryValue> MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object);
+
+ // Maps primitive values like BinaryValue, StringValue, IntegerValue... (all
+ // but dictionaries and lists). By default copies |onc_primitive|. Result of
+ // the mapping is returned. On error returns NULL.
+ virtual scoped_ptr<base::Value> MapPrimitive(
+ const OncValueSignature& signature,
+ const base::Value& onc_primitive);
+
+ // Maps each field of the given |onc_object| according to
+ // |object_signature|. Adds the mapping of each field to |result| using
+ // |MapField| and drops unknown fields by default. Sets
+ // |found_unknown_field| to true if this dictionary contains any unknown
+ // fields. Set |nested_error_occured| to true if nested errors occured.
+ virtual void MapFields(
+ const OncValueSignature& object_signature,
+ const base::DictionaryValue& onc_object,
+ bool* found_unknown_field,
+ bool* nested_error_occured,
+ base::DictionaryValue* result);
+
+ // Maps the value |onc_value| of field |field_name| according to its field
+ // signature in |object_signature| using |MapValue|. Sets
+ // |found_unknown_field| to true if |field_name| cannot be found in
+ // |object_signature|, which by default is an error. Result of the mapping is
+ // returned. On error returns NULL.
+ virtual scoped_ptr<base::Value> MapField(
+ const std::string& field_name,
+ const OncValueSignature& object_signature,
+ const base::Value& onc_value,
+ bool* found_unknown_field);
+
+ // Maps the array |onc_array| according to |array_signature|, which defines
+ // the type of the entries. Maps each entry by calling |MapValue|. If any of
+ // the nested mappings failed, the flag |nested_error_occured| is set to true
+ // and the entry is dropped from the result. The resulting array is
+ // returned. On error returns NULL.
+ virtual scoped_ptr<base::ListValue> MapArray(
+ const OncValueSignature& array_signature,
+ const base::ListValue& onc_array,
+ bool* nested_error_occured);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Mapper);
+};
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_MAPPER_H_
diff --git a/chromeos/network/onc/onc_merger.cc b/chromeos/network/onc/onc_merger.cc
new file mode 100644
index 0000000..fca8d37
--- /dev/null
+++ b/chromeos/network/onc/onc_merger.cc
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_merger.h"
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+
+namespace chromeos {
+namespace onc {
+namespace {
+
+typedef scoped_ptr<base::DictionaryValue> DictionaryPtr;
+
+// Inserts |true| at every field name in |result| that is recommended in policy.
+void MarkRecommendedFieldnames(const base::DictionaryValue& policy,
+ base::DictionaryValue* result) {
+ const ListValue* recommended_value = NULL;
+ if (!policy.GetListWithoutPathExpansion(kRecommended, &recommended_value))
+ return;
+ for (ListValue::const_iterator it = recommended_value->begin();
+ it != recommended_value->end(); ++it) {
+ std::string entry;
+ if ((*it)->GetAsString(&entry))
+ result->SetBooleanWithoutPathExpansion(entry, true);
+ }
+}
+
+// Split the given |policy| into its recommended and mandatory settings. The
+// dictionary |result_editable| contains a |true| value at each path that is
+// editable by the user.
+void SplitPolicy(const base::DictionaryValue& policy,
+ DictionaryPtr* result_recommended,
+ DictionaryPtr* result_editable,
+ DictionaryPtr* result_mandatory) {
+ result_recommended->reset(new base::DictionaryValue);
+ result_editable->reset(new base::DictionaryValue);
+ result_mandatory->reset(new base::DictionaryValue);
+
+ MarkRecommendedFieldnames(policy, result_editable->get());
+
+ // Distribute policy's entries into |result_recommended| and |result_editable|
+ // according to |result_editable| and recurse into nested dictionaries.
+ for (base::DictionaryValue::Iterator it(policy); it.HasNext(); it.Advance()) {
+ if (it.key() == kRecommended)
+ continue;
+
+ const base::DictionaryValue* child_policy = NULL;
+ if (it.value().GetAsDictionary(&child_policy)) {
+ DictionaryPtr child_recommended, child_editable, child_mandatory;
+ SplitPolicy(*child_policy, &child_recommended, &child_editable,
+ &child_mandatory);
+
+ (*result_recommended)->SetWithoutPathExpansion(
+ it.key(), child_recommended.release());
+ (*result_editable)->SetWithoutPathExpansion(
+ it.key(), child_editable.release());
+ (*result_mandatory)->SetWithoutPathExpansion(
+ it.key(), child_mandatory.release());
+ } else {
+ bool is_recommended = false;
+ (*result_editable)->GetBooleanWithoutPathExpansion(it.key(),
+ &is_recommended);
+ if (is_recommended) {
+ (*result_recommended)->SetWithoutPathExpansion(
+ it.key(), it.value().DeepCopy());
+ } else {
+ (*result_mandatory)->SetWithoutPathExpansion(
+ it.key(), it.value().DeepCopy());
+ }
+ }
+ }
+}
+
+// Copy values from |user| at paths that are assigned true in |editable|. The
+// values are copied to a new dictionary which is returned.
+DictionaryPtr DeepCopyIf(
+ const base::DictionaryValue& user,
+ const base::DictionaryValue& editable) {
+ DictionaryPtr result(new base::DictionaryValue);
+
+ for (base::DictionaryValue::Iterator it(user); it.HasNext(); it.Advance()) {
+ const base::DictionaryValue* user_child = NULL;
+ if (it.value().GetAsDictionary(&user_child)) {
+ const base::DictionaryValue* editable_child = NULL;
+ if (editable.GetDictionaryWithoutPathExpansion(it.key(),
+ &editable_child)) {
+ result->SetWithoutPathExpansion(
+ it.key(), DeepCopyIf(*user_child, *editable_child).release());
+ }
+ } else {
+ bool editable_flag;
+ if (editable.GetBooleanWithoutPathExpansion(it.key(), &editable_flag) &&
+ editable_flag) {
+ result->SetWithoutPathExpansion(it.key(), it.value().DeepCopy());
+ }
+ }
+ }
+
+ return result.Pass();
+}
+
+void MergeDictionaryIfNotNULL(const base::DictionaryValue* update,
+ base::DictionaryValue* result) {
+ if (update != NULL)
+ result->MergeDictionary(update);
+}
+
+} // namespace
+
+DictionaryPtr MergeSettingsWithPolicies(
+ const base::DictionaryValue* user_policy,
+ const base::DictionaryValue* device_policy,
+ const base::DictionaryValue* user_onc,
+ const base::DictionaryValue* shared_onc) {
+ DictionaryPtr user_mandatory;
+ DictionaryPtr user_editable;
+ DictionaryPtr user_recommended;
+ if (user_policy != NULL) {
+ SplitPolicy(*user_policy, &user_recommended, &user_editable,
+ &user_mandatory);
+ } else {
+ user_mandatory.reset(new base::DictionaryValue);
+ user_recommended.reset(new base::DictionaryValue);
+ // 'user_editable == NULL' means everything is editable
+ }
+
+ DictionaryPtr device_mandatory;
+ DictionaryPtr device_editable;
+ DictionaryPtr device_recommended;
+ if (device_policy != NULL) {
+ SplitPolicy(*device_policy, &device_recommended, &device_editable,
+ &device_mandatory);
+ } else {
+ device_mandatory.reset(new base::DictionaryValue);
+ device_recommended.reset(new base::DictionaryValue);
+ // 'device_editable == NULL' means everything is editable
+ }
+
+ DictionaryPtr reduced_user_onc(
+ user_onc != NULL ? user_onc->DeepCopy() : new base::DictionaryValue);
+ if (user_editable.get() != NULL)
+ reduced_user_onc = DeepCopyIf(*reduced_user_onc, *user_editable);
+ if (device_editable.get() != NULL)
+ reduced_user_onc = DeepCopyIf(*reduced_user_onc, *device_editable);
+
+ DictionaryPtr reduced_shared_onc(
+ shared_onc != NULL ? shared_onc->DeepCopy() : new base::DictionaryValue);
+ if (user_editable.get() != NULL)
+ reduced_shared_onc = DeepCopyIf(*reduced_shared_onc, *user_editable);
+ if (device_editable.get() != NULL)
+ reduced_shared_onc = DeepCopyIf(*reduced_shared_onc, *device_editable);
+
+ // Merge the settings layers according to their priority: From lowest to
+ // highest priority.
+ DictionaryPtr result(new base::DictionaryValue);
+ MergeDictionaryIfNotNULL(device_recommended.get(), result.get());
+ MergeDictionaryIfNotNULL(user_recommended.get(), result.get());
+ MergeDictionaryIfNotNULL(reduced_shared_onc.get(), result.get());
+ MergeDictionaryIfNotNULL(reduced_user_onc.get(), result.get());
+ MergeDictionaryIfNotNULL(device_mandatory.get(), result.get());
+ MergeDictionaryIfNotNULL(user_mandatory.get(), result.get());
+
+ return result.Pass();
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_merger.h b/chromeos/network/onc/onc_merger.h
new file mode 100644
index 0000000..8797f85
--- /dev/null
+++ b/chromeos/network/onc/onc_merger.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_MERGER_H_
+#define CHROMEOS_NETWORK_ONC_ONC_MERGER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+namespace onc {
+
+// Merges the given |user_onc| and |shared_onc| settings with the given
+// |user_policy| and |device_policy| settings. Each can be omitted by providing
+// a NULL pointer. Each dictionary has to be a valid ONC dictionary. They don't
+// have to describe top-level ONC but should refer to the same section in
+// ONC. |user_onc| and |shared_onc| should not contain kRecommended fields. The
+// resulting dictionary is valid ONC but may contain dispensable fields (e.g. in
+// a network with type: "WiFi", the field "VPN" is dispensable) that can be
+// removed by the caller using the ONC normalizer. ONC conformance of the
+// arguments is not checked. Use ONC validator for that.
+CHROMEOS_EXPORT scoped_ptr<base::DictionaryValue> MergeSettingsWithPolicies(
+ const base::DictionaryValue* user_policy,
+ const base::DictionaryValue* device_policy,
+ const base::DictionaryValue* user_onc,
+ const base::DictionaryValue* shared_onc);
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_MERGER_H_
diff --git a/chromeos/network/onc/onc_merger_unittest.cc b/chromeos/network/onc/onc_merger_unittest.cc
new file mode 100644
index 0000000..c7d5190
--- /dev/null
+++ b/chromeos/network/onc/onc_merger_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_merger.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+namespace {
+
+// Checks that both dictionaries contain an entry at |path| with the same value.
+::testing::AssertionResult HaveSameValueAt(const base::DictionaryValue& a,
+ const base::DictionaryValue& b,
+ const std::string& path) {
+ const base::Value* a_value = NULL;
+ if (!a.Get(path, &a_value)) {
+ return ::testing::AssertionFailure()
+ << "First dictionary '" << a << "' doesn't contain " << path;
+ }
+
+ const base::Value* b_value = NULL;
+ if (!b.Get(path, &b_value)) {
+ return ::testing::AssertionFailure()
+ << "Second dictionary '" << b << "' doesn't contain " << path;
+ }
+
+ if (base::Value::Equals(a_value, b_value)) {
+ return ::testing::AssertionSuccess()
+ << "Entries at '" << path << "' are equal";
+ } else {
+ return ::testing::AssertionFailure()
+ << "Entries at '" << path << "' not equal but are '"
+ << *a_value << "' and '" << *b_value << "'";
+ }
+}
+
+} // namespace
+
+namespace merger {
+
+class ONCMergerTest : public testing::Test {
+ public:
+ scoped_ptr<const base::DictionaryValue> user_;
+ scoped_ptr<const base::DictionaryValue> policy_;
+ scoped_ptr<const base::DictionaryValue> policy_without_recommended_;
+ scoped_ptr<const base::DictionaryValue> device_policy_;
+
+ virtual void SetUp() {
+ policy_ = test_utils::ReadTestDictionary("policy.onc");
+ policy_without_recommended_ =
+ test_utils::ReadTestDictionary("policy_without_recommended.onc");
+ user_ = test_utils::ReadTestDictionary("user.onc");
+ device_policy_ = test_utils::ReadTestDictionary("device_policy.onc");
+ }
+};
+
+TEST_F(ONCMergerTest, MandatoryValueOverwritesUserValue) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "Type"));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "IPConfigs"));
+}
+
+TEST_F(ONCMergerTest, MandatoryValueAndNoUserValue) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "GUID"));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "VPN.OpenVPN.Username"));
+}
+
+TEST_F(ONCMergerTest, MandatoryDictionaryAndNoUserValue) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_without_recommended_,
+ "VPN.OpenVPN.ClientCertPattern"));
+}
+
+TEST_F(ONCMergerTest, UserValueOverwritesRecommendedValue) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *user_, "VPN.Host"));
+}
+
+TEST_F(ONCMergerTest, UserValueAndRecommendedUnset) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *user_, "VPN.OpenVPN.Password"));
+}
+
+TEST_F(ONCMergerTest, UserDictionaryAndNoPolicyValue) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(policy_.get(), NULL, user_.get(), NULL));
+ const base::Value* value = NULL;
+ EXPECT_FALSE(merged->Get("ProxySettings", &value));
+}
+
+TEST_F(ONCMergerTest, MergeWithEmptyPolicyProhibitsEverything) {
+ base::DictionaryValue emptyDict;
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(&emptyDict, NULL, user_.get(), NULL));
+ EXPECT_TRUE(merged->empty());
+}
+
+TEST_F(ONCMergerTest, MergeWithoutPolicyAllowsAnything) {
+ scoped_ptr<base::DictionaryValue> merged(
+ MergeSettingsWithPolicies(NULL, NULL, user_.get(), NULL));
+ EXPECT_TRUE(test_utils::Equals(user_.get(), merged.get()));
+}
+
+TEST_F(ONCMergerTest, MergeWithoutUserSettings) {
+ base::DictionaryValue emptyDict;
+ scoped_ptr<base::DictionaryValue> merged;
+
+ merged = MergeSettingsWithPolicies(policy_.get(), NULL, &emptyDict, NULL);
+ EXPECT_TRUE(test_utils::Equals(policy_without_recommended_.get(),
+ merged.get()));
+
+ merged = MergeSettingsWithPolicies(policy_.get(), NULL, NULL, NULL);
+ EXPECT_TRUE(test_utils::Equals(policy_without_recommended_.get(),
+ merged.get()));
+}
+
+TEST_F(ONCMergerTest, MandatoryUserPolicyOverwriteDevicePolicy) {
+ scoped_ptr<base::DictionaryValue> merged(MergeSettingsWithPolicies(
+ policy_.get(), device_policy_.get(), user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *policy_, "VPN.OpenVPN.Port"));
+}
+
+TEST_F(ONCMergerTest, MandatoryDevicePolicyOverwritesRecommendedUserPolicy) {
+ scoped_ptr<base::DictionaryValue> merged(MergeSettingsWithPolicies(
+ policy_.get(), device_policy_.get(), user_.get(), NULL));
+ EXPECT_TRUE(HaveSameValueAt(*merged, *device_policy_,
+ "VPN.OpenVPN.Username"));
+}
+
+} // namespace merger
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_normalizer.cc b/chromeos/network/onc/onc_normalizer.cc
new file mode 100644
index 0000000..4f99867
--- /dev/null
+++ b/chromeos/network/onc/onc_normalizer.cc
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_normalizer.h"
+
+#include <string>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+
+namespace chromeos {
+namespace onc {
+
+Normalizer::Normalizer(bool remove_recommended_fields)
+ : remove_recommended_fields_(remove_recommended_fields) {
+}
+
+Normalizer::~Normalizer() {
+}
+
+scoped_ptr<base::DictionaryValue> Normalizer::NormalizeObject(
+ const OncValueSignature* object_signature,
+ const base::DictionaryValue& onc_object) {
+ CHECK(object_signature != NULL);
+ return MapObject(*object_signature, onc_object);
+}
+
+scoped_ptr<base::DictionaryValue> Normalizer::MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object) {
+ scoped_ptr<base::DictionaryValue> normalized =
+ Mapper::MapObject(signature, onc_object);
+
+ if (normalized.get() == NULL)
+ return scoped_ptr<base::DictionaryValue>();
+
+ if (remove_recommended_fields_)
+ normalized->RemoveWithoutPathExpansion(kRecommended, NULL);
+
+ if (&signature == &kNetworkConfigurationSignature)
+ NormalizeNetworkConfiguration(normalized.get());
+ else if (&signature == &kVPNSignature)
+ NormalizeVPN(normalized.get());
+ else if (&signature == &kIPsecSignature)
+ NormalizeIPsec(normalized.get());
+
+ return normalized.Pass();
+}
+
+namespace {
+void RemoveEntryUnless(base::DictionaryValue* dict,
+ const std::string path,
+ bool condition) {
+ if (!condition)
+ dict->RemoveWithoutPathExpansion(path, NULL);
+}
+} // namespace
+
+void Normalizer::NormalizeIPsec(base::DictionaryValue* ipsec) {
+ using namespace vpn;
+
+ std::string auth_type;
+ ipsec->GetStringWithoutPathExpansion(kAuthenticationType, &auth_type);
+ RemoveEntryUnless(ipsec, kClientCertType, auth_type == kCert);
+ RemoveEntryUnless(ipsec, kServerCARef, auth_type == kCert);
+ RemoveEntryUnless(ipsec, kPSK, auth_type == kPSK);
+ RemoveEntryUnless(ipsec, kSaveCredentials, auth_type == kPSK);
+
+ std::string clientcert_type;
+ ipsec->GetStringWithoutPathExpansion(kClientCertType, &clientcert_type);
+ RemoveEntryUnless(ipsec, kClientCertPattern,
+ clientcert_type == certificate::kPattern);
+ RemoveEntryUnless(ipsec, kClientCertRef,
+ clientcert_type == certificate::kRef);
+
+ int ike_version = -1;
+ ipsec->GetIntegerWithoutPathExpansion(kIKEVersion, &ike_version);
+ RemoveEntryUnless(ipsec, kEAP, ike_version == 2);
+ RemoveEntryUnless(ipsec, kGroup, ike_version == 1);
+ RemoveEntryUnless(ipsec, kXAUTH, ike_version == 1);
+}
+
+void Normalizer::NormalizeVPN(base::DictionaryValue* vpn) {
+ using namespace vpn;
+ std::string type;
+ vpn->GetStringWithoutPathExpansion(vpn::kType, &type);
+ RemoveEntryUnless(vpn, kOpenVPN, type == kOpenVPN);
+ RemoveEntryUnless(vpn, kIPsec, type == kIPsec || type == kTypeL2TP_IPsec);
+ RemoveEntryUnless(vpn, kL2TP, type == kTypeL2TP_IPsec);
+}
+
+void Normalizer::NormalizeNetworkConfiguration(base::DictionaryValue* network) {
+ std::string type;
+ network->GetStringWithoutPathExpansion(kType, &type);
+ RemoveEntryUnless(network, kEthernet, type == kEthernet);
+ RemoveEntryUnless(network, kVPN, type == kVPN);
+ RemoveEntryUnless(network, kWiFi, type == kWiFi);
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_normalizer.h b/chromeos/network/onc/onc_normalizer.h
new file mode 100644
index 0000000..5d00f00
--- /dev/null
+++ b/chromeos/network/onc/onc_normalizer.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_NORMALIZER_H_
+#define CHROMEOS_NETWORK_ONC_ONC_NORMALIZER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+#include "chromeos/network/onc/onc_mapper.h"
+
+namespace chromeos {
+namespace onc {
+
+struct OncValueSignature;
+
+class CHROMEOS_EXPORT Normalizer : public Mapper {
+ public:
+ explicit Normalizer(bool remove_recommended_fields);
+ virtual ~Normalizer();
+
+ // Removes all fields that are ignored/irrelevant because of the value of
+ // other fields. E.g. the "WiFi" field is irrelevant if the configurations
+ // type is "Ethernet". If |remove_recommended_fields| is true, kRecommended
+ // arrays are removed (the array itself and not the field names listed
+ // there). |object_signature| must point to one of the signatures in
+ // |onc_signature.h|.
+ scoped_ptr<base::DictionaryValue> NormalizeObject(
+ const OncValueSignature* object_signature,
+ const base::DictionaryValue& onc_object);
+
+ private:
+ // Dispatch to the right normalization function according to |signature|.
+ virtual scoped_ptr<base::DictionaryValue> MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object) OVERRIDE;
+
+ void NormalizeIPsec(base::DictionaryValue* ipsec);
+ void NormalizeVPN(base::DictionaryValue* vpn);
+ void NormalizeNetworkConfiguration(base::DictionaryValue* network);
+
+ const bool remove_recommended_fields_;
+
+ DISALLOW_COPY_AND_ASSIGN(Normalizer);
+};
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_NORMALIZER_H_
diff --git a/chromeos/network/onc/onc_normalizer_unittest.cc b/chromeos/network/onc/onc_normalizer_unittest.cc
new file mode 100644
index 0000000..d0c5601
--- /dev/null
+++ b/chromeos/network/onc/onc_normalizer_unittest.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_normalizer.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+
+// This test case is about validating valid ONC objects.
+TEST(ONCNormalizerTest, NormalizeNetworkConfiguration) {
+ Normalizer normalizer(true);
+ scoped_ptr<const base::DictionaryValue> data(
+ test_utils::ReadTestDictionary("settings_with_normalization.json"));
+
+ const base::DictionaryValue* original;
+ const base::DictionaryValue* expected_normalized;
+ data->GetDictionary("ethernet-and-vpn", &original);
+ data->GetDictionary("ethernet-and-vpn-normalized", &expected_normalized);
+
+ scoped_ptr<base::DictionaryValue> actual_normalized =
+ normalizer.NormalizeObject(&kNetworkConfigurationSignature, *original);
+ EXPECT_TRUE(test_utils::Equals(expected_normalized, actual_normalized.get()));
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_signature.cc b/chromeos/network/onc/onc_signature.cc
new file mode 100644
index 0000000..f71328c
--- /dev/null
+++ b/chromeos/network/onc/onc_signature.cc
@@ -0,0 +1,313 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_signature.h"
+
+#include "chromeos/network/onc/onc_constants.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+using base::Value;
+
+namespace chromeos {
+namespace onc {
+namespace {
+
+const OncValueSignature kBoolSignature = {
+ Value::TYPE_BOOLEAN, NULL
+};
+const OncValueSignature kStringSignature = {
+ Value::TYPE_STRING, NULL
+};
+const OncValueSignature kIntegerSignature = {
+ Value::TYPE_INTEGER, NULL
+};
+const OncValueSignature kStringListSignature = {
+ Value::TYPE_LIST, NULL, &kStringSignature
+};
+const OncValueSignature kIPConfigListSignature = {
+ Value::TYPE_LIST, NULL, &kIPConfigSignature
+};
+const OncValueSignature kCertificateListSignature = {
+ Value::TYPE_LIST, NULL, &kCertificateSignature
+};
+const OncValueSignature kNetworkConfigurationListSignature = {
+ Value::TYPE_LIST, NULL, &kNetworkConfigurationSignature
+};
+
+const OncFieldSignature issuer_subject_pattern_fields[] = {
+ { certificate::kCommonName, NULL, &kStringSignature },
+ { certificate::kLocality, NULL, &kStringSignature },
+ { certificate::kOrganization, NULL, &kStringSignature },
+ { certificate::kOrganizationalUnit, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature certificate_pattern_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { certificate::kEnrollmentURI, NULL, &kStringListSignature },
+ { certificate::kIssuer, NULL, &kIssuerSubjectPatternSignature },
+ { certificate::kIssuerCARef, NULL, &kStringListSignature },
+ { certificate::kSubject, NULL, &kIssuerSubjectPatternSignature },
+ { NULL }
+};
+
+const OncFieldSignature eap_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { eap::kAnonymousIdentity, flimflam::kEapAnonymousIdentityProperty,
+ &kStringSignature },
+ { eap::kClientCertPattern, NULL, &kCertificatePatternSignature },
+ { eap::kClientCertRef, NULL, &kStringSignature },
+ { eap::kClientCertType, NULL, &kStringSignature },
+ { eap::kIdentity, flimflam::kEapIdentityProperty, &kStringSignature },
+ { eap::kInner, flimflam::kEapPhase2AuthProperty, &kStringSignature },
+ { eap::kOuter, flimflam::kEapMethodProperty, &kStringSignature },
+ { eap::kPassword, flimflam::kEapPasswordProperty, &kStringSignature },
+ { eap::kSaveCredentials, flimflam::kSaveCredentialsProperty,
+ &kBoolSignature },
+ { eap::kServerCARef, flimflam::kEapCaCertNssProperty, &kStringSignature },
+ { eap::kUseSystemCAs, flimflam::kEapUseSystemCasProperty, &kBoolSignature },
+ { NULL }
+};
+
+const OncFieldSignature ipsec_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ // Ignored by Shill, not necessary to synchronize.
+ // Would be: flimflam::kL2tpIpsecAuthenticationType
+ { vpn::kAuthenticationType, NULL, &kStringSignature },
+ { vpn::kClientCertPattern, NULL, &kCertificatePatternSignature },
+ { vpn::kClientCertRef, NULL, &kStringSignature },
+ { vpn::kClientCertType, NULL, &kStringSignature },
+ { vpn::kGroup, flimflam::kL2tpIpsecGroupNameProperty, &kStringSignature },
+ // Ignored by Shill, not necessary to synchronize.
+ // Would be: flimflam::kL2tpIpsecIkeVersion
+ { vpn::kIKEVersion, NULL, &kIntegerSignature },
+ { vpn::kPSK, flimflam::kL2tpIpsecPskProperty, &kStringSignature },
+ { vpn::kSaveCredentials, flimflam::kSaveCredentialsProperty,
+ &kBoolSignature },
+ { vpn::kServerCARef, flimflam::kL2tpIpsecCaCertNssProperty,
+ &kStringSignature },
+ // Not yet supported.
+ // { vpn::kEAP, NULL, &kEAPSignature },
+ // { vpn::kXAUTH, NULL, &kXAUTHSignature },
+ { NULL }
+};
+
+const OncFieldSignature l2tp_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { vpn::kPassword, flimflam::kL2tpIpsecPasswordProperty, &kStringSignature },
+ // We don't synchronize l2tp's SaveCredentials field for now, as Shill doesn't
+ // support separate settings for ipsec and l2tp.
+ { vpn::kSaveCredentials, NULL, &kBoolSignature },
+ { vpn::kUsername, flimflam::kL2tpIpsecUserProperty, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature openvpn_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { vpn::kAuth, flimflam::kOpenVPNAuthProperty, &kStringSignature },
+ { vpn::kAuthNoCache, flimflam::kOpenVPNAuthNoCacheProperty, &kBoolSignature },
+ { vpn::kAuthRetry, flimflam::kOpenVPNAuthRetryProperty, &kStringSignature },
+ { vpn::kCipher, flimflam::kOpenVPNCipherProperty, &kStringSignature },
+ { vpn::kClientCertPattern, NULL, &kCertificatePatternSignature },
+ { vpn::kClientCertRef, NULL, &kStringSignature },
+ { vpn::kClientCertType, NULL, &kStringSignature },
+ { vpn::kCompLZO, flimflam::kOpenVPNCompLZOProperty, &kStringSignature },
+ { vpn::kCompNoAdapt, flimflam::kOpenVPNCompNoAdaptProperty, &kBoolSignature },
+ { vpn::kKeyDirection, flimflam::kOpenVPNKeyDirectionProperty,
+ &kStringSignature },
+ { vpn::kNsCertType, flimflam::kOpenVPNNsCertTypeProperty, &kStringSignature },
+ { vpn::kPassword, flimflam::kOpenVPNPasswordProperty, &kStringSignature },
+ { vpn::kPort, flimflam::kOpenVPNPortProperty, &kIntegerSignature },
+ { vpn::kProto, flimflam::kOpenVPNProtoProperty, &kStringSignature },
+ { vpn::kPushPeerInfo, flimflam::kOpenVPNPushPeerInfoProperty,
+ &kBoolSignature },
+ { vpn::kRemoteCertEKU, flimflam::kOpenVPNRemoteCertEKUProperty,
+ &kStringSignature },
+ // This field is converted during translation, see onc_translator_*.
+ { vpn::kRemoteCertKU, NULL, &kStringListSignature },
+ { vpn::kRemoteCertTLS, flimflam::kOpenVPNRemoteCertTLSProperty,
+ &kStringSignature },
+ { vpn::kRenegSec, flimflam::kOpenVPNRenegSecProperty, &kIntegerSignature },
+ { vpn::kSaveCredentials, flimflam::kSaveCredentialsProperty,
+ &kBoolSignature },
+ { vpn::kServerCARef, flimflam::kOpenVPNCaCertNSSProperty, &kStringSignature },
+ { vpn::kServerCertRef, NULL, &kStringSignature },
+ { vpn::kServerPollTimeout, flimflam::kOpenVPNServerPollTimeoutProperty,
+ &kIntegerSignature },
+ { vpn::kShaper, flimflam::kOpenVPNShaperProperty, &kIntegerSignature },
+ { vpn::kStaticChallenge, flimflam::kOpenVPNStaticChallengeProperty,
+ &kStringSignature },
+ { vpn::kTLSAuthContents, flimflam::kOpenVPNTLSAuthContentsProperty,
+ &kStringSignature },
+ { vpn::kTLSRemote, flimflam::kOpenVPNTLSRemoteProperty, &kStringSignature },
+ { vpn::kUsername, flimflam::kOpenVPNUserProperty, &kStringSignature },
+ { vpn::kVerb, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature vpn_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { vpn::kHost, flimflam::kProviderHostProperty, &kStringSignature },
+ { vpn::kIPsec, NULL, &kIPsecSignature },
+ { vpn::kL2TP, NULL, &kL2TPSignature },
+ { vpn::kOpenVPN, NULL, &kOpenVPNSignature },
+ // This field is converted during translation, see onc_translator_*.
+ { kType, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature ethernet_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { ethernet::kAuthentication, NULL, &kStringSignature },
+ { ethernet::kEAP, NULL, &kEAPSignature },
+ { NULL }
+};
+
+const OncFieldSignature ipconfig_fields[] = {
+ { ipconfig::kGateway, NULL, &kStringSignature },
+ { ipconfig::kIPAddress, NULL, &kStringSignature },
+ { kNameServers, NULL, &kStringSignature },
+ { ipconfig::kRoutingPrefix, NULL, &kIntegerSignature },
+ { kSearchDomains, NULL, &kStringListSignature },
+ // This field is converted during translation, see onc_translator_*.
+ { kType, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature proxy_location_fields[] = {
+ { proxy::kHost, NULL, &kStringSignature },
+ { proxy::kPort, NULL, &kIntegerSignature },
+ { NULL }
+};
+
+const OncFieldSignature proxy_manual_fields[] = {
+ { proxy::kFtp, NULL, &kProxyLocationSignature },
+ { proxy::kHttp, NULL, &kProxyLocationSignature },
+ { proxy::kHttps, NULL, &kProxyLocationSignature },
+ { proxy::kSocks, NULL, &kProxyLocationSignature },
+ { NULL }
+};
+
+const OncFieldSignature proxy_settings_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { proxy::kExcludeDomains, NULL, &kStringListSignature },
+ { proxy::kManual, NULL, &kProxyManualSignature },
+ { proxy::kPAC, NULL, &kStringSignature },
+ { kType, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature wifi_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { wifi::kAutoConnect, flimflam::kAutoConnectProperty, &kBoolSignature },
+ { wifi::kEAP, NULL, &kEAPSignature },
+ { wifi::kHiddenSSID, flimflam::kWifiHiddenSsid, &kBoolSignature },
+ { wifi::kPassphrase, flimflam::kPassphraseProperty, &kStringSignature },
+ { wifi::kSSID, flimflam::kSSIDProperty, &kStringSignature },
+ { wifi::kSecurity, flimflam::kSecurityProperty, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature network_configuration_fields[] = {
+ { kRecommended, NULL, &kRecommendedSignature },
+ { kEthernet, NULL, &kEthernetSignature },
+ { kGUID, flimflam::kGuidProperty, &kStringSignature },
+ { kIPConfigs, NULL, &kIPConfigListSignature },
+ { kName, flimflam::kNameProperty, &kStringSignature },
+ { kNameServers, NULL, &kStringListSignature },
+ { kProxySettings, NULL, &kProxySettingsSignature },
+ { kRemove, NULL, &kBoolSignature },
+ { kSearchDomains, NULL, &kStringListSignature },
+ // This field is converted during translation, see onc_translator_*.
+ { kType, NULL, &kStringSignature },
+ { kVPN, NULL, &kVPNSignature },
+ { kWiFi, NULL, &kWiFiSignature },
+ { NULL }
+};
+
+const OncFieldSignature certificate_fields[] = {
+ { kGUID, flimflam::kGuidProperty, &kStringSignature },
+ { certificate::kPKCS12, NULL, &kStringSignature },
+ { kRemove, NULL, &kBoolSignature },
+ { certificate::kTrust, NULL, &kStringListSignature },
+ { kType, NULL, &kStringSignature },
+ { certificate::kX509, NULL, &kStringSignature },
+ { NULL }
+};
+
+const OncFieldSignature unencrypted_configuration_fields[] = {
+ { kCertificates, NULL, &kCertificateListSignature },
+ { kNetworkConfigurations, NULL, &kNetworkConfigurationListSignature },
+ { kType, NULL, &kStringSignature },
+ { NULL }
+};
+
+} // namespace
+
+const OncValueSignature kRecommendedSignature = {
+ Value::TYPE_LIST, NULL, &kStringSignature
+};
+const OncValueSignature kEAPSignature = {
+ Value::TYPE_DICTIONARY, eap_fields, NULL
+};
+const OncValueSignature kIssuerSubjectPatternSignature = {
+ Value::TYPE_DICTIONARY, issuer_subject_pattern_fields, NULL
+};
+const OncValueSignature kCertificatePatternSignature = {
+ Value::TYPE_DICTIONARY, certificate_pattern_fields, NULL
+};
+const OncValueSignature kIPsecSignature = {
+ Value::TYPE_DICTIONARY, ipsec_fields, NULL
+};
+const OncValueSignature kL2TPSignature = {
+ Value::TYPE_DICTIONARY, l2tp_fields, NULL
+};
+const OncValueSignature kOpenVPNSignature = {
+ Value::TYPE_DICTIONARY, openvpn_fields, NULL
+};
+const OncValueSignature kVPNSignature = {
+ Value::TYPE_DICTIONARY, vpn_fields, NULL
+};
+const OncValueSignature kEthernetSignature = {
+ Value::TYPE_DICTIONARY, ethernet_fields, NULL
+};
+const OncValueSignature kIPConfigSignature = {
+ Value::TYPE_DICTIONARY, ipconfig_fields, NULL
+};
+const OncValueSignature kProxyLocationSignature = {
+ Value::TYPE_DICTIONARY, proxy_location_fields, NULL
+};
+const OncValueSignature kProxyManualSignature = {
+ Value::TYPE_DICTIONARY, proxy_manual_fields, NULL
+};
+const OncValueSignature kProxySettingsSignature = {
+ Value::TYPE_DICTIONARY, proxy_settings_fields, NULL
+};
+const OncValueSignature kWiFiSignature = {
+ Value::TYPE_DICTIONARY, wifi_fields, NULL
+};
+const OncValueSignature kCertificateSignature = {
+ Value::TYPE_DICTIONARY, certificate_fields, NULL
+};
+const OncValueSignature kNetworkConfigurationSignature = {
+ Value::TYPE_DICTIONARY, network_configuration_fields, NULL
+};
+const OncValueSignature kUnencryptedConfigurationSignature = {
+ Value::TYPE_DICTIONARY, unencrypted_configuration_fields, NULL
+};
+
+const OncFieldSignature* GetFieldSignature(const OncValueSignature& signature,
+ const std::string& onc_field_name) {
+ if (!signature.fields)
+ return NULL;
+ for (const OncFieldSignature* field_signature = signature.fields;
+ field_signature->onc_field_name != NULL; ++field_signature) {
+ if (onc_field_name == field_signature->onc_field_name)
+ return field_signature;
+ }
+ return NULL;
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_signature.h b/chromeos/network/onc/onc_signature.h
new file mode 100644
index 0000000..1a9b507
--- /dev/null
+++ b/chromeos/network/onc/onc_signature.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_SIGNATURE_H_
+#define CHROMEOS_NETWORK_ONC_ONC_SIGNATURE_H_
+
+#include <string>
+
+#include "base/values.h"
+#include "chromeos/chromeos_export.h"
+
+namespace chromeos {
+namespace onc {
+
+struct OncValueSignature;
+
+struct OncFieldSignature {
+ const char* onc_field_name;
+ const char* shill_property_name;
+ const OncValueSignature* value_signature;
+};
+
+struct CHROMEOS_EXPORT OncValueSignature {
+ base::Value::Type onc_type;
+ const OncFieldSignature* fields;
+ const OncValueSignature* onc_array_entry_signature;
+};
+
+const OncFieldSignature* GetFieldSignature(const OncValueSignature& signature,
+ const std::string& onc_field_name);
+
+CHROMEOS_EXPORT extern const OncValueSignature kRecommendedSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kEAPSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kIssuerSubjectPatternSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kCertificatePatternSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kIPsecSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kL2TPSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kOpenVPNSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kVPNSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kEthernetSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kIPConfigSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kProxyLocationSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kProxyManualSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kProxySettingsSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kWiFiSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kCertificateSignature;
+CHROMEOS_EXPORT extern const OncValueSignature kNetworkConfigurationSignature;
+CHROMEOS_EXPORT extern const OncValueSignature
+ kUnencryptedConfigurationSignature;
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_SIGNATURE_H_
diff --git a/chromeos/network/onc/onc_test_utils.cc b/chromeos/network/onc/onc_test_utils.cc
new file mode 100644
index 0000000..0a948bf
--- /dev/null
+++ b/chromeos/network/onc/onc_test_utils.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_test_utils.h"
+
+#include "base/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/values.h"
+#include "chromeos/chromeos_test_utils.h"
+
+namespace chromeos {
+namespace onc {
+namespace test_utils {
+
+namespace {
+
+// The name of the component directory to get the test data from.
+const char kNetworkComponentDirectory[] = "network";
+
+} // namespace
+
+scoped_ptr<base::DictionaryValue> ReadTestDictionary(
+ const std::string& filename) {
+ base::DictionaryValue* dict = NULL;
+ FilePath path;
+ if (!chromeos::test_utils::GetTestDataPath(kNetworkComponentDirectory,
+ filename,
+ &path)) {
+ NOTREACHED() << "Unable to get test dictionary path for "
+ << kNetworkComponentDirectory << "/" << filename;
+ return make_scoped_ptr(dict);
+ }
+
+ JSONFileValueSerializer serializer(path);
+ serializer.set_allow_trailing_comma(true);
+
+ std::string error_message;
+ base::Value* content = serializer.Deserialize(NULL, &error_message);
+ CHECK(content != NULL) << "Couldn't json-deserialize file '"
+ << filename << "': " << error_message;
+
+ CHECK(content->GetAsDictionary(&dict))
+ << "File '" << filename
+ << "' does not contain a dictionary as expected, but type "
+ << content->GetType();
+ return make_scoped_ptr(dict);
+}
+
+::testing::AssertionResult Equals(const base::DictionaryValue* expected,
+ const base::DictionaryValue* actual) {
+ CHECK(expected != NULL);
+ if (actual == NULL)
+ return ::testing::AssertionFailure() << "Actual dictionary pointer is NULL";
+
+ if (expected->Equals(actual))
+ return ::testing::AssertionSuccess() << "Dictionaries are equal";
+
+ return ::testing::AssertionFailure() << "Dictionaries are unequal.\n"
+ << "Expected dictionary:\n" << *expected
+ << "Actual dictionary:\n" << *actual;
+}
+
+} // namespace test_utils
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_test_utils.h b/chromeos/network/onc/onc_test_utils.h
new file mode 100644
index 0000000..bc0d80e
--- /dev/null
+++ b/chromeos/network/onc/onc_test_utils.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_TEST_UTILS_H_
+#define CHROMEOS_NETWORK_ONC_ONC_TEST_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+namespace onc {
+namespace test_utils {
+
+// Read a JSON dictionary from |filename| and return it as a
+// DictionaryValue. CHECKs if any error occurs.
+scoped_ptr<base::DictionaryValue> ReadTestDictionary(
+ const std::string& filename);
+
+// Checks that the pointer |actual| is not NULL but points to a dictionary that
+// equals |expected| (using Value::Equals). The intended use case is:
+// EXPECT_TRUE(test_utils::Equals(expected, actual));
+::testing::AssertionResult Equals(const base::DictionaryValue* expected,
+ const base::DictionaryValue* actual);
+
+} // namespace test_utils
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_TEST_UTILS_H_
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc
new file mode 100644
index 0000000..abe978f
--- /dev/null
+++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_translation_tables.h"
+
+#include <cstddef>
+
+#include "chromeos/network/onc/onc_constants.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+namespace onc {
+
+const StringTranslationEntry kNetworkTypeTable[] = {
+ { kEthernet, flimflam::kTypeEthernet },
+ { kWiFi, flimflam::kTypeWifi },
+ { kCellular, flimflam::kTypeCellular },
+ { kVPN, flimflam::kTypeVPN },
+ { NULL }
+};
+
+const StringTranslationEntry kVPNTypeTable[] = {
+ { vpn::kTypeL2TP_IPsec, flimflam::kProviderL2tpIpsec },
+ { vpn::kOpenVPN, flimflam::kProviderOpenVpn },
+ { NULL }
+};
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_translation_tables.h b/chromeos/network/onc/onc_translation_tables.h
new file mode 100644
index 0000000..f9b54a8
--- /dev/null
+++ b/chromeos/network/onc/onc_translation_tables.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_TRANSLATION_TABLES_H_
+#define CHROMEOS_NETWORK_ONC_ONC_TRANSLATION_TABLES_H_
+
+namespace chromeos {
+namespace onc {
+
+struct StringTranslationEntry {
+ const char* onc_value;
+ const char* shill_value;
+};
+
+// These tables contain the mapping from ONC strings to Shill strings.
+// These are NULL-terminated arrays.
+extern const StringTranslationEntry kNetworkTypeTable[];
+extern const StringTranslationEntry kVPNTypeTable[];
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_TRANSLATION_TABLES_H_
diff --git a/chromeos/network/onc/onc_translator.h b/chromeos/network/onc/onc_translator.h
new file mode 100644
index 0000000..270247c
--- /dev/null
+++ b/chromeos/network/onc/onc_translator.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_TRANSLATOR_H_
+#define CHROMEOS_NETWORK_ONC_ONC_TRANSLATOR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+namespace onc {
+
+struct OncValueSignature;
+
+// Translates a hierarchical ONC dictionary |onc_object| to a flat Shill
+// dictionary. The |signature| declares the type of |onc_object| and must point
+// to one of the signature objects in "onc_signature.h". The resulting Shill
+// dictionary is returned.
+//
+// This function is used to translate network settings from ONC to Shill's
+// format before sending them to Shill.
+CHROMEOS_EXPORT
+scoped_ptr<base::DictionaryValue> TranslateONCObjectToShill(
+ const OncValueSignature* signature,
+ const base::DictionaryValue& onc_object);
+
+// Translates a |shill_dictionary| to an ONC object according to the given
+// |onc_signature|. |onc_signature| must point to a signature object in
+// "onc_signature.h". The resulting ONC object is returned.
+//
+// This function is used to translate network settings coming from Shill to ONC
+// before sending them to the UI. The result doesn't have to be valid ONC, but
+// only a subset of it and includes only the values that are actually required
+// by the UI.
+CHROMEOS_EXPORT
+scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart(
+ const base::DictionaryValue& shill_dictionary,
+ const OncValueSignature* onc_signature);
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_TRANSLATOR_H_
diff --git a/chromeos/network/onc/onc_translator_onc_to_shill.cc b/chromeos/network/onc/onc_translator_onc_to_shill.cc
new file mode 100644
index 0000000..cd8a4eb
--- /dev/null
+++ b/chromeos/network/onc/onc_translator_onc_to_shill.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The implementation of TranslateONCObjectToShill is structured in two parts:
+// - The recursion through the existing ONC hierarchy
+// see TranslateONCHierarchy
+// - The local translation of an object depending on the associated signature
+// see LocalTranslator::TranslateFields
+
+#include "chromeos/network/onc/onc_translator.h"
+
+#include <string>
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_translation_tables.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+namespace onc {
+
+namespace {
+
+scoped_ptr<base::StringValue> ConvertValueToString(const base::Value& value) {
+ std::string str;
+ if (!value.GetAsString(&str))
+ base::JSONWriter::Write(&value, &str);
+ return make_scoped_ptr(base::Value::CreateStringValue(str));
+}
+
+// This class is responsible to translate the local fields of the given
+// |onc_object| according to |onc_signature| into |shill_dictionary|. This
+// translation should consider (if possible) only fields of this ONC object and
+// not nested objects because recursion is handled by the calling function
+// TranslateONCHierarchy.
+class LocalTranslator {
+ public:
+ LocalTranslator(const OncValueSignature& onc_signature,
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* shill_dictionary)
+ : onc_signature_(&onc_signature),
+ onc_object_(&onc_object),
+ shill_dictionary_(shill_dictionary) {
+ }
+
+ void TranslateFields();
+
+ private:
+ void TranslateOpenVPN();
+ void TranslateVPN();
+ void TranslateNetworkConfiguration();
+
+ // Copies all entries from |onc_object_| to |shill_dictionary_| for which a
+ // translation (shill_property_name) is defined by |onc_signature_|.
+ void CopyFieldsAccordingToSignature();
+
+ // Adds |value| to |shill_dictionary| at the field shill_property_name given
+ // by the associated signature. Takes ownership of |value|. Does nothing if
+ // |value| is NULL or the property name cannot be read from the signature.
+ void AddValueAccordingToSignature(const std::string& onc_field_name,
+ scoped_ptr<base::Value> value);
+
+ // If existent, translates the entry at |onc_field_name| in |onc_object_|
+ // using |table|. It is an error if no matching table entry is found. Writes
+ // the result as entry at |shill_property_name| in |shill_dictionary_|.
+ void TranslateWithTableAndSet(const std::string& onc_field_name,
+ const StringTranslationEntry table[],
+ const std::string& shill_property_name);
+
+ const OncValueSignature* onc_signature_;
+ const base::DictionaryValue* onc_object_;
+ base::DictionaryValue* shill_dictionary_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalTranslator);
+};
+
+void LocalTranslator::TranslateFields() {
+ if (onc_signature_ == &kNetworkConfigurationSignature)
+ TranslateNetworkConfiguration();
+ else if (onc_signature_ == &kVPNSignature)
+ TranslateVPN();
+ else if (onc_signature_ == &kOpenVPNSignature)
+ TranslateOpenVPN();
+ else
+ CopyFieldsAccordingToSignature();
+}
+
+void LocalTranslator::TranslateOpenVPN() {
+ // Shill supports only one RemoteCertKU but ONC a list.
+ // Copy only the first entry if existing.
+ const base::ListValue* certKUs;
+ std::string certKU;
+ if (onc_object_->GetListWithoutPathExpansion(vpn::kRemoteCertKU, &certKUs) &&
+ certKUs->GetString(0, &certKU)) {
+ shill_dictionary_->SetStringWithoutPathExpansion(
+ flimflam::kOpenVPNRemoteCertKUProperty, certKU);
+ }
+
+ for (base::DictionaryValue::Iterator it(*onc_object_); it.HasNext();
+ it.Advance()) {
+ scoped_ptr<base::Value> translated;
+ if (it.key() == vpn::kSaveCredentials || it.key() == vpn::kRemoteCertKU) {
+ translated.reset(it.value().DeepCopy());
+ } else {
+ // Shill wants all Provider/VPN fields to be strings.
+ translated = ConvertValueToString(it.value());
+ }
+ AddValueAccordingToSignature(it.key(), translated.Pass());
+ }
+}
+
+void LocalTranslator::TranslateVPN() {
+ TranslateWithTableAndSet(kType, kVPNTypeTable,
+ flimflam::kProviderTypeProperty);
+ CopyFieldsAccordingToSignature();
+}
+
+void LocalTranslator::TranslateNetworkConfiguration() {
+ TranslateWithTableAndSet(kType, kNetworkTypeTable, flimflam::kTypeProperty);
+ CopyFieldsAccordingToSignature();
+}
+
+void LocalTranslator::CopyFieldsAccordingToSignature() {
+ for (base::DictionaryValue::Iterator it(*onc_object_); it.HasNext();
+ it.Advance()) {
+ AddValueAccordingToSignature(it.key(),
+ make_scoped_ptr(it.value().DeepCopy()));
+ }
+}
+
+void LocalTranslator::AddValueAccordingToSignature(
+ const std::string& onc_name,
+ scoped_ptr<base::Value> value) {
+ if (value.get() == NULL)
+ return;
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(*onc_signature_, onc_name);
+ DCHECK(field_signature != NULL);
+ if (field_signature == NULL || field_signature->shill_property_name == NULL)
+ return;
+
+ shill_dictionary_->SetWithoutPathExpansion(
+ field_signature->shill_property_name, value.release());
+}
+
+void LocalTranslator::TranslateWithTableAndSet(
+ const std::string& onc_field_name,
+ const StringTranslationEntry table[],
+ const std::string& shill_property_name) {
+ std::string onc_value;
+ if (!onc_object_->GetStringWithoutPathExpansion(onc_field_name, &onc_value))
+ return;
+
+ for (int i = 0; table[i].onc_value != NULL; ++i) {
+ if (onc_value != table[i].onc_value)
+ continue;
+ shill_dictionary_->SetStringWithoutPathExpansion(shill_property_name,
+ table[i].shill_value);
+ return;
+ }
+ // As we previously validate ONC, this case should never occur. If it still
+ // occurs, we should check here. Otherwise the failure will only show up much
+ // later in Shill.
+ LOG(ERROR) << "Value '" << onc_value << "' for field '"
+ << onc_field_name << "' cannot be translated to Shill";
+}
+
+// Iterates recursively over |onc_object| and its |signature|. At each object
+// applies the local translation using LocalTranslator::TranslateFields. The
+// results are written to |shill_dictionary|.
+void TranslateONCHierarchy(const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* shill_dictionary) {
+ // Translates fields of |onc_object| and writes them to |shill_dictionary_|.
+ LocalTranslator translator(signature, onc_object, shill_dictionary);
+ translator.TranslateFields();
+
+ // Recurse into nested objects.
+ for (base::DictionaryValue::Iterator it(onc_object); it.HasNext();
+ it.Advance()) {
+ const base::DictionaryValue* inner_object;
+ if (!it.value().GetAsDictionary(&inner_object))
+ continue;
+
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(signature, it.key());
+
+ TranslateONCHierarchy(*field_signature->value_signature, *inner_object,
+ shill_dictionary);
+ }
+}
+
+} // namespace
+
+scoped_ptr<base::DictionaryValue> TranslateONCObjectToShill(
+ const OncValueSignature* onc_signature,
+ const base::DictionaryValue& onc_object) {
+ CHECK(onc_signature != NULL);
+ scoped_ptr<base::DictionaryValue> shill_dictionary(new base::DictionaryValue);
+ TranslateONCHierarchy(*onc_signature, onc_object, shill_dictionary.get());
+ return shill_dictionary.Pass();
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_translator_shill_to_onc.cc b/chromeos/network/onc/onc_translator_shill_to_onc.cc
new file mode 100644
index 0000000..c33e0a9
--- /dev/null
+++ b/chromeos/network/onc/onc_translator_shill_to_onc.cc
@@ -0,0 +1,231 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_translator.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_translation_tables.h"
+#include "third_party/cros_system_api/dbus/service_constants.h"
+
+namespace chromeos {
+namespace onc {
+
+namespace {
+
+// Converts |str| to a base::Value of the given |type|. If the conversion fails,
+// returns NULL.
+scoped_ptr<base::Value> ConvertStringToValue(const std::string& str,
+ base::Value::Type type) {
+ base::Value* value;
+ if (type == base::Value::TYPE_STRING)
+ value = base::Value::CreateStringValue(str);
+ else
+ value = base::JSONReader::Read(str);
+
+ if (value == NULL || value->GetType() != type) {
+ delete value;
+ value = NULL;
+ }
+ return make_scoped_ptr(value);
+}
+
+// This class implements the translation of properties from the given
+// |shill_dictionary| to a new ONC object of signature |onc_signature|. Using
+// recursive calls to TranslateShillServiceToONCPart, nested objects are
+// translated.
+class ShillToONCTranslator {
+ public:
+ ShillToONCTranslator(const base::DictionaryValue& shill_dictionary,
+ const OncValueSignature& onc_signature)
+ : shill_dictionary_(&shill_dictionary),
+ onc_signature_(&onc_signature) {
+ }
+
+ // Translates the associated Shill dictionary and creates an ONC object of the
+ // given signature.
+ scoped_ptr<base::DictionaryValue> CreateTranslatedONCObject();
+
+ private:
+ void TranslateOpenVPN();
+ void TranslateVPN();
+ void TranslateNetworkConfiguration();
+
+ // Creates an ONC object from |shill_dictionary| according to the signature
+ // associated to |onc_field_name| and adds it to |onc_object_| at
+ // |onc_field_name|.
+ void TranslateAndAddNestedObject(const std::string& onc_field_name);
+
+ // Copies all entries from |shill_dictionary_| to |onc_object_| for which a
+ // translation (shill_property_name) is defined by |onc_signature_|.
+ void CopyPropertiesAccordingToSignature();
+
+ // If existent, translates the entry at |shill_property_name| in
+ // |shill_dictionary_| using |table|. It is an error if no matching table
+ // entry is found. Writes the result as entry at |onc_field_name| in
+ // |onc_object_|.
+ void TranslateWithTableAndSet(const std::string& shill_property_name,
+ const StringTranslationEntry table[],
+ const std::string& onc_field_name);
+
+ const base::DictionaryValue* shill_dictionary_;
+ const OncValueSignature* onc_signature_;
+ scoped_ptr<base::DictionaryValue> onc_object_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShillToONCTranslator);
+};
+
+scoped_ptr<base::DictionaryValue>
+ShillToONCTranslator::CreateTranslatedONCObject() {
+ onc_object_.reset(new base::DictionaryValue);
+ if (onc_signature_ == &kNetworkConfigurationSignature)
+ TranslateNetworkConfiguration();
+ else if (onc_signature_ == &kVPNSignature)
+ TranslateVPN();
+ else if (onc_signature_ == &kOpenVPNSignature)
+ TranslateOpenVPN();
+ else
+ CopyPropertiesAccordingToSignature();
+ return onc_object_.Pass();
+}
+
+void ShillToONCTranslator::TranslateOpenVPN() {
+ // Shill supports only one RemoteCertKU but ONC requires a list. If existing,
+ // wraps the value into a list.
+ std::string certKU;
+ if (shill_dictionary_->GetStringWithoutPathExpansion(
+ flimflam::kOpenVPNRemoteCertKUProperty, &certKU)) {
+ scoped_ptr<base::ListValue> certKUs(new base::ListValue);
+ certKUs->AppendString(certKU);
+ onc_object_->SetWithoutPathExpansion(vpn::kRemoteCertKU, certKUs.release());
+ }
+
+ for (const OncFieldSignature* field_signature = onc_signature_->fields;
+ field_signature->onc_field_name != NULL; ++field_signature) {
+ const base::Value* shill_value;
+ if (field_signature->shill_property_name == NULL ||
+ !shill_dictionary_->GetWithoutPathExpansion(
+ field_signature->shill_property_name, &shill_value)) {
+ continue;
+ }
+
+ scoped_ptr<base::Value> translated;
+ std::string shill_str;
+ const std::string& onc_field_name = field_signature->onc_field_name;
+ if (onc_field_name == vpn::kSaveCredentials ||
+ onc_field_name == vpn::kRemoteCertKU) {
+ translated.reset(shill_value->DeepCopy());
+ } else if (shill_value->GetAsString(&shill_str)) {
+ // Shill wants all Provider/VPN fields to be strings. Translates these
+ // strings back to the correct ONC type.
+ translated = ConvertStringToValue(
+ shill_str,
+ field_signature->value_signature->onc_type);
+
+ if (translated.get() == NULL) {
+ LOG(ERROR) << "Shill property '" << field_signature->shill_property_name
+ << "' with value '" << shill_value
+ << "' couldn't be converted to base::Value::Type "
+ << field_signature->value_signature->onc_type;
+ }
+ } else {
+ LOG(ERROR) << "Shill property '" << field_signature->shill_property_name
+ << "' has value '" << shill_value
+ << "', but expected a string";
+ }
+ onc_object_->SetWithoutPathExpansion(onc_field_name, translated.release());
+ }
+}
+
+void ShillToONCTranslator::TranslateVPN() {
+ TranslateWithTableAndSet(flimflam::kProviderTypeProperty, kVPNTypeTable,
+ kType);
+ CopyPropertiesAccordingToSignature();
+
+ std::string vpn_type;
+ if (onc_object_->GetStringWithoutPathExpansion(kType, &vpn_type)) {
+ if (vpn_type == vpn::kTypeL2TP_IPsec) {
+ TranslateAndAddNestedObject(vpn::kIPsec);
+ TranslateAndAddNestedObject(vpn::kL2TP);
+ } else {
+ TranslateAndAddNestedObject(vpn_type);
+ }
+ }
+}
+
+void ShillToONCTranslator::TranslateAndAddNestedObject(
+ const std::string& onc_field_name) {
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(*onc_signature_, onc_field_name);
+ ShillToONCTranslator nested_translator(*shill_dictionary_,
+ *field_signature->value_signature);
+ scoped_ptr<base::DictionaryValue> nested_object =
+ nested_translator.CreateTranslatedONCObject();
+ onc_object_->SetWithoutPathExpansion(onc_field_name, nested_object.release());
+}
+
+void ShillToONCTranslator::TranslateNetworkConfiguration() {
+ TranslateWithTableAndSet(flimflam::kTypeProperty, kNetworkTypeTable, kType);
+ CopyPropertiesAccordingToSignature();
+
+ std::string network_type;
+ if (onc_object_->GetStringWithoutPathExpansion(kType, &network_type))
+ TranslateAndAddNestedObject(network_type);
+}
+
+void ShillToONCTranslator::CopyPropertiesAccordingToSignature() {
+ for (const OncFieldSignature* field_signature = onc_signature_->fields;
+ field_signature->onc_field_name != NULL; ++field_signature) {
+ const base::Value* shill_value;
+ if (field_signature->shill_property_name == NULL ||
+ !shill_dictionary_->GetWithoutPathExpansion(
+ field_signature->shill_property_name, &shill_value)) {
+ continue;
+ }
+ onc_object_->SetWithoutPathExpansion(
+ field_signature->onc_field_name, shill_value->DeepCopy());
+ }
+}
+
+void ShillToONCTranslator::TranslateWithTableAndSet(
+ const std::string& shill_property_name,
+ const StringTranslationEntry table[],
+ const std::string& onc_field_name) {
+ std::string shill_value;
+ if (!shill_dictionary_->GetStringWithoutPathExpansion(shill_property_name,
+ &shill_value)) {
+ return;
+ }
+
+ for (int i = 0; table[i].onc_value != NULL; ++i) {
+ if (shill_value != table[i].shill_value)
+ continue;
+ onc_object_->SetStringWithoutPathExpansion(onc_field_name,
+ table[i].onc_value);
+ return;
+ }
+ LOG(ERROR) << "Shill property '" << shill_property_name << "' with value '"
+ << shill_value << "' couldn't be translated to ONC";
+}
+
+} // namespace
+
+scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart(
+ const base::DictionaryValue& shill_dictionary,
+ const OncValueSignature* onc_signature) {
+ CHECK(onc_signature != NULL);
+
+ ShillToONCTranslator translator(shill_dictionary, *onc_signature);
+ return translator.CreateTranslatedONCObject();
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_translator_unittest.cc b/chromeos/network/onc/onc_translator_unittest.cc
new file mode 100644
index 0000000..a9b8fb5
--- /dev/null
+++ b/chromeos/network/onc/onc_translator_unittest.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_translator.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+
+// First parameter: Filename of source ONC.
+// Second parameter: Filename of expected translated Shill json.
+class ONCTranslatorOncToShillTest
+ : public ::testing::TestWithParam<std::pair<std::string, std::string> > {
+};
+
+// Test the translation from ONC to Shill json.
+TEST_P(ONCTranslatorOncToShillTest, Translate) {
+ std::string source_onc_filename = GetParam().first;
+ scoped_ptr<const base::DictionaryValue> onc_network(
+ test_utils::ReadTestDictionary(source_onc_filename));
+ std::string result_json_filename = GetParam().second;
+ scoped_ptr<const base::DictionaryValue> shill_network(
+ test_utils::ReadTestDictionary(result_json_filename));
+
+ scoped_ptr<base::DictionaryValue> translation(TranslateONCObjectToShill(
+ &kNetworkConfigurationSignature, *onc_network));
+
+ EXPECT_TRUE(test_utils::Equals(shill_network.get(), translation.get()));
+}
+
+// Test different network types, such that each ONC object type is tested at
+// least once.
+INSTANTIATE_TEST_CASE_P(
+ ONCTranslatorOncToShillTest,
+ ONCTranslatorOncToShillTest,
+ ::testing::Values(
+ std::make_pair("valid.onc", "shill_ethernet.json"),
+ std::make_pair("valid_l2tpipsec.onc", "shill_l2tpipsec.json"),
+ std::make_pair("valid_openvpn.onc", "shill_openvpn.json")));
+
+// Test the translation from Shill json to ONC.
+//
+// Note: This translation direction doesn't have to reconstruct all of the ONC
+// fields, as Chrome doesn't need all of a Service's properties.
+TEST(ONCTranslatorShillToOncTest, L2TPIPsec) {
+ scoped_ptr<base::DictionaryValue> onc_network(
+ test_utils::ReadTestDictionary("valid_l2tpipsec.onc"));
+
+ // These two fields are part of the ONC (and are required). However, they
+ // don't exist explicitly in the Shill dictionary. As there is no use-case
+ // yet, that requires to reconstruct these fields from a Shill dictionary, we
+ // don't require their translation.
+ onc_network->Remove("VPN.IPsec.AuthenticationType", NULL);
+ onc_network->Remove("VPN.IPsec.IKEVersion", NULL);
+
+ scoped_ptr<const base::DictionaryValue> shill_network(
+ test_utils::ReadTestDictionary("shill_l2tpipsec.json"));
+
+ scoped_ptr<base::DictionaryValue> translation(TranslateShillServiceToONCPart(
+ *shill_network, &kNetworkConfigurationSignature));
+
+ EXPECT_TRUE(test_utils::Equals(onc_network.get(), translation.get()));
+}
+
+TEST(ONCTranslatorShillToOncTest, OpenVPN) {
+ scoped_ptr<const base::DictionaryValue> onc_network(
+ test_utils::ReadTestDictionary("valid_openvpn.onc"));
+
+ scoped_ptr<const base::DictionaryValue> shill_network(
+ test_utils::ReadTestDictionary("shill_openvpn.json"));
+
+ scoped_ptr<base::DictionaryValue> translation(TranslateShillServiceToONCPart(
+ *shill_network, &kNetworkConfigurationSignature));
+
+ EXPECT_TRUE(test_utils::Equals(onc_network.get(), translation.get()));
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_utils.cc b/chromeos/network/onc/onc_utils.cc
new file mode 100644
index 0000000..62e0d46
--- /dev/null
+++ b/chromeos/network/onc/onc_utils.cc
@@ -0,0 +1,164 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_utils.h"
+
+#include "base/base64.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "chromeos/network/network_event_log.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "crypto/encryptor.h"
+#include "crypto/hmac.h"
+#include "crypto/symmetric_key.h"
+
+#define ONC_LOG_WARNING(message) NET_LOG_WARNING("ONC", message)
+#define ONC_LOG_ERROR(message) NET_LOG_ERROR("ONC", message)
+
+namespace chromeos {
+namespace onc {
+
+namespace {
+
+const char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC";
+const char kUnableToDecode[] = "Unable to decode encrypted ONC";
+
+} // namespace
+
+scoped_ptr<base::DictionaryValue> ReadDictionaryFromJson(
+ const std::string& json) {
+ std::string error;
+ base::Value* root = base::JSONReader::ReadAndReturnError(
+ json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, &error);
+
+ base::DictionaryValue* dict_ptr = NULL;
+ if (!root || !root->GetAsDictionary(&dict_ptr)) {
+ ONC_LOG_ERROR("Invalid JSON Dictionary: " + error);
+ delete root;
+ }
+
+ return make_scoped_ptr(dict_ptr);
+}
+
+scoped_ptr<base::DictionaryValue> Decrypt(const std::string& passphrase,
+ const base::DictionaryValue& root) {
+ const int kKeySizeInBits = 256;
+ const int kMaxIterationCount = 500000;
+ std::string onc_type;
+ std::string initial_vector;
+ std::string salt;
+ std::string cipher;
+ std::string stretch_method;
+ std::string hmac_method;
+ std::string hmac;
+ int iterations;
+ std::string ciphertext;
+
+ if (!root.GetString(encrypted::kCiphertext, &ciphertext) ||
+ !root.GetString(encrypted::kCipher, &cipher) ||
+ !root.GetString(encrypted::kHMAC, &hmac) ||
+ !root.GetString(encrypted::kHMACMethod, &hmac_method) ||
+ !root.GetString(encrypted::kIV, &initial_vector) ||
+ !root.GetInteger(encrypted::kIterations, &iterations) ||
+ !root.GetString(encrypted::kSalt, &salt) ||
+ !root.GetString(encrypted::kStretch, &stretch_method) ||
+ !root.GetString(encrypted::kType, &onc_type) ||
+ onc_type != kEncryptedConfiguration) {
+
+ ONC_LOG_ERROR("Encrypted ONC malformed.");
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ if (hmac_method != encrypted::kSHA1 ||
+ cipher != encrypted::kAES256 ||
+ stretch_method != encrypted::kPBKDF2) {
+ ONC_LOG_ERROR("Encrypted ONC unsupported encryption scheme.");
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ // Make sure iterations != 0, since that's not valid.
+ if (iterations == 0) {
+ ONC_LOG_ERROR(kUnableToDecrypt);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ // Simply a sanity check to make sure we can't lock up the machine
+ // for too long with a huge number (or a negative number).
+ if (iterations < 0 || iterations > kMaxIterationCount) {
+ ONC_LOG_ERROR("Too many iterations in encrypted ONC");
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ if (!base::Base64Decode(salt, &salt)) {
+ ONC_LOG_ERROR(kUnableToDecode);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ scoped_ptr<crypto::SymmetricKey> key(
+ crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES,
+ passphrase,
+ salt,
+ iterations,
+ kKeySizeInBits));
+
+ if (!base::Base64Decode(initial_vector, &initial_vector)) {
+ ONC_LOG_ERROR(kUnableToDecode);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+ if (!base::Base64Decode(ciphertext, &ciphertext)) {
+ ONC_LOG_ERROR(kUnableToDecode);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+ if (!base::Base64Decode(hmac, &hmac)) {
+ ONC_LOG_ERROR(kUnableToDecode);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ crypto::HMAC hmac_verifier(crypto::HMAC::SHA1);
+ if (!hmac_verifier.Init(key.get()) ||
+ !hmac_verifier.Verify(ciphertext, hmac)) {
+ ONC_LOG_ERROR(kUnableToDecrypt);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ crypto::Encryptor decryptor;
+ if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector)) {
+ ONC_LOG_ERROR(kUnableToDecrypt);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ std::string plaintext;
+ if (!decryptor.Decrypt(ciphertext, &plaintext)) {
+ ONC_LOG_ERROR(kUnableToDecrypt);
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ scoped_ptr<base::DictionaryValue> new_root =
+ ReadDictionaryFromJson(plaintext);
+ if (new_root.get() == NULL) {
+ ONC_LOG_ERROR("Property dictionary malformed.");
+ return scoped_ptr<base::DictionaryValue>();
+ }
+
+ return new_root.Pass();
+}
+
+std::string GetSourceAsString(ONCSource source) {
+ switch (source) {
+ case ONC_SOURCE_DEVICE_POLICY:
+ return "device policy";
+ case ONC_SOURCE_USER_POLICY:
+ return "user policy";
+ case ONC_SOURCE_NONE:
+ return "none";
+ case ONC_SOURCE_USER_IMPORT:
+ return "user import";
+ }
+ NOTREACHED() << "unknown ONC source " << source;
+ return "unknown";
+}
+
+} // chromeos
+} // onc
diff --git a/chromeos/network/onc/onc_utils.h b/chromeos/network/onc/onc_utils.h
new file mode 100644
index 0000000..96b8a1e
--- /dev/null
+++ b/chromeos/network/onc/onc_utils.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_UTILS_H_
+#define CHROMEOS_NETWORK_ONC_ONC_UTILS_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+#include "chromeos/network/onc/onc_constants.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace chromeos {
+namespace onc {
+
+// Parses |json| according to the JSON format. If |json| is a JSON formatted
+// dictionary, the function returns the dictionary as a DictionaryValue.
+// Otherwise returns NULL.
+CHROMEOS_EXPORT scoped_ptr<base::DictionaryValue> ReadDictionaryFromJson(
+ const std::string& json);
+
+// Decrypt the given EncryptedConfiguration |onc| (see the ONC specification)
+// using |passphrase|. The resulting UnencryptedConfiguration is returned. If an
+// error occurs, returns NULL.
+CHROMEOS_EXPORT scoped_ptr<base::DictionaryValue> Decrypt(
+ const std::string& passphrase,
+ const base::DictionaryValue& onc);
+
+// For logging only: strings not user facing.
+CHROMEOS_EXPORT std::string GetSourceAsString(ONCSource source);
+
+} // chromeos
+} // onc
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_UTILS_H_
diff --git a/chromeos/network/onc/onc_utils_unittest.cc b/chromeos/network/onc/onc_utils_unittest.cc
new file mode 100644
index 0000000..93ed390
--- /dev/null
+++ b/chromeos/network/onc/onc_utils_unittest.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_utils.h"
+
+#include <string>
+
+#include "base/values.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+
+TEST(ONCDecrypterTest, BrokenEncryptionIterations) {
+ scoped_ptr<base::DictionaryValue> encrypted_onc =
+ test_utils::ReadTestDictionary("broken-encrypted-iterations.onc");
+
+ scoped_ptr<base::DictionaryValue> decrypted_onc =
+ Decrypt("test0000", *encrypted_onc);
+
+ EXPECT_EQ(NULL, decrypted_onc.get());
+}
+
+TEST(ONCDecrypterTest, BrokenEncryptionZeroIterations) {
+ scoped_ptr<base::DictionaryValue> encrypted_onc =
+ test_utils::ReadTestDictionary("broken-encrypted-zero-iterations.onc");
+
+ std::string error;
+ scoped_ptr<base::DictionaryValue> decrypted_onc =
+ Decrypt("test0000", *encrypted_onc);
+
+ EXPECT_EQ(NULL, decrypted_onc.get());
+}
+
+TEST(ONCDecrypterTest, LoadEncryptedOnc) {
+ scoped_ptr<base::DictionaryValue> encrypted_onc =
+ test_utils::ReadTestDictionary("encrypted.onc");
+ scoped_ptr<base::DictionaryValue> expected_decrypted_onc =
+ test_utils::ReadTestDictionary("decrypted.onc");
+
+ std::string error;
+ scoped_ptr<base::DictionaryValue> actual_decrypted_onc =
+ Decrypt("test0000", *encrypted_onc);
+
+ base::DictionaryValue emptyDict;
+ EXPECT_TRUE(test_utils::Equals(expected_decrypted_onc.get(),
+ actual_decrypted_onc.get()));
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_validator.cc b/chromeos/network/onc/onc_validator.cc
new file mode 100644
index 0000000..684eca3
--- /dev/null
+++ b/chromeos/network/onc/onc_validator.cc
@@ -0,0 +1,562 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_validator.h"
+
+#include <algorithm>
+#include <string>
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+
+namespace chromeos {
+namespace onc {
+
+Validator::Validator(
+ bool error_on_unknown_field,
+ bool error_on_wrong_recommended,
+ bool error_on_missing_field,
+ bool managed_onc)
+ : error_on_unknown_field_(error_on_unknown_field),
+ error_on_wrong_recommended_(error_on_wrong_recommended),
+ error_on_missing_field_(error_on_missing_field),
+ managed_onc_(managed_onc) {
+}
+
+Validator::~Validator() {
+}
+
+scoped_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject(
+ const OncValueSignature* object_signature,
+ const base::DictionaryValue& onc_object) {
+ CHECK(object_signature != NULL);
+ scoped_ptr<base::Value> result_value =
+ MapValue(*object_signature, onc_object);
+ base::DictionaryValue* result_dict = NULL;
+ if (result_value.get() != NULL) {
+ result_value.release()->GetAsDictionary(&result_dict);
+ CHECK(result_dict != NULL);
+ }
+
+ return make_scoped_ptr(result_dict);
+}
+
+scoped_ptr<base::Value> Validator::MapValue(
+ const OncValueSignature& signature,
+ const base::Value& onc_value) {
+ if (onc_value.GetType() != signature.onc_type) {
+ DVLOG(1) << "Wrong type. Expected " << signature.onc_type
+ << ", but found " << onc_value.GetType();
+ return scoped_ptr<base::Value>();
+ }
+
+ scoped_ptr<base::Value> repaired = Mapper::MapValue(signature, onc_value);
+ if (repaired.get() != NULL)
+ CHECK_EQ(repaired->GetType(), signature.onc_type);
+ return repaired.Pass();
+}
+
+scoped_ptr<base::DictionaryValue> Validator::MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object) {
+ scoped_ptr<base::DictionaryValue> repaired(new base::DictionaryValue);
+
+ bool valid;
+ if (&signature == &kNetworkConfigurationSignature)
+ valid = ValidateNetworkConfiguration(onc_object, repaired.get());
+ else if (&signature == &kEthernetSignature)
+ valid = ValidateEthernet(onc_object, repaired.get());
+ else if (&signature == &kIPConfigSignature)
+ valid = ValidateIPConfig(onc_object, repaired.get());
+ else if (&signature == &kWiFiSignature)
+ valid = ValidateWiFi(onc_object, repaired.get());
+ else if (&signature == &kVPNSignature)
+ valid = ValidateVPN(onc_object, repaired.get());
+ else if (&signature == &kIPsecSignature)
+ valid = ValidateIPsec(onc_object, repaired.get());
+ else if (&signature == &kOpenVPNSignature)
+ valid = ValidateOpenVPN(onc_object, repaired.get());
+ else if (&signature == &kCertificatePatternSignature)
+ valid = ValidateCertificatePattern(onc_object, repaired.get());
+ else if (&signature == &kProxySettingsSignature)
+ valid = ValidateProxySettings(onc_object, repaired.get());
+ else if (&signature == &kProxyLocationSignature)
+ valid = ValidateProxyLocation(onc_object, repaired.get());
+ else if (&signature == &kEAPSignature)
+ valid = ValidateEAP(onc_object, repaired.get());
+ else if (&signature == &kCertificateSignature)
+ valid = ValidateCertificate(onc_object, repaired.get());
+ else
+ valid = ValidateObjectDefault(signature, onc_object, repaired.get());
+
+ if (valid)
+ return repaired.Pass();
+ else
+ return scoped_ptr<base::DictionaryValue>();
+}
+
+bool Validator::ValidateObjectDefault(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ bool found_unknown_field = false;
+ bool nested_error_occured = false;
+ MapFields(signature, onc_object, &found_unknown_field, &nested_error_occured,
+ result);
+ if (nested_error_occured)
+ return false;
+
+ if (found_unknown_field) {
+ if (error_on_unknown_field_) {
+ DVLOG(1) << "Unknown field name. Aborting.";
+ return false;
+ }
+ DVLOG(1) << "Unknown field name. Ignoring.";
+ }
+
+ return ValidateRecommendedField(signature, result);
+}
+
+bool Validator::ValidateRecommendedField(
+ const OncValueSignature& object_signature,
+ base::DictionaryValue* result) {
+ CHECK(result != NULL);
+
+ scoped_ptr<base::ListValue> recommended;
+ base::Value* recommended_value;
+ // This remove passes ownership to |recommended_value|.
+ if (!result->RemoveWithoutPathExpansion(onc::kRecommended,
+ &recommended_value)) {
+ return true;
+ }
+ base::ListValue* recommended_list;
+ recommended_value->GetAsList(&recommended_list);
+ CHECK(recommended_list != NULL);
+
+ recommended.reset(recommended_list);
+
+ if (!managed_onc_) {
+ DVLOG(1) << "Found a " << onc::kRecommended
+ << " field in unmanaged ONC. Removing it.";
+ return true;
+ }
+
+ scoped_ptr<base::ListValue> repaired_recommended(new base::ListValue);
+ for (base::ListValue::iterator it = recommended->begin();
+ it != recommended->end(); ++it) {
+ std::string field_name;
+ if (!(*it)->GetAsString(&field_name)) {
+ NOTREACHED();
+ continue;
+ }
+
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(object_signature, field_name);
+
+ bool found_error = false;
+ std::string error_cause;
+ if (field_signature == NULL) {
+ found_error = true;
+ error_cause = "unknown";
+ } else if (field_signature->value_signature->onc_type ==
+ base::Value::TYPE_DICTIONARY) {
+ found_error = true;
+ error_cause = "dictionary-typed";
+ }
+
+ if (found_error) {
+ DVLOG(1) << "Found " << error_cause << " field name '" << field_name
+ << "' in kRecommended array. "
+ << (error_on_wrong_recommended_ ? "Aborting." : "Ignoring.");
+ if (error_on_wrong_recommended_)
+ return false;
+ else
+ continue;
+ }
+
+ repaired_recommended->Append((*it)->DeepCopy());
+ }
+
+ result->Set(onc::kRecommended, repaired_recommended.release());
+ return true;
+}
+
+namespace {
+
+std::string JoinStringRange(const char** range_begin,
+ const char** range_end,
+ const std::string& separator) {
+ std::vector<std::string> string_vector;
+ std::copy(range_begin, range_end, std::back_inserter(string_vector));
+ return JoinString(string_vector, separator);
+}
+
+bool RequireAnyOf(const std::string &actual, const char** valid_values) {
+ const char** it = valid_values;
+ for (; *it != NULL; ++it) {
+ if (actual == *it)
+ return true;
+ }
+ DVLOG(1) << "Found " << actual << ", but expected one of "
+ << JoinStringRange(valid_values, it, ", ");
+ return false;
+}
+
+bool IsInRange(int actual, int lower_bound, int upper_bound) {
+ if (lower_bound <= actual && actual <= upper_bound)
+ return true;
+ DVLOG(1) << "Found " << actual << ", which is out of range [" << lower_bound
+ << ", " << upper_bound << "]";
+ return false;
+}
+
+bool RequireField(const base::DictionaryValue& dict, std::string key) {
+ if (dict.HasKey(key))
+ return true;
+ DVLOG(1) << "Required field " << key << " missing.";
+ return false;
+}
+
+} // namespace
+
+bool Validator::ValidateNetworkConfiguration(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ if (!ValidateObjectDefault(kNetworkConfigurationSignature,
+ onc_object, result)) {
+ return false;
+ }
+
+ std::string type;
+ static const char* kValidTypes[] = { kEthernet, kVPN, kWiFi, NULL };
+ if (result->GetStringWithoutPathExpansion(kType, &type) &&
+ !RequireAnyOf(type, kValidTypes)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kGUID);
+
+ bool remove = false;
+ result->GetBooleanWithoutPathExpansion(kRemove, &remove);
+ if (!remove) {
+ allRequiredExist &= RequireField(*result, kName);
+ allRequiredExist &= RequireField(*result, kType);
+ allRequiredExist &= type.empty() || RequireField(*result, type);
+ }
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateEthernet(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::ethernet;
+ if (!ValidateObjectDefault(kEthernetSignature, onc_object, result))
+ return false;
+
+ std::string auth;
+ static const char* kValidAuthentications[] = { kNone, k8021X, NULL };
+ if (result->GetStringWithoutPathExpansion(kAuthentication, &auth) &&
+ !RequireAnyOf(auth, kValidAuthentications)) {
+ return false;
+ }
+
+ bool allRequiredExist = true;
+ if (auth == k8021X)
+ allRequiredExist &= RequireField(*result, kEAP);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateIPConfig(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::ipconfig;
+ if (!ValidateObjectDefault(kIPConfigSignature, onc_object, result))
+ return false;
+
+ std::string type;
+ static const char* kValidTypes[] = { kIPv4, kIPv6, NULL };
+ if (result->GetStringWithoutPathExpansion(ipconfig::kType, &type) &&
+ !RequireAnyOf(type, kValidTypes)) {
+ return false;
+ }
+
+ int routing_prefix;
+ int lower_bound = 1;
+ // In case of missing type, choose higher upper_bound.
+ int upper_bound = (type == kIPv4) ? 32 : 128;
+ if (result->GetIntegerWithoutPathExpansion(kRoutingPrefix, &routing_prefix) &&
+ !IsInRange(routing_prefix, lower_bound, upper_bound)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kIPAddress) &
+ RequireField(*result, kRoutingPrefix) &
+ RequireField(*result, ipconfig::kType);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateWiFi(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::wifi;
+ if (!ValidateObjectDefault(kWiFiSignature, onc_object, result))
+ return false;
+
+ std::string security;
+ static const char* kValidSecurities[] =
+ { kNone, kWEP_PSK, kWEP_8021X, kWPA_PSK, kWPA_EAP, NULL };
+ if (result->GetStringWithoutPathExpansion(kSecurity, &security) &&
+ !RequireAnyOf(security, kValidSecurities)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kSecurity) &
+ RequireField(*result, kSSID);
+ if (security == kWEP_8021X || security == kWPA_EAP)
+ allRequiredExist &= RequireField(*result, kEAP);
+ else if (security == kWEP_PSK || security == kWPA_PSK)
+ allRequiredExist &= RequireField(*result, kPassphrase);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateVPN(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace vpn;
+ if (!ValidateObjectDefault(kVPNSignature, onc_object, result))
+ return false;
+
+ std::string type;
+ static const char* kValidTypes[] =
+ { kIPsec, kTypeL2TP_IPsec, kOpenVPN, NULL };
+ if (result->GetStringWithoutPathExpansion(vpn::kType, &type) &&
+ !RequireAnyOf(type, kValidTypes)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, vpn::kType);
+
+ if (type == kOpenVPN) {
+ allRequiredExist &= RequireField(*result, kOpenVPN);
+ } else if (type == kIPsec) {
+ allRequiredExist &= RequireField(*result, kIPsec);
+ } else if (type == kTypeL2TP_IPsec) {
+ allRequiredExist &= RequireField(*result, kIPsec) &
+ RequireField(*result, kL2TP);
+ }
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateIPsec(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::vpn;
+ using namespace onc::certificate;
+ if (!ValidateObjectDefault(kIPsecSignature, onc_object, result))
+ return false;
+
+ std::string auth;
+ static const char* kValidAuthentications[] = { kPSK, kCert, NULL };
+ if (result->GetStringWithoutPathExpansion(kAuthenticationType, &auth) &&
+ !RequireAnyOf(auth, kValidAuthentications)) {
+ return false;
+ }
+
+ std::string cert_type;
+ static const char* kValidCertTypes[] = { kRef, kPattern, NULL };
+ if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) &&
+ !RequireAnyOf(cert_type, kValidCertTypes)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kAuthenticationType) &
+ RequireField(*result, kIKEVersion);
+ if (auth == kCert) {
+ allRequiredExist &= RequireField(*result, kClientCertType) &
+ RequireField(*result, kServerCARef);
+ }
+ if (cert_type == kPattern)
+ allRequiredExist &= RequireField(*result, kClientCertPattern);
+ else if (cert_type == kRef)
+ allRequiredExist &= RequireField(*result, kClientCertRef);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateOpenVPN(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::vpn;
+ using namespace onc::openvpn;
+ using namespace onc::certificate;
+ if (!ValidateObjectDefault(kOpenVPNSignature, onc_object, result))
+ return false;
+
+ std::string auth_retry;
+ static const char* kValidAuthRetryValues[] =
+ { openvpn::kNone, kInteract, kNoInteract, NULL };
+ if (result->GetStringWithoutPathExpansion(kAuthRetry, &auth_retry) &&
+ !RequireAnyOf(auth_retry, kValidAuthRetryValues)) {
+ return false;
+ }
+
+ std::string cert_type;
+ static const char* kValidCertTypes[] =
+ { certificate::kNone, kRef, kPattern, NULL };
+ if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) &&
+ !RequireAnyOf(cert_type, kValidCertTypes)) {
+ return false;
+ }
+
+ std::string cert_tls;
+ static const char* kValidCertTlsValues[] =
+ { openvpn::kNone, openvpn::kServer, NULL };
+ if (result->GetStringWithoutPathExpansion(kRemoteCertTLS, &cert_tls) &&
+ !RequireAnyOf(cert_tls, kValidCertTlsValues)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kClientCertType);
+ if (cert_type == kPattern)
+ allRequiredExist &= RequireField(*result, kClientCertPattern);
+ else if (cert_type == kRef)
+ allRequiredExist &= RequireField(*result, kClientCertRef);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateCertificatePattern(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::certificate;
+ if (!ValidateObjectDefault(kCertificatePatternSignature, onc_object, result))
+ return false;
+
+ bool allRequiredExist = true;
+ if (!result->HasKey(kSubject) && !result->HasKey(kIssuer) &&
+ !result->HasKey(kIssuerCARef)) {
+ allRequiredExist = false;
+ DVLOG(1) << "None of the fields " << kSubject << ", " << kIssuer << ", and "
+ << kIssuerCARef << " exists, but at least one is required.";
+ }
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateProxySettings(const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::proxy;
+ if (!ValidateObjectDefault(kProxySettingsSignature, onc_object, result))
+ return false;
+
+ std::string type;
+ static const char* kValidTypes[] = { kDirect, kManual, kPAC, kWPAD, NULL };
+ if (result->GetStringWithoutPathExpansion(proxy::kType, &type) &&
+ !RequireAnyOf(type, kValidTypes)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, proxy::kType);
+
+ if (type == kManual)
+ allRequiredExist &= RequireField(*result, kManual);
+ else if (type == kPAC)
+ allRequiredExist &= RequireField(*result, kPAC);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateProxyLocation(const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::proxy;
+ if (!ValidateObjectDefault(kProxyLocationSignature, onc_object, result))
+ return false;
+
+ bool allRequiredExist = RequireField(*result, kHost) &
+ RequireField(*result, kPort);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateEAP(const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::eap;
+ using namespace onc::certificate;
+ if (!ValidateObjectDefault(kEAPSignature, onc_object, result))
+ return false;
+
+ std::string inner;
+ static const char* kValidInnerValues[] =
+ { kAutomatic, kMD5, kMSCHAPv2, kPAP, NULL };
+ if (result->GetStringWithoutPathExpansion(kInner, &inner) &&
+ !RequireAnyOf(inner, kValidInnerValues)) {
+ return false;
+ }
+
+ std::string outer;
+ static const char* kValidOuterValues[] =
+ { kPEAP, kEAP_TLS, kEAP_TTLS, kLEAP, kEAP_SIM, kEAP_FAST, kEAP_AKA,
+ NULL };
+ if (result->GetStringWithoutPathExpansion(kOuter, &outer) &&
+ !RequireAnyOf(outer, kValidOuterValues)) {
+ return false;
+ }
+
+ std::string cert_type;
+ static const char* kValidCertTypes[] = { kRef, kPattern, NULL };
+ if (result->GetStringWithoutPathExpansion(kClientCertType, &cert_type) &&
+ !RequireAnyOf(cert_type, kValidCertTypes )) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kOuter);
+
+ if (cert_type == kPattern)
+ allRequiredExist &= RequireField(*result, kClientCertPattern);
+ else if (cert_type == kRef)
+ allRequiredExist &= RequireField(*result, kClientCertRef);
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+bool Validator::ValidateCertificate(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result) {
+ using namespace onc::certificate;
+ if (!ValidateObjectDefault(kCertificateSignature, onc_object, result))
+ return false;
+
+ std::string type;
+ static const char* kValidTypes[] = { kClient, kServer, kAuthority, NULL };
+ if (result->GetStringWithoutPathExpansion(certificate::kType, &type) &&
+ !RequireAnyOf(type, kValidTypes)) {
+ return false;
+ }
+
+ bool allRequiredExist = RequireField(*result, kGUID);
+
+ bool remove = false;
+ result->GetBooleanWithoutPathExpansion(kRemove, &remove);
+ if (!remove) {
+ allRequiredExist &= RequireField(*result, certificate::kType);
+
+ if (type == kClient)
+ allRequiredExist &= RequireField(*result, kPKCS12);
+ else if (type == kServer || type == kAuthority)
+ allRequiredExist &= RequireField(*result, kX509);
+ }
+
+ return !error_on_missing_field_ || allRequiredExist;
+}
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/network/onc/onc_validator.h b/chromeos/network/onc/onc_validator.h
new file mode 100644
index 0000000..4a74272
--- /dev/null
+++ b/chromeos/network/onc/onc_validator.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_NETWORK_ONC_ONC_VALIDATOR_H_
+#define CHROMEOS_NETWORK_ONC_ONC_VALIDATOR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "chromeos/chromeos_export.h"
+#include "chromeos/network/onc/onc_mapper.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}
+
+namespace chromeos {
+namespace onc {
+
+struct OncValueSignature;
+
+class CHROMEOS_EXPORT Validator : public Mapper {
+ public:
+ // Creates a Validator that searches for the following invalid cases:
+ // - a field name is found that is not part of the signature
+ // (controlled by |error_on_unknown_field|)
+ //
+ // - a kRecommended array contains a field name that is not part of the
+ // enclosing object's signature or if that field is dictionary typed
+ // (controlled by |error_on_wrong_recommended|)
+ //
+ // - |managed_onc| is false and a field with name kRecommended is found
+ // (always ignored)
+ //
+ // - a required field is missing (controlled by |error_on_missing_field|)
+ //
+ // If one of these invalid cases occurs and the controlling flag is true, then
+ // it is an error and the validation stops. The function
+ // ValidateAndRepairObject returns NULL.
+ //
+ // If no error occurred, then a DeepCopy of the validated object is created,
+ // which contains all but the invalid fields and values.
+ Validator(bool error_on_unknown_field,
+ bool error_on_wrong_recommended,
+ bool error_on_missing_field,
+ bool managed_onc);
+
+ virtual ~Validator();
+
+ // Validate the given |onc_object| according to |object_signature|. The
+ // |object_signature| has to be a pointer to one of the signatures in
+ // |onc_signature.h|. If an error is found, the function returns NULL. If
+ // possible (no error encountered) a DeepCopy is created that contains all but
+ // the invalid fields and values and returns this "repaired" object.
+ // That means, if not handled as an error, then the following are ignored:
+ // - unknown fields
+ // - invalid field names in kRecommended arrays
+ // - kRecommended fields in an unmanaged ONC
+ // For details, see the comment at the Constructor.
+ scoped_ptr<base::DictionaryValue> ValidateAndRepairObject(
+ const OncValueSignature* object_signature,
+ const base::DictionaryValue& onc_object);
+
+ private:
+ // Overriden from Mapper:
+ // Compare |onc_value|s type with |onc_type| and validate/repair according to
+ // |signature|. On error returns NULL.
+ virtual scoped_ptr<base::Value> MapValue(
+ const OncValueSignature& signature,
+ const base::Value& onc_value) OVERRIDE;
+
+ // Dispatch to the right validation function according to
+ // |signature|. Iterates over all fields and recursively validates/repairs
+ // these. All valid fields are added to the result dictionary. Returns the
+ // repaired dictionary. On error returns NULL.
+ virtual scoped_ptr<base::DictionaryValue> MapObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object) OVERRIDE;
+
+ // This is the default validation of objects/dictionaries. Validates
+ // |onc_object| according to |object_signature|. |result| must point to a
+ // dictionary into which the repaired fields are written.
+ bool ValidateObjectDefault(
+ const OncValueSignature& object_signature,
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ // Validates/repairs the kRecommended array in |result| according to
+ // |object_signature| of the enclosing object.
+ bool ValidateRecommendedField(
+ const OncValueSignature& object_signature,
+ base::DictionaryValue* result);
+
+ bool ValidateNetworkConfiguration(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateEthernet(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateIPConfig(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateWiFi(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateVPN(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateIPsec(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateOpenVPN(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateCertificatePattern(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateProxySettings(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateProxyLocation(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateEAP(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ bool ValidateCertificate(
+ const base::DictionaryValue& onc_object,
+ base::DictionaryValue* result);
+
+ const bool error_on_unknown_field_;
+ const bool error_on_wrong_recommended_;
+ const bool error_on_missing_field_;
+ const bool managed_onc_;
+
+ DISALLOW_COPY_AND_ASSIGN(Validator);
+};
+
+} // namespace onc
+} // namespace chromeos
+
+#endif // CHROMEOS_NETWORK_ONC_ONC_VALIDATOR_H_
diff --git a/chromeos/network/onc/onc_validator_unittest.cc b/chromeos/network/onc/onc_validator_unittest.cc
new file mode 100644
index 0000000..f02e9cf
--- /dev/null
+++ b/chromeos/network/onc/onc_validator_unittest.cc
@@ -0,0 +1,143 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/network/onc/onc_validator.h"
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chromeos/network/onc/onc_constants.h"
+#include "chromeos/network/onc/onc_signature.h"
+#include "chromeos/network/onc/onc_test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+namespace onc {
+namespace {
+
+// Create a strict validator that complains about every error.
+scoped_ptr<Validator> CreateStrictValidator(bool managed_onc) {
+ return make_scoped_ptr(new Validator(true, true, true, managed_onc));
+}
+
+// Create a liberal validator that ignores or repairs non-critical errors.
+scoped_ptr<Validator> CreateLiberalValidator(bool managed_onc) {
+ return make_scoped_ptr(new Validator(false, false, false, managed_onc));
+}
+} // namespace
+
+// This test case is about validating valid ONC objects.
+TEST(ONCValidatorValidTest, ValidPolicyOnc) {
+ scoped_ptr<Validator> validator(CreateStrictValidator(true));
+ scoped_ptr<const base::DictionaryValue> network;
+ scoped_ptr<base::DictionaryValue> repaired;
+
+ network = test_utils::ReadTestDictionary("policy.onc");
+ repaired = validator->ValidateAndRepairObject(
+ &kNetworkConfigurationSignature,
+ *network);
+ EXPECT_TRUE(test_utils::Equals(network.get(), repaired.get()));
+
+ network = test_utils::ReadTestDictionary("valid.onc");
+ repaired = validator->ValidateAndRepairObject(
+ &kNetworkConfigurationSignature,
+ *network);
+ EXPECT_TRUE(test_utils::Equals(network.get(), repaired.get()));
+}
+
+// Validate invalid ONC objects and check the resulting repaired object. This
+// test fixture loads a test json file into |invalid_| containing several test
+// objects which can be accessed by their path. The test boolean parameter
+// determines wether to use the strict or the liberal validator.
+class ONCValidatorInvalidTest : public ::testing::TestWithParam<bool> {
+ public:
+ // Validate the entry at |path_to_object| with the given
+ // |signature|. Depending on |managed_onc| the object is interpreted as a
+ // managed onc (with recommended fields) or not. The resulting repaired object
+ // must match the entry at |path_to_repaired| if the liberal validator is
+ // used.
+ void ValidateInvalid(const std::string& path_to_object,
+ const std::string& path_to_repaired,
+ const OncValueSignature* signature,
+ bool managed_onc) {
+ scoped_ptr<Validator> validator;
+ if (GetParam())
+ validator = CreateStrictValidator(managed_onc);
+ else
+ validator = CreateLiberalValidator(managed_onc);
+
+ const base::DictionaryValue* object = NULL;
+ ASSERT_TRUE(invalid_->GetDictionary(path_to_object, &object));
+
+ scoped_ptr<base::DictionaryValue> actual_repaired =
+ validator->ValidateAndRepairObject(signature, *object);
+ if (GetParam() || path_to_repaired == "") {
+ EXPECT_EQ(NULL, actual_repaired.get());
+ } else {
+ const base::DictionaryValue* expected_repaired = NULL;
+ invalid_->GetDictionary(path_to_repaired, &expected_repaired);
+ EXPECT_TRUE(test_utils::Equals(expected_repaired, actual_repaired.get()));
+ }
+ }
+
+ virtual void SetUp() {
+ invalid_ =
+ test_utils::ReadTestDictionary("invalid_settings_with_repairs.json");
+ }
+
+ scoped_ptr<const base::DictionaryValue> invalid_;
+};
+
+TEST_P(ONCValidatorInvalidTest, UnknownFieldName) {
+ ValidateInvalid("network-unknown-fieldname",
+ "network-repaired",
+ &kNetworkConfigurationSignature, false);
+ ValidateInvalid("managed-network-unknown-fieldname",
+ "managed-network-repaired",
+ &kNetworkConfigurationSignature, true);
+}
+
+TEST_P(ONCValidatorInvalidTest, UnknownType) {
+ ValidateInvalid("network-unknown-type",
+ "",
+ &kNetworkConfigurationSignature, false);
+ ValidateInvalid("managed-network-unknown-type",
+ "",
+ &kNetworkConfigurationSignature, true);
+}
+
+TEST_P(ONCValidatorInvalidTest, UnknownRecommendedFieldName) {
+ ValidateInvalid("managed-network-unknown-recommended",
+ "managed-network-repaired",
+ &kNetworkConfigurationSignature, true);
+}
+
+TEST_P(ONCValidatorInvalidTest, DictionaryRecommended) {
+ ValidateInvalid("managed-network-dict-recommended",
+ "managed-network-repaired",
+ &kNetworkConfigurationSignature, true);
+}
+
+TEST_P(ONCValidatorInvalidTest, MissingRequiredField) {
+ ValidateInvalid("network-missing-required",
+ "network-missing-required",
+ &kNetworkConfigurationSignature, false);
+ ValidateInvalid("managed-network-missing-required",
+ "managed-network-missing-required",
+ &kNetworkConfigurationSignature, true);
+}
+
+TEST_P(ONCValidatorInvalidTest, RecommendedInUnmanaged) {
+ ValidateInvalid("network-illegal-recommended",
+ "network-repaired",
+ &kNetworkConfigurationSignature, false);
+}
+
+INSTANTIATE_TEST_CASE_P(ONCValidatorInvalidTest,
+ ONCValidatorInvalidTest,
+ ::testing::Bool());
+
+} // namespace onc
+} // namespace chromeos
diff --git a/chromeos/test/data/network/broken-encrypted-iterations.onc b/chromeos/test/data/network/broken-encrypted-iterations.onc
new file mode 100644
index 0000000..3bbbce4
--- /dev/null
+++ b/chromeos/test/data/network/broken-encrypted-iterations.onc
@@ -0,0 +1,11 @@
+{
+ "Cipher": "AES256",
+ "Ciphertext": "bnJmCTSw4SJJB3+3SnlAsd+7kmtDOjpxXfiXOK7XEhKh3uvJ3ntO5eleN+cvG2EAxubzkzp/y7siQKolLqFMvz63sYWEYYRY250j5b3Tjjaqk+PJidhyIwT9DiuSYTqPMip8cRBfQtj1dHNGADWcq7NwrmjW2EAL8/G+s8YC4w2Lnw3pzlNRKginUutRdJ4ShorXkFey6LDvS3A1WofGfr6C+//Da0aXYJiNaNm/qOxAuoxJBEXDNhgNuGrbCviZkJOAviYvYGFhWF2lZMvwwiWSjmhy8yzDefjyJxNl7XB9iZeqh1Lqh8Uh6/pVRpVq3fNUpRZwY2SdT9VvYVOtlesXrONKpTHKRXArzsjK2py5mux7DcmVRqj9XeDIiPH2aTHA51T+PPo851HktSrrmlwMUVwrMlH2BpkaibCo+AevekHWYYv0uMY8xLAYI4T5bQ3O9HwN3DmTzjjkcOKkTSyfq68bHUvLInHWoh+k3W/+9l3SuuCAelfWu0XyoWzRr+C66tCO5Ugn9hjMZIE4NQiEsPigg3RQh+JpT5wCBPxpsxGWi/GhxEadb1ZF4CKnpqPVhJ7uOFgIT1z2Ieh8lw==",
+ "HMAC": "vFzwTgOCx3OOWwYUOSxWx0iu1LM=",
+ "HMACMethod": "SHA1",
+ "IV": "ugpkaONKt34MfDu511WSqQ==",
+ "Iterations": -1,
+ "Salt": "LvO2ljvR4LY=",
+ "Stretch": "PBKDF2",
+ "Type": "EncryptedConfiguration"
+} \ No newline at end of file
diff --git a/chromeos/test/data/network/broken-encrypted-zero-iterations.onc b/chromeos/test/data/network/broken-encrypted-zero-iterations.onc
new file mode 100644
index 0000000..f8977a4
--- /dev/null
+++ b/chromeos/test/data/network/broken-encrypted-zero-iterations.onc
@@ -0,0 +1,11 @@
+{
+ "Cipher": "AES256",
+ "Ciphertext": "bnJmCTSw4SJJB3+3SnlAsd+7kmtDOjpxXfiXOK7XEhKh3uvJ3ntO5eleN+cvG2EAxubzkzp/y7siQKolLqFMvz63sYWEYYRY250j5b3Tjjaqk+PJidhyIwT9DiuSYTqPMip8cRBfQtj1dHNGADWcq7NwrmjW2EAL8/G+s8YC4w2Lnw3pzlNRKginUutRdJ4ShorXkFey6LDvS3A1WofGfr6C+//Da0aXYJiNaNm/qOxAuoxJBEXDNhgNuGrbCviZkJOAviYvYGFhWF2lZMvwwiWSjmhy8yzDefjyJxNl7XB9iZeqh1Lqh8Uh6/pVRpVq3fNUpRZwY2SdT9VvYVOtlesXrONKpTHKRXArzsjK2py5mux7DcmVRqj9XeDIiPH2aTHA51T+PPo851HktSrrmlwMUVwrMlH2BpkaibCo+AevekHWYYv0uMY8xLAYI4T5bQ3O9HwN3DmTzjjkcOKkTSyfq68bHUvLInHWoh+k3W/+9l3SuuCAelfWu0XyoWzRr+C66tCO5Ugn9hjMZIE4NQiEsPigg3RQh+JpT5wCBPxpsxGWi/GhxEadb1ZF4CKnpqPVhJ7uOFgIT1z2Ieh8lw==",
+ "HMAC": "vFzwTgOCx3OOWwYUOSxWx0iu1LM=",
+ "HMACMethod": "SHA1",
+ "IV": "ugpkaONKt34MfDu511WSqQ==",
+ "Iterations": 0,
+ "Salt": "LvO2ljvR4LY=",
+ "Stretch": "PBKDF2",
+ "Type": "EncryptedConfiguration"
+} \ No newline at end of file
diff --git a/chromeos/test/data/network/certificate-client-update.onc b/chromeos/test/data/network/certificate-client-update.onc
new file mode 100644
index 0000000..8d2ff44
--- /dev/null
+++ b/chromeos/test/data/network/certificate-client-update.onc
@@ -0,0 +1,9 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697ad}",
+ "PKCS12": "MIIGUQIBAzCCBhcGCSqGSIb3DQEHAaCCBggEggYEMIIGADCCAv8GCSqGSIb3DQEHBqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIHnFaWM2Y0BgCAggAgIICuG4ou9mxkhpus8WictLJe+JOnSQrdNXV3FMQr4pPJ6aJJFBMKZ80W2GpR8XNY/SSKkdaNr1puDm1bDBFGaHQuCKXYcWO8ynBQ1uoZaFaTTFxWbbHo89Jrvw+gIrgpoOHQ0KECEbh5vOZCjGHoaQb4QZOkw/6Cuc4QRoCPJAI3pbSPG44kRbOuOaTZvBHSIPkGf3+R6byTvZ3Yiuw7IIzxUp2fYjtpCWd/NvtI70heJCWdb5hwCeNafIEpX+MTVuhUegysIFkOMMlUBIQSI5ky8kjx0Yi82BT/dpz9QgrqFL8NnTMXp0JlKFGLQwsIQhvGjw/E52fEWRy85B5eezgNsD4QOLeZkF0bQAz8kXfLi+0djxsHvH9W9X2pwaFiAveXR15/v+wfCwQGSsRhISGLzg/gO1agbQdaexI9GlEeZW0FEY7TblarKh8TVGNrauU7GCGDmD2w7wx2HTXfo9SbViFoYVKuxcrpHGGEtBffnIeAwN6BBee4v11jxv0i/QUdK5G6FbHqlD1AhHsm0YvidYKqJ0cnN262xIJH7dhKq/qUiAT+qk3+d3/obqxbvVY+bDoJQ10Gzj1ASMy4zcSL7KW1l99xxMr6OlKr4Sr23oGw4BIN73FB8S8qMzz/VzL4azDUyGpPkzWl0yXPsHpFWh1nZlsQehyknyWDH/waKrrG8tVWxHZLgq+zrFxQTh63UHXSD+TXB+AQg2xmQMeWlfvRcsKL8titZ6PnWCHTmZY+3ibv5avDsg7He6OcZOi9ZmYMx82QHuzb4aZ/T+OC05oA97nVNbTN6t8okkRtBamMvVhtTJANVpsdPi8saEaVF8e9liwmpq2w7pqXnzgdzvjSUpPAa4dZBjWnZJvFOHuxZqiRzQdZbeh9+bXwsQJhRNe+d4EgFwuqebQOczeUi4NVTHTFiuPEjCCAvkGCSqGSIb3DQEHAaCCAuoEggLmMIIC4jCCAt4GCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAi0znbEekG/MgICCAAEggKAJfFPaQyYYLohEA1ruAZfepwMVrR8eLMx00kkfXN9EoZeFPj2q7TGdqmbkUSqXnZK1ums7pFCPLgP1CsPlsq/4ZPDT2LLVFZNLOgmdQBOSTvycfsj0iKYrwRC55wJI2OXsc062sT7oa99apkgrEyHq7JbOhszfnv5+aVy/6O115dncqFPW2ei4CBzLEZyYa+Mka6CGqSdm97WVmv0emDKTFEP/FN4TH/tS8Qm6Y7DTKGCujC+hb6lTRFYJAD4uld132dv0xQFkwDZGfdnuGJuNZBDC0gZk3BYvOaCUD8Y9UB5IjfGJax2yrurY1wSGSlTurafDTPrKqIdBovwCPsad2xz1YHC2Yy0h1FyR+2uitDyNfTiETfug3bFbjwodu9wmt31A2ZFn4JpUrTYoZ3LZXngC3nNTayU0Tkd1ICMep2GbCReL3ajOlgOKGFVoOm/qDnhiH6W/ebtAQXqVpuKut8uY0X0Ocmx7mTpmxlfDSRiBY9rvnrGfnpfLMxtFeF9jv3n8vSwvA0Xn0okAv1FWYLStiCpNxnD6lmXQvcmL/skAlJJpHY9/58qt/e5sGYrkKBw3jnX40zaK4W7GeJvhij0MRr6yUL2lvaEcWDnK6K1F90G/ybKRCTHBCJzyBe7yHhZCc+ZcvKK6DTi83fELTyupy08BkXt7oPdapxmKlZxTldo9FpPXSqrdRtAWhDkEkIEf8dMf8QrQr3glCWfbcQ047URYX45AHRnLTLLkJfdY8+Y3KsHoqL2UrOrct+J1u0mmnLbonN3pB2B4nd9X9vf9/uSFrgvk0iPO0Ro3UPRUIIYEP2Kx51pZZVDd++hl5gXtqe0NIpphGhxLycIdzElMCMGCSqGSIb3DQEJFTEWBBR1uVpGjHRddIEYuJhz/FgG4Onh6jAxMCEwCQYFKw4DAhoFAAQU1M+0WRDkoVGbGg1jj7q2fI67qHIECBzRYESpgt5iAgIIAA==",
+ "Type": "Client"
+ }
+ ]
+}
diff --git a/chromeos/test/data/network/certificate-client.onc b/chromeos/test/data/network/certificate-client.onc
new file mode 100644
index 0000000..75a244a
--- /dev/null
+++ b/chromeos/test/data/network/certificate-client.onc
@@ -0,0 +1,9 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697ac}",
+ "PKCS12": "MIIGUQIBAzCCBhcGCSqGSIb3DQEHAaCCBggEggYEMIIGADCCAv8GCSqGSIb3DQEHBqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIHnFaWM2Y0BgCAggAgIICuG4ou9mxkhpus8WictLJe+JOnSQrdNXV3FMQr4pPJ6aJJFBMKZ80W2GpR8XNY/SSKkdaNr1puDm1bDBFGaHQuCKXYcWO8ynBQ1uoZaFaTTFxWbbHo89Jrvw+gIrgpoOHQ0KECEbh5vOZCjGHoaQb4QZOkw/6Cuc4QRoCPJAI3pbSPG44kRbOuOaTZvBHSIPkGf3+R6byTvZ3Yiuw7IIzxUp2fYjtpCWd/NvtI70heJCWdb5hwCeNafIEpX+MTVuhUegysIFkOMMlUBIQSI5ky8kjx0Yi82BT/dpz9QgrqFL8NnTMXp0JlKFGLQwsIQhvGjw/E52fEWRy85B5eezgNsD4QOLeZkF0bQAz8kXfLi+0djxsHvH9W9X2pwaFiAveXR15/v+wfCwQGSsRhISGLzg/gO1agbQdaexI9GlEeZW0FEY7TblarKh8TVGNrauU7GCGDmD2w7wx2HTXfo9SbViFoYVKuxcrpHGGEtBffnIeAwN6BBee4v11jxv0i/QUdK5G6FbHqlD1AhHsm0YvidYKqJ0cnN262xIJH7dhKq/qUiAT+qk3+d3/obqxbvVY+bDoJQ10Gzj1ASMy4zcSL7KW1l99xxMr6OlKr4Sr23oGw4BIN73FB8S8qMzz/VzL4azDUyGpPkzWl0yXPsHpFWh1nZlsQehyknyWDH/waKrrG8tVWxHZLgq+zrFxQTh63UHXSD+TXB+AQg2xmQMeWlfvRcsKL8titZ6PnWCHTmZY+3ibv5avDsg7He6OcZOi9ZmYMx82QHuzb4aZ/T+OC05oA97nVNbTN6t8okkRtBamMvVhtTJANVpsdPi8saEaVF8e9liwmpq2w7pqXnzgdzvjSUpPAa4dZBjWnZJvFOHuxZqiRzQdZbeh9+bXwsQJhRNe+d4EgFwuqebQOczeUi4NVTHTFiuPEjCCAvkGCSqGSIb3DQEHAaCCAuoEggLmMIIC4jCCAt4GCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAi0znbEekG/MgICCAAEggKAJfFPaQyYYLohEA1ruAZfepwMVrR8eLMx00kkfXN9EoZeFPj2q7TGdqmbkUSqXnZK1ums7pFCPLgP1CsPlsq/4ZPDT2LLVFZNLOgmdQBOSTvycfsj0iKYrwRC55wJI2OXsc062sT7oa99apkgrEyHq7JbOhszfnv5+aVy/6O115dncqFPW2ei4CBzLEZyYa+Mka6CGqSdm97WVmv0emDKTFEP/FN4TH/tS8Qm6Y7DTKGCujC+hb6lTRFYJAD4uld132dv0xQFkwDZGfdnuGJuNZBDC0gZk3BYvOaCUD8Y9UB5IjfGJax2yrurY1wSGSlTurafDTPrKqIdBovwCPsad2xz1YHC2Yy0h1FyR+2uitDyNfTiETfug3bFbjwodu9wmt31A2ZFn4JpUrTYoZ3LZXngC3nNTayU0Tkd1ICMep2GbCReL3ajOlgOKGFVoOm/qDnhiH6W/ebtAQXqVpuKut8uY0X0Ocmx7mTpmxlfDSRiBY9rvnrGfnpfLMxtFeF9jv3n8vSwvA0Xn0okAv1FWYLStiCpNxnD6lmXQvcmL/skAlJJpHY9/58qt/e5sGYrkKBw3jnX40zaK4W7GeJvhij0MRr6yUL2lvaEcWDnK6K1F90G/ybKRCTHBCJzyBe7yHhZCc+ZcvKK6DTi83fELTyupy08BkXt7oPdapxmKlZxTldo9FpPXSqrdRtAWhDkEkIEf8dMf8QrQr3glCWfbcQ047URYX45AHRnLTLLkJfdY8+Y3KsHoqL2UrOrct+J1u0mmnLbonN3pB2B4nd9X9vf9/uSFrgvk0iPO0Ro3UPRUIIYEP2Kx51pZZVDd++hl5gXtqe0NIpphGhxLycIdzElMCMGCSqGSIb3DQEJFTEWBBR1uVpGjHRddIEYuJhz/FgG4Onh6jAxMCEwCQYFKw4DAhoFAAQU1M+0WRDkoVGbGg1jj7q2fI67qHIECBzRYESpgt5iAgIIAA==",
+ "Type": "Client"
+ }
+ ]
+}
diff --git a/chromeos/test/data/network/certificate-server-update.onc b/chromeos/test/data/network/certificate-server-update.onc
new file mode 100644
index 0000000..45db3bd
--- /dev/null
+++ b/chromeos/test/data/network/certificate-server-update.onc
@@ -0,0 +1,14 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697ab}",
+ "Trust": [
+ "Web"
+ ],
+ "Type": "Server",
+ "X509": "MIICWDCCAcECAxAAATANBgkqhkiG9w0BAQQFADCBkzEVMBMGA1UEChMMR29vZ2xlLCBJbmMuMREwDwYDVQQLEwhDaHJvbWVPUzEiMCAGCSqGSIb3DQEJARYTZ3NwZW5jZXJAZ29vZ2xlLmNvbTEaMBgGA1UEBxMRTW91bnRhaW4gVmlldywgQ0ExCzAJBgNVBAgTAkNBMQswCQYDVQQGEwJVUzENMAsGA1UEAxMEbG1hbzAeFw0xMTAzMTYyMzQ5MzhaFw0xMjAzMTUyMzQ5MzhaMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEChMMR29vZ2xlLCBJbmMuMREwDwYDVQQLEwhDaHJvbWVPUzENMAsGA1UEAxMEbG1hbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA31WiJ9LvprrhKtDlW0RdLFAO7Qjkvs+sG6j2Vp2aBSrlhALG/0BVHUhWi4F/HHJho+ncLHAg5AGO0sdAjYUdQG6tfPqjLsIALtoKEZZdFe/JhmqOEaxWsSdu2S2RdPgCQOsP79EH58gXwu2gejCkJDmU22WL4YLuqOc17nxbDC8CAwEAATANBgkqhkiG9w0BAQQFAAOBgQCv4vMD+PMlfnftu4/6Yf/oMLE8yCOqZTQ/dWCxB9PiJnOefiBeSzSZE6Uv3G7qnblZPVZaFeJMd+ostt0viCyPucFsFgLMyyoV1dMVPVwJT5Iq1AHehWXnTBbxUK9wioA5jOEKdroKjuSSsg/Q8Wx6cpJmttQz5olGPgstmACRWA=="
+ }
+ ],
+ "NetworkConfigurations": [],
+ "Type": "UnencryptedConfiguration"
+}
diff --git a/chromeos/test/data/network/certificate-server.onc b/chromeos/test/data/network/certificate-server.onc
new file mode 100644
index 0000000..2a820e3
--- /dev/null
+++ b/chromeos/test/data/network/certificate-server.onc
@@ -0,0 +1,14 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697aa}",
+ "Trust": [
+ "Web"
+ ],
+ "Type": "Server",
+ "X509": "leading junk \n-----BEGIN CERTIFICATE----- \nMIICWDCCAcECAxAAATANBgkqhkiG9w0BAQQFADCBkzEVMBMGA1UEChMMR29vZ2xlLCBJbm\nMuMREwDwYDVQQLEwhDaHJvbWVPUzEiMCAGCSqGSIb3DQEJARYTZ3NwZW5jZXJAZ29vZ2xl\nLmNvbTEaMBgGA1UEBxMRTW91bnRhaW4gVmlldywgQ0ExCzAJBgNVBAgTAkNBMQswCQYDVQ\nQGEwJVUzENMAsGA1UEAxMEbG1hbzAeFw0xMTAzMTYyMzQ5MzhaFw0xMjAzMTUyMzQ5Mzha\nMFMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEChMMR29vZ2xlLCBJbmMuMR\nEwDwYDVQQLEwhDaHJvbWVPUzENMAsGA1UEAxMEbG1hbzCBnzANBgkqhkiG9w0BAQEFAAOB\njQAwgYkCgYEA31WiJ9LvprrhKtDlW0RdLFAO7Qjkvs+sG6j2Vp2aBSrlhALG/0BVHUhWi4\nF/HHJho+ncLHAg5AGO0sdAjYUdQG6tfPqjLsIALtoKEZZdFe/JhmqOEaxWsSdu2S2RdPgC\nQOsP79EH58gXwu2gejCkJDmU22WL4YLuqOc17nxbDC8CAwEAATANBgkqhkiG9w0BAQQFAA\nOBgQCv4vMD+PMlfnftu4/6Yf/oMLE8yCOqZTQ/dWCxB9PiJnOefiBeSzSZE6Uv3G7qnblZ\nPVZaFeJMd+ostt0viCyPucFsFgLMyyoV1dMVPVwJT5Iq1AHehWXnTBbxUK9wioA5jOEKdr\noKjuSSsg/Q8Wx6cpJmttQz5olGPgstmACRWA==\n-----END CERTIFICATE----- \ntrailing junk"
+ }
+ ],
+ "NetworkConfigurations": [],
+ "Type": "UnencryptedConfiguration"
+}
diff --git a/chromeos/test/data/network/certificate-web-authority-update.onc b/chromeos/test/data/network/certificate-web-authority-update.onc
new file mode 100644
index 0000000..ca0de2d
--- /dev/null
+++ b/chromeos/test/data/network/certificate-web-authority-update.onc
@@ -0,0 +1,14 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697ac}",
+ "Trust": [
+ "Web"
+ ],
+ "Type": "Authority",
+ "X509": "MIIDojCCAwugAwIBAgIJAKGvi5ZgEWDVMA0GCSqGSIb3DQEBBAUAMIGTMRUwEwYDVQQKEwxHb29nbGUsIEluYy4xETAPBgNVBAsTCENocm9tZU9TMSIwIAYJKoZIhvcNAQkBFhNnc3BlbmNlckBnb29nbGUuY29tMRowGAYDVQQHExFNb3VudGFpbiBWaWV3LCBDQTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMQ0wCwYDVQQDEwRsbWFvMB4XDTExMDMxNjIzNDcxMFoXDTEyMDMxNTIzNDcxMFowgZMxFTATBgNVBAoTDEdvb2dsZSwgSW5jLjERMA8GA1UECxMIQ2hyb21lT1MxIjAgBgkqhkiG9w0BCQEWE2dzcGVuY2VyQGdvb2dsZS5jb20xGjAYBgNVBAcTEU1vdW50YWluIFZpZXcsIENBMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMxDTALBgNVBAMTBGxtYW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMDX6BQz2JUzIAVjetiXxDznd2wdqVqVHfNkbSRW+xBywgqUaIXmFEGUol7VzPfmeFV8o8ok/eFlQB0h6ycqgwwMd0KjtJs2ys/k0F5GuN0G7fsgr+NRnhVgxj21yF6gYTN/8a9kscla/svdmp8ekexbALFnghbLBx3CgcqUxT+tAgMBAAGjgfswgfgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUbYygbSkl4kpjCNuxoezFGupA97UwgcgGA1UdIwSBwDCBvYAUbYygbSkl4kpjCNuxoezFGupA97WhgZmkgZYwgZMxFTATBgNVBAoTDEdvb2dsZSwgSW5jLjERMA8GA1UECxMIQ2hyb21lT1MxIjAgBgkqhkiG9w0BCQEWE2dzcGVuY2VyQGdvb2dsZS5jb20xGjAYBgNVBAcTEU1vdW50YWluIFZpZXcsIENBMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMxDTALBgNVBAMTBGxtYW+CCQChr4uWYBFg1TANBgkqhkiG9w0BAQQFAAOBgQCDq9wiQ4uVuf1CQU3sXfXCy1yqi5m8AsO9FxHvah5/SVFNwKllqTfedpCaWEswJ55YAojW9e+pY2Fh3Fo/Y9YkF88KCtLuBjjqDKCRLxF4LycjHODKyQQ7mN/t5AtP9yKOsNvWF+M4IfReg51kohau6FauQx87by5NIRPdkNPvkQ=="
+ }
+ ],
+ "NetworkConfigurations": [],
+ "Type": "UnencryptedConfiguration"
+}
diff --git a/chromeos/test/data/network/certificate-web-authority.onc b/chromeos/test/data/network/certificate-web-authority.onc
new file mode 100644
index 0000000..81e316d
--- /dev/null
+++ b/chromeos/test/data/network/certificate-web-authority.onc
@@ -0,0 +1,14 @@
+{
+ "Certificates": [
+ {
+ "GUID": "{f998f760-272b-6939-4c2beffe428697ab}",
+ "Trust": [
+ "Web"
+ ],
+ "Type": "Authority",
+ "X509": "MIIDojCCAwugAwIBAgIJAKGvi5ZgEWDVMA0GCSqGSIb3DQEBBAUAMIGTMRUwEwYDVQQKEwxHb29nbGUsIEluYy4xETAPBgNVBAsTCENocm9tZU9TMSIwIAYJKoZIhvcNAQkBFhNnc3BlbmNlckBnb29nbGUuY29tMRowGAYDVQQHExFNb3VudGFpbiBWaWV3LCBDQTELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMQ0wCwYDVQQDEwRsbWFvMB4XDTExMDMxNjIzNDcxMFoXDTEyMDMxNTIzNDcxMFowgZMxFTATBgNVBAoTDEdvb2dsZSwgSW5jLjERMA8GA1UECxMIQ2hyb21lT1MxIjAgBgkqhkiG9w0BCQEWE2dzcGVuY2VyQGdvb2dsZS5jb20xGjAYBgNVBAcTEU1vdW50YWluIFZpZXcsIENBMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMxDTALBgNVBAMTBGxtYW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMDX6BQz2JUzIAVjetiXxDznd2wdqVqVHfNkbSRW+xBywgqUaIXmFEGUol7VzPfmeFV8o8ok/eFlQB0h6ycqgwwMd0KjtJs2ys/k0F5GuN0G7fsgr+NRnhVgxj21yF6gYTN/8a9kscla/svdmp8ekexbALFnghbLBx3CgcqUxT+tAgMBAAGjgfswgfgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUbYygbSkl4kpjCNuxoezFGupA97UwgcgGA1UdIwSBwDCBvYAUbYygbSkl4kpjCNuxoezFGupA97WhgZmkgZYwgZMxFTATBgNVBAoTDEdvb2dsZSwgSW5jLjERMA8GA1UECxMIQ2hyb21lT1MxIjAgBgkqhkiG9w0BCQEWE2dzcGVuY2VyQGdvb2dsZS5jb20xGjAYBgNVBAcTEU1vdW50YWluIFZpZXcsIENBMQswCQYDVQQIEwJDQTELMAkGA1UEBhMCVVMxDTALBgNVBAMTBGxtYW+CCQChr4uWYBFg1TANBgkqhkiG9w0BAQQFAAOBgQCDq9wiQ4uVuf1CQU3sXfXCy1yqi5m8AsO9FxHvah5/SVFNwKllqTfedpCaWEswJ55YAojW9e+pY2Fh3Fo/Y9YkF88KCtLuBjjqDKCRLxF4LycjHODKyQQ7mN/t5AtP9yKOsNvWF+M4IfReg51kohau6FauQx87by5NIRPdkNPvkQ=="
+ }
+ ],
+ "NetworkConfigurations": [],
+ "Type": "UnencryptedConfiguration"
+} \ No newline at end of file
diff --git a/chromeos/test/data/network/decrypted.onc b/chromeos/test/data/network/decrypted.onc
new file mode 100644
index 0000000..b0bfee4
--- /dev/null
+++ b/chromeos/test/data/network/decrypted.onc
@@ -0,0 +1,18 @@
+{
+ "Certificates": [ ],
+ "NetworkConfigurations": [ {
+ "GUID": "{64369ad3-9aec-0d1e-e7bb495970da2f33}",
+ "Name": "WirelessNetwork",
+ "ProxySettings": {
+ "PAC": "http://www.youtube.com/watch?v=oHg5SJYRHA0",
+ "Type": "PAC"
+ },
+ "Type": "WiFi",
+ "WiFi": {
+ "AutoConnect": false,
+ "HiddenSSID": false,
+ "SSID": "WirelessNetwork",
+ "Security": "None"
+ }
+ } ]
+}
diff --git a/chromeos/test/data/network/device_policy.onc b/chromeos/test/data/network/device_policy.onc
new file mode 100644
index 0000000..663af5b
--- /dev/null
+++ b/chromeos/test/data/network/device_policy.onc
@@ -0,0 +1,26 @@
+{ "GUID": "123",
+ "Type": "VPN",
+ "Name": "testopenvpn",
+ "IPConfigs": [
+ { "Type": "IPv4",
+ "IPAddress": "127.0.0.1" }
+ ],
+ "VPN": {
+ "Host": "device policys host",
+ "Recommended": ["Host"],
+ "Type": "OpenVPN",
+ "OpenVPN": {
+ "Port": 0,
+ "Username": "device user",
+ "Recommended": [ "Password", "Port" ],
+ "ClientCertType": "Pattern",
+ "ClientCertPattern": {
+ "IssuerCARef": [ "openvpn-test-ca" ],
+ "Recommended": [ "EnrollmentURI", "IssuerCARef" ]
+ }
+ },
+ "IPsec": {
+ "PSK": "sharedkey"
+ }
+ }
+}
diff --git a/chromeos/test/data/network/encrypted.onc b/chromeos/test/data/network/encrypted.onc
new file mode 100644
index 0000000..82da98f
--- /dev/null
+++ b/chromeos/test/data/network/encrypted.onc
@@ -0,0 +1,11 @@
+{
+ "Cipher": "AES256",
+ "Ciphertext": "bnJmCTSw4SJJB3+3SnlAsd+7kmtDOjpxXfiXOK7XEhKh3uvJ3ntO5eleN+cvG2EAxubzkzp/y7siQKolLqFMvz63sYWEYYRY250j5b3Tjjaqk+PJidhyIwT9DiuSYTqPMip8cRBfQtj1dHNGADWcq7NwrmjW2EAL8/G+s8YC4w2Lnw3pzlNRKginUutRdJ4ShorXkFey6LDvS3A1WofGfr6C+//Da0aXYJiNaNm/qOxAuoxJBEXDNhgNuGrbCviZkJOAviYvYGFhWF2lZMvwwiWSjmhy8yzDefjyJxNl7XB9iZeqh1Lqh8Uh6/pVRpVq3fNUpRZwY2SdT9VvYVOtlesXrONKpTHKRXArzsjK2py5mux7DcmVRqj9XeDIiPH2aTHA51T+PPo851HktSrrmlwMUVwrMlH2BpkaibCo+AevekHWYYv0uMY8xLAYI4T5bQ3O9HwN3DmTzjjkcOKkTSyfq68bHUvLInHWoh+k3W/+9l3SuuCAelfWu0XyoWzRr+C66tCO5Ugn9hjMZIE4NQiEsPigg3RQh+JpT5wCBPxpsxGWi/GhxEadb1ZF4CKnpqPVhJ7uOFgIT1z2Ieh8lw==",
+ "HMAC": "vFzwTgOCx3OOWwYUOSxWx0iu1LM=",
+ "HMACMethod": "SHA1",
+ "IV": "ugpkaONKt34MfDu511WSqQ==",
+ "Iterations": 20000,
+ "Salt": "LvO2ljvR4LY=",
+ "Stretch": "PBKDF2",
+ "Type": "EncryptedConfiguration"
+} \ No newline at end of file
diff --git a/chromeos/test/data/network/invalid_settings_with_repairs.json b/chromeos/test/data/network/invalid_settings_with_repairs.json
new file mode 100644
index 0000000..34f521f
--- /dev/null
+++ b/chromeos/test/data/network/invalid_settings_with_repairs.json
@@ -0,0 +1,92 @@
+{
+ "managed-network-repaired": {
+ "Recommended": [],
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "managed-network-unknown-fieldname": {
+ "abc": "def",
+ "Recommended": [],
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "managed-network-unknown-type": {
+ "GUID": "guid",
+ "Type": "LTE",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "managed-network-unknown-recommended": {
+ "Recommended": ["abc"],
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "managed-network-dict-recommended": {
+ "Recommended": ["Ethernet"],
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "managed-network-missing-required": {
+ "Recommended": [],
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "network-repaired": {
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "network-unknown-fieldname": {
+ "abc": "def",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "network-unknown-type": {
+ "Type": "LTE",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "network-missing-required": {
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+ "network-illegal-recommended": {
+ "Recommended": [],
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+}
diff --git a/chromeos/test/data/network/policy.onc b/chromeos/test/data/network/policy.onc
new file mode 100644
index 0000000..4c52e97
--- /dev/null
+++ b/chromeos/test/data/network/policy.onc
@@ -0,0 +1,29 @@
+{ "GUID": "123",
+ "Type": "VPN",
+ "Name": "testopenvpn",
+ "IPConfigs": [
+ { "Type": "IPv4",
+ "IPAddress": "127.0.0.1",
+ "RoutingPrefix": 32 }
+ ],
+ "VPN": {
+ "Host": "policys host",
+ "Recommended": ["Host"],
+ "Type": "OpenVPN",
+ "OpenVPN": {
+ "Port": 1194,
+ "Username": "policy user",
+ "Recommended": [ "Username", "Password" ],
+ "ClientCertType": "Pattern",
+ "ClientCertPattern": {
+ "IssuerCARef": [ "openvpn-test-ca" ],
+ "Recommended": [ "EnrollmentURI", "IssuerCARef" ]
+ }
+ },
+ "IPsec": {
+ "AuthenticationType": "PSK",
+ "PSK": "sharedkey",
+ "IKEVersion": 1
+ }
+ }
+}
diff --git a/chromeos/test/data/network/policy_without_recommended.onc b/chromeos/test/data/network/policy_without_recommended.onc
new file mode 100644
index 0000000..037bf53
--- /dev/null
+++ b/chromeos/test/data/network/policy_without_recommended.onc
@@ -0,0 +1,26 @@
+{ "GUID": "123",
+ "Type": "VPN",
+ "Name": "testopenvpn",
+ "IPConfigs": [
+ { "Type": "IPv4",
+ "IPAddress": "127.0.0.1",
+ "RoutingPrefix": 32 }
+ ],
+ "VPN": {
+ "Host": "policys host",
+ "Type": "OpenVPN",
+ "OpenVPN": {
+ "Port": 1194,
+ "Username": "policy user",
+ "ClientCertType": "Pattern",
+ "ClientCertPattern": {
+ "IssuerCARef": [ "openvpn-test-ca" ],
+ }
+ },
+ "IPsec": {
+ "AuthenticationType": "PSK",
+ "PSK": "sharedkey",
+ "IKEVersion": 1
+ }
+ }
+}
diff --git a/chromeos/test/data/network/settings_with_normalization.json b/chromeos/test/data/network/settings_with_normalization.json
new file mode 100644
index 0000000..08a78a4
--- /dev/null
+++ b/chromeos/test/data/network/settings_with_normalization.json
@@ -0,0 +1,33 @@
+{
+ "ethernet-and-vpn": {
+ "Recommended": [],
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ },
+ "VPN": {
+ "Type": "L2TP-IPsec",
+ "Host": "some.host.org",
+ "IPsec": {
+ "AuthenticationType": "PSK",
+ "IKEVersion": 1,
+ "PSK": "some_preshared_key",
+ "SaveCredentials": true
+ },
+ "L2TP": {
+ "Username": "some username",
+ "Password": "some password"
+ }
+ }
+ },
+ "ethernet-and-vpn-normalized": {
+ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+ },
+}
diff --git a/chromeos/test/data/network/shill_ethernet.json b/chromeos/test/data/network/shill_ethernet.json
new file mode 100644
index 0000000..7754932
--- /dev/null
+++ b/chromeos/test/data/network/shill_ethernet.json
@@ -0,0 +1,4 @@
+{ "GUID": "guid",
+ "Type": "ethernet",
+ "Name": "name",
+}
diff --git a/chromeos/test/data/network/shill_l2tpipsec.json b/chromeos/test/data/network/shill_l2tpipsec.json
new file mode 100644
index 0000000..f529a97
--- /dev/null
+++ b/chromeos/test/data/network/shill_l2tpipsec.json
@@ -0,0 +1,10 @@
+{ "GUID": "guid",
+ "Type": "vpn",
+ "Name": "MyL2TPVPN",
+ "Provider.Host": "some.host.org",
+ "Provider.Type": "l2tpipsec",
+ "L2TPIPsec.PSK": "some_preshared_key",
+ "L2TPIPsec.User": "some username",
+ "L2TPIPsec.Password": "some password",
+ "SaveCredentials": true
+}
diff --git a/chromeos/test/data/network/shill_openvpn.json b/chromeos/test/data/network/shill_openvpn.json
new file mode 100644
index 0000000..1bf84ef
--- /dev/null
+++ b/chromeos/test/data/network/shill_openvpn.json
@@ -0,0 +1,25 @@
+{
+ "GUID": "{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
+ "Type": "vpn",
+ "Name": "google-dogfood",
+ "SaveCredentials": false,
+ "Provider.Type": "openvpn",
+ "Provider.Host": "v.ext.google.com",
+ "OpenVPN.AuthRetry": "interact",
+ "OpenVPN.User": "some username",
+ "OpenVPN.Password": "some password",
+ "OpenVPN.StaticChallenge": "Please enter token OTP",
+ "OpenVPN.CompLZO": "true",
+ "OpenVPN.ServerPollTimeout": "10",
+ "OpenVPN.RemoteCertKU": "e0",
+ "OpenVPN.CACertNSS": "{14ff4d51-64c1-4c86-a622-054d8e55e750}",
+ "OpenVPN.RemoteCertTLS": "server",
+ "OpenVPN.Port": "443",
+ "OpenVPN.TLSRemote": "v.ext.google.com",
+ "OpenVPN.KeyDirection": "1",
+ "OpenVPN.RenegSec": "0",
+ "OpenVPN.RemoteCertEKU": "TLS Web Server Authentication",
+ "OpenVPN.Proto": "udp",
+ "OpenVPN.PushPeerInfo": "true",
+ "OpenVPN.TLSAuthContents": "-----BEGIN OpenVPN Static key V1-----\nf8a19b8fddda3a38e06b883c48c83474\n0d1dfe7e5cadb2891387ac2d85198cdf\ne14274d03d12c4128c6c8967f84b1b39\n22452fb869280cde868bcf3932615927\nab1948d951c19183276fa249fd6e1e5f\nfc1c41400b59493a39cad872f5c7e777\n310d685d9c30abff7f206afc09cc99d9\ne40b2091e29af5b37d191628fba6d86e\n3a239b45b1b5a6be3229663ee7a7dc67\n2fa5be59fe7ceb41d2c78f94237c2185\n0aea1b06d739bca83240671167ddd2c2\n9cfd931df468536bfd1be99cd05160d8\n09ebfe4b8da88fb1923b19ad1abfc45e\n0402321e41af54e7d19ac95b1a7cec9a\nbf60a8eabcd5eecc84773af211477ee9\n9c988269638f9f1bb668028bb688dea2\nEND OpenVPN Static key V1-----\n"
+}
diff --git a/chromeos/test/data/network/user.onc b/chromeos/test/data/network/user.onc
new file mode 100644
index 0000000..bd97bb3
--- /dev/null
+++ b/chromeos/test/data/network/user.onc
@@ -0,0 +1,21 @@
+{ "Type": "Ethernet",
+ "Name": "testopenvpn",
+ "VPN": {
+ "Host": "users host",
+ "Type": "OpenVPN",
+ "OpenVPN": {
+ "Port": 1195,
+ "Password": "users password"
+ }
+ },
+ "ProxySettings": {
+ "Type": "Direct"
+ },
+ "IPConfigs": [
+ { "Type": "IPv4",
+ "IPAddress": "127.0.0.1",
+ "SearchDomains": [ "acme.org" ] },
+ { "Type": "IPv4",
+ "IPAddress": "1.2.3.4" }
+ ]
+}
diff --git a/chromeos/test/data/network/valid.onc b/chromeos/test/data/network/valid.onc
new file mode 100644
index 0000000..56828bc
--- /dev/null
+++ b/chromeos/test/data/network/valid.onc
@@ -0,0 +1,7 @@
+{ "GUID": "guid",
+ "Type": "Ethernet",
+ "Name": "name",
+ "Ethernet": {
+ "Authentication": "None"
+ }
+}
diff --git a/chromeos/test/data/network/valid_l2tpipsec.onc b/chromeos/test/data/network/valid_l2tpipsec.onc
new file mode 100644
index 0000000..0e0bc5f
--- /dev/null
+++ b/chromeos/test/data/network/valid_l2tpipsec.onc
@@ -0,0 +1,18 @@
+{ "GUID": "guid",
+ "Type": "VPN",
+ "Name": "MyL2TPVPN",
+ "VPN": {
+ "Type": "L2TP-IPsec",
+ "Host": "some.host.org",
+ "IPsec": {
+ "AuthenticationType": "PSK",
+ "IKEVersion": 1,
+ "PSK": "some_preshared_key",
+ "SaveCredentials": true
+ },
+ "L2TP": {
+ "Username": "some username",
+ "Password": "some password"
+ }
+ }
+}
diff --git a/chromeos/test/data/network/valid_openvpn.onc b/chromeos/test/data/network/valid_openvpn.onc
new file mode 100644
index 0000000..512cb0e
--- /dev/null
+++ b/chromeos/test/data/network/valid_openvpn.onc
@@ -0,0 +1,31 @@
+{
+ "GUID": "{a3860e83-f03d-4cb1-bafa-b22c9e746950}",
+ "Name": "google-dogfood",
+ "Type": "VPN",
+ "VPN": {
+ "Host": "v.ext.google.com",
+ "Type": "OpenVPN",
+ "OpenVPN": {
+ "AuthRetry": "interact",
+ "CompLZO": "true",
+ "KeyDirection": "1",
+ "Password": "some password",
+ "Port": 443,
+ "Proto": "udp",
+ "PushPeerInfo": true,
+ "RemoteCertEKU": "TLS Web Server Authentication",
+ "RemoteCertKU": [
+ "e0"
+ ],
+ "RemoteCertTLS": "server",
+ "RenegSec": 0,
+ "SaveCredentials": false,
+ "ServerCARef": "{14ff4d51-64c1-4c86-a622-054d8e55e750}",
+ "ServerPollTimeout": 10,
+ "StaticChallenge": "Please enter token OTP",
+ "TLSAuthContents": "-----BEGIN OpenVPN Static key V1-----\nf8a19b8fddda3a38e06b883c48c83474\n0d1dfe7e5cadb2891387ac2d85198cdf\ne14274d03d12c4128c6c8967f84b1b39\n22452fb869280cde868bcf3932615927\nab1948d951c19183276fa249fd6e1e5f\nfc1c41400b59493a39cad872f5c7e777\n310d685d9c30abff7f206afc09cc99d9\ne40b2091e29af5b37d191628fba6d86e\n3a239b45b1b5a6be3229663ee7a7dc67\n2fa5be59fe7ceb41d2c78f94237c2185\n0aea1b06d739bca83240671167ddd2c2\n9cfd931df468536bfd1be99cd05160d8\n09ebfe4b8da88fb1923b19ad1abfc45e\n0402321e41af54e7d19ac95b1a7cec9a\nbf60a8eabcd5eecc84773af211477ee9\n9c988269638f9f1bb668028bb688dea2\nEND OpenVPN Static key V1-----\n",
+ "TLSRemote": "v.ext.google.com",
+ "Username": "some username",
+ }
+ }
+}