// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/onc/onc_utils.h"
#include <string>
#include <vector>
#include "base/base64.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chromeos/components/onc/onc_mapper.h"
#include "chromeos/components/onc/onc_signature.h"
#include "chromeos/components/onc/onc_validator.h"
#include "chromeos/components/onc/variable_expander.h"
#include "components/device_event_log/device_event_log.h"
#include "crypto/encryptor.h"
#include "crypto/hmac.h"
#include "crypto/symmetric_key.h"
#include "net/cert/x509_certificate.h"
#include "third_party/boringssl/src/pki/pem.h"
namespace chromeos::onc {
namespace {
using IdToAPNMap = std::map<std::string, const base::Value::Dict*>;
// 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";
bool GetString(const base::Value::Dict& dict,
const char* key,
std::string* result) {
const std::string* value = dict.FindString(key);
if (!value) {
return false;
}
*result = *value;
return true;
}
bool GetInt(const base::Value::Dict& dict, const char* key, int* result) {
const std::optional<int> value = dict.FindInt(key);
if (!value) {
return false;
}
*result = value.value();
return true;
}
// Runs |variable_expander.ExpandString| on the field |fieldname| in
// |onc_object|.
void ExpandField(const std::string& fieldname,
const VariableExpander& variable_expander,
base::Value::Dict* onc_object) {
std::string* field_value = onc_object->FindString(fieldname);
if (!field_value) {
return;
}
variable_expander.ExpandString(field_value);
}
bool CanContainPasswordPlaceholder(const std::string& field_name,
const OncValueSignature& object_signature) {
return (&object_signature == &kEAPSignature &&
field_name == ::onc::eap::kPassword) ||
(&object_signature == &kL2TPSignature &&
field_name == ::onc::l2tp::kPassword);
}
bool IsUserLoginPasswordPlaceholder(const std::string& field_name,
const OncValueSignature& object_signature,
const base::Value& onc_value) {
if (!CanContainPasswordPlaceholder(field_name, object_signature)) {
return false;
}
DCHECK(onc_value.is_string());
return onc_value.GetString() ==
::onc::substitutes::kPasswordPlaceholderVerbatim;
}
// A |Mapper| for masking sensitive fields (e.g. credentials such as
// passphrases) in ONC.
class OncMaskValues : public Mapper {
public:
static base::Value::Dict Mask(const OncValueSignature& signature,
const base::Value::Dict& onc_object,
const std::string& mask) {
OncMaskValues masker(mask);
bool error = false;
base::Value::Dict result = masker.MapObject(signature, onc_object, &error);
return result;
}
protected:
explicit OncMaskValues(const std::string& mask) : mask_(mask) {}
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 (IsUserLoginPasswordPlaceholder(field_name, object_signature,
onc_value)) {
return Mapper::MapField(field_name, object_signature, onc_value,
found_unknown_field, error);
}
return base::Value(mask_);
} else {
return Mapper::MapField(field_name, object_signature, onc_value,
found_unknown_field, error);
}
}
private:
// 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::Value::List& certificates) {
CertPEMsByGUIDMap certs_by_guid;
for (const auto& cert_value : certificates) {
const base::Value::Dict& cert = cert_value.GetDict();
const std::string* guid = cert.FindString(::onc::certificate::kGUID);
if (!guid || guid->empty()) {
NET_LOG(ERROR) << "Certificate with missing or empty GUID.";
continue;
}
const std::string* cert_type = cert.FindString(::onc::certificate::kType);
DCHECK(cert_type);
if (*cert_type != ::onc::certificate::kServer &&
*cert_type != ::onc::certificate::kAuthority) {
continue;
}
const std::string* x509_data = cert.FindString(::onc::certificate::kX509);
std::string der;
if (x509_data) {
der = DecodePEM(*x509_data);
}
std::string pem;
if (der.empty() || !net::X509Certificate::GetPEMEncodedFromDER(der, &pem)) {
NET_LOG(ERROR) << "Certificate not PEM encoded, GUID: " << *guid;
continue;
}
certs_by_guid[*guid] = pem;
}
return certs_by_guid;
}
// Set APN dictionary and associated recommended values to solve the issue
// of setting the APN for managed eSIM profiles (see http://b/295226668) in
// old APN UI.
void SetAPNDictAndRecommendedIfNone(base::Value::Dict& cellular_fields) {
if (cellular_fields.Find(::onc::cellular::kAPN)) {
return;
}
auto apn_recommended_list = base::Value::List()
.Append(::onc::cellular_apn::kAccessPointName)
.Append(::onc::cellular_apn::kAttach)
.Append(::onc::cellular_apn::kAuthentication)
.Append(::onc::cellular_apn::kUsername)
.Append(::onc::cellular_apn::kPassword);
base::Value* apn_dict = cellular_fields.Set(
::onc::cellular::kAPN, base::Value(base::Value::Type::DICT));
apn_dict->GetDict().Set(::onc::kRecommended, std::move(apn_recommended_list));
}
// Modify recommended list to include custom APN list field to solve the issue
// of setting the APN for managed eSIM profiles (see http://b/295226668) in
// revamp APN UI.
void AddCustomAPNListToRecommended(base::Value::Dict& cellular_fields) {
auto* recommended = cellular_fields.Find(::onc::kRecommended);
if (!recommended) {
recommended = cellular_fields.Set(::onc::kRecommended,
base::Value(base::Value::Type::LIST));
}
for (const auto& field : recommended->GetList()) {
if (field == ::onc::cellular::kCustomAPNList) {
return;
}
}
recommended->GetList().Append(::onc::cellular::kCustomAPNList);
}
void FillInCellularDefaultsInOncObject(const OncValueSignature& signature,
base::Value::Dict& onc_object,
bool allow_apn_modification) {
if (&signature == &kCellularSignature) {
if (allow_apn_modification) {
AddCustomAPNListToRecommended(onc_object);
} else {
onc_object.Set(::onc::cellular::kCustomAPNList, base::Value::List());
}
SetAPNDictAndRecommendedIfNone(onc_object);
return;
}
// The function takes any ONC object and recursively searches until it finds a
// Cellular dictionary to set the default values.
for (auto it : onc_object) {
if (!it.second.is_dict()) {
continue;
}
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature) {
continue;
}
FillInCellularDefaultsInOncObject(*field_signature->value_signature,
it.second.GetDict(),
allow_apn_modification);
}
}
// Creates an APN dict with nested recommended field in cellular entries lacking
// an APN dict in |network_configs| list. If |allow_apn_modification| is true,
// "CustomAPNList" is added as a recommended field to the cellular config,
// otherwise, the CustomAPNList field is set to an empty list.
void FillInCellularDefaultsInNetworks(base::Value::List& network_configs,
bool allow_apn_modification) {
for (auto& network : network_configs) {
FillInCellularDefaultsInOncObject(kNetworkConfigurationSignature,
network.GetDict(),
allow_apn_modification);
}
}
// Creates a map from APN IDs to their corresponding configuration dictionaries.
IdToAPNMap BuildIdToAPNMap(const base::Value::List* apn_list) {
IdToAPNMap apn_map;
if (!apn_list) {
return apn_map;
}
for (const base::Value& apn_value : *apn_list) {
const base::Value::Dict& apn_dict = apn_value.GetDict();
const std::string* apn_id = apn_dict.FindString(::onc::cellular_apn::kId);
if (apn_id) {
apn_map.emplace(*apn_id, &apn_dict);
}
}
return apn_map;
}
// Extracts a list of APN dictionaries based on a provided list of APN IDs, such
// that |apn_id_list| is a list of string IDs representing the APNs to extract,
// and |apn_map| is a map of all available APN dictionaries with key being APN
// ID. Returns a base::List if IDs are successfully extracted and the source is
// set successfully, and an std::nullopt otherwise.
std::optional<base::Value::List> ExtractAPNsByIdsAndSetAdminSource(
const base::Value::List* apn_id_list,
const IdToAPNMap& apn_map) {
base::Value::List result = base::Value::List();
for (const base::Value& apn_id_value : *apn_id_list) {
const std::string apn_id = apn_id_value.GetString();
// Find the APN in the map
auto it = apn_map.find(apn_id);
if (it == apn_map.end()) {
NET_LOG(ERROR)
<< "Failed to find an admin provided APN associated to an ID of "
<< apn_id;
return std::nullopt;
}
base::Value::Dict apn_cpy = it->second->Clone();
apn_cpy.Set(::onc::cellular_apn::kSource,
::onc::cellular_apn::kSourceAdmin);
result.Append(std::move(apn_cpy));
}
return result;
}
// Updates a cellular network configuration with custom APN information from
// admin-assigned APNs. Looks for a list of admin-assigned APN IDs in
// |cellular_fields|. If found, it extracts the corresponding APN dictionaries
// from |admin_apn_by_id| and sets the CustomAPNList field in |cellular_fields|.
// Note that if |admin_apn_by_id| is null, no changes are made to
// |cellular_fields|. Also note that each extracted APN will have a
// |::onc::cellular_apn::kSource| of
// |::onc::cellular_apn::kSourceAdmin|. Returns true if |cellular_fields| are
// successfully updated.
bool UpdateCellularFieldsWithAdminApns(base::Value::Dict& cellular_fields,
const IdToAPNMap& admin_apn_by_id) {
const base::Value::List* admin_apn_id_list =
cellular_fields.FindList(::onc::cellular::kAdminAssignedAPNIds);
if (!admin_apn_id_list) {
return true;
}
if (admin_apn_id_list->empty()) {
cellular_fields.Set(::onc::cellular::kCustomAPNList, base::Value::List());
return true;
}
std::optional<base::Value::List> admin_apns =
ExtractAPNsByIdsAndSetAdminSource(admin_apn_id_list, admin_apn_by_id);
if (!admin_apns.has_value()) {
NET_LOG(ERROR) << "Failed to extract admin APNs";
return false;
}
cellular_fields.Set(::onc::cellular::kCustomAPNList, std::move(*admin_apns));
return true;
}
bool ConstructAndSetPSIMAdminAPNs(base::Value::Dict& global_network_config,
const IdToAPNMap& admin_apn_by_id) {
if (admin_apn_by_id.empty()) {
return true;
}
const base::Value::List* psim_admin_apn_id_list =
global_network_config.FindList(
::onc::global_network_config::kPSIMAdminAssignedAPNIds);
if (!psim_admin_apn_id_list) {
return true;
}
std::optional<base::Value::List> psim_admin_apns =
ExtractAPNsByIdsAndSetAdminSource(psim_admin_apn_id_list,
admin_apn_by_id);
if (!psim_admin_apns.has_value()) {
NET_LOG(ERROR) << "Failed to extract pSIM admin APNs";
return false;
}
global_network_config.Set(
::onc::global_network_config::kPSIMAdminAssignedAPNs,
std::move(*psim_admin_apns));
return true;
}
// Recursively traverses the |onc_object|, searching for
// cellular dictionaries. If found, it updates the 'CustomAPNList' field within
// the Cellular dictionary using |admin_apn_by_id| if applicable.
//
// The recursion is guided by the |signature|, which defines the structure of
// the ONC object and helps the function determine which fields to traverse.
// Returns true if admin APNs are successfully applied.
bool ApplyAdminApnsToOncObject(const OncValueSignature& signature,
base::Value::Dict& onc_object,
const IdToAPNMap& admin_apn_by_id) {
if (&signature == &kCellularSignature) {
return UpdateCellularFieldsWithAdminApns(onc_object, admin_apn_by_id);
}
// The function takes any ONC object and recursively searches until it finds a
// Cellular dictionary to set the Custom APN List.
for (auto it : onc_object) {
if (!it.second.is_dict()) {
continue;
}
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature) {
continue;
}
if (!ApplyAdminApnsToOncObject(*field_signature->value_signature,
it.second.GetDict(), admin_apn_by_id)) {
return false;
}
}
return true;
}
// Processes a list of network configurations, identifying those of cellular
// type. For each cellular configuration, it associates and embeds the
// corresponding admin defined APN details found in |admin_apn_by_id|. This is
// achieved by recursively traversing the cellular configuration's structure and
// updating the APN information where applicable.
//
// The function relies on a top-level ONC configuration that contains a list of
// APNs provided by administrators. Cellular networks within the configuration
// may reference these APNs using unique identifiers (IDs).
//
// Ultimately, this function ensures that the cellular networks in the provided
// |network_configs| list are populated with the complete APN configurations
// that they are associated with. Otherwise, it returns false.
bool ConfigureAdminApnsInCellularNetworks(base::Value::List& network_configs,
const IdToAPNMap& admin_apn_by_id) {
if (admin_apn_by_id.empty()) {
return true;
}
for (auto& network : network_configs) {
if (!ApplyAdminApnsToOncObject(kNetworkConfigurationSignature,
network.GetDict(), admin_apn_by_id)) {
return false;
}
}
return true;
}
// Fills HexSSID fields in all entries in the |network_configs| list.
void FillInHexSSIDFieldsInNetworks(base::Value::List& network_configs) {
for (auto& network : network_configs) {
FillInHexSSIDFieldsInOncObject(kNetworkConfigurationSignature,
network.GetDict());
}
}
// Sets HiddenSSID fields in all entries in the |network_configs| list.
void SetHiddenSSIDFieldsInNetworks(base::Value::List& network_configs) {
for (auto& network : network_configs) {
SetHiddenSSIDFieldInOncObject(kNetworkConfigurationSignature,
network.GetDict());
}
}
// 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::Value::Dict& onc_object) {
std::string* guid_ref = onc_object.FindString(key_guid_ref);
if (!guid_ref) {
return true;
}
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, *guid_ref, &pem_encoded)) {
return false;
}
onc_object.Remove(key_guid_ref);
onc_object.Set(key_pem, 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::Value::Dict& onc_object) {
const base::Value::List* guid_ref_list =
onc_object.FindList(key_guid_ref_list);
if (!guid_ref_list) {
return true;
}
base::Value::List pem_list;
for (const auto& entry : *guid_ref_list) {
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, entry.GetString(), &pem_encoded)) {
return false;
}
pem_list.Append(pem_encoded);
}
onc_object.Remove(key_guid_ref_list);
onc_object.Set(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::Value::Dict& onc_object) {
std::string* guid_ref = onc_object.FindString(key_guid_ref);
if (!guid_ref) {
return true;
}
std::string pem_encoded;
if (!GUIDRefToPEMEncoding(certs_by_guid, *guid_ref, &pem_encoded)) {
return false;
}
base::Value::List pem_list;
pem_list.Append(pem_encoded);
onc_object.Remove(key_guid_ref);
onc_object.Set(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::Value::Dict& onc_dict) {
if (onc_dict.contains(key_guid_refs)) {
if (onc_dict.contains(key_guid_ref)) {
LOG(ERROR) << "Found both " << key_guid_refs << " and " << key_guid_ref
<< ". Ignoring and removing the latter.";
onc_dict.Remove(key_guid_ref);
}
return ResolveCertRefList(certs_by_guid, key_guid_refs, key_pem_list,
onc_dict);
}
// Only resolve |key_guid_ref| if |key_guid_refs| isn't present.
return ResolveSingleCertRefToList(certs_by_guid, key_guid_ref, key_pem_list,
onc_dict);
}
// Resolve known server and authority certificate reference fields in
// |onc_object|.
bool ResolveServerCertRefsInObject(const CertPEMsByGUIDMap& certs_by_guid,
const OncValueSignature& signature,
base::Value::Dict& onc_object) {
if (&signature == &kCertificatePatternSignature) {
if (!ResolveCertRefList(certs_by_guid, ::onc::client_cert::kIssuerCARef,
::onc::client_cert::kIssuerCAPEMs, onc_object)) {
return false;
}
} else if (&signature == &kEAPSignature) {
if (!ResolveCertRefsOrRefToList(certs_by_guid, ::onc::eap::kServerCARefs,
::onc::eap::kServerCARef,
::onc::eap::kServerCAPEMs, onc_object)) {
return false;
}
} else if (&signature == &kIPsecSignature) {
if (!ResolveCertRefsOrRefToList(certs_by_guid, ::onc::ipsec::kServerCARefs,
::onc::ipsec::kServerCARef,
::onc::ipsec::kServerCAPEMs, onc_object)) {
return false;
}
} else if (&signature == &kIPsecSignature ||
&signature == &kOpenVPNSignature) {
if (!ResolveSingleCertRef(certs_by_guid, ::onc::openvpn::kServerCertRef,
::onc::openvpn::kServerCertPEM, onc_object) ||
!ResolveCertRefsOrRefToList(
certs_by_guid, ::onc::openvpn::kServerCARefs,
::onc::openvpn::kServerCARef, ::onc::openvpn::kServerCAPEMs,
onc_object)) {
return false;
}
}
// Recurse into nested objects.
for (auto it : onc_object) {
if (!it.second.is_dict()) {
continue;
}
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature) {
continue;
}
if (!ResolveServerCertRefsInObject(certs_by_guid,
*field_signature->value_signature,
it.second.GetDict())) {
return false;
}
}
return true;
}
} // namespace
std::optional<base::Value::Dict> ReadDictionaryFromJson(
const std::string& json) {
if (json.empty()) {
// Policy may contain empty values, just log a debug message.
NET_LOG(DEBUG) << "Empty json string";
return std::nullopt;
}
auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
json,
base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS);
if (!parsed_json.has_value()) {
NET_LOG(ERROR) << "Invalid JSON Dictionary: "
<< parsed_json.error().message;
return std::nullopt;
}
if (!parsed_json->is_dict()) {
NET_LOG(ERROR) << "Invalid JSON Dictionary: Expected a dictionary.";
return std::nullopt;
}
return std::move(*parsed_json).TakeDict();
}
std::optional<base::Value::Dict> Decrypt(const std::string& passphrase,
const base::Value::Dict& 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, ::onc::encrypted::kCiphertext, &ciphertext) ||
!GetString(root, ::onc::encrypted::kCipher, &cipher) ||
!GetString(root, ::onc::encrypted::kHMAC, &hmac) ||
!GetString(root, ::onc::encrypted::kHMACMethod, &hmac_method) ||
!GetString(root, ::onc::encrypted::kIV, &initial_vector) ||
!GetInt(root, ::onc::encrypted::kIterations, &iterations) ||
!GetString(root, ::onc::encrypted::kSalt, &salt) ||
!GetString(root, ::onc::encrypted::kStretch, &stretch_method) ||
!GetString(root, ::onc::toplevel_config::kType, &onc_type) ||
onc_type != ::onc::toplevel_config::kEncryptedConfiguration) {
NET_LOG(ERROR) << "Encrypted ONC malformed.";
return std::nullopt;
}
if (hmac_method != ::onc::encrypted::kSHA1 ||
cipher != ::onc::encrypted::kAES256 ||
stretch_method != ::onc::encrypted::kPBKDF2) {
NET_LOG(ERROR) << "Encrypted ONC unsupported encryption scheme.";
return std::nullopt;
}
// Make sure iterations != 0, since that's not valid.
if (iterations == 0) {
NET_LOG(ERROR) << kUnableToDecrypt;
return std::nullopt;
}
// 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 std::nullopt;
}
if (!base::Base64Decode(salt, &salt)) {
NET_LOG(ERROR) << kUnableToDecode;
return std::nullopt;
}
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 std::nullopt;
}
if (!base::Base64Decode(ciphertext, &ciphertext)) {
NET_LOG(ERROR) << kUnableToDecode;
return std::nullopt;
}
if (!base::Base64Decode(hmac, &hmac)) {
NET_LOG(ERROR) << kUnableToDecode;
return std::nullopt;
}
crypto::HMAC hmac_verifier(crypto::HMAC::SHA1);
if (!hmac_verifier.Init(key.get()) ||
!hmac_verifier.Verify(ciphertext, hmac)) {
NET_LOG(ERROR) << kUnableToDecrypt;
return std::nullopt;
}
crypto::Encryptor decryptor;
if (!decryptor.Init(key.get(), crypto::Encryptor::CBC, initial_vector)) {
NET_LOG(ERROR) << kUnableToDecrypt;
return std::nullopt;
}
std::string plaintext;
if (!decryptor.Decrypt(ciphertext, &plaintext)) {
NET_LOG(ERROR) << kUnableToDecrypt;
return std::nullopt;
}
std::optional<base::Value::Dict> new_root = ReadDictionaryFromJson(plaintext);
if (!new_root) {
NET_LOG(ERROR) << "Property dictionary malformed.";
}
return new_root;
}
std::string GetSourceAsString(::onc::ONCSource source) {
switch (source) {
case ::onc::ONC_SOURCE_UNKNOWN:
return "unknown";
case ::onc::ONC_SOURCE_NONE:
return "none";
case ::onc::ONC_SOURCE_DEVICE_POLICY:
return "device policy";
case ::onc::ONC_SOURCE_USER_POLICY:
return "user policy";
case ::onc::ONC_SOURCE_USER_IMPORT:
return "user import";
}
NOTREACHED_IN_MIGRATION();
return "unknown";
}
void ExpandStringsInOncObject(const OncValueSignature& signature,
const VariableExpander& variable_expander,
base::Value::Dict* onc_object) {
if (&signature == &kEAPSignature) {
ExpandField(::onc::eap::kAnonymousIdentity, variable_expander, onc_object);
ExpandField(::onc::eap::kIdentity, variable_expander, onc_object);
} else if (&signature == &kL2TPSignature ||
&signature == &kOpenVPNSignature) {
ExpandField(::onc::vpn::kUsername, variable_expander, onc_object);
}
// Recurse into nested objects.
for (auto it : *onc_object) {
if (!it.second.is_dict())
continue;
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature)
continue;
ExpandStringsInOncObject(*field_signature->value_signature,
variable_expander, &it.second.GetDict());
}
}
void ExpandStringsInNetworks(const VariableExpander& variable_expander,
base::Value::List& network_configs) {
for (auto& network : network_configs) {
ExpandStringsInOncObject(kNetworkConfigurationSignature, variable_expander,
&network.GetDict());
}
}
void FillInCellularCustomAPNListField(
base::Value::Dict& cellular_fields,
const base::Value::List* custom_apn_list) {
if (cellular_fields.Find(::onc::cellular::kCustomAPNList)) {
NET_LOG(DEBUG) << "kCustomAPNList found, skipping";
return;
}
NET_LOG(DEBUG) << "Filling in kCustomAPNList with "
<< custom_apn_list->DebugString();
cellular_fields.Set(::onc::cellular::kCustomAPNList,
custom_apn_list->Clone());
}
void FillInCellularCustomAPNListFieldsInOncObject(
const OncValueSignature& signature,
base::Value::Dict& onc_object,
const base::Value::List* custom_apn_list) {
if (&signature == &kCellularSignature) {
FillInCellularCustomAPNListField(onc_object, custom_apn_list);
}
for (auto it : onc_object) {
if (!it.second.is_dict()) {
continue;
}
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature) {
continue;
}
FillInCellularCustomAPNListFieldsInOncObject(
*field_signature->value_signature, it.second.GetDict(),
custom_apn_list);
}
}
void FillInHexSSIDFieldsInOncObject(const OncValueSignature& signature,
base::Value::Dict& onc_object) {
if (&signature == &kWiFiSignature)
FillInHexSSIDField(onc_object);
// Recurse into nested objects.
for (auto it : onc_object) {
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.GetDict());
}
}
void FillInHexSSIDField(base::Value::Dict& wifi_fields) {
if (wifi_fields.Find(::onc::wifi::kHexSSID)) {
return;
}
std::string* ssid = wifi_fields.FindString(::onc::wifi::kSSID);
if (!ssid) {
return;
}
if (ssid->empty()) {
NET_LOG(ERROR) << "Found empty SSID field.";
return;
}
wifi_fields.Set(::onc::wifi::kHexSSID, base::HexEncode(*ssid));
}
void SetHiddenSSIDFieldInOncObject(const OncValueSignature& signature,
base::Value::Dict& onc_object) {
if (&signature == &kWiFiSignature) {
SetHiddenSSIDField(onc_object);
}
// Recurse into nested objects.
for (auto it : onc_object) {
if (!it.second.is_dict()) {
continue;
}
const OncFieldSignature* field_signature =
GetFieldSignature(signature, it.first);
if (!field_signature) {
continue;
}
SetHiddenSSIDFieldInOncObject(*field_signature->value_signature,
it.second.GetDict());
}
}
void SetHiddenSSIDField(base::Value::Dict& wifi_fields) {
if (wifi_fields.Find(::onc::wifi::kHiddenSSID)) {
return;
}
wifi_fields.Set(::onc::wifi::kHiddenSSID, false);
}
base::Value::Dict MaskCredentialsInOncObject(
const OncValueSignature& signature,
const base::Value::Dict& 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);
bssl::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,
::onc::ONCSource onc_source,
const std::string& passphrase,
base::Value::List* network_configs,
base::Value::Dict* global_network_config,
base::Value::List* 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::optional<base::Value::Dict> toplevel_onc =
ReadDictionaryFromJson(onc_blob);
if (!toplevel_onc) {
NET_LOG(ERROR) << "Not a valid ONC JSON dictionary: "
<< GetSourceAsString(onc_source);
return false;
}
// Check and see if this is an encrypted ONC file. If so, decrypt it.
std::string onc_type;
if (GetString(toplevel_onc.value(), ::onc::toplevel_config::kType,
&onc_type) &&
onc_type == ::onc::toplevel_config::kEncryptedConfiguration) {
toplevel_onc = Decrypt(passphrase, toplevel_onc.value());
if (!toplevel_onc.has_value()) {
NET_LOG(ERROR) << "Unable to decrypt ONC from "
<< GetSourceAsString(onc_source);
return false;
}
}
bool from_policy = (onc_source == ::onc::ONC_SOURCE_USER_POLICY ||
onc_source == ::onc::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(/*error_on_unknown_field=*/false,
/*error_on_wrong_recommended=*/false,
/*error_on_missing_field=*/true,
/*managed_onc=*/from_policy,
/*log_warnings=*/true);
validator.SetOncSource(onc_source);
Validator::Result validation_result;
std::optional<base::Value::Dict> validated_toplevel_onc =
validator.ValidateAndRepairObject(&kToplevelConfigurationSignature,
toplevel_onc.value(),
&validation_result);
bool success = true;
if (validation_result == Validator::VALID_WITH_WARNINGS) {
NET_LOG(DEBUG) << "ONC validation produced warnings: "
<< GetSourceAsString(onc_source);
success = false;
} else if (validation_result == Validator::INVALID ||
!validated_toplevel_onc.has_value()) {
NET_LOG(ERROR) << "ONC is invalid and couldn't be repaired: "
<< GetSourceAsString(onc_source);
return false;
}
if (certificates) {
base::Value::List* validated_certs =
validated_toplevel_onc->FindList(::onc::toplevel_config::kCertificates);
if (validated_certs)
*certificates = std::move(*validated_certs);
}
// 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::List* validated_networks_list = validated_toplevel_onc->FindList(
::onc::toplevel_config::kNetworkConfigurations);
base::Value::Dict* validated_global_config = validated_toplevel_onc->FindDict(
::onc::toplevel_config::kGlobalNetworkConfiguration);
const IdToAPNMap id_to_apn_map = BuildIdToAPNMap(
validated_toplevel_onc->FindList(::onc::toplevel_config::kAdminAPNList));
if (validated_networks_list) {
FillInHexSSIDFieldsInNetworks(*validated_networks_list);
bool allow_apn_modification = true;
if (validated_global_config) {
allow_apn_modification =
(validated_global_config->FindBool(
::onc::global_network_config::kAllowAPNModification))
.value_or(allow_apn_modification);
}
FillInCellularDefaultsInNetworks(*validated_networks_list,
allow_apn_modification);
// Sets the CustomAPNList for cellular networks if an AdminAPNList and
// AdminAssignedAPNIds have been specified for a cellular network.
if (!ConfigureAdminApnsInCellularNetworks(*validated_networks_list,
id_to_apn_map)) {
success = false;
}
// Set HiddenSSID to default value to solve the issue crbug.com/1171837
SetHiddenSSIDFieldsInNetworks(*validated_networks_list);
CertPEMsByGUIDMap server_and_ca_certs =
GetServerAndCACertsByGUID(*certificates);
if (!ResolveServerCertRefsInNetworks(server_and_ca_certs,
*validated_networks_list)) {
NET_LOG(ERROR) << "Some certificate references in the ONC policy could "
"not be resolved: "
<< GetSourceAsString(onc_source);
success = false;
}
if (network_configs) {
*network_configs = std::move(*validated_networks_list);
}
}
if (global_network_config) {
if (validated_global_config) {
// Constructs and sets the PSIMAdminAssignedAPNs global network
// configuration field if an AdminAPNList and PSIMAdminAssignedAPNIds have
// been specified.
if (!ConstructAndSetPSIMAdminAPNs(*validated_global_config,
id_to_apn_map)) {
success = false;
}
*global_network_config = std::move(*validated_global_config);
}
}
return success;
}
bool ResolveServerCertRefsInNetworks(const CertPEMsByGUIDMap& certs_by_guid,
base::Value::List& network_configs) {
bool success = true;
base::Value::List filtered_configs;
for (base::Value& network : network_configs) {
if (!ResolveServerCertRefsInNetwork(certs_by_guid, network.GetDict())) {
std::string* guid =
network.GetDict().FindString(::onc::network_config::kGUID);
// 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 ? *guid : "(unable to find GUID)");
success = false;
continue;
}
filtered_configs.Append(std::move(network));
}
network_configs = std::move(filtered_configs);
return success;
}
bool ResolveServerCertRefsInNetwork(const CertPEMsByGUIDMap& certs_by_guid,
base::Value::Dict& network_config) {
return ResolveServerCertRefsInObject(
certs_by_guid, kNetworkConfigurationSignature, network_config);
}
} // namespace chromeos::onc