| // 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 <memory> |
| #include <utility> |
| |
| #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_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/values.h" |
| #include "chromeos/network/managed_network_configuration_handler.h" |
| #include "chromeos/network/network_configuration_handler.h" |
| #include "chromeos/network/network_event_log.h" |
| #include "chromeos/network/network_profile.h" |
| #include "chromeos/network/network_profile_handler.h" |
| #include "chromeos/network/network_state.h" |
| #include "chromeos/network/network_state_handler.h" |
| #include "chromeos/network/network_ui_data.h" |
| #include "chromeos/network/onc/onc_mapper.h" |
| #include "chromeos/network/onc/onc_normalizer.h" |
| #include "chromeos/network/onc/onc_signature.h" |
| #include "chromeos/network/onc/onc_translator.h" |
| #include "chromeos/network/onc/onc_utils.h" |
| #include "chromeos/network/onc/onc_validator.h" |
| #include "chromeos/network/tether_constants.h" |
| #include "components/account_id/account_id.h" |
| #include "components/device_event_log/device_event_log.h" |
| #include "components/onc/onc_constants.h" |
| #include "components/onc/onc_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/proxy_config/proxy_config_dictionary.h" |
| #include "components/url_formatter/url_fixer.h" |
| #include "components/user_manager/user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "crypto/encryptor.h" |
| #include "crypto/hmac.h" |
| #include "crypto/symmetric_key.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/proxy_server.h" |
| #include "net/cert/pem_tokenizer.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/cert/x509_util_nss.h" |
| #include "net/proxy_resolution/proxy_bypass_rules.h" |
| #include "net/proxy_resolution/proxy_config.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| using namespace ::onc; |
| |
| namespace chromeos { |
| namespace onc { |
| |
| namespace { |
| |
| // 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"; |
| |
| // 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"; |
| |
| std::string GetString(const base::Value& dict, const char* key) { |
| const base::Value* value = dict.FindKeyOfType(key, base::Value::Type::STRING); |
| if (!value) |
| return std::string(); |
| return value->GetString(); |
| } |
| |
| bool GetString(const base::Value& dict, const char* key, std::string* result) { |
| const base::Value* value = dict.FindKeyOfType(key, base::Value::Type::STRING); |
| if (!value) |
| return false; |
| *result = value->GetString(); |
| return true; |
| } |
| |
| int GetInt(const base::Value& dict, const char* key, int default_value) { |
| const base::Value* value = |
| dict.FindKeyOfType(key, base::Value::Type::INTEGER); |
| if (!value) |
| return default_value; |
| return value->GetInt(); |
| } |
| |
| bool GetInt(const base::Value& dict, const char* key, int* result) { |
| const base::Value* value = |
| dict.FindKeyOfType(key, base::Value::Type::INTEGER); |
| if (!value) |
| return false; |
| *result = value->GetInt(); |
| return true; |
| } |
| |
| // Runs |variable_expander.ExpandString| on the field |fieldname| in |
| // |onc_object|. |
| void ExpandField(const std::string& fieldname, |
| const VariableExpander& variable_expander, |
| base::DictionaryValue* onc_object) { |
| std::string field_value; |
| if (!onc_object->GetStringWithoutPathExpansion(fieldname, &field_value)) |
| return; |
| |
| variable_expander.ExpandString(&field_value); |
| |
| onc_object->SetKey(fieldname, base::Value(field_value)); |
| } |
| |
| // 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( |
| 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)) { |
| // 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::kPasswordPlaceholderVerbatim) { |
| return Mapper::MapField(field_name, object_signature, onc_value, |
| found_unknown_field, error); |
| } |
| return std::unique_ptr<base::Value>(new base::Value(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_; |
| }; |
| |
| // 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; |
| for (const auto& entry : certificates) { |
| const base::DictionaryValue* cert = nullptr; |
| bool entry_is_dictionary = entry.GetAsDictionary(&cert); |
| DCHECK(entry_is_dictionary); |
| |
| 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; |
| } |
| |
| // Fills HexSSID fields in all entries in the |network_configs| list. |
| void FillInHexSSIDFieldsInNetworks(base::Value* network_configs) { |
| for (auto& network : network_configs->GetList()) |
| FillInHexSSIDFieldsInOncObject(kNetworkConfigurationSignature, &network); |
| } |
| |
| // 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) { |
| 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; |
| } |
| |
| // 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, |
| 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->SetKey(key_pem, base::Value(pem_encoded)); |
| 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, |
| 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 auto& entry : *guid_ref_list) { |
| std::string guid_ref; |
| bool entry_is_string = entry.GetAsString(&guid_ref); |
| DCHECK(entry_is_string); |
| |
| 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, std::move(pem_list)); |
| 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, |
| 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, std::move(pem_list)); |
| 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); |
| } |
| |
| // 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) { |
| 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; |
| } |
| |
| net::ProxyServer ConvertOncProxyLocationToHostPort( |
| net::ProxyServer::Scheme default_proxy_scheme, |
| const base::Value& onc_proxy_location) { |
| std::string host = GetString(onc_proxy_location, ::onc::proxy::kHost); |
| // Parse |host| according to the format [<scheme>"://"]<server>[":"<port>]. |
| net::ProxyServer proxy_server = |
| net::ProxyServer::FromURI(host, default_proxy_scheme); |
| int port = GetInt(onc_proxy_location, ::onc::proxy::kPort, 0); |
| |
| // 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::Value& onc_manual, |
| const std::string& onc_scheme, |
| std::string* spec) { |
| const base::Value* onc_proxy_location = onc_manual.FindKey(onc_scheme); |
| if (!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::Value& onc_exclude_domains) { |
| net::ProxyBypassRules rules; |
| for (const base::Value& value : onc_exclude_domains.GetList()) { |
| if (!value.is_string()) { |
| LOG(ERROR) << "Badly formatted ONC exclude domains"; |
| continue; |
| } |
| rules.AddRuleFromString(value.GetString()); |
| } |
| 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::PROXY_LIST) { |
| proxy_list = &proxy_rules.single_proxies; |
| } else if (proxy_rules.type == |
| net::ProxyConfig::ProxyRules::Type::PROXY_LIST_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->SetKey(::onc::proxy::kHost, base::Value(host)); |
| url_dict->SetKey(::onc::proxy::kPort, |
| base::Value(server.host_port_pair().port())); |
| 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::Value> ReadDictionaryFromJson(const std::string& json) { |
| std::string error; |
| std::unique_ptr<base::Value> root = |
| base::JSONReader::ReadAndReturnErrorDeprecated( |
| json, base::JSON_ALLOW_TRAILING_COMMAS, nullptr, &error); |
| if (!root || !root->is_dict()) { |
| NET_LOG(ERROR) << "Invalid JSON Dictionary: " << error; |
| return nullptr; |
| } |
| return root; |
| } |
| |
| std::unique_ptr<base::Value> Decrypt(const std::string& passphrase, |
| const base::Value& 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 (!GetString(root, encrypted::kCiphertext, &ciphertext) || |
| !GetString(root, encrypted::kCipher, &cipher) || |
| !GetString(root, encrypted::kHMAC, &hmac) || |
| !GetString(root, encrypted::kHMACMethod, &hmac_method) || |
| !GetString(root, encrypted::kIV, &initial_vector) || |
| !GetInt(root, encrypted::kIterations, &iterations) || |
| !GetString(root, encrypted::kSalt, &salt) || |
| !GetString(root, encrypted::kStretch, &stretch_method) || |
| !GetString(root, 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::DeriveKeyFromPasswordUsingPbkdf2( |
| 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::Value> 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::Value* onc_object) { |
| DCHECK(onc_object->is_dict()); |
| if (&signature == &kWiFiSignature) |
| FillInHexSSIDField(onc_object); |
| |
| // Recurse into nested objects. |
| for (auto it : onc_object->DictItems()) { |
| if (!it.second.is_dict()) |
| continue; |
| |
| const OncFieldSignature* field_signature = |
| GetFieldSignature(signature, it.first); |
| if (!field_signature) |
| continue; |
| |
| FillInHexSSIDFieldsInOncObject(*field_signature->value_signature, |
| &it.second); |
| } |
| } |
| |
| void FillInHexSSIDField(base::Value* wifi_fields) { |
| if (wifi_fields->FindKey(::onc::wifi::kHexSSID)) |
| return; |
| base::Value* ssid = |
| wifi_fields->FindKeyOfType(::onc::wifi::kSSID, base::Value::Type::STRING); |
| if (!ssid) |
| return; |
| std::string ssid_string = ssid->GetString(); |
| 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::Value> 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; |
| if (GetString(*toplevel_onc, toplevel_config::kType, &onc_type) && |
| 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, |
| true); // Log warnings. |
| validator.SetOncSource(onc_source); |
| |
| Validator::Result validation_result; |
| std::unique_ptr<base::DictionaryValue> toplevel_onc_dict = |
| base::DictionaryValue::From(std::move(toplevel_onc)); |
| toplevel_onc = validator.ValidateAndRepairObject( |
| &kToplevelConfigurationSignature, *toplevel_onc_dict, &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; |
| } |
| |
| if (certificates) { |
| base::Value* validated_certs = toplevel_onc->FindKeyOfType( |
| toplevel_config::kCertificates, base::Value::Type::LIST); |
| if (validated_certs) |
| certificates->GetList().swap(validated_certs->GetList()); |
| } |
| |
| // 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). |
| base::Value* validated_networks = toplevel_onc->FindKeyOfType( |
| toplevel_config::kNetworkConfigurations, base::Value::Type::LIST); |
| base::ListValue* validated_networks_list; |
| if (validated_networks && |
| validated_networks->GetAsList(&validated_networks_list)) { |
| FillInHexSSIDFieldsInNetworks(validated_networks_list); |
| |
| CertPEMsByGUIDMap server_and_ca_certs = |
| GetServerAndCACertsByGUID(*certificates); |
| |
| if (!ResolveServerCertRefsInNetworks(server_and_ca_certs, |
| validated_networks_list)) { |
| 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_list); |
| } |
| |
| if (global_network_config) { |
| base::Value* validated_global_config = toplevel_onc->FindKeyOfType( |
| toplevel_config::kGlobalNetworkConfiguration, |
| base::Value::Type::DICTIONARY); |
| if (validated_global_config) { |
| base::DictionaryValue* validated_global_config_dict = nullptr; |
| if (validated_global_config->GetAsDictionary( |
| &validated_global_config_dict)) { |
| global_network_config->Swap(validated_global_config_dict); |
| } |
| } |
| } |
| |
| 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(::onc::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(); |
| } |
| |
| base::Value ConvertOncProxySettingsToProxyConfig( |
| const base::Value& onc_proxy_settings) { |
| std::string type = GetString(onc_proxy_settings, ::onc::proxy::kType); |
| |
| if (type == ::onc::proxy::kDirect) { |
| return ProxyConfigDictionary::CreateDirect(); |
| } |
| if (type == ::onc::proxy::kWPAD) { |
| return ProxyConfigDictionary::CreateAutoDetect(); |
| } |
| if (type == ::onc::proxy::kPAC) { |
| std::string pac_url = GetString(onc_proxy_settings, ::onc::proxy::kPAC); |
| GURL url(url_formatter::FixupURL(pac_url, std::string())); |
| return ProxyConfigDictionary::CreatePacScript( |
| url.is_valid() ? url.spec() : std::string(), false); |
| } |
| if (type == ::onc::proxy::kManual) { |
| const base::Value* manual_dict = |
| onc_proxy_settings.FindKey(::onc::proxy::kManual); |
| 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); |
| |
| net::ProxyBypassRules bypass_rules; |
| const base::Value* exclude_domains = onc_proxy_settings.FindKeyOfType( |
| ::onc::proxy::kExcludeDomains, base::Value::Type::LIST); |
| if (exclude_domains) |
| bypass_rules = ConvertOncExcludeDomainsToBypassRules(*exclude_domains); |
| return ProxyConfigDictionary::CreateFixedServers(manual_spec, |
| bypass_rules.ToString()); |
| } |
| NOTREACHED(); |
| return base::Value(); |
| } |
| |
| base::Value ConvertProxyConfigToOncProxySettings( |
| const base::Value& proxy_config_value) { |
| // Create a ProxyConfigDictionary from the dictionary. |
| ProxyConfigDictionary proxy_config(proxy_config_value.Clone()); |
| |
| // Create the result DictionaryValue and populate it. |
| base::Value proxy_settings(base::Value::Type::DICTIONARY); |
| ProxyPrefs::ProxyMode mode; |
| if (!proxy_config.GetMode(&mode)) |
| return base::Value(); |
| switch (mode) { |
| case ProxyPrefs::MODE_DIRECT: { |
| proxy_settings.SetKey(::onc::proxy::kType, |
| base::Value(::onc::proxy::kDirect)); |
| break; |
| } |
| case ProxyPrefs::MODE_AUTO_DETECT: { |
| proxy_settings.SetKey(::onc::proxy::kType, |
| base::Value(::onc::proxy::kWPAD)); |
| break; |
| } |
| case ProxyPrefs::MODE_PAC_SCRIPT: { |
| proxy_settings.SetKey(::onc::proxy::kType, |
| base::Value(::onc::proxy::kPAC)); |
| std::string pac_url; |
| proxy_config.GetPacUrl(&pac_url); |
| proxy_settings.SetKey(::onc::proxy::kPAC, base::Value(pac_url)); |
| break; |
| } |
| case ProxyPrefs::MODE_FIXED_SERVERS: { |
| proxy_settings.SetKey(::onc::proxy::kType, |
| base::Value(::onc::proxy::kManual)); |
| base::DictionaryValue manual; |
| 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); |
| SetProxyForScheme(proxy_rules, url::kHttpScheme, ::onc::proxy::kHttp, |
| &manual); |
| SetProxyForScheme(proxy_rules, url::kHttpsScheme, ::onc::proxy::kHttps, |
| &manual); |
| SetProxyForScheme(proxy_rules, kSocksScheme, ::onc::proxy::kSocks, |
| &manual); |
| } |
| proxy_settings.SetKey(::onc::proxy::kManual, std::move(manual)); |
| |
| // 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); |
| base::ListValue exclude_domains; |
| for (const auto& rule : bypass_rules.rules()) |
| exclude_domains.AppendString(rule->ToString()); |
| if (!exclude_domains.empty()) { |
| proxy_settings.SetKey(::onc::proxy::kExcludeDomains, |
| std::move(exclude_domains)); |
| } |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << "Unexpected proxy mode in Shill config: " << mode; |
| return base::Value(); |
| } |
| } |
| return proxy_settings; |
| } |
| |
| void ExpandStringPlaceholdersInNetworksForUser( |
| const user_manager::User* user, |
| base::ListValue* network_configs) { |
| if (!user) { |
| // In tests no user may be logged in. It's not harmful if we just don't |
| // expand the strings. |
| return; |
| } |
| |
| // 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, |
| const base::ListValue& network_configs, |
| std::string* error) { |
| error->clear(); |
| |
| std::unique_ptr<base::ListValue> expanded_networks( |
| network_configs.DeepCopy()); |
| ExpandStringPlaceholdersInNetworksForUser(user, expanded_networks.get()); |
| |
| const NetworkProfile* profile = |
| NetworkHandler::Get()->network_profile_handler()->GetProfileForUserhash( |
| user->username_hash()); |
| if (!profile) { |
| *error = "User profile doesn't exist."; |
| return; |
| } |
| |
| bool ethernet_not_found = false; |
| for (base::ListValue::const_iterator it = expanded_networks->begin(); |
| it != expanded_networks->end(); ++it) { |
| const base::DictionaryValue* network = NULL; |
| it->GetAsDictionary(&network); |
| DCHECK(network); |
| |
| // Remove irrelevant fields. |
| onc::Normalizer normalizer(true /* remove recommended fields */); |
| std::unique_ptr<base::DictionaryValue> normalized_network = |
| normalizer.NormalizeObject(&onc::kNetworkConfigurationSignature, |
| *network); |
| |
| // TODO(pneubeck): Use ONC and ManagedNetworkConfigurationHandler instead. |
| // crbug.com/457936 |
| std::unique_ptr<base::DictionaryValue> shill_dict = |
| onc::TranslateONCObjectToShill(&onc::kNetworkConfigurationSignature, |
| *normalized_network); |
| |
| std::unique_ptr<NetworkUIData> ui_data( |
| NetworkUIData::CreateFromONC(::onc::ONC_SOURCE_USER_IMPORT)); |
| shill_dict->SetKey(shill::kUIDataProperty, |
| base::Value(ui_data->GetAsJson())); |
| shill_dict->SetKey(shill::kProfileProperty, base::Value(profile->path)); |
| |
| std::string type; |
| shill_dict->GetStringWithoutPathExpansion(shill::kTypeProperty, &type); |
| NetworkConfigurationHandler* config_handler = |
| NetworkHandler::Get()->network_configuration_handler(); |
| if (NetworkTypePattern::Ethernet().MatchesType(type)) { |
| // Ethernet has to be configured using an existing Ethernet service. |
| const NetworkState* ethernet = |
| NetworkHandler::Get()->network_state_handler()->FirstNetworkByType( |
| NetworkTypePattern::Ethernet()); |
| if (ethernet) { |
| config_handler->SetShillProperties(ethernet->path(), *shill_dict, |
| base::Closure(), |
| network_handler::ErrorCallback()); |
| } else { |
| ethernet_not_found = true; |
| } |
| |
| } else { |
| config_handler->CreateShillConfiguration( |
| *shill_dict, network_handler::ServiceResultCallback(), |
| network_handler::ErrorCallback()); |
| } |
| } |
| |
| if (ethernet_not_found) |
| *error = "No Ethernet available to configure."; |
| } |
| |
| const base::DictionaryValue* FindPolicyForActiveUser( |
| const std::string& guid, |
| ::onc::ONCSource* onc_source) { |
| const user_manager::User* user = |
| user_manager::UserManager::Get()->GetActiveUser(); |
| std::string username_hash = user ? user->username_hash() : std::string(); |
| return NetworkHandler::Get() |
| ->managed_network_configuration_handler() |
| ->FindPolicyByGUID(username_hash, guid, onc_source); |
| } |
| |
| bool PolicyAllowsOnlyPolicyNetworksToAutoconnect(bool for_active_user) { |
| const base::DictionaryValue* global_config = |
| GetGlobalConfigFromPolicy(for_active_user); |
| if (!global_config) |
| return false; // By default, all networks are allowed to autoconnect. |
| |
| bool only_policy_autoconnect = false; |
| global_config->GetBooleanWithoutPathExpansion( |
| ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect, |
| &only_policy_autoconnect); |
| return only_policy_autoconnect; |
| } |
| |
| const base::DictionaryValue* GetPolicyForNetwork( |
| const PrefService* profile_prefs, |
| const PrefService* local_state_prefs, |
| const NetworkState& network, |
| ::onc::ONCSource* onc_source) { |
| VLOG(2) << "GetPolicyForNetwork: " << network.path(); |
| *onc_source = ::onc::ONC_SOURCE_NONE; |
| |
| const base::DictionaryValue* network_policy = GetPolicyForNetworkFromPref( |
| profile_prefs, ::onc::prefs::kOpenNetworkConfiguration, network); |
| if (network_policy) { |
| VLOG(1) << "Network " << network.path() << " is managed by user policy."; |
| *onc_source = ::onc::ONC_SOURCE_USER_POLICY; |
| return network_policy; |
| } |
| network_policy = GetPolicyForNetworkFromPref( |
| local_state_prefs, ::onc::prefs::kDeviceOpenNetworkConfiguration, |
| network); |
| if (network_policy) { |
| VLOG(1) << "Network " << network.path() << " is managed by device policy."; |
| *onc_source = ::onc::ONC_SOURCE_DEVICE_POLICY; |
| return network_policy; |
| } |
| VLOG(2) << "Network " << network.path() << " is unmanaged."; |
| return NULL; |
| } |
| |
| bool HasPolicyForNetwork(const PrefService* profile_prefs, |
| const PrefService* local_state_prefs, |
| const NetworkState& network) { |
| ::onc::ONCSource ignored_onc_source; |
| const base::DictionaryValue* policy = onc::GetPolicyForNetwork( |
| profile_prefs, local_state_prefs, network, &ignored_onc_source); |
| return policy != NULL; |
| } |
| |
| bool HasUserPasswordSubsitutionVariable(const OncValueSignature& signature, |
| base::DictionaryValue* onc_object) { |
| if (&signature == &kEAPSignature) { |
| std::string password_field; |
| if (!onc_object->GetStringWithoutPathExpansion(::onc::eap::kPassword, |
| &password_field)) { |
| return false; |
| } |
| |
| if (password_field == ::onc::substitutes::kPasswordPlaceholderVerbatim) { |
| return true; |
| } |
| } |
| |
| // 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; |
| |
| bool result = HasUserPasswordSubsitutionVariable( |
| *field_signature->value_signature, inner_object); |
| if (result) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool HasUserPasswordSubsitutionVariable(base::ListValue* network_configs) { |
| for (auto& entry : *network_configs) { |
| base::DictionaryValue* network = nullptr; |
| entry.GetAsDictionary(&network); |
| DCHECK(network); |
| |
| bool result = HasUserPasswordSubsitutionVariable( |
| kNetworkConfigurationSignature, network); |
| if (result) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace onc |
| } // namespace chromeos |