blob: 4eed8d7df78de2930132c222f70055a7ff0957a5 [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/onc/onc_mapper.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_utils.h"
#include "chromeos/network/onc/onc_validator.h"
#include "components/device_event_log/device_event_log.h"
#include "components/proxy_config/proxy_config_dictionary.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/symmetric_key.h"
#include "net/base/host_port_pair.h"
#include "net/cert/pem_tokenizer.h"
#include "net/cert/x509_certificate.h"
#include "net/proxy/proxy_bypass_rules.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_server.h"
#include "url/url_constants.h"
using namespace ::onc;
namespace chromeos {
namespace onc {
namespace {
const char kUnableToDecrypt[] = "Unable to decrypt encrypted ONC";
const char kUnableToDecode[] = "Unable to decode encrypted ONC";
} // 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 ExpandField(const std::string& fieldname,
const StringSubstitution& substitution,
base::DictionaryValue* onc_object) {
std::string user_string;
if (!onc_object->GetStringWithoutPathExpansion(fieldname, &user_string))
return;
std::string login_id;
if (substitution.GetSubstitute(substitutes::kLoginIDField, &login_id)) {
base::ReplaceSubstringsAfterOffset(&user_string, 0,
substitutes::kLoginIDField,
login_id);
}
std::string email;
if (substitution.GetSubstitute(substitutes::kEmailField, &email)) {
base::ReplaceSubstringsAfterOffset(&user_string, 0,
substitutes::kEmailField,
email);
}
onc_object->SetStringWithoutPathExpansion(fieldname, user_string);
}
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 (base::Value* 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->SetStringWithoutPathExpansion(
::onc::wifi::kHexSSID,
base::HexEncode(ssid_string.c_str(), ssid_string.size()));
}
namespace {
class OncMaskValues : public Mapper {
public:
static std::unique_ptr<base::DictionaryValue> Mask(
const OncValueSignature& signature,
const base::DictionaryValue& onc_object,
const std::string& mask) {
OncMaskValues masker(mask);
bool unused_error;
return masker.MapObject(signature, onc_object, &unused_error);
}
protected:
explicit OncMaskValues(const std::string& mask)
: mask_(mask) {
}
std::unique_ptr<base::Value> MapField(
const std::string& field_name,
const OncValueSignature& object_signature,
const base::Value& onc_value,
bool* found_unknown_field,
bool* error) override {
if (FieldIsCredential(object_signature, field_name)) {
return std::unique_ptr<base::Value>(new base::StringValue(mask_));
} else {
return Mapper::MapField(field_name, object_signature, onc_value,
found_unknown_field, error);
}
}
// Mask to insert in place of the sensitive values.
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);
}
namespace {
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;
}
CertPEMsByGUIDMap GetServerAndCACertsByGUID(
const base::ListValue& certificates) {
CertPEMsByGUIDMap certs_by_guid;
for (const base::Value* entry : certificates) {
const base::DictionaryValue* cert = nullptr;
entry->GetAsDictionary(&cert);
std::string guid;
cert->GetStringWithoutPathExpansion(certificate::kGUID, &guid);
std::string cert_type;
cert->GetStringWithoutPathExpansion(certificate::kType, &cert_type);
if (cert_type != certificate::kServer &&
cert_type != certificate::kAuthority) {
continue;
}
std::string x509_data;
cert->GetStringWithoutPathExpansion(certificate::kX509, &x509_data);
std::string der = DecodePEM(x509_data);
std::string pem;
if (der.empty() || !net::X509Certificate::GetPEMEncodedFromDER(der, &pem)) {
LOG(ERROR) << "Certificate with GUID " << guid
<< " is not in PEM encoding.";
continue;
}
certs_by_guid[guid] = pem;
}
return certs_by_guid;
}
void FillInHexSSIDFieldsInNetworks(base::ListValue* network_configs) {
for (base::Value* entry : *network_configs) {
base::DictionaryValue* network = nullptr;
entry->GetAsDictionary(&network);
DCHECK(network);
FillInHexSSIDFieldsInOncObject(kNetworkConfigurationSignature, network);
}
}
} // 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) {
network_configs->Clear();
global_network_config->Clear();
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 (toplevel_onc->GetListWithoutPathExpansion(toplevel_config::kCertificates,
&validated_certs)) {
certificates->Swap(validated_certs);
}
base::ListValue* validated_networks = nullptr;
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;
}
network_configs->Swap(validated_networks);
}
base::DictionaryValue* validated_global_config = nullptr;
if (toplevel_onc->GetDictionaryWithoutPathExpansion(
toplevel_config::kGlobalNetworkConfiguration,
&validated_global_config)) {
global_network_config->Swap(validated_global_config);
}
return success;
}
scoped_refptr<net::X509Certificate> DecodePEMCertificate(
const std::string& pem_encoded) {
std::string decoded = DecodePEM(pem_encoded);
scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromBytes(decoded.data(), decoded.size());
LOG_IF(ERROR, !cert.get()) << "Couldn't create certificate from X509 data: "
<< decoded;
return cert;
}
namespace {
bool GUIDRefToPEMEncoding(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& guid_ref,
std::string* pem_encoded) {
CertPEMsByGUIDMap::const_iterator it = certs_by_guid.find(guid_ref);
if (it == certs_by_guid.end()) {
LOG(ERROR) << "Couldn't resolve certificate reference " << guid_ref;
return false;
}
*pem_encoded = it->second;
if (pem_encoded->empty()) {
LOG(ERROR) << "Couldn't PEM-encode certificate with GUID " << guid_ref;
return false;
}
return true;
}
bool ResolveSingleCertRef(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref,
const std::string& key_pem,
base::DictionaryValue* onc_object) {
std::string guid_ref;
if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref))
return true;
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
return false;
onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
onc_object->SetStringWithoutPathExpansion(key_pem, pem_encoded);
return true;
}
bool ResolveCertRefList(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref_list,
const std::string& key_pem_list,
base::DictionaryValue* onc_object) {
const base::ListValue* guid_ref_list = nullptr;
if (!onc_object->GetListWithoutPathExpansion(key_guid_ref_list,
&guid_ref_list)) {
return true;
}
std::unique_ptr<base::ListValue> pem_list(new base::ListValue);
for (const base::Value* entry : *guid_ref_list) {
std::string guid_ref;
entry->GetAsString(&guid_ref);
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
return false;
pem_list->AppendString(pem_encoded);
}
onc_object->RemoveWithoutPathExpansion(key_guid_ref_list, nullptr);
onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release());
return true;
}
bool ResolveSingleCertRefToList(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_ref,
const std::string& key_pem_list,
base::DictionaryValue* onc_object) {
std::string guid_ref;
if (!onc_object->GetStringWithoutPathExpansion(key_guid_ref, &guid_ref))
return true;
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, guid_ref, &pem_encoded))
return false;
std::unique_ptr<base::ListValue> pem_list(new base::ListValue);
pem_list->AppendString(pem_encoded);
onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
onc_object->SetWithoutPathExpansion(key_pem_list, pem_list.release());
return true;
}
// Resolves the reference list at |key_guid_refs| if present and otherwise the
// single reference at |key_guid_ref|. Returns whether the respective resolving
// was successful.
bool ResolveCertRefsOrRefToList(const CertPEMsByGUIDMap& certs_by_guid,
const std::string& key_guid_refs,
const std::string& key_guid_ref,
const std::string& key_pem_list,
base::DictionaryValue* onc_object) {
if (onc_object->HasKey(key_guid_refs)) {
if (onc_object->HasKey(key_guid_ref)) {
LOG(ERROR) << "Found both " << key_guid_refs << " and " << key_guid_ref
<< ". Ignoring and removing the latter.";
onc_object->RemoveWithoutPathExpansion(key_guid_ref, nullptr);
}
return ResolveCertRefList(
certs_by_guid, key_guid_refs, key_pem_list, onc_object);
}
// Only resolve |key_guid_ref| if |key_guid_refs| isn't present.
return ResolveSingleCertRefToList(
certs_by_guid, key_guid_ref, key_pem_list, onc_object);
}
bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap& certs_by_guid,
const OncValueSignature& signature,
base::DictionaryValue* onc_object) {
if (&signature == &kCertificatePatternSignature) {
if (!ResolveCertRefList(certs_by_guid,
client_cert::kIssuerCARef,
client_cert::kIssuerCAPEMs,
onc_object)) {
return false;
}
} else if (&signature == &kEAPSignature) {
if (!ResolveCertRefsOrRefToList(certs_by_guid,
eap::kServerCARefs,
eap::kServerCARef,
eap::kServerCAPEMs,
onc_object)) {
return false;
}
} else if (&signature == &kIPsecSignature) {
if (!ResolveCertRefsOrRefToList(certs_by_guid,
ipsec::kServerCARefs,
ipsec::kServerCARef,
ipsec::kServerCAPEMs,
onc_object)) {
return false;
}
} else if (&signature == &kIPsecSignature ||
&signature == &kOpenVPNSignature) {
if (!ResolveSingleCertRef(certs_by_guid,
openvpn::kServerCertRef,
openvpn::kServerCertPEM,
onc_object) ||
!ResolveCertRefsOrRefToList(certs_by_guid,
openvpn::kServerCARefs,
openvpn::kServerCARef,
openvpn::kServerCAPEMs,
onc_object)) {
return false;
}
}
// 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;
if (!ResolveServerCertRefsInObject(certs_by_guid,
*field_signature->value_signature,
inner_object)) {
return false;
}
}
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::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::StringValue(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) {
std::string host;
onc_proxy_location.GetStringWithoutPathExpansion(::onc::proxy::kHost, &host);
// Parse |host| according to the format [<scheme>"://"]<server>[":"<port>].
net::ProxyServer proxy_server =
net::ProxyServer::FromURI(host, default_proxy_scheme);
int port = 0;
onc_proxy_location.GetIntegerWithoutPathExpansion(::onc::proxy::kPort, &port);
// Replace the port parsed from |host| by the provided |port|.
return net::ProxyServer(
proxy_server.scheme(),
net::HostPortPair(proxy_server.host_port_pair().host(),
static_cast<uint16_t>(port)));
}
void AppendProxyServerForScheme(const base::DictionaryValue& onc_manual,
const std::string& onc_scheme,
std::string* spec) {
const base::DictionaryValue* onc_proxy_location = nullptr;
if (!onc_manual.GetDictionaryWithoutPathExpansion(onc_scheme,
&onc_proxy_location)) {
return;
}
net::ProxyServer::Scheme default_proxy_scheme = net::ProxyServer::SCHEME_HTTP;
std::string url_scheme;
if (onc_scheme == ::onc::proxy::kFtp) {
url_scheme = url::kFtpScheme;
} else if (onc_scheme == ::onc::proxy::kHttp) {
url_scheme = url::kHttpScheme;
} else if (onc_scheme == ::onc::proxy::kHttps) {
url_scheme = url::kHttpsScheme;
} else if (onc_scheme == ::onc::proxy::kSocks) {
default_proxy_scheme = net::ProxyServer::SCHEME_SOCKS4;
url_scheme = kSocksScheme;
} else {
NOTREACHED();
}
net::ProxyServer proxy_server = ConvertOncProxyLocationToHostPort(
default_proxy_scheme, *onc_proxy_location);
ProxyConfigDictionary::EncodeAndAppendProxyServer(url_scheme, proxy_server,
spec);
}
net::ProxyBypassRules ConvertOncExcludeDomainsToBypassRules(
const base::ListValue& onc_exclude_domains) {
net::ProxyBypassRules rules;
for (base::ListValue::const_iterator it = onc_exclude_domains.begin();
it != onc_exclude_domains.end(); ++it) {
std::string rule;
(*it)->GetAsString(&rule);
rules.AddRuleFromString(rule);
}
return rules;
}
std::string SchemeToString(net::ProxyServer::Scheme scheme) {
switch (scheme) {
case net::ProxyServer::SCHEME_DIRECT:
return kDirectScheme;
case net::ProxyServer::SCHEME_HTTP:
return url::kHttpScheme;
case net::ProxyServer::SCHEME_SOCKS4:
return kSocks4Scheme;
case net::ProxyServer::SCHEME_SOCKS5:
return kSocks5Scheme;
case net::ProxyServer::SCHEME_HTTPS:
return url::kHttpsScheme;
case net::ProxyServer::SCHEME_QUIC:
return kQuicScheme;
case net::ProxyServer::SCHEME_INVALID:
break;
}
NOTREACHED();
return "";
}
void SetProxyForScheme(const net::ProxyConfig::ProxyRules& proxy_rules,
const std::string& scheme,
const std::string& onc_scheme,
base::DictionaryValue* dict) {
const net::ProxyList* proxy_list = nullptr;
if (proxy_rules.type == net::ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY) {
proxy_list = &proxy_rules.single_proxies;
} else if (proxy_rules.type ==
net::ProxyConfig::ProxyRules::TYPE_PROXY_PER_SCHEME) {
proxy_list = proxy_rules.MapUrlSchemeToProxyList(scheme);
}
if (!proxy_list || proxy_list->IsEmpty())
return;
const net::ProxyServer& server = proxy_list->Get();
std::unique_ptr<base::DictionaryValue> url_dict(new base::DictionaryValue);
std::string host = server.host_port_pair().host();
// For all proxy types except SOCKS, the default scheme of the proxy host is
// HTTP.
net::ProxyServer::Scheme default_scheme =
(onc_scheme == ::onc::proxy::kSocks) ? net::ProxyServer::SCHEME_SOCKS4
: net::ProxyServer::SCHEME_HTTP;
// Only prefix the host with a non-default scheme.
if (server.scheme() != default_scheme)
host = SchemeToString(server.scheme()) + "://" + host;
url_dict->SetStringWithoutPathExpansion(::onc::proxy::kHost, host);
url_dict->SetIntegerWithoutPathExpansion(::onc::proxy::kPort,
server.host_port_pair().port());
dict->SetWithoutPathExpansion(onc_scheme, url_dict.release());
}
} // namespace
std::unique_ptr<base::DictionaryValue> ConvertOncProxySettingsToProxyConfig(
const base::DictionaryValue& onc_proxy_settings) {
std::string type;
onc_proxy_settings.GetStringWithoutPathExpansion(::onc::proxy::kType, &type);
std::unique_ptr<base::DictionaryValue> proxy_dict;
if (type == ::onc::proxy::kDirect) {
proxy_dict.reset(ProxyConfigDictionary::CreateDirect());
} else if (type == ::onc::proxy::kWPAD) {
proxy_dict.reset(ProxyConfigDictionary::CreateAutoDetect());
} else if (type == ::onc::proxy::kPAC) {
std::string pac_url;
onc_proxy_settings.GetStringWithoutPathExpansion(::onc::proxy::kPAC,
&pac_url);
GURL url(pac_url);
DCHECK(url.is_valid()) << "Invalid URL in ProxySettings.PAC";
proxy_dict.reset(ProxyConfigDictionary::CreatePacScript(url.spec(), false));
} else if (type == ::onc::proxy::kManual) {
const base::DictionaryValue* manual_dict = nullptr;
onc_proxy_settings.GetDictionaryWithoutPathExpansion(::onc::proxy::kManual,
&manual_dict);
std::string manual_spec;
AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kFtp, &manual_spec);
AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kHttp, &manual_spec);
AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kSocks,
&manual_spec);
AppendProxyServerForScheme(*manual_dict, ::onc::proxy::kHttps,
&manual_spec);
const base::ListValue* exclude_domains = nullptr;
net::ProxyBypassRules bypass_rules;
if (onc_proxy_settings.GetListWithoutPathExpansion(
::onc::proxy::kExcludeDomains, &exclude_domains)) {
bypass_rules.AssignFrom(
ConvertOncExcludeDomainsToBypassRules(*exclude_domains));
}
proxy_dict.reset(ProxyConfigDictionary::CreateFixedServers(
manual_spec, bypass_rules.ToString()));
} else {
NOTREACHED();
}
return proxy_dict;
}
std::unique_ptr<base::DictionaryValue> ConvertProxyConfigToOncProxySettings(
const base::DictionaryValue& proxy_config_value) {
// Create a ProxyConfigDictionary from the DictionaryValue.
std::unique_ptr<ProxyConfigDictionary> proxy_config(
new ProxyConfigDictionary(&proxy_config_value));
// Create the result DictionaryValue and populate it.
std::unique_ptr<base::DictionaryValue> proxy_settings(
new base::DictionaryValue);
ProxyPrefs::ProxyMode mode;
if (!proxy_config->GetMode(&mode))
return nullptr;
switch (mode) {
case ProxyPrefs::MODE_DIRECT: {
proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
::onc::proxy::kDirect);
break;
}
case ProxyPrefs::MODE_AUTO_DETECT: {
proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
::onc::proxy::kWPAD);
break;
}
case ProxyPrefs::MODE_PAC_SCRIPT: {
proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kType,
::onc::proxy::kPAC);
std::string pac_url;
proxy_config->GetPacUrl(&pac_url);
proxy_settings->SetStringWithoutPathExpansion(::onc::proxy::kPAC,
pac_url);
break;
}
case ProxyPrefs::MODE_FIXED_SERVERS: {
proxy_settings->SetString(::onc::proxy::kType, ::onc::proxy::kManual);
std::unique_ptr<base::DictionaryValue> manual(new base::DictionaryValue);
std::string proxy_rules_string;
if (proxy_config->GetProxyServer(&proxy_rules_string)) {
net::ProxyConfig::ProxyRules proxy_rules;
proxy_rules.ParseFromString(proxy_rules_string);
SetProxyForScheme(proxy_rules, url::kFtpScheme, ::onc::proxy::kFtp,
manual.get());
SetProxyForScheme(proxy_rules, url::kHttpScheme, ::onc::proxy::kHttp,
manual.get());
SetProxyForScheme(proxy_rules, url::kHttpsScheme, ::onc::proxy::kHttps,
manual.get());
SetProxyForScheme(proxy_rules, kSocksScheme, ::onc::proxy::kSocks,
manual.get());
}
proxy_settings->SetWithoutPathExpansion(::onc::proxy::kManual,
manual.release());
// Convert the 'bypass_list' string into dictionary entries.
std::string bypass_rules_string;
if (proxy_config->GetBypassList(&bypass_rules_string)) {
net::ProxyBypassRules bypass_rules;
bypass_rules.ParseFromString(bypass_rules_string);
std::unique_ptr<base::ListValue> exclude_domains(new base::ListValue);
for (const net::ProxyBypassRules::Rule* rule : bypass_rules.rules())
exclude_domains->AppendString(rule->ToString());
if (!exclude_domains->empty()) {
proxy_settings->SetWithoutPathExpansion(::onc::proxy::kExcludeDomains,
exclude_domains.release());
}
}
break;
}
default: {
LOG(ERROR) << "Unexpected proxy mode in Shill config: " << mode;
return nullptr;
}
}
return proxy_settings;
}
} // namespace onc
} // namespace chromeos