blob: 8ac099adfaa2edf742125d8865bbedf3ac3bfa34 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "shill/eap_credentials.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/strings/string_piece.h>
#include <base/strings/string_split.h>
#include <base/strings/string_tokenizer.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include <chromeos/dbus/service_constants.h>
#include <libpasswordprovider/password.h>
#include <libpasswordprovider/password_provider.h>
#include "shill/certificate_file.h"
#include "shill/error.h"
#include "shill/key_value_store.h"
#include "shill/logging.h"
#include "shill/metrics.h"
#include "shill/property_accessor.h"
#include "shill/property_store.h"
#include "shill/service.h"
#include "shill/store_interface.h"
#include "shill/supplicant/wpa_supplicant.h"
namespace shill {
namespace {
// Chrome sends key value pairs for "phase2" inner EAP configuration and shill
// just forwards that to wpa_supplicant. This function adds additional flags for
// phase2 if necessary.
// Currently it adds the mschapv2_retry=0 flag if MSCHAPV2 auth is being used
// so that wpa_supplicant does not auto-retry. The auto-retry would expect shill
// to send a new identity/password (https://crbug.com/1027323).
std::string AddAdditionalInnerEapParams(const std::string& inner_eap) {
if (inner_eap.empty())
return std::string();
std::vector<base::StringPiece> params = base::SplitStringPiece(
inner_eap, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
bool has_mschapv2_auth = false;
for (const auto& param : params) {
if (param == WPASupplicant::kFlagInnerEapAuthMSCHAPV2) {
has_mschapv2_auth = true;
break;
}
}
if (!has_mschapv2_auth)
return inner_eap;
return inner_eap + " " + WPASupplicant::kFlagInnerEapNoMSCHAPV2Retry;
}
// Deprecated to migrate from ROT47 to plaintext.
// TODO(crbug.com/1084279) Remove after migration is complete.
const char kStorageDeprecatedEapAnonymousIdentity[] = "EAP.AnonymousIdentity";
const char kStorageDeprecatedEapIdentity[] = "EAP.Identity";
const char kStorageDeprecatedEapPassword[] = "EAP.Password";
} // namespace
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kService;
static std::string ObjectID(const EapCredentials* e) {
return "(eap_credentials)";
}
} // namespace Logging
const char EapCredentials::kStorageCredentialEapAnonymousIdentity[] =
"EAP.Credential.AnonymousIdentity";
const char EapCredentials::kStorageCredentialEapIdentity[] =
"EAP.Credential.Identity";
const char EapCredentials::kStorageCredentialEapPassword[] =
"EAP.Credential.Password";
const char EapCredentials::kStorageEapCACertID[] = "EAP.CACertID";
const char EapCredentials::kStorageEapCACertPEM[] = "EAP.CACertPEM";
const char EapCredentials::kStorageEapCertID[] = "EAP.CertID";
const char EapCredentials::kStorageEapEap[] = "EAP.EAP";
const char EapCredentials::kStorageEapInnerEap[] = "EAP.InnerEAP";
const char EapCredentials::kStorageEapTLSVersionMax[] = "EAP.TLSVersionMax";
const char EapCredentials::kStorageEapKeyID[] = "EAP.KeyID";
const char EapCredentials::kStorageEapKeyManagement[] = "EAP.KeyMgmt";
const char EapCredentials::kStorageEapPin[] = "EAP.PIN";
const char EapCredentials::kStorageEapSubjectMatch[] = "EAP.SubjectMatch";
const char EapCredentials::kStorageEapUseProactiveKeyCaching[] =
"EAP.UseProactiveKeyCaching";
const char EapCredentials::kStorageEapUseSystemCAs[] = "EAP.UseSystemCAs";
const char EapCredentials::kStorageEapUseLoginPassword[] =
"EAP.UseLoginPassword";
constexpr char kStorageEapSubjectAlternativeNameMatch[] =
"EAP.SubjectAlternativeNameMatch";
constexpr char kStorageEapDomainSuffixMatch[] = "EAP.DomainSuffixMatch";
EapCredentials::EapCredentials()
: use_system_cas_(true),
use_proactive_key_caching_(false),
use_login_password_(false),
password_provider_(
std::make_unique<password_provider::PasswordProvider>()) {}
EapCredentials::~EapCredentials() = default;
// static
void EapCredentials::PopulateSupplicantProperties(
CertificateFile* certificate_file, KeyValueStore* params) const {
std::string ca_cert;
if (!ca_cert_pem_.empty()) {
base::FilePath certfile =
certificate_file->CreatePEMFromStrings(ca_cert_pem_);
if (certfile.empty()) {
LOG(ERROR) << "Unable to extract PEM certificate.";
} else {
ca_cert = certfile.value();
}
}
std::string updated_inner_eap = AddAdditionalInnerEapParams(inner_eap_);
using KeyVal = std::pair<const char*, const char*>;
std::vector<KeyVal> propertyvals = {
// Authentication properties.
KeyVal(WPASupplicant::kNetworkPropertyEapAnonymousIdentity,
anonymous_identity_.c_str()),
KeyVal(WPASupplicant::kNetworkPropertyEapIdentity, identity_.c_str()),
// Non-authentication properties.
KeyVal(WPASupplicant::kNetworkPropertyEapCaCert, ca_cert.c_str()),
KeyVal(WPASupplicant::kNetworkPropertyEapCaCertId, ca_cert_id_.c_str()),
KeyVal(WPASupplicant::kNetworkPropertyEapEap, eap_.c_str()),
KeyVal(WPASupplicant::kNetworkPropertyEapInnerEap,
updated_inner_eap.c_str()),
KeyVal(WPASupplicant::kNetworkPropertyEapSubjectMatch,
subject_match_.c_str()),
};
base::Optional<std::string> altsubject_match =
TranslateSubjectAlternativeNameMatch(
subject_alternative_name_match_list_);
if (altsubject_match.has_value()) {
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyEapSubjectAlternativeNameMatch,
altsubject_match.value().c_str()));
}
base::Optional<std::string> domain_suffix_match =
TranslateDomainSuffixMatch(domain_suffix_match_list_);
if (domain_suffix_match.has_value()) {
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyEapDomainSuffixMatch,
domain_suffix_match.value().c_str()));
}
if (use_system_cas_) {
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyCaPath, WPASupplicant::kCaPath));
} else if (ca_cert.empty()) {
LOG(WARNING) << __func__ << ": No certificate authorities are configured."
<< " Server certificates will be accepted"
<< " unconditionally.";
}
if (ClientAuthenticationUsesCryptoToken()) {
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyEapCertId, cert_id_.c_str()));
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyEapKeyId, key_id_.c_str()));
}
if (ClientAuthenticationUsesCryptoToken() || !ca_cert_id_.empty()) {
propertyvals.push_back(
KeyVal(WPASupplicant::kNetworkPropertyEapPin, pin_.c_str()));
propertyvals.push_back(KeyVal(WPASupplicant::kNetworkPropertyEngineId,
WPASupplicant::kEnginePKCS11));
// We can't use the propertyvals vector for this since this argument
// is a uint32_t, not a string.
params->Set<uint32_t>(WPASupplicant::kNetworkPropertyEngine,
WPASupplicant::kDefaultEngine);
}
if (use_proactive_key_caching_) {
params->Set<uint32_t>(WPASupplicant::kNetworkPropertyEapProactiveKeyCaching,
WPASupplicant::kProactiveKeyCachingEnabled);
} else {
params->Set<uint32_t>(WPASupplicant::kNetworkPropertyEapProactiveKeyCaching,
WPASupplicant::kProactiveKeyCachingDisabled);
}
if (tls_version_max_ == kEapTLSVersion1p0) {
params->Set<std::string>(
WPASupplicant::kNetworkPropertyEapOuterEap,
std::string(WPASupplicant::kFlagDisableEapTLS1p1) + " " +
std::string(WPASupplicant::kFlagDisableEapTLS1p2));
} else if (tls_version_max_ == kEapTLSVersion1p1) {
params->Set<std::string>(WPASupplicant::kNetworkPropertyEapOuterEap,
WPASupplicant::kFlagDisableEapTLS1p2);
}
if (use_login_password_) {
std::unique_ptr<password_provider::Password> password =
password_provider_->GetPassword();
if (password == nullptr || password->size() == 0) {
LOG(WARNING) << "Unable to retrieve user password";
} else {
params->Set<std::string>(
WPASupplicant::kNetworkPropertyEapCaPassword,
std::string(password->GetRaw(), password->size()));
}
} else {
if (!password_.empty()) {
params->Set<std::string>(WPASupplicant::kNetworkPropertyEapCaPassword,
password_);
}
}
for (const auto& keyval : propertyvals) {
if (strlen(keyval.second) > 0) {
params->Set<std::string>(keyval.first, keyval.second);
}
}
}
void EapCredentials::InitPropertyStore(PropertyStore* store) {
// Authentication properties.
store->RegisterString(kEapAnonymousIdentityProperty, &anonymous_identity_);
store->RegisterString(kEapCertIdProperty, &cert_id_);
store->RegisterString(kEapIdentityProperty, &identity_);
store->RegisterString(kEapKeyIdProperty, &key_id_);
HelpRegisterDerivedString(store, kEapKeyMgmtProperty,
&EapCredentials::GetKeyManagement,
&EapCredentials::SetKeyManagement);
HelpRegisterWriteOnlyDerivedString(store, kEapPasswordProperty,
&EapCredentials::SetEapPassword, nullptr,
&password_);
store->RegisterString(kEapPinProperty, &pin_);
store->RegisterBool(kEapUseLoginPasswordProperty, &use_login_password_);
// Non-authentication properties.
store->RegisterStrings(kEapCaCertPemProperty, &ca_cert_pem_);
store->RegisterString(kEapCaCertIdProperty, &ca_cert_id_);
store->RegisterString(kEapMethodProperty, &eap_);
store->RegisterString(kEapPhase2AuthProperty, &inner_eap_);
store->RegisterString(kEapTLSVersionMaxProperty, &tls_version_max_);
store->RegisterString(kEapSubjectMatchProperty, &subject_match_);
store->RegisterStrings(kEapSubjectAlternativeNameMatchProperty,
&subject_alternative_name_match_list_);
store->RegisterStrings(kEapDomainSuffixMatchProperty,
&domain_suffix_match_list_);
store->RegisterBool(kEapUseProactiveKeyCachingProperty,
&use_proactive_key_caching_);
store->RegisterBool(kEapUseSystemCasProperty, &use_system_cas_);
}
// static
bool EapCredentials::IsEapAuthenticationProperty(const std::string property) {
return property == kEapAnonymousIdentityProperty ||
property == kEapCertIdProperty || property == kEapIdentityProperty ||
property == kEapKeyIdProperty || property == kEapKeyMgmtProperty ||
property == kEapPasswordProperty || property == kEapPinProperty ||
property == kEapUseLoginPasswordProperty;
}
bool EapCredentials::IsConnectable() const {
// Identity is required.
if (identity_.empty()) {
SLOG(this, 2) << "Not connectable: Identity is empty.";
return false;
}
if (!cert_id_.empty()) {
// If a client certificate is being used, we must have a private key.
if (key_id_.empty()) {
SLOG(this, 2)
<< "Not connectable: Client certificate but no private key.";
return false;
}
}
if (!cert_id_.empty() || !key_id_.empty() || !ca_cert_id_.empty()) {
// If PKCS#11 data is needed, a PIN is required.
if (pin_.empty()) {
SLOG(this, 2) << "Not connectable: PKCS#11 data but no PIN.";
return false;
}
}
// For EAP-TLS, a client certificate is required.
if (eap_.empty() || eap_ == kEapMethodTLS) {
if (!cert_id_.empty() && !key_id_.empty()) {
SLOG(this, 2) << "Connectable: EAP-TLS with a client cert and key.";
return true;
}
}
// For EAP types other than TLS (e.g. EAP-TTLS or EAP-PEAP, password is the
// minimum requirement), at least an identity + password is required.
if (eap_.empty() || eap_ != kEapMethodTLS) {
if (!password_.empty()) {
SLOG(this, 2) << "Connectable. !EAP-TLS and has a password.";
return true;
}
}
SLOG(this, 2) << "Not connectable: No suitable EAP configuration was found.";
return false;
}
bool EapCredentials::IsConnectableUsingPassphrase() const {
return !identity_.empty() && !password_.empty();
}
void EapCredentials::Load(const StoreInterface* storage,
const std::string& id) {
// Authentication properties.
storage->GetCryptedString(id, kStorageDeprecatedEapAnonymousIdentity,
kStorageCredentialEapAnonymousIdentity,
&anonymous_identity_);
storage->GetString(id, kStorageEapCertID, &cert_id_);
storage->GetCryptedString(id, kStorageDeprecatedEapIdentity,
kStorageCredentialEapIdentity, &identity_);
storage->GetString(id, kStorageEapKeyID, &key_id_);
std::string key_management;
storage->GetString(id, kStorageEapKeyManagement, &key_management);
SetKeyManagement(key_management, nullptr);
storage->GetCryptedString(id, kStorageDeprecatedEapPassword,
kStorageCredentialEapPassword, &password_);
storage->GetString(id, kStorageEapPin, &pin_);
storage->GetBool(id, kStorageEapUseLoginPassword, &use_login_password_);
// Non-authentication properties.
storage->GetString(id, kStorageEapCACertID, &ca_cert_id_);
storage->GetStringList(id, kStorageEapCACertPEM, &ca_cert_pem_);
storage->GetString(id, kStorageEapEap, &eap_);
storage->GetString(id, kStorageEapInnerEap, &inner_eap_);
storage->GetString(id, kStorageEapTLSVersionMax, &tls_version_max_);
storage->GetString(id, kStorageEapSubjectMatch, &subject_match_);
storage->GetStringList(id, kStorageEapSubjectAlternativeNameMatch,
&subject_alternative_name_match_list_);
storage->GetStringList(id, kStorageEapDomainSuffixMatch,
&domain_suffix_match_list_);
storage->GetBool(id, kStorageEapUseProactiveKeyCaching,
&use_proactive_key_caching_);
storage->GetBool(id, kStorageEapUseSystemCAs, &use_system_cas_);
}
void EapCredentials::MigrateDeprecatedStorage(StoreInterface* storage,
const std::string& id) const {
// Note that if we found any of these keys, then we already know that
// save_credentials was true during the last Save, and therefore can set the
// new (key, plaintext_value).
//
// TODO(crbug.com/1084279) Remove after migration is complete.
if (storage->DeleteKey(id, kStorageDeprecatedEapAnonymousIdentity)) {
storage->SetString(id, kStorageCredentialEapAnonymousIdentity,
anonymous_identity_);
}
if (storage->DeleteKey(id, kStorageDeprecatedEapIdentity)) {
storage->SetString(id, kStorageCredentialEapIdentity, identity_);
}
if (storage->DeleteKey(id, kStorageDeprecatedEapPassword)) {
storage->SetString(id, kStorageCredentialEapPassword, password_);
}
}
void EapCredentials::OutputConnectionMetrics(Metrics* metrics,
Technology technology) const {
Metrics::EapOuterProtocol outer_protocol =
Metrics::EapOuterProtocolStringToEnum(eap_);
metrics->SendEnumToUMA(
metrics->GetFullMetricName(Metrics::kMetricNetworkEapOuterProtocolSuffix,
technology),
outer_protocol, Metrics::kMetricNetworkEapOuterProtocolMax);
Metrics::EapInnerProtocol inner_protocol =
Metrics::EapInnerProtocolStringToEnum(inner_eap_);
metrics->SendEnumToUMA(
metrics->GetFullMetricName(Metrics::kMetricNetworkEapInnerProtocolSuffix,
technology),
inner_protocol, Metrics::kMetricNetworkEapInnerProtocolMax);
}
void EapCredentials::Save(StoreInterface* storage,
const std::string& id,
bool save_credentials) const {
// Authentication properties.
Service::SaveStringOrClear(storage, id,
kStorageCredentialEapAnonymousIdentity,
save_credentials ? anonymous_identity_ : "");
Service::SaveStringOrClear(storage, id, kStorageEapCertID,
save_credentials ? cert_id_ : "");
Service::SaveStringOrClear(storage, id, kStorageCredentialEapIdentity,
save_credentials ? identity_ : "");
Service::SaveStringOrClear(storage, id, kStorageEapKeyID,
save_credentials ? key_id_ : "");
Service::SaveStringOrClear(storage, id, kStorageEapKeyManagement,
key_management_);
Service::SaveStringOrClear(storage, id, kStorageCredentialEapPassword,
save_credentials ? password_ : "");
Service::SaveStringOrClear(storage, id, kStorageEapPin,
save_credentials ? pin_ : "");
storage->SetBool(id, kStorageEapUseLoginPassword, use_login_password_);
// Non-authentication properties.
Service::SaveStringOrClear(storage, id, kStorageEapCACertID, ca_cert_id_);
if (ca_cert_pem_.empty()) {
storage->DeleteKey(id, kStorageEapCACertPEM);
} else {
storage->SetStringList(id, kStorageEapCACertPEM, ca_cert_pem_);
}
Service::SaveStringOrClear(storage, id, kStorageEapEap, eap_);
Service::SaveStringOrClear(storage, id, kStorageEapInnerEap, inner_eap_);
Service::SaveStringOrClear(storage, id, kStorageEapTLSVersionMax,
tls_version_max_);
Service::SaveStringOrClear(storage, id, kStorageEapSubjectMatch,
subject_match_);
storage->SetStringList(id, kStorageEapSubjectAlternativeNameMatch,
subject_alternative_name_match_list_);
storage->SetStringList(id, kStorageEapDomainSuffixMatch,
domain_suffix_match_list_);
storage->SetBool(id, kStorageEapUseProactiveKeyCaching,
use_proactive_key_caching_);
storage->SetBool(id, kStorageEapUseSystemCAs, use_system_cas_);
}
void EapCredentials::Reset() {
// Authentication properties.
anonymous_identity_ = "";
cert_id_ = "";
identity_ = "";
key_id_ = "";
// Do not reset key_management_, since it should never be emptied.
password_ = "";
pin_ = "";
use_login_password_ = false;
// Non-authentication properties.
ca_cert_id_ = "";
ca_cert_pem_.clear();
domain_suffix_match_list_.clear();
eap_ = "";
inner_eap_ = "";
subject_match_ = "";
subject_alternative_name_match_list_.clear();
use_system_cas_ = true;
use_proactive_key_caching_ = false;
}
bool EapCredentials::SetEapPassword(const std::string& password,
Error* /*error*/) {
if (use_login_password_) {
LOG(WARNING) << "Setting EAP password for configuration requiring the "
"user's login password";
return false;
}
if (password_ == password) {
return false;
}
password_ = password;
return true;
}
std::string EapCredentials::GetKeyManagement(Error* /*error*/) {
return key_management_;
}
bool EapCredentials::SetKeyManagement(const std::string& key_management,
Error* /*error*/) {
if (key_management.empty()) {
return false;
}
if (key_management_ == key_management) {
return false;
}
key_management_ = key_management;
return true;
}
bool EapCredentials::ClientAuthenticationUsesCryptoToken() const {
return (eap_.empty() || eap_ == kEapMethodTLS ||
inner_eap_ == kEapMethodTLS) &&
(!cert_id_.empty() || !key_id_.empty());
}
void EapCredentials::HelpRegisterDerivedString(
PropertyStore* store,
const std::string& name,
std::string (EapCredentials::*get)(Error* error),
bool (EapCredentials::*set)(const std::string&, Error*)) {
store->RegisterDerivedString(
name, StringAccessor(new CustomAccessor<EapCredentials, std::string>(
this, get, set)));
}
void EapCredentials::HelpRegisterWriteOnlyDerivedString(
PropertyStore* store,
const std::string& name,
bool (EapCredentials::*set)(const std::string&, Error*),
void (EapCredentials::*clear)(Error* error),
const std::string* default_value) {
store->RegisterDerivedString(
name,
StringAccessor(new CustomWriteOnlyAccessor<EapCredentials, std::string>(
this, set, clear, default_value)));
}
// static
bool EapCredentials::ValidSubjectAlternativeNameMatchType(
const std::string& type) {
return type == kEapSubjectAlternativeNameMatchTypeEmail ||
type == kEapSubjectAlternativeNameMatchTypeDNS ||
type == kEapSubjectAlternativeNameMatchTypeURI;
}
// static
bool EapCredentials::ValidDomainSuffixMatch(
const std::string& domain_suffix_match) {
if (domain_suffix_match.empty() || domain_suffix_match.size() > 255)
return false;
std::vector<base::StringPiece> labels = base::SplitStringPiece(
domain_suffix_match, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
DCHECK(!labels.empty());
for (const base::StringPiece& label : labels) {
if (label.size() == 0 || label.size() > 63)
return false;
// Labels can't start and end with hyphens.
if (label.front() == '-' || label.back() == '-')
return false;
for (auto it = label.begin(); it != label.end(); ++it) {
// The top level domain must contain only letters.
if (label == labels.back()) {
if (!base::IsAsciiAlpha(*it))
return false;
} else {
if (!base::IsAsciiAlpha(*it) && !base::IsAsciiDigit(*it) &&
(*it) != '-') {
return false;
}
}
}
}
return true;
}
// static
base::Optional<std::string> EapCredentials::TranslateDomainSuffixMatch(
const std::vector<std::string>& domain_suffix_match_list) {
if (domain_suffix_match_list.empty())
return base::nullopt;
std::vector<std::string> filtered_domains;
for (const std::string& domain : domain_suffix_match_list) {
if (ValidDomainSuffixMatch(domain)) {
filtered_domains.push_back(domain);
} else {
LOG(ERROR)
<< "Ignoring invalid domain name in EAP.DomainSuffixMatch list: "
<< domain;
}
}
if (filtered_domains.empty())
return base::nullopt;
return base::JoinString(filtered_domains, ";");
}
// static
base::Optional<std::string>
EapCredentials::TranslateSubjectAlternativeNameMatch(
const std::vector<std::string>& subject_alternative_name_match_list) {
std::vector<std::string> entries;
for (const auto& subject_alternative_name_match :
subject_alternative_name_match_list) {
auto json_value = base::JSONReader::ReadAndReturnValueWithError(
subject_alternative_name_match, base::JSON_PARSE_RFC);
if (!json_value.value || !json_value.value->is_dict()) {
LOG(ERROR)
<< "Could not deserialize a subject alternative name match. Error: "
<< json_value.error_message;
return base::nullopt;
}
base::Value deserialized_value = std::move(*json_value.value);
const std::string* type = deserialized_value.FindStringKey(
kEapSubjectAlternativeNameMatchTypeProperty);
if (!type) {
LOG(ERROR) << "Could not find "
<< kEapSubjectAlternativeNameMatchTypeProperty
<< " of a subject alternative name match.";
return base::nullopt;
}
if (!ValidSubjectAlternativeNameMatchType(*type)) {
LOG(ERROR) << "Subject alternative name match type: \"" << *type
<< "\" is not supported.";
return base::nullopt;
}
const std::string* value = deserialized_value.FindStringKey(
kEapSubjectAlternativeNameMatchValueProperty);
if (!value) {
LOG(ERROR) << "Could not find "
<< kEapSubjectAlternativeNameMatchValueProperty
<< " of a subject alternative name match.";
return base::nullopt;
}
std::string translated_entry = *type + ":" + *value;
entries.push_back(translated_entry);
}
return base::JoinString(entries, ";");
}
std::string EapCredentials::GetEapPassword(Error* error) const {
if (use_login_password_ || password_.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported,
"EAP config has no password.");
return std::string();
}
return password_;
}
} // namespace shill