Support additional placeholders in ONC identity fields
Support additional placeholders in ONC identity fields such as Identity
or Username:
- CERT_SUBJECT_COMMON_NAME: the ASCII CommonName of the selected client
certificate (replaced when a CertPattern is used).
- DEVICE_SERIAL_NUMBER: the serial number of the device. Only replaced
in DeviceOpenNetworkConfiguration.
- DEVICE_ASSET_ID: the Asset ID of the device. Only replaced in
DeviceOpenNetworkConfiguration.
This CL also changes ONC identity field variable replacement to use
|VariableExpander| instead of custom code.
chromeos_unittests --gtet_filter=ClientCertResolverTest*
Bug: 833426
Test: unit_tests --gtest_filter=*NetworkConfigurationUpdater* && \
Change-Id: I436792ea0115230554f9726d56288cae09e80d40
Reviewed-on: https://chromium-review.googlesource.com/1016920
Commit-Queue: Pavol Marko <pmarko@chromium.org>
Reviewed-by: Maksim Ivanov <emaxx@chromium.org>
Reviewed-by: Lutz Justen <ljusten@chromium.org>
Reviewed-by: Steven Bennetts <stevenjb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553087}
diff --git a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
index a7ac918a..2ed36289d 100644
--- a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
+++ b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
@@ -201,7 +201,8 @@
chromeos::NetworkHandler::Get()
->managed_network_configuration_handler(),
chromeos::NetworkHandler::Get()->network_device_handler(),
- chromeos::CrosSettings::Get());
+ chromeos::CrosSettings::Get(),
+ DeviceNetworkConfigurationUpdater::DeviceAssetIDFetcher());
bluetooth_policy_handler_ =
std::make_unique<BluetoothPolicyHandler>(chromeos::CrosSettings::Get());
diff --git a/chrome/browser/chromeos/policy/device_network_configuration_updater.cc b/chrome/browser/chromeos/policy/device_network_configuration_updater.cc
index 9599abe..23da89c 100644
--- a/chrome/browser/chromeos/policy/device_network_configuration_updater.cc
+++ b/chrome/browser/chromeos/policy/device_network_configuration_updater.cc
@@ -4,21 +4,38 @@
#include "chrome/browser/chromeos/policy/device_network_configuration_updater.h"
+#include <map>
+
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/network/managed_network_configuration_handler.h"
#include "chromeos/network/network_device_handler.h"
+#include "chromeos/network/onc/onc_utils.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/settings/cros_settings_provider.h"
+#include "chromeos/system/statistics_provider.h"
+#include "chromeos/tools/variable_expander.h"
#include "components/policy/policy_constants.h"
+#include "content/public/browser/browser_thread.h"
namespace policy {
+namespace {
+
+std::string GetDeviceAssetID() {
+ return g_browser_process->platform_part()
+ ->browser_policy_connector_chromeos()
+ ->GetDeviceAssetID();
+}
+
+} // namespace
+
DeviceNetworkConfigurationUpdater::~DeviceNetworkConfigurationUpdater() {}
// static
@@ -27,11 +44,13 @@
PolicyService* policy_service,
chromeos::ManagedNetworkConfigurationHandler* network_config_handler,
chromeos::NetworkDeviceHandler* network_device_handler,
- chromeos::CrosSettings* cros_settings) {
+ chromeos::CrosSettings* cros_settings,
+ const DeviceNetworkConfigurationUpdater::DeviceAssetIDFetcher&
+ device_asset_id_fetcher) {
std::unique_ptr<DeviceNetworkConfigurationUpdater> updater(
new DeviceNetworkConfigurationUpdater(
policy_service, network_config_handler, network_device_handler,
- cros_settings));
+ cros_settings, device_asset_id_fetcher));
updater->Init();
return updater;
}
@@ -40,13 +59,16 @@
PolicyService* policy_service,
chromeos::ManagedNetworkConfigurationHandler* network_config_handler,
chromeos::NetworkDeviceHandler* network_device_handler,
- chromeos::CrosSettings* cros_settings)
+ chromeos::CrosSettings* cros_settings,
+ const DeviceNetworkConfigurationUpdater::DeviceAssetIDFetcher&
+ device_asset_id_fetcher)
: NetworkConfigurationUpdater(onc::ONC_SOURCE_DEVICE_POLICY,
key::kDeviceOpenNetworkConfiguration,
policy_service,
network_config_handler),
network_device_handler_(network_device_handler),
cros_settings_(cros_settings),
+ device_asset_id_fetcher_(device_asset_id_fetcher),
weak_factory_(this) {
DCHECK(network_device_handler_);
data_roaming_setting_subscription_ = cros_settings->AddSettingsObserver(
@@ -54,6 +76,8 @@
base::Bind(
&DeviceNetworkConfigurationUpdater::OnDataRoamingSettingChanged,
base::Unretained(this)));
+ if (device_asset_id_fetcher_.is_null())
+ device_asset_id_fetcher_ = base::BindRepeating(&GetDeviceAssetID);
}
std::vector<std::string>
@@ -121,6 +145,25 @@
void DeviceNetworkConfigurationUpdater::ApplyNetworkPolicy(
base::ListValue* network_configs_onc,
base::DictionaryValue* global_network_config) {
+ // Ensure this is runnng on the UI thead because we're accessing global data
+ // to populate the substitutions.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+ // Expand device-specific placeholders. Note: It is OK that if the serial
+ // number or Asset ID are empty, the placeholders will be expanded to an empty
+ // string. This is to be consistent with user policy identity string
+ // expansions.
+ std::map<std::string, std::string> substitutions;
+ substitutions[::onc::substitutes::kDeviceSerialNumber] =
+ chromeos::system::StatisticsProvider::GetInstance()
+ ->GetEnterpriseMachineID();
+ substitutions[::onc::substitutes::kDeviceAssetId] =
+ device_asset_id_fetcher_.Run();
+
+ chromeos::VariableExpander variable_expander(std::move(substitutions));
+ chromeos::onc::ExpandStringsInNetworks(variable_expander,
+ network_configs_onc);
+
network_config_handler_->SetPolicy(onc_source_,
std::string() /* no username hash */,
*network_configs_onc,
diff --git a/chrome/browser/chromeos/policy/device_network_configuration_updater.h b/chrome/browser/chromeos/policy/device_network_configuration_updater.h
index 246f5a74..56161163 100644
--- a/chrome/browser/chromeos/policy/device_network_configuration_updater.h
+++ b/chrome/browser/chromeos/policy/device_network_configuration_updater.h
@@ -36,15 +36,23 @@
public:
~DeviceNetworkConfigurationUpdater() override;
+ // Fetches the device's administrator-annotated asset ID.
+ using DeviceAssetIDFetcher = base::RepeatingCallback<std::string()>;
+
// Creates an updater that applies the ONC device policy from |policy_service|
// once the policy service is completely initialized and on each policy
- // change. The argument objects must outlive the returned updater.
+ // change. The argument objects passed as pointers must outlive the returned
+ // updater. |device_assed_id_fetcher| should return the
+ // administrator-annotated asset ID of the device and is used for variable
+ // replacement. If a null callback is passed, the asset ID from device policy
+ // will be used.
static std::unique_ptr<DeviceNetworkConfigurationUpdater>
CreateForDevicePolicy(
PolicyService* policy_service,
chromeos::ManagedNetworkConfigurationHandler* network_config_handler,
chromeos::NetworkDeviceHandler* network_device_handler,
- chromeos::CrosSettings* cros_settings);
+ chromeos::CrosSettings* cros_settings,
+ const DeviceAssetIDFetcher& device_asset_id_fetcher);
// Returns all authority certificates from the currently applied ONC device
// policy.
@@ -55,7 +63,8 @@
PolicyService* policy_service,
chromeos::ManagedNetworkConfigurationHandler* network_config_handler,
chromeos::NetworkDeviceHandler* network_device_handler,
- chromeos::CrosSettings* cros_settings);
+ chromeos::CrosSettings* cros_settings,
+ const DeviceAssetIDFetcher& device_asset_id_fetcher);
void Init() override;
void ImportCertificates(const base::ListValue& certificates_onc) override;
@@ -69,6 +78,9 @@
std::unique_ptr<base::CallbackList<void(void)>::Subscription>
data_roaming_setting_subscription_;
+ // Returns the device's administrator-set asset id.
+ DeviceAssetIDFetcher device_asset_id_fetcher_;
+
base::WeakPtrFactory<DeviceNetworkConfigurationUpdater> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DeviceNetworkConfigurationUpdater);
diff --git a/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc b/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
index eb250d6..053b56d 100644
--- a/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
+++ b/chrome/browser/chromeos/policy/network_configuration_updater_unittest.cc
@@ -25,6 +25,8 @@
#include "chromeos/network/onc/onc_parsed_certificates.h"
#include "chromeos/network/onc/onc_test_utils.h"
#include "chromeos/network/onc/onc_utils.h"
+#include "chromeos/system/fake_statistics_provider.h"
+#include "chromeos/system/statistics_provider.h"
#include "components/onc/onc_constants.h"
#include "components/policy/core/common/external_data_fetcher.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
@@ -58,6 +60,8 @@
const char kFakeUserEmail[] = "fake email";
const char kFakeUsernameHash[] = "fake hash";
+const char kFakeSerialNumber[] = "FakeSerial";
+const char kFakeAssetId[] = "FakeAssetId";
class FakeUser : public user_manager::User {
public:
@@ -167,26 +171,41 @@
DISALLOW_COPY_AND_ASSIGN(FakeCertificateImporter);
};
-const char kFakeONC[] =
- "{ \"NetworkConfigurations\": ["
- " { \"GUID\": \"{485d6076-dd44-6b6d-69787465725f5040}\","
- " \"Type\": \"WiFi\","
- " \"Name\": \"My WiFi Network\","
- " \"WiFi\": {"
- " \"HexSSID\": \"737369642D6E6F6E65\"," // "ssid-none"
- " \"Security\": \"None\" }"
- " }"
- " ],"
- " \"GlobalNetworkConfiguration\": {"
- " \"AllowOnlyPolicyNetworksToAutoconnect\": true,"
- " },"
- " \"Certificates\": ["
- " { \"GUID\": \"{f998f760-272b-6939-4c2beffe428697ac}\","
- " \"PKCS12\": \"YWJj\","
- " \"Type\": \"Client\" }"
- " ],"
- " \"Type\": \"UnencryptedConfiguration\""
- "}";
+// Note: HexSSID 737369642D6E6F6E65 maps to "ssid-none".
+// HexSSID 7465737431323334 maps to "test1234"
+const char kFakeONC[] = R"(
+ { "NetworkConfigurations": [
+ { "GUID": "{485d6076-dd44-6b6d-69787465725f5040}",
+ "Type": "WiFi",
+ "Name": "My WiFi Network",
+ "WiFi": {
+ "HexSSID": "737369642D6E6F6E65",
+ "Security": "None" }
+ },
+ { "GUID": "{guid-for-wifi-with-device-exp}",
+ "Type": "WiFi",
+ "Name": "My WiFi with device placeholder expansions",
+ "WiFi": {
+ "EAP": {
+ "Outer": "EAP-TLS",
+ "Identity": "${DEVICE_SERIAL_NUMBER}-${DEVICE_ASSET_ID}"
+ },
+ "HexSSID": "7465737431323334",
+ "Security": "WPA-EAP",
+ "SSID": "test1234",
+ }
+ }
+ ],
+ "GlobalNetworkConfiguration": {
+ "AllowOnlyPolicyNetworksToAutoconnect": true,
+ },
+ "Certificates": [
+ { "GUID": "{f998f760-272b-6939-4c2beffe428697ac}",
+ "PKCS12": "YWJj",
+ "Type": "Client" }
+ ],
+ "Type": "UnencryptedConfiguration"
+ })";
std::string ValueToString(const base::Value& value) {
std::stringstream str;
@@ -194,11 +213,6 @@
return str.str();
}
-void AppendAll(const base::ListValue& from, base::ListValue* to) {
- for (const auto& value : from)
- to->Append(value.CreateDeepCopy());
-}
-
// Matcher to match base::Value.
MATCHER_P(IsEqualTo,
value,
@@ -224,6 +238,9 @@
NetworkConfigurationUpdaterTest() : certificate_importer_(NULL) {}
void SetUp() override {
+ fake_statistics_provider_.SetMachineStatistic(
+ chromeos::system::kSerialNumberKey, kFakeSerialNumber);
+
EXPECT_CALL(provider_, IsInitializationComplete(_))
.WillRepeatedly(Return(false));
provider_.Init();
@@ -234,11 +251,6 @@
std::unique_ptr<base::DictionaryValue> fake_toplevel_onc =
chromeos::onc::ReadDictionaryFromJson(kFakeONC);
- base::ListValue* network_configs = NULL;
- fake_toplevel_onc->GetListWithoutPathExpansion(
- onc::toplevel_config::kNetworkConfigurations, &network_configs);
- AppendAll(*network_configs, &fake_network_configs_);
-
base::DictionaryValue* global_config = NULL;
fake_toplevel_onc->GetDictionaryWithoutPathExpansion(
onc::toplevel_config::kGlobalNetworkConfiguration, &global_config);
@@ -254,6 +266,26 @@
certificate_importer_owned_.reset(certificate_importer_);
}
+ base::Value* GetExpectedFakeNetworkConfigs(::onc::ONCSource source) {
+ std::unique_ptr<base::DictionaryValue> fake_toplevel_onc =
+ chromeos::onc::ReadDictionaryFromJson(kFakeONC);
+ fake_network_configs_ =
+ fake_toplevel_onc->FindKey(onc::toplevel_config::kNetworkConfigurations)
+ ->Clone();
+ if (source == ::onc::ONC_SOURCE_DEVICE_POLICY) {
+ std::string expected_identity =
+ std::string(kFakeSerialNumber) + "-" + std::string(kFakeAssetId);
+ SetExpectedValueInNetworkConfig(
+ &fake_network_configs_, "{guid-for-wifi-with-device-exp}",
+ {"WiFi", "EAP", "Identity"}, base::Value(expected_identity));
+ }
+ return &fake_network_configs_;
+ }
+
+ base::Value* GetExpectedFakeGlobalNetworkConfig() {
+ return &fake_global_network_config_;
+ }
+
void TearDown() override {
network_configuration_updater_.reset();
provider_.Shutdown();
@@ -295,23 +327,23 @@
}
void CreateNetworkConfigurationUpdaterForDevicePolicy() {
+ auto testing_device_asset_id_getter =
+ base::BindRepeating([] { return std::string(kFakeAssetId); });
network_configuration_updater_ =
DeviceNetworkConfigurationUpdater::CreateForDevicePolicy(
- policy_service_.get(),
- &network_config_handler_,
- &network_device_handler_,
- chromeos::CrosSettings::Get());
+ policy_service_.get(), &network_config_handler_,
+ &network_device_handler_, chromeos::CrosSettings::Get(),
+ testing_device_asset_id_getter);
}
content::TestBrowserThreadBundle thread_bundle_;
- base::ListValue fake_network_configs_;
- base::DictionaryValue fake_global_network_config_;
std::unique_ptr<chromeos::onc::OncParsedCertificates> fake_certificates_;
StrictMock<chromeos::MockManagedNetworkConfigurationHandler>
network_config_handler_;
FakeNetworkDeviceHandler network_device_handler_;
chromeos::ScopedCrosSettingsTestHelper settings_helper_;
+ chromeos::system::ScopedFakeStatisticsProvider fake_statistics_provider_;
// Ownership of certificate_importer_owned_ is passed to the
// NetworkConfigurationUpdater. When that happens, |certificate_importer_|
@@ -328,6 +360,25 @@
TestingProfile profile_;
std::unique_ptr<NetworkConfigurationUpdater> network_configuration_updater_;
+
+ private:
+ void SetExpectedValueInNetworkConfig(
+ base::Value* network_configs,
+ base::StringPiece guid,
+ std::initializer_list<base::StringPiece> path,
+ base::Value value) {
+ for (base::Value& network_config : network_configs->GetList()) {
+ const base::Value* guid_value =
+ network_config.FindKey(::onc::network_config::kGUID);
+ if (!guid_value || guid_value->GetString() != guid)
+ continue;
+ network_config.SetPath(path, std::move(value));
+ break;
+ }
+ }
+
+ base::Value fake_network_configs_;
+ base::DictionaryValue fake_global_network_config_;
};
TEST_F(NetworkConfigurationUpdaterTest, CellularAllowRoaming) {
@@ -510,11 +561,11 @@
std::make_unique<base::Value>(kFakeONC), nullptr);
UpdateProviderPolicy(policy);
+ ::onc::ONCSource source = onc::ONC_SOURCE_USER_POLICY;
EXPECT_CALL(network_config_handler_,
- SetPolicy(onc::ONC_SOURCE_USER_POLICY,
- kFakeUsernameHash,
- IsEqualTo(&fake_network_configs_),
- IsEqualTo(&fake_global_network_config_)));
+ SetPolicy(source, kFakeUsernameHash,
+ IsEqualTo(GetExpectedFakeNetworkConfigs(source)),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
UserNetworkConfigurationUpdater* updater =
CreateNetworkConfigurationUpdaterForUserPolicy(
@@ -535,6 +586,23 @@
EXPECT_EQ(1u, certificate_importer_->GetAndResetImportCount());
}
+TEST_F(NetworkConfigurationUpdaterTest, ReplaceDeviceOncPlaceholders) {
+ PolicyMap policy;
+ policy.Set(key::kDeviceOpenNetworkConfiguration, POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(kFakeONC), nullptr);
+ UpdateProviderPolicy(policy);
+
+ ::onc::ONCSource source = onc::ONC_SOURCE_DEVICE_POLICY;
+ EXPECT_CALL(network_config_handler_,
+ SetPolicy(source, std::string(),
+ IsEqualTo(GetExpectedFakeNetworkConfigs(source)),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
+
+ CreateNetworkConfigurationUpdaterForDevicePolicy();
+ MarkPolicyProviderInitialized();
+}
+
class NetworkConfigurationUpdaterTestWithParam
: public NetworkConfigurationUpdaterTest,
public testing::WithParamInterface<const char*> {
@@ -579,11 +647,11 @@
nullptr);
UpdateProviderPolicy(policy);
- EXPECT_CALL(network_config_handler_,
- SetPolicy(CurrentONCSource(),
- ExpectedUsernameHash(),
- IsEqualTo(&fake_network_configs_),
- IsEqualTo(&fake_global_network_config_)));
+ EXPECT_CALL(
+ network_config_handler_,
+ SetPolicy(CurrentONCSource(), ExpectedUsernameHash(),
+ IsEqualTo(GetExpectedFakeNetworkConfigs(CurrentONCSource())),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
certificate_importer_->SetExpectedONCCertificates(
std::move(fake_certificates_));
certificate_importer_->SetExpectedONCSource(CurrentONCSource());
@@ -607,11 +675,11 @@
Mock::VerifyAndClearExpectations(&network_config_handler_);
EXPECT_EQ(0u, certificate_importer_->GetAndResetImportCount());
- EXPECT_CALL(network_config_handler_,
- SetPolicy(CurrentONCSource(),
- ExpectedUsernameHash(),
- IsEqualTo(&fake_network_configs_),
- IsEqualTo(&fake_global_network_config_)));
+ EXPECT_CALL(
+ network_config_handler_,
+ SetPolicy(CurrentONCSource(), ExpectedUsernameHash(),
+ IsEqualTo(GetExpectedFakeNetworkConfigs(CurrentONCSource())),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
certificate_importer_->SetExpectedONCSource(CurrentONCSource());
certificate_importer_->SetExpectedONCCertificates(
std::move(fake_certificates_));
@@ -631,11 +699,11 @@
nullptr);
UpdateProviderPolicy(policy);
- EXPECT_CALL(network_config_handler_,
- SetPolicy(CurrentONCSource(),
- ExpectedUsernameHash(),
- IsEqualTo(&fake_network_configs_),
- IsEqualTo(&fake_global_network_config_)));
+ EXPECT_CALL(
+ network_config_handler_,
+ SetPolicy(CurrentONCSource(), ExpectedUsernameHash(),
+ IsEqualTo(GetExpectedFakeNetworkConfigs(CurrentONCSource())),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
certificate_importer_->SetExpectedONCSource(CurrentONCSource());
certificate_importer_->SetExpectedONCCertificates(
std::move(fake_certificates_));
@@ -658,11 +726,11 @@
certificate_importer_->GetAndResetImportCount());
// The Updater should update if policy changes.
- EXPECT_CALL(network_config_handler_,
- SetPolicy(CurrentONCSource(),
- _,
- IsEqualTo(&fake_network_configs_),
- IsEqualTo(&fake_global_network_config_)));
+ EXPECT_CALL(
+ network_config_handler_,
+ SetPolicy(CurrentONCSource(), _,
+ IsEqualTo(GetExpectedFakeNetworkConfigs(CurrentONCSource())),
+ IsEqualTo(GetExpectedFakeGlobalNetworkConfig())));
certificate_importer_->SetExpectedONCSource(CurrentONCSource());
certificate_importer_->SetExpectedONCCertificates(
std::move(fake_certificates_));
diff --git a/chromeos/network/certificate_helper.cc b/chromeos/network/certificate_helper.cc
index b72cb86..b82193b 100644
--- a/chromeos/network/certificate_helper.cc
+++ b/chromeos/network/certificate_helper.cc
@@ -83,6 +83,10 @@
return name;
}
+std::string GetCertAsciiSubjectCommonName(CERTCertificate* cert_handle) {
+ return Stringize(CERT_GetCommonName(&cert_handle->subject), std::string());
+}
+
std::string GetCertAsciiNameOrNickname(CERTCertificate* cert_handle) {
std::string alternative_text = GetNickname(cert_handle);
return Stringize(CERT_GetCommonName(&cert_handle->subject), alternative_text);
diff --git a/chromeos/network/certificate_helper.h b/chromeos/network/certificate_helper.h
index 28368118..f3228249 100644
--- a/chromeos/network/certificate_helper.h
+++ b/chromeos/network/certificate_helper.h
@@ -35,7 +35,14 @@
// value (see GetCertAsciiNameOrNickname instead).
CHROMEOS_EXPORT std::string GetCertNameOrNickname(CERTCertificate* cert_handle);
-// Returns the unformated ASCII common name for |cert_handle|->subject.
+// Returns the unformated ASCII common name for |cert_handle|->subject. Returns
+// an empty string if the subject name is unavailable or empty.
+CHROMEOS_EXPORT std::string GetCertAsciiSubjectCommonName(
+ CERTCertificate* cert_handle);
+
+// Returns the unformatted ASCII common name for |cert_handle|->subject or for
+// |cert_handle|->nickname if the subject name is unavailable or empty. If both
+// are not available, returns an empty string.
CHROMEOS_EXPORT std::string GetCertAsciiNameOrNickname(
CERTCertificate* cert_handle);
diff --git a/chromeos/network/client_cert_resolver.cc b/chromeos/network/client_cert_resolver.cc
index d350134..ddd329e 100644
--- a/chromeos/network/client_cert_resolver.cc
+++ b/chromeos/network/client_cert_resolver.cc
@@ -17,14 +17,15 @@
#include "base/logging.h"
#include "base/optional.h"
#include "base/stl_util.h"
-#include "base/strings/string_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/time/clock.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/shill_service_client.h"
+#include "chromeos/network/certificate_helper.h"
#include "chromeos/network/managed_network_configuration_handler.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state.h"
+#include "chromeos/tools/variable_expander.h"
#include "components/onc/onc_constants.h"
#include "dbus/object_path.h"
#include "net/cert/scoped_nss_types.h"
@@ -80,6 +81,39 @@
// pattern resolution.
enum class ResolveStatus { kResolving, kResolved };
+// Returns substitutions based on |cert|'s contents to be used in a
+// VariableExpander.
+std::map<std::string, std::string> GetSubstitutionsForCert(
+ CERTCertificate* cert) {
+ std::map<std::string, std::string> substitutions;
+
+ {
+ std::vector<std::string> names;
+ net::x509_util::GetRFC822SubjectAltNames(cert, &names);
+ // Currently, we only use the first specified RFC8222
+ // SubjectAlternativeName.
+ std::string firstSANEmail;
+ if (!names.empty())
+ firstSANEmail = names[0];
+ substitutions[onc::substitutes::kCertSANEmail] = firstSANEmail;
+ }
+
+ {
+ std::vector<std::string> names;
+ net::x509_util::GetUPNSubjectAltNames(cert, &names);
+ // Currently, we only use the first specified UPN SubjectAlternativeName.
+ std::string firstSANUPN;
+ if (!names.empty())
+ firstSANUPN = names[0];
+ substitutions[onc::substitutes::kCertSANUPN] = firstSANUPN;
+ }
+
+ substitutions[onc::substitutes::kCertSubjectCommonName] =
+ certificate::GetCertAsciiSubjectCommonName(cert);
+
+ return substitutions;
+}
+
} // namespace
namespace internal {
@@ -286,7 +320,6 @@
std::string pkcs11_id;
int slot_id = -1;
- std::string identity;
pkcs11_id =
CertLoader::GetPkcs11IdAndSlotForCert(cert_it->cert.get(), &slot_id);
@@ -297,31 +330,14 @@
continue;
}
- // If the policy specifies an identity containing ${CERT_SAN_xxx},
- // see if the cert contains a suitable subjectAltName that can be
- // stuffed into the shill properties.
- identity = network_and_pattern.cert_config.policy_identity;
- std::vector<std::string> names;
-
- size_t offset = identity.find(onc::substitutes::kCertSANEmail, 0);
- if (offset != std::string::npos) {
- std::vector<std::string> names;
- net::x509_util::GetRFC822SubjectAltNames(cert_it->cert.get(), &names);
- if (!names.empty()) {
- base::ReplaceSubstringsAfterOffset(
- &identity, offset, onc::substitutes::kCertSANEmail, names[0]);
- }
- }
-
- offset = identity.find(onc::substitutes::kCertSANUPN, 0);
- if (offset != std::string::npos) {
- std::vector<std::string> names;
- net::x509_util::GetUPNSubjectAltNames(cert_it->cert.get(), &names);
- if (!names.empty()) {
- base::ReplaceSubstringsAfterOffset(
- &identity, offset, onc::substitutes::kCertSANUPN, names[0]);
- }
- }
+ // Expand placeholders in the identity string that are specific to the
+ // client certificate.
+ VariableExpander variable_expander(
+ GetSubstitutionsForCert(cert_it->cert.get()));
+ std::string identity = network_and_pattern.cert_config.policy_identity;
+ const bool success = variable_expander.ExpandString(&identity);
+ LOG_IF(ERROR, !success)
+ << "Error during variable expansion in ONC-configured identity";
matches.push_back(NetworkAndMatchingCert(
network_and_pattern, MatchingCert(pkcs11_id, slot_id, identity)));
diff --git a/chromeos/network/client_cert_resolver_unittest.cc b/chromeos/network/client_cert_resolver_unittest.cc
index b41edc92..d69b055f 100644
--- a/chromeos/network/client_cert_resolver_unittest.cc
+++ b/chromeos/network/client_cert_resolver_unittest.cc
@@ -731,6 +731,16 @@
GetServiceProperty(shill::kEapIdentityProperty, &identity);
EXPECT_EQ("upn-santest@ad.corp.example.com-suffix", identity);
EXPECT_EQ(2, network_properties_changed_count_);
+
+ // Verify that after changing the ONC policy to request the subject CommonName
+ // field, the correct value is substituted into the shill service entry.
+ SetupPolicyMatchingIssuerPEM(onc::ONC_SOURCE_USER_POLICY,
+ "subject-cn-${CERT_SUBJECT_COMMON_NAME}-suffix");
+ scoped_task_environment_.RunUntilIdle();
+
+ GetServiceProperty(shill::kEapIdentityProperty, &identity);
+ EXPECT_EQ("subject-cn-Client Cert F-suffix", identity);
+ EXPECT_EQ(3, network_properties_changed_count_);
}
// Test for crbug.com/781276. A notification which results in no networks to be
diff --git a/chromeos/network/onc/onc_translator_onc_to_shill.cc b/chromeos/network/onc/onc_translator_onc_to_shill.cc
index 7cb3bfe..7a1e315 100644
--- a/chromeos/network/onc/onc_translator_onc_to_shill.cc
+++ b/chromeos/network/onc/onc_translator_onc_to_shill.cc
@@ -323,8 +323,8 @@
// password substitution variable is set.
const base::Value* password_field =
onc_object_->FindKey(::onc::eap::kPassword);
- if (password_field &&
- password_field->GetString() == ::onc::substitutes::kPasswordField) {
+ if (password_field && password_field->GetString() ==
+ ::onc::substitutes::kPasswordPlaceholderVerbatim) {
shill_dictionary_->SetKey(shill::kEapUseLoginPasswordProperty,
base::Value(true));
}
diff --git a/chromeos/network/onc/onc_translator_shill_to_onc.cc b/chromeos/network/onc/onc_translator_shill_to_onc.cc
index faf2104..f9baff2 100644
--- a/chromeos/network/onc/onc_translator_shill_to_onc.cc
+++ b/chromeos/network/onc/onc_translator_shill_to_onc.cc
@@ -709,8 +709,9 @@
if (shill_dictionary_->GetBooleanWithoutPathExpansion(
shill::kEapUseLoginPasswordProperty, &use_login_password) &&
use_login_password) {
- onc_object_->SetKey(::onc::eap::kPassword,
- base::Value(::onc::substitutes::kPasswordField));
+ onc_object_->SetKey(
+ ::onc::eap::kPassword,
+ base::Value(::onc::substitutes::kPasswordPlaceholderVerbatim));
}
}
diff --git a/chromeos/network/onc/onc_utils.cc b/chromeos/network/onc/onc_utils.cc
index c086a7ee..4baca3a 100644
--- a/chromeos/network/onc/onc_utils.cc
+++ b/chromeos/network/onc/onc_utils.cc
@@ -65,277 +65,33 @@
namespace {
-const char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC";
-const char kUnableToDecode[] = "Unable to decode encrypted ONC";
+// Error messages that can be reported when decrypting encrypted ONC.
+constexpr char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC";
+constexpr char kUnableToDecode[] = "Unable to decode encrypted ONC";
-// This class defines which string placeholders of ONC are replaced by which
-// user attribute.
-class UserStringSubstitution : public chromeos::onc::StringSubstitution {
- public:
- explicit UserStringSubstitution(const user_manager::User* user)
- : user_(user) {}
- ~UserStringSubstitution() override = default;
+// Scheme strings for supported |net::ProxyServer::SCHEME_*| enum values.
+constexpr char kDirectScheme[] = "direct";
+constexpr char kQuicScheme[] = "quic";
+constexpr char kSocksScheme[] = "socks";
+constexpr char kSocks4Scheme[] = "socks4";
+constexpr char kSocks5Scheme[] = "socks5";
- bool GetSubstitute(const std::string& placeholder,
- std::string* substitute) const override {
- if (placeholder == ::onc::substitutes::kLoginIDField)
- *substitute = user_->GetAccountName(false);
- else if (placeholder == ::onc::substitutes::kEmailField)
- *substitute = user_->GetAccountId().GetUserEmail();
- else
- return false;
- return true;
- }
-
- private:
- const user_manager::User* user_;
-
- DISALLOW_COPY_AND_ASSIGN(UserStringSubstitution);
-};
-
-} // namespace
-
-const char kEmptyUnencryptedConfiguration[] =
- "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[],"
- "\"Certificates\":[]}";
-
-std::unique_ptr<base::DictionaryValue> ReadDictionaryFromJson(
- const std::string& json) {
- std::string error;
- std::unique_ptr<base::Value> root = base::JSONReader::ReadAndReturnError(
- json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error);
-
- base::DictionaryValue* dict_ptr = nullptr;
- if (!root || !root->GetAsDictionary(&dict_ptr)) {
- NET_LOG(ERROR) << "Invalid JSON Dictionary: " << error;
- return nullptr;
- }
- ignore_result(root.release());
- return base::WrapUnique(dict_ptr);
-}
-
-std::unique_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(toplevel_config::kType, &onc_type) ||
- onc_type != toplevel_config::kEncryptedConfiguration) {
- NET_LOG(ERROR) << "Encrypted ONC malformed.";
- return nullptr;
- }
-
- if (hmac_method != encrypted::kSHA1 ||
- cipher != encrypted::kAES256 ||
- stretch_method != encrypted::kPBKDF2) {
- NET_LOG(ERROR) << "Encrypted ONC unsupported encryption scheme.";
- return nullptr;
- }
-
- // Make sure iterations != 0, since that's not valid.
- if (iterations == 0) {
- NET_LOG(ERROR) << kUnableToDecrypt;
- return nullptr;
- }
-
- // 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) {
- NET_LOG(ERROR) << "Too many iterations in encrypted ONC";
- return nullptr;
- }
-
- if (!base::Base64Decode(salt, &salt)) {
- NET_LOG(ERROR) << kUnableToDecode;
- return nullptr;
- }
-
- std::unique_ptr<crypto::SymmetricKey> key(
- crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES,
- passphrase, salt, iterations,
- kKeySizeInBits));
-
- if (!base::Base64Decode(initial_vector, &initial_vector)) {
- NET_LOG(ERROR) << kUnableToDecode;
- return nullptr;
- }
- if (!base::Base64Decode(ciphertext, &ciphertext)) {
- NET_LOG(ERROR) << kUnableToDecode;
- return nullptr;
- }
- if (!base::Base64Decode(hmac, &hmac)) {
- NET_LOG(ERROR) << kUnableToDecode;
- return nullptr;
- }
-
- crypto::HMAC hmac_verifier(crypto::HMAC::SHA1);
- if (!hmac_verifier.Init(key.get()) ||
- !hmac_verifier.Verify(ciphertext, hmac)) {
- NET_LOG(ERROR) << kUnableToDecrypt;
- return nullptr;
- }
-
- crypto::Encryptor decryptor;
- if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector)) {
- NET_LOG(ERROR) << kUnableToDecrypt;
- return nullptr;
- }
-
- std::string plaintext;
- if (!decryptor.Decrypt(ciphertext, &plaintext)) {
- NET_LOG(ERROR) << kUnableToDecrypt;
- return nullptr;
- }
-
- std::unique_ptr<base::DictionaryValue> new_root =
- ReadDictionaryFromJson(plaintext);
- if (!new_root) {
- NET_LOG(ERROR) << "Property dictionary malformed.";
- return nullptr;
- }
-
- return new_root;
-}
-
-std::string GetSourceAsString(ONCSource source) {
- switch (source) {
- case ONC_SOURCE_UNKNOWN:
- return "unknown";
- case ONC_SOURCE_NONE:
- return "none";
- case ONC_SOURCE_DEVICE_POLICY:
- return "device policy";
- case ONC_SOURCE_USER_POLICY:
- return "user policy";
- case ONC_SOURCE_USER_IMPORT:
- return "user import";
- }
- NOTREACHED() << "unknown ONC source " << source;
- return "unknown";
-}
-
+// Runs |variable_expander.ExpandString| on the field |fieldname| in
+// |onc_object|.
void ExpandField(const std::string& fieldname,
- const StringSubstitution& substitution,
+ const VariableExpander& variable_expander,
base::DictionaryValue* onc_object) {
- std::string user_string;
- if (!onc_object->GetStringWithoutPathExpansion(fieldname, &user_string))
+ std::string field_value;
+ if (!onc_object->GetStringWithoutPathExpansion(fieldname, &field_value))
return;
- std::string login_id;
- if (substitution.GetSubstitute(substitutes::kLoginIDField, &login_id)) {
- base::ReplaceSubstringsAfterOffset(&user_string, 0,
- substitutes::kLoginIDField,
- login_id);
- }
+ variable_expander.ExpandString(&field_value);
- std::string email;
- if (substitution.GetSubstitute(substitutes::kEmailField, &email)) {
- base::ReplaceSubstringsAfterOffset(&user_string, 0,
- substitutes::kEmailField,
- email);
- }
-
- onc_object->SetKey(fieldname, base::Value(user_string));
+ onc_object->SetKey(fieldname, base::Value(field_value));
}
-void ExpandStringsInOncObject(
- const OncValueSignature& signature,
- const StringSubstitution& substitution,
- base::DictionaryValue* onc_object) {
- if (&signature == &kEAPSignature) {
- ExpandField(eap::kAnonymousIdentity, substitution, onc_object);
- ExpandField(eap::kIdentity, substitution, onc_object);
- } else if (&signature == &kL2TPSignature ||
- &signature == &kOpenVPNSignature) {
- ExpandField(vpn::kUsername, substitution, onc_object);
- }
-
- // Recurse into nested objects.
- for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
- it.Advance()) {
- base::DictionaryValue* inner_object = nullptr;
- if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
- continue;
-
- const OncFieldSignature* field_signature =
- GetFieldSignature(signature, it.key());
- if (!field_signature)
- continue;
-
- ExpandStringsInOncObject(*field_signature->value_signature,
- substitution, inner_object);
- }
-}
-
-void ExpandStringsInNetworks(const StringSubstitution& substitution,
- base::ListValue* network_configs) {
- for (auto& entry : *network_configs) {
- base::DictionaryValue* network = nullptr;
- entry.GetAsDictionary(&network);
- DCHECK(network);
- ExpandStringsInOncObject(
- kNetworkConfigurationSignature, substitution, network);
- }
-}
-
-void FillInHexSSIDFieldsInOncObject(const OncValueSignature& signature,
- base::DictionaryValue* onc_object) {
- if (&signature == &kWiFiSignature)
- FillInHexSSIDField(onc_object);
-
- // Recurse into nested objects.
- for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
- it.Advance()) {
- base::DictionaryValue* inner_object = nullptr;
- if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
- continue;
-
- const OncFieldSignature* field_signature =
- GetFieldSignature(signature, it.key());
- if (!field_signature)
- continue;
-
- FillInHexSSIDFieldsInOncObject(*field_signature->value_signature,
- inner_object);
- }
-}
-
-void FillInHexSSIDField(base::DictionaryValue* wifi_fields) {
- std::string ssid_string;
- if (wifi_fields->HasKey(::onc::wifi::kHexSSID) ||
- !wifi_fields->GetStringWithoutPathExpansion(::onc::wifi::kSSID,
- &ssid_string)) {
- return;
- }
- if (ssid_string.empty()) {
- NET_LOG(ERROR) << "Found empty SSID field.";
- return;
- }
- wifi_fields->SetKey(
- ::onc::wifi::kHexSSID,
- base::Value(base::HexEncode(ssid_string.c_str(), ssid_string.size())));
-}
-
-namespace {
-
+// A |Mapper| for masking sensitive fields (e.g. credentials such as
+// passphrases) in ONC.
class OncMaskValues : public Mapper {
public:
static std::unique_ptr<base::DictionaryValue> Mask(
@@ -362,7 +118,7 @@
// If it's the password field and the substitution string is used, don't
// mask it.
if (&object_signature == &kEAPSignature && field_name == eap::kPassword &&
- onc_value.GetString() == substitutes::kPasswordField) {
+ onc_value.GetString() == substitutes::kPasswordPlaceholderVerbatim) {
return Mapper::MapField(field_name, object_signature, onc_value,
found_unknown_field, error);
}
@@ -377,44 +133,8 @@
std::string mask_;
};
-} // namespace
-
-std::unique_ptr<base::DictionaryValue> MaskCredentialsInOncObject(
- const OncValueSignature& signature,
- const base::DictionaryValue& onc_object,
- const std::string& mask) {
- return OncMaskValues::Mask(signature, onc_object, mask);
-}
-
-std::string DecodePEM(const std::string& pem_encoded) {
- // 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";
-
- std::vector<std::string> pem_headers;
- pem_headers.push_back(kCertificateHeader);
- pem_headers.push_back(kX509CertificateHeader);
-
- net::PEMTokenizer pem_tokenizer(pem_encoded, pem_headers);
- std::string decoded;
- if (pem_tokenizer.GetNext()) {
- decoded = pem_tokenizer.data();
- } else {
- // If we failed to read the data as a PEM file, then try plain base64 decode
- // in case the PEM marker strings are missing. For this to work, there has
- // to be no white space, and it has to only contain the base64-encoded data.
- if (!base::Base64Decode(pem_encoded, &decoded)) {
- LOG(ERROR) << "Unable to base64 decode X509 data: " << pem_encoded;
- return std::string();
- }
- }
- return decoded;
-}
-
-namespace {
-
+// Returns a map GUID->PEM of all server and authority certificates defined in
+// the Certificates section of ONC, which is passed in as |certificates|.
CertPEMsByGUIDMap GetServerAndCACertsByGUID(
const base::ListValue& certificates) {
CertPEMsByGUIDMap certs_by_guid;
@@ -447,6 +167,7 @@
return certs_by_guid;
}
+// Fills HexSSID fields in all entries in the |network_configs| list.
void FillInHexSSIDFieldsInNetworks(base::ListValue* network_configs) {
for (auto& entry : *network_configs) {
base::DictionaryValue* network = nullptr;
@@ -456,129 +177,10 @@
}
}
-} // namespace
-
-bool ParseAndValidateOncForImport(const std::string& onc_blob,
- ONCSource onc_source,
- const std::string& passphrase,
- base::ListValue* network_configs,
- base::DictionaryValue* global_network_config,
- base::ListValue* certificates) {
- if (network_configs)
- network_configs->Clear();
- if (global_network_config)
- global_network_config->Clear();
- if (certificates)
- certificates->Clear();
- if (onc_blob.empty())
- return true;
-
- std::unique_ptr<base::DictionaryValue> toplevel_onc =
- ReadDictionaryFromJson(onc_blob);
- if (!toplevel_onc) {
- LOG(ERROR) << "ONC loaded from " << GetSourceAsString(onc_source)
- << " is not a valid JSON dictionary.";
- return false;
- }
-
- // Check and see if this is an encrypted ONC file. If so, decrypt it.
- std::string onc_type;
- toplevel_onc->GetStringWithoutPathExpansion(toplevel_config::kType,
- &onc_type);
- if (onc_type == toplevel_config::kEncryptedConfiguration) {
- toplevel_onc = Decrypt(passphrase, *toplevel_onc);
- if (!toplevel_onc) {
- LOG(ERROR) << "Couldn't decrypt the ONC from "
- << GetSourceAsString(onc_source);
- return false;
- }
- }
-
- bool from_policy = (onc_source == ONC_SOURCE_USER_POLICY ||
- onc_source == ONC_SOURCE_DEVICE_POLICY);
-
- // Validate the ONC dictionary. We are liberal and ignore unknown field
- // names and ignore invalid field names in kRecommended arrays.
- Validator validator(false, // Ignore unknown fields.
- false, // Ignore invalid recommended field names.
- true, // Fail on missing fields.
- from_policy);
- validator.SetOncSource(onc_source);
-
- Validator::Result validation_result;
- toplevel_onc = validator.ValidateAndRepairObject(
- &kToplevelConfigurationSignature,
- *toplevel_onc,
- &validation_result);
-
- if (from_policy) {
- UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation",
- validation_result == Validator::VALID);
- }
-
- bool success = true;
- if (validation_result == Validator::VALID_WITH_WARNINGS) {
- LOG(WARNING) << "ONC from " << GetSourceAsString(onc_source)
- << " produced warnings.";
- success = false;
- } else if (validation_result == Validator::INVALID || !toplevel_onc) {
- LOG(ERROR) << "ONC from " << GetSourceAsString(onc_source)
- << " is invalid and couldn't be repaired.";
- return false;
- }
-
- base::ListValue* validated_certs = nullptr;
- if (certificates && toplevel_onc->GetListWithoutPathExpansion(
- toplevel_config::kCertificates, &validated_certs)) {
- certificates->Swap(validated_certs);
- }
-
- base::ListValue* validated_networks = nullptr;
- // Note that this processing is performed even if |network_configs| is
- // nullptr, because ResolveServerCertRefsInNetworks could affect the return
- // value of the function (which is supposed to aggregate validation issues in
- // all segments of the ONC blob).
- if (toplevel_onc->GetListWithoutPathExpansion(
- toplevel_config::kNetworkConfigurations, &validated_networks)) {
- FillInHexSSIDFieldsInNetworks(validated_networks);
-
- CertPEMsByGUIDMap server_and_ca_certs =
- GetServerAndCACertsByGUID(*certificates);
-
- if (!ResolveServerCertRefsInNetworks(server_and_ca_certs,
- validated_networks)) {
- LOG(ERROR) << "Some certificate references in the ONC policy for source "
- << GetSourceAsString(onc_source) << " could not be resolved.";
- success = false;
- }
-
- if (network_configs)
- network_configs->Swap(validated_networks);
- }
-
- base::DictionaryValue* validated_global_config = nullptr;
- if (global_network_config && toplevel_onc->GetDictionaryWithoutPathExpansion(
- toplevel_config::kGlobalNetworkConfiguration,
- &validated_global_config)) {
- global_network_config->Swap(validated_global_config);
- }
-
- return success;
-}
-
-net::ScopedCERTCertificate DecodePEMCertificate(
- const std::string& pem_encoded) {
- std::string decoded = DecodePEM(pem_encoded);
- net::ScopedCERTCertificate cert =
- net::x509_util::CreateCERTCertificateFromBytes(
- reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
- LOG_IF(ERROR, !cert.get()) << "Couldn't create certificate from X509 data: "
- << decoded;
- return cert;
-}
-
-namespace {
-
+// Given a GUID->PEM certificate mapping |certs_by_guid|, looks up the PEM
+// encoded certificate referenced by |guid_ref|. If a match is found, sets
+// |*pem_encoded| to the PEM encoded certificate and returns true. Otherwise,
+// returns false.
bool GUIDRefToPEMEncoding(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& guid_ref,
std::string* pem_encoded) {
@@ -595,6 +197,12 @@
return true;
}
+// Given a GUID-> PM certificate mapping |certs_by_guid|, attempts to resolve
+// the certificate referenced by the |key_guid_ref| field in |onc_object|.
+// * If |onc_object| has no |key_guid_ref| field, returns true.
+// * If no matching certificate is found in |certs_by_guid|, returns false.
+// * If a matching certificate is found, removes the |key_guid_ref| field,
+// fills the |key_pem| field in |onc_object| and returns true.
bool ResolveSingleCertRef(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref,
const std::string& key_pem,
@@ -612,6 +220,16 @@
return true;
}
+// Given a GUID-> PM certificate mapping |certs_by_guid|, attempts to resolve
+// the certificates referenced by the list-of-strings field |key_guid_ref_list|
+// in |onc_object|.
+// * If |key_guid_ref_list| does not exist in |onc_object|, returns true.
+// * If any element |key_guid_ref_list| can not be found in |certs_by_guid|,
+// aborts processing and returns false. |onc_object| is unchanged in this
+// case.
+// * Otherwise, sets |key_pem_list| to be a list-of-strings field in
+// |onc_object|, containing all PEM encoded resolved certificates in order and
+// returns true.
bool ResolveCertRefList(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref_list,
const std::string& key_pem_list,
@@ -640,6 +258,8 @@
return true;
}
+// Same as |ResolveSingleCertRef|, but the output |key_pem_list| will be set to
+// a list with exactly one value when resolution succeeds.
bool ResolveSingleCertRefToList(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref,
const std::string& key_pem_list,
@@ -682,6 +302,8 @@
certs_by_guid, key_guid_ref, key_pem_list, onc_object);
}
+// Resolve known server and authority certiifcate reference fields in
+// |onc_object|.
bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap& certs_by_guid,
const OncValueSignature& signature,
base::DictionaryValue* onc_object) {
@@ -744,88 +366,6 @@
return true;
}
-} // namespace
-
-bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap& certs_by_guid,
- base::ListValue* network_configs) {
- bool success = true;
- for (base::ListValue::iterator it = network_configs->begin();
- it != network_configs->end(); ) {
- base::DictionaryValue* network = nullptr;
- it->GetAsDictionary(&network);
- if (!ResolveServerCertRefsInNetwork(certs_by_guid, network)) {
- std::string guid;
- network->GetStringWithoutPathExpansion(network_config::kGUID, &guid);
- // This might happen even with correct validation, if the referenced
- // certificate couldn't be imported.
- LOG(ERROR) << "Couldn't resolve some certificate reference of network "
- << guid;
- it = network_configs->Erase(it, nullptr);
- success = false;
- continue;
- }
- ++it;
- }
- return success;
-}
-
-bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap& certs_by_guid,
- base::DictionaryValue* network_config) {
- return ResolveServerCertRefsInObject(certs_by_guid,
- kNetworkConfigurationSignature,
- network_config);
-}
-
-NetworkTypePattern NetworkTypePatternFromOncType(const std::string& type) {
- if (type == ::onc::network_type::kAllTypes)
- return NetworkTypePattern::Default();
- if (type == ::onc::network_type::kCellular)
- return NetworkTypePattern::Cellular();
- if (type == ::onc::network_type::kEthernet)
- return NetworkTypePattern::Ethernet();
- if (type == ::onc::network_type::kTether)
- return NetworkTypePattern::Tether();
- if (type == ::onc::network_type::kVPN)
- return NetworkTypePattern::VPN();
- if (type == ::onc::network_type::kWiFi)
- return NetworkTypePattern::WiFi();
- if (type == ::onc::network_type::kWimax)
- return NetworkTypePattern::Wimax();
- if (type == ::onc::network_type::kWireless)
- return NetworkTypePattern::Wireless();
- NOTREACHED() << "Unrecognized ONC type: " << type;
- return NetworkTypePattern::Default();
-}
-
-bool IsRecommendedValue(const base::DictionaryValue* onc,
- const std::string& property_key) {
- std::string property_basename, recommended_property_key;
- size_t pos = property_key.find_last_of('.');
- if (pos != std::string::npos) {
- // 'WiFi.AutoConnect' -> 'AutoConnect', 'WiFi.Recommended'
- property_basename = property_key.substr(pos + 1);
- recommended_property_key =
- property_key.substr(0, pos + 1) + ::onc::kRecommended;
- } else {
- // 'Name' -> 'Name', 'Recommended'
- property_basename = property_key;
- recommended_property_key = ::onc::kRecommended;
- }
-
- const base::ListValue* recommended_keys = nullptr;
- return (onc->GetList(recommended_property_key, &recommended_keys) &&
- recommended_keys->Find(base::Value(property_basename)) !=
- recommended_keys->end());
-}
-
-namespace {
-
-const char kDirectScheme[] = "direct";
-const char kQuicScheme[] = "quic";
-const char kSocksScheme[] = "socks";
-const char kSocks4Scheme[] = "socks4";
-const char kSocks5Scheme[] = "socks5";
-
net::ProxyServer ConvertOncProxyLocationToHostPort(
net::ProxyServer::Scheme default_proxy_scheme,
const base::DictionaryValue& onc_proxy_location) {
@@ -939,8 +479,591 @@
dict->SetWithoutPathExpansion(onc_scheme, std::move(url_dict));
}
+// Returns the NetworkConfiugration with |guid| from |network_configs|, or
+// nullptr if no such NetworkConfiguration is found.
+const base::DictionaryValue* GetNetworkConfigByGUID(
+ const base::ListValue& network_configs,
+ const std::string& guid) {
+ for (base::ListValue::const_iterator it = network_configs.begin();
+ it != network_configs.end(); ++it) {
+ const base::DictionaryValue* network = NULL;
+ it->GetAsDictionary(&network);
+ DCHECK(network);
+
+ std::string current_guid;
+ network->GetStringWithoutPathExpansion(::onc::network_config::kGUID,
+ ¤t_guid);
+ if (current_guid == guid)
+ return network;
+ }
+ return NULL;
+}
+
+// Returns the first Ethernet NetworkConfiguration from |network_configs| with
+// "Authentication: None", or nullptr if no such NetworkConfiguration is found.
+const base::DictionaryValue* GetNetworkConfigForEthernetWithoutEAP(
+ const base::ListValue& network_configs) {
+ VLOG(2) << "Search for ethernet policy without EAP.";
+ for (base::ListValue::const_iterator it = network_configs.begin();
+ it != network_configs.end(); ++it) {
+ const base::DictionaryValue* network = NULL;
+ it->GetAsDictionary(&network);
+ DCHECK(network);
+
+ std::string type;
+ network->GetStringWithoutPathExpansion(::onc::network_config::kType, &type);
+ if (type != ::onc::network_type::kEthernet)
+ continue;
+
+ const base::DictionaryValue* ethernet = NULL;
+ network->GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet,
+ ðernet);
+
+ std::string auth;
+ ethernet->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
+ &auth);
+ if (auth == ::onc::ethernet::kAuthenticationNone)
+ return network;
+ }
+ return NULL;
+}
+
+// Returns the NetworkConfiguration object for |network| from
+// |network_configs| or nullptr if no matching NetworkConfiguration is found. If
+// |network| is a non-Ethernet network, performs a lookup by GUID. If |network|
+// is an Ethernet network, tries lookup of the GUID of the shared EthernetEAP
+// service, or otherwise returns the first Ethernet NetworkConfiguration with
+// "Authentication: None".
+const base::DictionaryValue* GetNetworkConfigForNetworkFromOnc(
+ const base::ListValue& network_configs,
+ const NetworkState& network) {
+ // In all cases except Ethernet, we use the GUID of |network|.
+ if (!network.Matches(NetworkTypePattern::Ethernet()))
+ return GetNetworkConfigByGUID(network_configs, network.guid());
+
+ // Ethernet is always shared and thus cannot store a GUID per user. Thus we
+ // search for any Ethernet policy intead of a matching GUID.
+ // EthernetEAP service contains only the EAP parameters and stores the GUID of
+ // the respective ONC policy. The EthernetEAP service itself is however never
+ // in state "connected". An EthernetEAP policy must be applied, if an Ethernet
+ // service is connected using the EAP parameters.
+ const NetworkState* ethernet_eap = NULL;
+ if (NetworkHandler::IsInitialized()) {
+ ethernet_eap =
+ NetworkHandler::Get()->network_state_handler()->GetEAPForEthernet(
+ network.path());
+ }
+
+ // The GUID associated with the EthernetEAP service refers to the ONC policy
+ // with "Authentication: 8021X".
+ if (ethernet_eap)
+ return GetNetworkConfigByGUID(network_configs, ethernet_eap->guid());
+
+ // Otherwise, EAP is not used and instead the Ethernet policy with
+ // "Authentication: None" applies.
+ return GetNetworkConfigForEthernetWithoutEAP(network_configs);
+}
+
+// Expects |pref_name| in |pref_service| to be a pref holding an ONC blob.
+// Returns the NetworkConfiguration ONC object for |network| from this ONC, or
+// nullptr if no configuration is found. See |GetNetworkConfigForNetworkFromOnc|
+// for the NetworkConfiguration lookup rules.
+const base::DictionaryValue* GetPolicyForNetworkFromPref(
+ const PrefService* pref_service,
+ const char* pref_name,
+ const NetworkState& network) {
+ if (!pref_service) {
+ VLOG(2) << "No pref service";
+ return NULL;
+ }
+
+ const PrefService::Preference* preference =
+ pref_service->FindPreference(pref_name);
+ if (!preference) {
+ VLOG(2) << "No preference " << pref_name;
+ // The preference may not exist in tests.
+ return NULL;
+ }
+
+ // User prefs are not stored in this Preference yet but only the policy.
+ //
+ // The policy server incorrectly configures the OpenNetworkConfiguration user
+ // policy as Recommended. To work around that, we handle the Recommended and
+ // the Mandatory value in the same way.
+ // TODO(pneubeck): Remove this workaround, once the server is fixed. See
+ // http://crbug.com/280553 .
+ if (preference->IsDefaultValue()) {
+ VLOG(2) << "Preference has no recommended or mandatory value.";
+ // No policy set.
+ return NULL;
+ }
+ VLOG(2) << "Preference with policy found.";
+ const base::Value* onc_policy_value = preference->GetValue();
+ DCHECK(onc_policy_value);
+
+ const base::ListValue* onc_policy = NULL;
+ onc_policy_value->GetAsList(&onc_policy);
+ DCHECK(onc_policy);
+
+ return GetNetworkConfigForNetworkFromOnc(*onc_policy, network);
+}
+
+// Returns the global network configuration dictionary from the ONC policy of
+// the active user if |for_active_user| is true, or from device policy if it is
+// false.
+const base::DictionaryValue* GetGlobalConfigFromPolicy(bool for_active_user) {
+ std::string username_hash;
+ if (for_active_user) {
+ const user_manager::User* user =
+ user_manager::UserManager::Get()->GetActiveUser();
+ if (!user) {
+ LOG(ERROR) << "No user logged in yet.";
+ return NULL;
+ }
+ username_hash = user->username_hash();
+ }
+ return NetworkHandler::Get()
+ ->managed_network_configuration_handler()
+ ->GetGlobalConfigFromPolicy(username_hash);
+}
+
} // namespace
+const char kEmptyUnencryptedConfiguration[] =
+ "{\"Type\":\"UnencryptedConfiguration\",\"NetworkConfigurations\":[],"
+ "\"Certificates\":[]}";
+
+std::unique_ptr<base::DictionaryValue> ReadDictionaryFromJson(
+ const std::string& json) {
+ std::string error;
+ std::unique_ptr<base::Value> root = base::JSONReader::ReadAndReturnError(
+ json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error);
+
+ base::DictionaryValue* dict_ptr = nullptr;
+ if (!root || !root->GetAsDictionary(&dict_ptr)) {
+ NET_LOG(ERROR) << "Invalid JSON Dictionary: " << error;
+ return nullptr;
+ }
+ ignore_result(root.release());
+ return base::WrapUnique(dict_ptr);
+}
+
+std::unique_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(toplevel_config::kType, &onc_type) ||
+ onc_type != toplevel_config::kEncryptedConfiguration) {
+ NET_LOG(ERROR) << "Encrypted ONC malformed.";
+ return nullptr;
+ }
+
+ if (hmac_method != encrypted::kSHA1 || cipher != encrypted::kAES256 ||
+ stretch_method != encrypted::kPBKDF2) {
+ NET_LOG(ERROR) << "Encrypted ONC unsupported encryption scheme.";
+ return nullptr;
+ }
+
+ // Make sure iterations != 0, since that's not valid.
+ if (iterations == 0) {
+ NET_LOG(ERROR) << kUnableToDecrypt;
+ return nullptr;
+ }
+
+ // 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) {
+ NET_LOG(ERROR) << "Too many iterations in encrypted ONC";
+ return nullptr;
+ }
+
+ if (!base::Base64Decode(salt, &salt)) {
+ NET_LOG(ERROR) << kUnableToDecode;
+ return nullptr;
+ }
+
+ std::unique_ptr<crypto::SymmetricKey> key(
+ crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES,
+ passphrase, salt, iterations,
+ kKeySizeInBits));
+
+ if (!base::Base64Decode(initial_vector, &initial_vector)) {
+ NET_LOG(ERROR) << kUnableToDecode;
+ return nullptr;
+ }
+ if (!base::Base64Decode(ciphertext, &ciphertext)) {
+ NET_LOG(ERROR) << kUnableToDecode;
+ return nullptr;
+ }
+ if (!base::Base64Decode(hmac, &hmac)) {
+ NET_LOG(ERROR) << kUnableToDecode;
+ return nullptr;
+ }
+
+ crypto::HMAC hmac_verifier(crypto::HMAC::SHA1);
+ if (!hmac_verifier.Init(key.get()) ||
+ !hmac_verifier.Verify(ciphertext, hmac)) {
+ NET_LOG(ERROR) << kUnableToDecrypt;
+ return nullptr;
+ }
+
+ crypto::Encryptor decryptor;
+ if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector)) {
+ NET_LOG(ERROR) << kUnableToDecrypt;
+ return nullptr;
+ }
+
+ std::string plaintext;
+ if (!decryptor.Decrypt(ciphertext, &plaintext)) {
+ NET_LOG(ERROR) << kUnableToDecrypt;
+ return nullptr;
+ }
+
+ std::unique_ptr<base::DictionaryValue> new_root =
+ ReadDictionaryFromJson(plaintext);
+ if (!new_root) {
+ NET_LOG(ERROR) << "Property dictionary malformed.";
+ return nullptr;
+ }
+
+ return new_root;
+}
+
+std::string GetSourceAsString(ONCSource source) {
+ switch (source) {
+ case ONC_SOURCE_UNKNOWN:
+ return "unknown";
+ case ONC_SOURCE_NONE:
+ return "none";
+ case ONC_SOURCE_DEVICE_POLICY:
+ return "device policy";
+ case ONC_SOURCE_USER_POLICY:
+ return "user policy";
+ case ONC_SOURCE_USER_IMPORT:
+ return "user import";
+ }
+ NOTREACHED() << "unknown ONC source " << source;
+ return "unknown";
+}
+
+void ExpandStringsInOncObject(const OncValueSignature& signature,
+ const VariableExpander& variable_expander,
+ base::DictionaryValue* onc_object) {
+ if (&signature == &kEAPSignature) {
+ ExpandField(eap::kAnonymousIdentity, variable_expander, onc_object);
+ ExpandField(eap::kIdentity, variable_expander, onc_object);
+ } else if (&signature == &kL2TPSignature ||
+ &signature == &kOpenVPNSignature) {
+ ExpandField(vpn::kUsername, variable_expander, onc_object);
+ }
+
+ // Recurse into nested objects.
+ for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
+ it.Advance()) {
+ base::DictionaryValue* inner_object = nullptr;
+ if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
+ continue;
+
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(signature, it.key());
+ if (!field_signature)
+ continue;
+
+ ExpandStringsInOncObject(*field_signature->value_signature,
+ variable_expander, inner_object);
+ }
+}
+
+void ExpandStringsInNetworks(const VariableExpander& variable_expander,
+ base::ListValue* network_configs) {
+ for (auto& entry : *network_configs) {
+ base::DictionaryValue* network = nullptr;
+ entry.GetAsDictionary(&network);
+ DCHECK(network);
+ ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
+ network);
+ }
+}
+
+void FillInHexSSIDFieldsInOncObject(const OncValueSignature& signature,
+ base::DictionaryValue* onc_object) {
+ if (&signature == &kWiFiSignature)
+ FillInHexSSIDField(onc_object);
+
+ // Recurse into nested objects.
+ for (base::DictionaryValue::Iterator it(*onc_object); !it.IsAtEnd();
+ it.Advance()) {
+ base::DictionaryValue* inner_object = nullptr;
+ if (!onc_object->GetDictionaryWithoutPathExpansion(it.key(), &inner_object))
+ continue;
+
+ const OncFieldSignature* field_signature =
+ GetFieldSignature(signature, it.key());
+ if (!field_signature)
+ continue;
+
+ FillInHexSSIDFieldsInOncObject(*field_signature->value_signature,
+ inner_object);
+ }
+}
+
+void FillInHexSSIDField(base::DictionaryValue* wifi_fields) {
+ std::string ssid_string;
+ if (wifi_fields->HasKey(::onc::wifi::kHexSSID) ||
+ !wifi_fields->GetStringWithoutPathExpansion(::onc::wifi::kSSID,
+ &ssid_string)) {
+ return;
+ }
+ if (ssid_string.empty()) {
+ NET_LOG(ERROR) << "Found empty SSID field.";
+ return;
+ }
+ wifi_fields->SetKey(
+ ::onc::wifi::kHexSSID,
+ base::Value(base::HexEncode(ssid_string.c_str(), ssid_string.size())));
+}
+
+std::unique_ptr<base::DictionaryValue> MaskCredentialsInOncObject(
+ const OncValueSignature& signature,
+ const base::DictionaryValue& onc_object,
+ const std::string& mask) {
+ return OncMaskValues::Mask(signature, onc_object, mask);
+}
+
+std::string DecodePEM(const std::string& pem_encoded) {
+ // 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";
+
+ std::vector<std::string> pem_headers;
+ pem_headers.push_back(kCertificateHeader);
+ pem_headers.push_back(kX509CertificateHeader);
+
+ net::PEMTokenizer pem_tokenizer(pem_encoded, pem_headers);
+ std::string decoded;
+ if (pem_tokenizer.GetNext()) {
+ decoded = pem_tokenizer.data();
+ } else {
+ // If we failed to read the data as a PEM file, then try plain base64 decode
+ // in case the PEM marker strings are missing. For this to work, there has
+ // to be no white space, and it has to only contain the base64-encoded data.
+ if (!base::Base64Decode(pem_encoded, &decoded)) {
+ LOG(ERROR) << "Unable to base64 decode X509 data: " << pem_encoded;
+ return std::string();
+ }
+ }
+ return decoded;
+}
+
+bool ParseAndValidateOncForImport(const std::string& onc_blob,
+ ONCSource onc_source,
+ const std::string& passphrase,
+ base::ListValue* network_configs,
+ base::DictionaryValue* global_network_config,
+ base::ListValue* certificates) {
+ if (network_configs)
+ network_configs->Clear();
+ if (global_network_config)
+ global_network_config->Clear();
+ if (certificates)
+ certificates->Clear();
+ if (onc_blob.empty())
+ return true;
+
+ std::unique_ptr<base::DictionaryValue> toplevel_onc =
+ ReadDictionaryFromJson(onc_blob);
+ if (!toplevel_onc) {
+ LOG(ERROR) << "ONC loaded from " << GetSourceAsString(onc_source)
+ << " is not a valid JSON dictionary.";
+ return false;
+ }
+
+ // Check and see if this is an encrypted ONC file. If so, decrypt it.
+ std::string onc_type;
+ toplevel_onc->GetStringWithoutPathExpansion(toplevel_config::kType,
+ &onc_type);
+ if (onc_type == toplevel_config::kEncryptedConfiguration) {
+ toplevel_onc = Decrypt(passphrase, *toplevel_onc);
+ if (!toplevel_onc) {
+ LOG(ERROR) << "Couldn't decrypt the ONC from "
+ << GetSourceAsString(onc_source);
+ return false;
+ }
+ }
+
+ bool from_policy = (onc_source == ONC_SOURCE_USER_POLICY ||
+ onc_source == ONC_SOURCE_DEVICE_POLICY);
+
+ // Validate the ONC dictionary. We are liberal and ignore unknown field
+ // names and ignore invalid field names in kRecommended arrays.
+ Validator validator(false, // Ignore unknown fields.
+ false, // Ignore invalid recommended field names.
+ true, // Fail on missing fields.
+ from_policy);
+ validator.SetOncSource(onc_source);
+
+ Validator::Result validation_result;
+ toplevel_onc = validator.ValidateAndRepairObject(
+ &kToplevelConfigurationSignature, *toplevel_onc, &validation_result);
+
+ if (from_policy) {
+ UMA_HISTOGRAM_BOOLEAN("Enterprise.ONC.PolicyValidation",
+ validation_result == Validator::VALID);
+ }
+
+ bool success = true;
+ if (validation_result == Validator::VALID_WITH_WARNINGS) {
+ LOG(WARNING) << "ONC from " << GetSourceAsString(onc_source)
+ << " produced warnings.";
+ success = false;
+ } else if (validation_result == Validator::INVALID || !toplevel_onc) {
+ LOG(ERROR) << "ONC from " << GetSourceAsString(onc_source)
+ << " is invalid and couldn't be repaired.";
+ return false;
+ }
+
+ base::ListValue* validated_certs = nullptr;
+ if (certificates && toplevel_onc->GetListWithoutPathExpansion(
+ toplevel_config::kCertificates, &validated_certs)) {
+ certificates->Swap(validated_certs);
+ }
+
+ base::ListValue* validated_networks = nullptr;
+ // Note that this processing is performed even if |network_configs| is
+ // nullptr, because ResolveServerCertRefsInNetworks could affect the return
+ // value of the function (which is supposed to aggregate validation issues in
+ // all segments of the ONC blob).
+ if (toplevel_onc->GetListWithoutPathExpansion(
+ toplevel_config::kNetworkConfigurations, &validated_networks)) {
+ FillInHexSSIDFieldsInNetworks(validated_networks);
+
+ CertPEMsByGUIDMap server_and_ca_certs =
+ GetServerAndCACertsByGUID(*certificates);
+
+ if (!ResolveServerCertRefsInNetworks(server_and_ca_certs,
+ validated_networks)) {
+ LOG(ERROR) << "Some certificate references in the ONC policy for source "
+ << GetSourceAsString(onc_source) << " could not be resolved.";
+ success = false;
+ }
+
+ if (network_configs)
+ network_configs->Swap(validated_networks);
+ }
+
+ base::DictionaryValue* validated_global_config = nullptr;
+ if (global_network_config && toplevel_onc->GetDictionaryWithoutPathExpansion(
+ toplevel_config::kGlobalNetworkConfiguration,
+ &validated_global_config)) {
+ global_network_config->Swap(validated_global_config);
+ }
+
+ return success;
+}
+
+net::ScopedCERTCertificate DecodePEMCertificate(
+ const std::string& pem_encoded) {
+ std::string decoded = DecodePEM(pem_encoded);
+ net::ScopedCERTCertificate cert =
+ net::x509_util::CreateCERTCertificateFromBytes(
+ reinterpret_cast<const uint8_t*>(decoded.data()), decoded.size());
+ LOG_IF(ERROR, !cert.get())
+ << "Couldn't create certificate from X509 data: " << decoded;
+ return cert;
+}
+
+bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap& certs_by_guid,
+ base::ListValue* network_configs) {
+ bool success = true;
+ for (base::ListValue::iterator it = network_configs->begin();
+ it != network_configs->end();) {
+ base::DictionaryValue* network = nullptr;
+ it->GetAsDictionary(&network);
+ if (!ResolveServerCertRefsInNetwork(certs_by_guid, network)) {
+ std::string guid;
+ network->GetStringWithoutPathExpansion(network_config::kGUID, &guid);
+ // This might happen even with correct validation, if the referenced
+ // certificate couldn't be imported.
+ LOG(ERROR) << "Couldn't resolve some certificate reference of network "
+ << guid;
+ it = network_configs->Erase(it, nullptr);
+ success = false;
+ continue;
+ }
+ ++it;
+ }
+ return success;
+}
+
+bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap& certs_by_guid,
+ base::DictionaryValue* network_config) {
+ return ResolveServerCertRefsInObject(
+ certs_by_guid, kNetworkConfigurationSignature, network_config);
+}
+
+NetworkTypePattern NetworkTypePatternFromOncType(const std::string& type) {
+ if (type == ::onc::network_type::kAllTypes)
+ return NetworkTypePattern::Default();
+ if (type == ::onc::network_type::kCellular)
+ return NetworkTypePattern::Cellular();
+ if (type == ::onc::network_type::kEthernet)
+ return NetworkTypePattern::Ethernet();
+ if (type == ::onc::network_type::kTether)
+ return NetworkTypePattern::Tether();
+ if (type == ::onc::network_type::kVPN)
+ return NetworkTypePattern::VPN();
+ if (type == ::onc::network_type::kWiFi)
+ return NetworkTypePattern::WiFi();
+ if (type == ::onc::network_type::kWimax)
+ return NetworkTypePattern::Wimax();
+ if (type == ::onc::network_type::kWireless)
+ return NetworkTypePattern::Wireless();
+ NOTREACHED() << "Unrecognized ONC type: " << type;
+ return NetworkTypePattern::Default();
+}
+
+bool IsRecommendedValue(const base::DictionaryValue* onc,
+ const std::string& property_key) {
+ std::string property_basename, recommended_property_key;
+ size_t pos = property_key.find_last_of('.');
+ if (pos != std::string::npos) {
+ // 'WiFi.AutoConnect' -> 'AutoConnect', 'WiFi.Recommended'
+ property_basename = property_key.substr(pos + 1);
+ recommended_property_key =
+ property_key.substr(0, pos + 1) + ::onc::kRecommended;
+ } else {
+ // 'Name' -> 'Name', 'Recommended'
+ property_basename = property_key;
+ recommended_property_key = ::onc::kRecommended;
+ }
+
+ const base::ListValue* recommended_keys = nullptr;
+ return (onc->GetList(recommended_property_key, &recommended_keys) &&
+ recommended_keys->Find(base::Value(property_basename)) !=
+ recommended_keys->end());
+}
+
std::unique_ptr<base::DictionaryValue> ConvertOncProxySettingsToProxyConfig(
const base::DictionaryValue& onc_proxy_settings) {
std::string type;
@@ -1058,125 +1181,6 @@
return proxy_settings;
}
-namespace {
-
-const base::DictionaryValue* GetNetworkConfigByGUID(
- const base::ListValue& network_configs,
- const std::string& guid) {
- for (base::ListValue::const_iterator it = network_configs.begin();
- it != network_configs.end(); ++it) {
- const base::DictionaryValue* network = NULL;
- it->GetAsDictionary(&network);
- DCHECK(network);
-
- std::string current_guid;
- network->GetStringWithoutPathExpansion(::onc::network_config::kGUID,
- ¤t_guid);
- if (current_guid == guid)
- return network;
- }
- return NULL;
-}
-
-const base::DictionaryValue* GetNetworkConfigForEthernetWithoutEAP(
- const base::ListValue& network_configs) {
- VLOG(2) << "Search for ethernet policy without EAP.";
- for (base::ListValue::const_iterator it = network_configs.begin();
- it != network_configs.end(); ++it) {
- const base::DictionaryValue* network = NULL;
- it->GetAsDictionary(&network);
- DCHECK(network);
-
- std::string type;
- network->GetStringWithoutPathExpansion(::onc::network_config::kType, &type);
- if (type != ::onc::network_type::kEthernet)
- continue;
-
- const base::DictionaryValue* ethernet = NULL;
- network->GetDictionaryWithoutPathExpansion(::onc::network_config::kEthernet,
- ðernet);
-
- std::string auth;
- ethernet->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
- &auth);
- if (auth == ::onc::ethernet::kAuthenticationNone)
- return network;
- }
- return NULL;
-}
-
-const base::DictionaryValue* GetNetworkConfigForNetworkFromOnc(
- const base::ListValue& network_configs,
- const NetworkState& network) {
- // In all cases except Ethernet, we use the GUID of |network|.
- if (!network.Matches(NetworkTypePattern::Ethernet()))
- return GetNetworkConfigByGUID(network_configs, network.guid());
-
- // Ethernet is always shared and thus cannot store a GUID per user. Thus we
- // search for any Ethernet policy intead of a matching GUID.
- // EthernetEAP service contains only the EAP parameters and stores the GUID of
- // the respective ONC policy. The EthernetEAP service itself is however never
- // in state "connected". An EthernetEAP policy must be applied, if an Ethernet
- // service is connected using the EAP parameters.
- const NetworkState* ethernet_eap = NULL;
- if (NetworkHandler::IsInitialized()) {
- ethernet_eap =
- NetworkHandler::Get()->network_state_handler()->GetEAPForEthernet(
- network.path());
- }
-
- // The GUID associated with the EthernetEAP service refers to the ONC policy
- // with "Authentication: 8021X".
- if (ethernet_eap)
- return GetNetworkConfigByGUID(network_configs, ethernet_eap->guid());
-
- // Otherwise, EAP is not used and instead the Ethernet policy with
- // "Authentication: None" applies.
- return GetNetworkConfigForEthernetWithoutEAP(network_configs);
-}
-
-const base::DictionaryValue* GetPolicyForNetworkFromPref(
- const PrefService* pref_service,
- const char* pref_name,
- const NetworkState& network) {
- if (!pref_service) {
- VLOG(2) << "No pref service";
- return NULL;
- }
-
- const PrefService::Preference* preference =
- pref_service->FindPreference(pref_name);
- if (!preference) {
- VLOG(2) << "No preference " << pref_name;
- // The preference may not exist in tests.
- return NULL;
- }
-
- // User prefs are not stored in this Preference yet but only the policy.
- //
- // The policy server incorrectly configures the OpenNetworkConfiguration user
- // policy as Recommended. To work around that, we handle the Recommended and
- // the Mandatory value in the same way.
- // TODO(pneubeck): Remove this workaround, once the server is fixed. See
- // http://crbug.com/280553 .
- if (preference->IsDefaultValue()) {
- VLOG(2) << "Preference has no recommended or mandatory value.";
- // No policy set.
- return NULL;
- }
- VLOG(2) << "Preference with policy found.";
- const base::Value* onc_policy_value = preference->GetValue();
- DCHECK(onc_policy_value);
-
- const base::ListValue* onc_policy = NULL;
- onc_policy_value->GetAsList(&onc_policy);
- DCHECK(onc_policy);
-
- return GetNetworkConfigForNetworkFromOnc(*onc_policy, network);
-}
-
-} // namespace
-
void ExpandStringPlaceholdersInNetworksForUser(
const user_manager::User* user,
base::ListValue* network_configs) {
@@ -1185,8 +1189,15 @@
// expand the strings.
return;
}
- UserStringSubstitution substitution(user);
- chromeos::onc::ExpandStringsInNetworks(substitution, network_configs);
+
+ // Note: It is OK for the placeholders to be replaced with empty strings if
+ // that is what the getters on |user| provide.
+ std::map<std::string, std::string> substitutions;
+ substitutions[::onc::substitutes::kLoginID] = user->GetAccountName(false);
+ substitutions[::onc::substitutes::kLoginEmail] =
+ user->GetAccountId().GetUserEmail();
+ VariableExpander variable_expander(std::move(substitutions));
+ chromeos::onc::ExpandStringsInNetworks(variable_expander, network_configs);
}
void ImportNetworksForUser(const user_manager::User* user,
@@ -1276,26 +1287,6 @@
->FindPolicyByGUID(username_hash, guid, onc_source);
}
-namespace {
-
-const base::DictionaryValue* GetGlobalConfigFromPolicy(bool for_active_user) {
- std::string username_hash;
- if (for_active_user) {
- const user_manager::User* user =
- user_manager::UserManager::Get()->GetActiveUser();
- if (!user) {
- LOG(ERROR) << "No user logged in yet.";
- return NULL;
- }
- username_hash = user->username_hash();
- }
- return NetworkHandler::Get()
- ->managed_network_configuration_handler()
- ->GetGlobalConfigFromPolicy(username_hash);
-}
-
-} // namespace
-
bool PolicyAllowsOnlyPolicyNetworksToAutoconnect(bool for_active_user) {
const base::DictionaryValue* global_config =
GetGlobalConfigFromPolicy(for_active_user);
@@ -1354,7 +1345,7 @@
return false;
}
- if (password_field == ::onc::substitutes::kPasswordField) {
+ if (password_field == ::onc::substitutes::kPasswordPlaceholderVerbatim) {
return true;
}
}
diff --git a/chromeos/network/onc/onc_utils.h b/chromeos/network/onc/onc_utils.h
index 800d7686..540e5aa 100644
--- a/chromeos/network/onc/onc_utils.h
+++ b/chromeos/network/onc/onc_utils.h
@@ -10,10 +10,10 @@
#include <string>
#include <vector>
-#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "chromeos/chromeos_export.h"
#include "chromeos/network/network_type_pattern.h"
+#include "chromeos/tools/variable_expander.h"
#include "components/onc/onc_constants.h"
#include "net/cert/scoped_nss_types.h"
@@ -58,35 +58,19 @@
// For logging only: strings not user facing.
CHROMEOS_EXPORT std::string GetSourceAsString(::onc::ONCSource source);
-// Used for string expansion with function ExpandStringInOncObject(...).
-class CHROMEOS_EXPORT StringSubstitution {
- public:
- StringSubstitution() {}
- virtual ~StringSubstitution() {}
-
- // Returns the replacement string for |placeholder| in
- // |substitute|. Currently, substitutes::kLoginIDField and
- // substitutes::kEmailField are supported.
- virtual bool GetSubstitute(const std::string& placeholder,
- std::string* substitute) const = 0;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(StringSubstitution);
-};
-
// Replaces all expandable fields that are mentioned in the ONC
-// specification. The object of |onc_object| is modified in place. Currently
-// substitutes::kLoginIDField and substitutes::kEmailField are expanded. The
-// replacement strings are obtained from |substitution|.
+// specification. The object of |onc_object| is modified in place.
+// The substitution is performed using the passed |variable_expander|, which
+// defines the placeholder-value mapping.
CHROMEOS_EXPORT void ExpandStringsInOncObject(
const OncValueSignature& signature,
- const StringSubstitution& substitution,
+ const VariableExpander& variable_expander,
base::DictionaryValue* onc_object);
// Replaces expandable fields in the networks of |network_configs|, which must
// be a list of ONC NetworkConfigurations. See ExpandStringsInOncObject above.
CHROMEOS_EXPORT void ExpandStringsInNetworks(
- const StringSubstitution& substitution,
+ const VariableExpander& variable_expander,
base::ListValue* network_configs);
// Fills in all missing HexSSID fields that are mentioned in the ONC
@@ -170,9 +154,9 @@
ConvertProxyConfigToOncProxySettings(
std::unique_ptr<base::DictionaryValue> proxy_config_value);
-// Replaces string placeholders in |network_configs|, which must be a list of
-// ONC NetworkConfigurations. Currently only user name placeholders are
-// implemented, which are replaced by attributes from |user|.
+// Replaces user-specific string placeholders in |network_configs|, which must
+// be a list of ONC NetworkConfigurations. Currently only user name placeholders
+// are implemented, which are replaced by attributes from |user|.
CHROMEOS_EXPORT void ExpandStringPlaceholdersInNetworksForUser(
const user_manager::User* user,
base::ListValue* network_configs);
diff --git a/chromeos/network/onc/onc_utils_unittest.cc b/chromeos/network/onc/onc_utils_unittest.cc
index 548877d..a65da7cf 100644
--- a/chromeos/network/onc/onc_utils_unittest.cc
+++ b/chromeos/network/onc/onc_utils_unittest.cc
@@ -16,6 +16,7 @@
#include "chromeos/network/network_ui_data.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_test_utils.h"
+#include "chromeos/tools/variable_expander.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
@@ -83,22 +84,12 @@
const char* kLoginId = "hans";
const char* kLoginEmail = "hans@my.domain.com";
-class StringSubstitutionStub : public StringSubstitution {
- public:
- StringSubstitutionStub() = default;
- bool GetSubstitute(const std::string& placeholder,
- std::string* substitute) const override {
- if (placeholder == ::onc::substitutes::kLoginIDField)
- *substitute = kLoginId;
- else if (placeholder ==::onc::substitutes::kEmailField)
- *substitute = kLoginEmail;
- else
- return false;
- return true;
- }
- private:
- DISALLOW_COPY_AND_ASSIGN(StringSubstitutionStub);
-};
+std::map<std::string, std::string> GetTestStringSubstutions() {
+ std::map<std::string, std::string> substitutions;
+ substitutions[::onc::substitutes::kLoginID] = kLoginId;
+ substitutions[::onc::substitutes::kLoginEmail] = kLoginEmail;
+ return substitutions;
+}
} // namespace
@@ -106,8 +97,8 @@
std::unique_ptr<base::DictionaryValue> vpn_onc =
test_utils::ReadTestDictionary("valid_openvpn.onc");
- StringSubstitutionStub substitution;
- ExpandStringsInOncObject(kNetworkConfigurationSignature, substitution,
+ VariableExpander variable_expander(GetTestStringSubstutions());
+ ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
vpn_onc.get());
std::string actual_expanded;
@@ -119,8 +110,8 @@
std::unique_ptr<base::DictionaryValue> wifi_onc =
test_utils::ReadTestDictionary("wifi_clientcert_with_cert_pems.onc");
- StringSubstitutionStub substitution;
- ExpandStringsInOncObject(kNetworkConfigurationSignature, substitution,
+ VariableExpander variable_expander(GetTestStringSubstutions());
+ ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
wifi_onc.get());
std::string actual_expanded;
diff --git a/chromeos/tools/variable_expander.cc b/chromeos/tools/variable_expander.cc
index b6c6357..4fe7ceedc 100644
--- a/chromeos/tools/variable_expander.cc
+++ b/chromeos/tools/variable_expander.cc
@@ -108,14 +108,14 @@
VariableExpander::~VariableExpander() = default;
-bool VariableExpander::ExpandString(std::string* str) {
+bool VariableExpander::ExpandString(std::string* str) const {
bool no_error = true;
for (const auto& kv : variables_)
no_error &= Expand(kv.first, kv.second, str);
return no_error;
}
-bool VariableExpander::ExpandValue(base::Value* value) {
+bool VariableExpander::ExpandValue(base::Value* value) const {
bool no_error = true;
switch (value->type()) {
case base::Value::Type::STRING: {
diff --git a/chromeos/tools/variable_expander.h b/chromeos/tools/variable_expander.h
index 4c45e55..fe3c007 100644
--- a/chromeos/tools/variable_expander.h
+++ b/chromeos/tools/variable_expander.h
@@ -41,13 +41,13 @@
// Expands all variables in |str|. Returns true if no error has occurred.
// Returns false if at least one variable was malformed and could not be
// expanded (the good ones are still expanded).
- bool ExpandString(std::string* str);
+ bool ExpandString(std::string* str) const;
// Calls ExpandString on every string contained in |value|. Recursively
// handles all hierarchy levels. Returns true if no error has occurred.
// Returns false if at least one variable was malformed and could not be
// expanded (the good ones are still expanded).
- bool ExpandValue(base::Value* value);
+ bool ExpandValue(base::Value* value) const;
private:
// Maps variable -> value.
diff --git a/components/onc/docs/onc_spec.md b/components/onc/docs/onc_spec.md
index 116b3057..8fec180 100644
--- a/components/onc/docs/onc_spec.md
+++ b/components/onc/docs/onc_spec.md
@@ -1741,29 +1741,45 @@
### The expansions are:
-* ${LOGIN_ID} - expands to the email address of the user, but before the '@'.
+* Placeholders that will only be replaced in user-specific ONC:
+ * ${LOGIN\_ID} - expands to the email address of the user, but before
+ the '@'.
+ * ${LOGIN\_EMAIL} - expands to the email address of the user.
-* ${LOGIN_EMAIL} - expands to the email address of the user.
+* Placeholders that will only be replaced in device-wide ONC:
+ * ${DEVICE\_SERIAL\_NUMBER} - expands to the serial number of the device.
+ * ${DEVICE\_ASSET\_ID} - expands to the administrator-set asset ID of the
+ device.
+
+* Placeholders that will only be replaced when a client certificate has been
+ matched by a [CertificatePattern](#CertificatePattern-type):
+ * ${CERT\_SAN\_EMAIL} - expands to the first RFC822 SubjectAlternativeName
+ extracted from the client certificate.
+ * ${CERT\_SAN\_UPN} - expands to the first OtherName SubjectAlternativeName
+ with OID 1.3.6.1.4.1.311.20.2.3 (UserPrincipalName) extracted from the
+ client certificate.
+ * ${CERT\_SUBJECT\_COMMON\_NAME} - expands to the ASCII value of the Subject
+ CommonName extracted from the client certificate.
### The following SED would properly handle resolution.
-* s/\$\{LOGIN_ID\}/bobquail$1/g
+* s/\$\{LOGIN\_ID\}/bobquail$1/g
-* s/\$\{LOGIN_EMAIL\}/bobquail@example.com$1/g
+* s/\$\{LOGIN\_EMAIL\}/bobquail@example.com$1/g
### Example expansions, assuming the user was bobquail@example.com:
-* "${LOGIN_ID}" -> "bobquail"
+* "${LOGIN\_ID}" -> "bobquail"
-* "${LOGIN_ID}@corp.example.com" -> "bobquail@corp.example.com"
+* "${LOGIN\_ID}@corp.example.com" -> "bobquail@corp.example.com"
-* "${LOGIN_EMAIL}" -> "bobquail@example.com"
+* "${LOGIN\_EMAIL}" -> "bobquail@example.com"
-* "${LOGIN_ID}X" -> "bobquailX"
+* "${LOGIN\_ID}X" -> "bobquailX"
-* "${LOGIN_IDX}" -> "${LOGIN_IDX}"
+* "${LOGIN\_IDX}" -> "${LOGIN\_IDX}"
-* "X${LOGIN_ID}" -> "Xbobquail"
+* "X${LOGIN\_ID}" -> "Xbobquail"
## String Substitutions
diff --git a/components/onc/onc_constants.cc b/components/onc/onc_constants.cc
index 22d2c85a..681a255 100644
--- a/components/onc/onc_constants.cc
+++ b/components/onc/onc_constants.cc
@@ -440,11 +440,20 @@
} // namespace proxy
namespace substitutes {
-const char kLoginIDField[] = "${LOGIN_ID}";
-const char kPasswordField[] = "${PASSWORD}";
-const char kEmailField[] = "${LOGIN_EMAIL}";
-const char kCertSANEmail[] = "${CERT_SAN_EMAIL}";
-const char kCertSANUPN[] = "${CERT_SAN_UPN}";
+const char kLoginID[] = "LOGIN_ID";
+const char kLoginEmail[] = "LOGIN_EMAIL";
+const char kCertSANEmail[] = "CERT_SAN_EMAIL";
+const char kCertSANUPN[] = "CERT_SAN_UPN";
+const char kCertSubjectCommonName[] = "CERT_SUBJECT_COMMON_NAME";
+const char kDeviceSerialNumber[] = "DEVICE_SERIAL_NUMBER";
+const char kDeviceAssetId[] = "DEVICE_ASSET_ID";
+// The password placeholder is defined as ${PASSWORD} because it's compared
+// verbatim against the policy-specified password field, and if it matches,
+// another bool (|shill::kEapUseLoginPasswordProperty|) is set, which makes
+// shill replace the whole password field.
+// The other placeholders above on the other hand are replaced using
+// VariableExpander.
+const char kPasswordPlaceholderVerbatim[] = "${PASSWORD}";
} // namespace substitutes
namespace global_network_config {
diff --git a/components/onc/onc_constants.h b/components/onc/onc_constants.h
index 281345b..a662bc4 100644
--- a/components/onc/onc_constants.h
+++ b/components/onc/onc_constants.h
@@ -438,11 +438,14 @@
} // namespace verify_x509
namespace substitutes {
-ONC_EXPORT extern const char kEmailField[];
-ONC_EXPORT extern const char kPasswordField[];
-ONC_EXPORT extern const char kLoginIDField[];
+ONC_EXPORT extern const char kLoginEmail[];
+ONC_EXPORT extern const char kLoginID[];
ONC_EXPORT extern const char kCertSANEmail[];
ONC_EXPORT extern const char kCertSANUPN[];
+ONC_EXPORT extern const char kCertSubjectCommonName[];
+ONC_EXPORT extern const char kDeviceSerialNumber[];
+ONC_EXPORT extern const char kDeviceAssetId[];
+ONC_EXPORT extern const char kPasswordPlaceholderVerbatim[];
} // namespace substitutes
namespace proxy {