// Copyright 2013 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/ash/components/network/client_cert_resolver.h"
#include <cert.h>
#include <pk11pub.h>
#include <memory>
#include <string_view>
#include "base/base64.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chromeos/ash/components/dbus/shill/shill_clients.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_profile_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler_impl.h"
#include "chromeos/ash/components/network/network_cert_loader.h"
#include "chromeos/ash/components/network/network_configuration_handler.h"
#include "chromeos/ash/components/network/network_profile_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/onc/onc_certificate_importer_impl.h"
#include "chromeos/ash/components/network/system_token_cert_db_storage.h"
#include "chromeos/components/onc/onc_test_utils.h"
#include "components/onc/onc_constants.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/scoped_test_nss_db.h"
#include "net/base/net_errors.h"
#include "net/cert/nss_cert_database_chromeos.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util_nss.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/pki/pem.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace ash {
using ::testing::IsEmpty;
using ::testing::Not;
namespace {
constexpr char kWifiStub[] = "wifi_stub";
constexpr char kWifiSSID[] = "wifi_ssid";
constexpr char kUserProfilePath[] = "user_profile";
constexpr char kUserHash[] = "user_hash";
void OnImportCompleted(base::OnceClosure loop_quit_closure, bool success) {
EXPECT_TRUE(success);
std::move(loop_quit_closure).Run();
}
void OnListCertsDone(base::OnceClosure loop_quit_closure,
net::ScopedCERTCertificateList* out_cert_list,
net::ScopedCERTCertificateList cert_list) {
out_cert_list->swap(cert_list);
std::move(loop_quit_closure).Run();
}
// Returns a |OncParsedCertificates| which contains exactly one client
// certificate with the contents of |client_cert_pkcs12_file| and the GUID
// |guid|. Returns nullptr if the file could not be read.
std::unique_ptr<chromeos::onc::OncParsedCertificates>
OncParsedCertificatesForPkcs12File(
const base::FilePath& client_cert_pkcs12_file,
std::string_view guid) {
std::string pkcs12_raw;
if (!base::ReadFileToString(client_cert_pkcs12_file, &pkcs12_raw))
return nullptr;
std::string pkcs12_base64_encoded = base::Base64Encode(pkcs12_raw);
auto onc_certificates =
base::Value::List().Append(base::Value::Dict()
.Set("GUID", guid)
.Set("Type", "Client")
.Set("PKCS12", pkcs12_base64_encoded));
return std::make_unique<chromeos::onc::OncParsedCertificates>(
onc_certificates);
}
std::string GetStringFromDict(const base::Value::Dict& dict, const char* key) {
const std::string* value = dict.FindString(key);
return value ? *value : std::string();
}
} // namespace
class ClientCertResolverTest : public testing::Test,
public ClientCertResolver::Observer {
public:
ClientCertResolverTest() = default;
ClientCertResolverTest(const ClientCertResolverTest&) = delete;
ClientCertResolverTest& operator=(const ClientCertResolverTest&) = delete;
~ClientCertResolverTest() override = default;
void SetUp() override {
ASSERT_TRUE(test_nssdb_.is_open());
ASSERT_TRUE(test_system_nssdb_.is_open());
// Use the same slot as public and private slot for the user's
// NSSCertDatabse for testing.
test_nsscertdb_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())),
crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
// Create a NSSCertDatabase for the system slot. While NetworkCertLoader
// does not care about the public slot in this database, NSSCertDatabase
// requires a public slot. Pass the system slot there for testing.
test_system_nsscertdb_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_system_nssdb_.slot())),
crypto::ScopedPK11Slot() /* private_slot */);
test_system_nsscertdb_->SetSystemSlot(
crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_system_nssdb_.slot())));
shill_clients::InitializeFakes();
service_test_ = ShillServiceClient::Get()->GetTestInterface();
profile_test_ = ShillProfileClient::Get()->GetTestInterface();
profile_test_->AddProfile(kUserProfilePath, kUserHash);
task_environment_.RunUntilIdle();
service_test_->ClearServices();
task_environment_.RunUntilIdle();
SystemTokenCertDbStorage::Initialize();
NetworkCertLoader::Initialize();
network_cert_loader_ = NetworkCertLoader::Get();
NetworkCertLoader::ForceAvailableForNetworkAuthForTesting();
}
void TearDown() override {
if (client_cert_resolver_)
client_cert_resolver_->RemoveObserver(this);
client_cert_resolver_.reset();
test_clock_.reset();
if (network_state_handler_)
network_state_handler_->Shutdown();
managed_config_handler_.reset();
network_config_handler_.reset();
network_profile_handler_.reset();
network_state_handler_.reset();
NetworkCertLoader::Shutdown();
SystemTokenCertDbStorage::Shutdown();
shill_clients::Shutdown();
}
protected:
void StartNetworkCertLoader() {
network_cert_loader_->SetUserNSSDB(test_nsscertdb_.get());
network_cert_loader_->SetSystemNssDbForTesting(
test_system_nsscertdb_.get());
if (test_client_cert_.get()) {
int slot_id = 0;
const std::string pkcs11_id =
NetworkCertLoader::GetPkcs11IdAndSlotForCert(test_client_cert_.get(),
&slot_id);
test_cert_id_ = base::StringPrintf("%i:%s", slot_id, pkcs11_id.c_str());
}
}
// Imports a client certificate. After a subsequent StartNetworkCertLoader()
// invocation, the PKCS#11 ID of the imported certificate will be stored in
// |test_cert_id_|. If |import_issuer| is true, also imports the CA cert
// (stored as PEM in test_ca_cert_pem_) that issued the client certificate.
void SetupTestCerts(const std::string& prefix, bool import_issuer) {
// Load a CA cert.
net::ScopedCERTCertificateList ca_cert_list =
net::CreateCERTCertificateListFromFile(
net::GetTestCertsDirectory(), prefix + "_ca.pem",
net::X509Certificate::FORMAT_AUTO);
ASSERT_TRUE(!ca_cert_list.empty());
net::x509_util::GetPEMEncoded(ca_cert_list[0].get(), &test_ca_cert_pem_);
ASSERT_TRUE(!test_ca_cert_pem_.empty());
if (import_issuer) {
net::NSSCertDatabase::ImportCertFailureList failures;
EXPECT_TRUE(test_nsscertdb_->ImportCACerts(
ca_cert_list, net::NSSCertDatabase::TRUST_DEFAULT, &failures));
ASSERT_TRUE(failures.empty())
<< net::ErrorToString(failures[0].net_error);
}
// Import a client cert signed by that CA.
net::ImportClientCertAndKeyFromFile(net::GetTestCertsDirectory(),
prefix + ".pem", prefix + ".pk8",
test_nssdb_.slot(), &test_client_cert_);
ASSERT_TRUE(test_client_cert_.get());
}
// Imports a client certificate with a subject CommonName encoded as
// PrintableString, but containing invalid characters. It is imported into the
// user slot. After a subsequent StartNetworkCertLoader() invocation, the
// PKCS#11 ID of the imported certificate will be stored in |test_cert_id_|.
void SetupTestCertWithBadPrintableString() {
base::FilePath certs_dir = net::GetTestNetDataDirectory().AppendASCII(
"parse_certificate_unittest");
ASSERT_TRUE(net::ImportSensitiveKeyFromFile(
certs_dir, "v3_certificate_template.pk8", test_nssdb_.slot()));
std::string file_data;
ASSERT_TRUE(base::ReadFileToString(
certs_dir.AppendASCII(
"subject_printable_string_containing_utf8_client_cert.pem"),
&file_data));
bssl::PEMTokenizer pem_tokenizer(file_data, {"CERTIFICATE"});
ASSERT_TRUE(pem_tokenizer.GetNext());
std::string cert_der(pem_tokenizer.data());
ASSERT_FALSE(pem_tokenizer.GetNext());
test_client_cert_ = net::x509_util::CreateCERTCertificateFromBytes(
base::as_bytes(base::make_span(cert_der)));
ASSERT_TRUE(test_client_cert_);
ASSERT_TRUE(net::ImportClientCertToSlot(test_client_cert_.get(),
test_nssdb_.slot()));
}
void SetupTestCertInSystemToken(const std::string& prefix) {
net::ImportClientCertAndKeyFromFile(
net::GetTestCertsDirectory(), prefix + ".pem", prefix + ".pk8",
test_system_nssdb_.slot(), &test_client_cert_);
ASSERT_TRUE(test_client_cert_.get());
}
void SetupNetworkHandlers() {
network_state_handler_ = NetworkStateHandler::InitializeForTest();
network_profile_handler_.reset(new NetworkProfileHandler());
network_config_handler_.reset(new NetworkConfigurationHandler());
managed_config_handler_.reset(new ManagedNetworkConfigurationHandlerImpl());
client_cert_resolver_ = std::make_unique<ClientCertResolver>();
test_clock_ = std::make_unique<base::SimpleTestClock>();
test_clock_->SetNow(base::Time::Now());
client_cert_resolver_->SetClockForTesting(test_clock_.get());
network_profile_handler_->Init();
network_config_handler_->Init(network_state_handler_.get(),
nullptr /* network_device_handler */);
managed_config_handler_->Init(
/*cellular_policy_handler=*/nullptr,
/*managed_cellular_pref_handler=*/nullptr, network_state_handler_.get(),
network_profile_handler_.get(), network_config_handler_.get(),
nullptr /* network_device_handler */,
nullptr /* prohibited_technologies_handler */,
/*hotspot_controller=*/nullptr);
// Run all notifications before starting the cert loader to reduce run time.
task_environment_.RunUntilIdle();
client_cert_resolver_->Init(network_state_handler_.get(),
managed_config_handler_.get());
client_cert_resolver_->AddObserver(this);
}
void SetupWifi() {
service_test_->SetServiceProperties(kWifiStub, kWifiStub, kWifiSSID,
shill::kTypeWifi, shill::kStateOnline,
true /* visible */);
// Set an arbitrary cert id, so that we can check afterwards whether we
// cleared the property or not.
service_test_->SetServiceProperty(kWifiStub, shill::kEapCertIdProperty,
base::Value("invalid id"));
profile_test_->AddService(kUserProfilePath, kWifiStub);
ShillManagerClient::Get()->GetTestInterface()->AddManagerService(kWifiStub,
true);
}
// Sets up a policy with a certificate pattern that matches any client cert
// with a certain Issuer CN. It will match the test client cert imported by
// SetupTestCerts.
void SetupPolicyMatchingIssuerCN(::onc::ONCSource onc_source) {
const char* test_policy = R"(
[ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Outer": "EAP-TLS",
"ClientCertType": "Pattern",
"ClientCertPattern": {
"Issuer": {
"CommonName": "B CA"
}
}
}
}
} ])";
ASSERT_NO_FATAL_FAILURE(SetManagedNetworkPolicy(onc_source, test_policy));
}
// Sets up a policy with a certificate pattern that matches any client cert
// with a Subject Organization set to "Blar". It will match the test client
// cert imported by SetupTestCertWithBadPrintableString.
void SetupPolicyMatchingSubjectOrgForBadPrintableStringCert(
::onc::ONCSource onc_source) {
const char* test_policy = R"(
[ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Outer": "EAP-TLS",
"ClientCertType": "Pattern",
"ClientCertPattern": {
"Subject": {
"Organization": "Blar"
}
}
}
}
} ])";
ASSERT_NO_FATAL_FAILURE(SetManagedNetworkPolicy(onc_source, test_policy));
}
void SetupCertificateConfigMatchingIssuerCN(
::onc::ONCSource onc_source,
client_cert::ClientCertConfig* client_cert_config) {
const char* test_onc_pattern = R"(
{
"Issuer": {
"CommonName": "B CA"
}
})";
auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
test_onc_pattern, base::JSON_ALLOW_TRAILING_COMMAS);
ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message;
client_cert_config->onc_source = onc_source;
client_cert_config->client_cert_type = ::onc::client_cert::kPattern;
client_cert_config->pattern.ReadFromONCDictionary(parsed_json->GetDict());
}
// Sets up a policy with a certificate pattern that matches any client cert
// that is signed by the test CA cert (stored in |test_ca_cert_pem_|). In
// particular it will match the test client cert.
void SetupPolicyMatchingIssuerPEM(::onc::ONCSource onc_source,
const std::string& identity) {
static constexpr char kTestPolicyTemplate[] = R"(
[ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Identity": "%s",
"Outer": "EAP-TLS",
"ClientCertType": "Pattern",
"ClientCertPattern": {
"IssuerCAPEMs": [ "%s" ]
}
}
}
} ])";
std::string policy_json = base::StringPrintf(
kTestPolicyTemplate, identity.c_str(), test_ca_cert_pem_.c_str());
ASSERT_NO_FATAL_FAILURE(SetManagedNetworkPolicy(onc_source, policy_json));
}
void SetManagedNetworkPolicy(::onc::ONCSource onc_source,
std::string_view policy_json) {
auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
policy_json,
base::JSON_ALLOW_TRAILING_COMMAS | base::JSON_ALLOW_CONTROL_CHARS);
ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message;
ASSERT_TRUE(parsed_json->is_list());
std::string user_hash =
onc_source == ::onc::ONC_SOURCE_USER_POLICY ? kUserHash : "";
managed_config_handler_->SetPolicy(
onc_source, user_hash, parsed_json->GetList(),
/*global_network_config=*/base::Value::Dict());
}
void SetWifiState(const std::string& state) {
ASSERT_TRUE(service_test_->SetServiceProperty(
kWifiStub, shill::kStateProperty, base::Value(state)));
}
void GetServiceProperty(const std::string& prop_name,
std::string* prop_value) {
prop_value->clear();
const base::Value::Dict* properties =
service_test_->GetServiceProperties(kWifiStub);
if (!properties)
return;
const std::string* value = properties->FindString(prop_name);
if (value)
*prop_value = *value;
}
// Returns a list of all certificates that are stored on |test_nsscertdb_|'s
// private slot.
net::ScopedCERTCertificateList ListCertsOnPrivateSlot() {
net::ScopedCERTCertificateList certs;
base::RunLoop run_loop;
test_nsscertdb_->ListCertsInSlot(
base::BindOnce(&OnListCertsDone, run_loop.QuitClosure(), &certs),
test_nsscertdb_->GetPrivateSlot().get());
run_loop.Run();
return certs;
}
void ResolveTestHelper(const char* test_policy_network, bool expect_failure) {
SetupWifi();
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
task_environment_.RunUntilIdle();
// Make sure that expiring client certs don't cause issues.
test_clock_->SetNow(base::Time::Min());
// Apply the network policy.
network_properties_changed_count_ = 0;
ASSERT_NO_FATAL_FAILURE(SetManagedNetworkPolicy(
::onc::ONC_SOURCE_USER_POLICY, test_policy_network));
task_environment_.RunUntilIdle();
// The referenced client cert does not exist yet, so expect that it has not
// been resolved.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_TRUE(pkcs11_id.empty());
EXPECT_EQ(1, network_properties_changed_count_);
// Now import a client certificate which has the GUID required using the
// |CertificateImporterImpl|.
auto onc_parsed_certificates = OncParsedCertificatesForPkcs12File(
net::GetTestCertsDirectory().AppendASCII("client-empty-password.p12"),
"{some-unique-guid}");
ASSERT_TRUE(onc_parsed_certificates);
onc::CertificateImporterImpl importer(
task_environment_.GetMainThreadTaskRunner(), test_nsscertdb_.get());
base::RunLoop import_loop;
importer.ImportClientCertificates(
onc_parsed_certificates->client_certificates(),
base::BindOnce(&OnImportCompleted, import_loop.QuitClosure()));
import_loop.Run();
task_environment_.RunUntilIdle();
// Find the imported cert and get its id.
net::ScopedCERTCertificateList private_slot_certs =
ListCertsOnPrivateSlot();
ASSERT_EQ(1u, private_slot_certs.size());
int slot_id = 0;
const std::string imported_cert_pkcs11_id =
NetworkCertLoader::GetPkcs11IdAndSlotForCert(
private_slot_certs[0].get(), &slot_id);
std::string imported_cert_formatted_pkcs11_id =
base::StringPrintf("%i:%s", slot_id, imported_cert_pkcs11_id.c_str());
// Verify that the resolver positively matched the pattern in the policy
// with the test client cert and configured the network.
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
if (!expect_failure) {
EXPECT_EQ(imported_cert_formatted_pkcs11_id, pkcs11_id);
EXPECT_EQ(2, network_properties_changed_count_);
} else {
EXPECT_NE(imported_cert_formatted_pkcs11_id, pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
}
base::test::TaskEnvironment task_environment_;
int network_properties_changed_count_ = 0;
std::string test_cert_id_;
std::unique_ptr<base::SimpleTestClock> test_clock_;
std::unique_ptr<ClientCertResolver> client_cert_resolver_;
raw_ptr<NetworkCertLoader, DanglingUntriaged> network_cert_loader_ = nullptr;
std::unique_ptr<net::NSSCertDatabaseChromeOS> test_nsscertdb_;
std::unique_ptr<net::NSSCertDatabaseChromeOS> test_system_nsscertdb_;
private:
// ClientCertResolver::Observer:
void ResolveRequestCompleted(bool network_properties_changed) override {
if (network_properties_changed)
++network_properties_changed_count_;
}
protected:
raw_ptr<ShillServiceClient::TestInterface, DanglingUntriaged> service_test_ =
nullptr;
raw_ptr<ShillProfileClient::TestInterface, DanglingUntriaged> profile_test_ =
nullptr;
std::unique_ptr<NetworkStateHandler> network_state_handler_;
std::unique_ptr<NetworkProfileHandler> network_profile_handler_;
std::unique_ptr<NetworkConfigurationHandler> network_config_handler_;
std::unique_ptr<ManagedNetworkConfigurationHandlerImpl>
managed_config_handler_;
net::ScopedCERTCertificate test_client_cert_;
std::string test_ca_cert_pem_;
crypto::ScopedTestNSSDB test_nssdb_;
crypto::ScopedTestNSSDB test_system_nssdb_;
};
TEST_F(ClientCertResolverTest, NoMatchingCertificates) {
SetupTestCerts("client_1", false /* do not import the issuer */);
StartNetworkCertLoader();
SetupWifi();
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
// Verify that no client certificate was configured.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(std::string(), pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
EXPECT_FALSE(client_cert_resolver_->IsAnyResolveTaskRunning());
}
TEST_F(ClientCertResolverTest, MatchIssuerCNWithoutIssuerInstalled) {
SetupTestCerts("client_1", false /* do not import the issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerCN(::onc::ONC_SOURCE_USER_POLICY));
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
// Test that matching works on a certificate with invalid characters in a
// PrintableString field. See crbug.com/788655.
TEST_F(ClientCertResolverTest, MatchSubjectOrgOnBadPrintableStringCert) {
ASSERT_NO_FATAL_FAILURE(SetupTestCertWithBadPrintableString());
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingSubjectOrgForBadPrintableStringCert(
::onc::ONC_SOURCE_USER_POLICY));
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
TEST_F(ClientCertResolverTest, ResolveOnCertificatesLoaded) {
SetupTestCerts("client_1", true /* import issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
TEST_F(ClientCertResolverTest, ResolveAfterPolicyApplication) {
SetupTestCerts("client_1", true /* import issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
SetupNetworkHandlers();
task_environment_.RunUntilIdle();
// Policy application will trigger the ClientCertResolver.
network_properties_changed_count_ = 0;
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
TEST_F(ClientCertResolverTest, ExpiringCertificate) {
SetupTestCerts("client_1", true /* import issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
SetWifiState(shill::kStateOnline);
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
// Verify that, after the certificate expired and the network disconnection
// happens, no client certificate was configured and the ClientCertResolver
// notified its observers with |network_properties_changed| = true.
network_properties_changed_count_ = 0;
test_clock_->SetNow(base::Time::Max());
SetWifiState(shill::kStateIdle);
task_environment_.RunUntilIdle();
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(std::string(), pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
}
// Test for crbug.com/765489. When the ClientCertResolver re-resolves a network
// as response to a NetworkConnectionStateChanged notification, and the
// resulting cert is the same as the last resolved cert, it should not call
// ResolveRequestCompleted with |network_properties_changed| = true.
TEST_F(ClientCertResolverTest, SameCertAfterNetworkConnectionStateChanged) {
SetupTestCerts("client_1", true /* import issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
SetWifiState(shill::kStateOnline);
task_environment_.RunUntilIdle();
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
// Verify that after the network disconnection happens, the configured
// certificate doesn't change and ClientCertResolver does not notify its
// observers with |network_properties_changed| = true.
network_properties_changed_count_ = 0;
SetWifiState(shill::kStateIdle);
task_environment_.RunUntilIdle();
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
EXPECT_EQ(0, network_properties_changed_count_);
}
TEST_F(ClientCertResolverTest, UserPolicyUsesSystemToken) {
SetupTestCertInSystemToken("client_1");
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerCN(::onc::ONC_SOURCE_USER_POLICY));
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
ASSERT_EQ(1U, network_cert_loader_->client_certs().size());
EXPECT_TRUE(network_cert_loader_->client_certs()[0].is_device_wide());
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
}
TEST_F(ClientCertResolverTest, UserPolicyUsesSystemTokenSync) {
SetupTestCertInSystemToken("client_1");
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
client_cert::ClientCertConfig client_cert_config;
SetupCertificateConfigMatchingIssuerCN(::onc::ONC_SOURCE_USER_POLICY,
&client_cert_config);
base::Value::Dict shill_properties;
ClientCertResolver::ResolveClientCertificateSync(
client_cert::ConfigType::kEap, client_cert_config, &shill_properties);
std::string pkcs11_id =
GetStringFromDict(shill_properties, shill::kEapCertIdProperty);
EXPECT_EQ(test_cert_id_, pkcs11_id);
}
TEST_F(ClientCertResolverTest, DevicePolicyUsesSystemToken) {
SetupTestCertInSystemToken("client_1");
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerCN(::onc::ONC_SOURCE_USER_POLICY));
task_environment_.RunUntilIdle();
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
ASSERT_EQ(1U, network_cert_loader_->client_certs().size());
EXPECT_TRUE(network_cert_loader_->client_certs()[0].is_device_wide());
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
}
TEST_F(ClientCertResolverTest, DevicePolicyUsesSystemTokenSync) {
SetupTestCertInSystemToken("client_1");
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
client_cert::ClientCertConfig client_cert_config;
SetupCertificateConfigMatchingIssuerCN(::onc::ONC_SOURCE_DEVICE_POLICY,
&client_cert_config);
base::Value::Dict shill_properties;
ClientCertResolver::ResolveClientCertificateSync(
client_cert::ConfigType::kEap, client_cert_config, &shill_properties);
std::string pkcs11_id =
GetStringFromDict(shill_properties, shill::kEapCertIdProperty);
EXPECT_EQ(test_cert_id_, pkcs11_id);
}
TEST_F(ClientCertResolverTest, DevicePolicyDoesNotUseUserToken) {
SetupTestCerts("client_1", false /* do not import the issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerCN(::onc::ONC_SOURCE_DEVICE_POLICY));
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
ASSERT_EQ(1U, network_cert_loader_->client_certs().size());
EXPECT_FALSE(network_cert_loader_->client_certs()[0].is_device_wide());
// Verify that no client certificate was configured.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(std::string(), pkcs11_id);
EXPECT_EQ(1, network_properties_changed_count_);
EXPECT_FALSE(client_cert_resolver_->IsAnyResolveTaskRunning());
}
TEST_F(ClientCertResolverTest, DevicePolicyDoesNotUseUserTokenSync) {
SetupTestCerts("client_1", false /* do not import the issuer */);
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
client_cert::ClientCertConfig client_cert_config;
SetupCertificateConfigMatchingIssuerCN(::onc::ONC_SOURCE_DEVICE_POLICY,
&client_cert_config);
base::Value::Dict shill_properties;
ClientCertResolver::ResolveClientCertificateSync(
client_cert::ConfigType::kEap, client_cert_config, &shill_properties);
std::string pkcs11_id =
GetStringFromDict(shill_properties, shill::kEapCertIdProperty);
EXPECT_EQ(std::string(), pkcs11_id);
}
TEST_F(ClientCertResolverTest, PopulateIdentityFromCert) {
SetupTestCerts("client_3", true /* import issuer */);
SetupWifi();
task_environment_.RunUntilIdle();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(SetupPolicyMatchingIssuerPEM(
::onc::ONC_SOURCE_USER_POLICY, "${CERT_SAN_EMAIL}"));
task_environment_.RunUntilIdle();
network_properties_changed_count_ = 0;
StartNetworkCertLoader();
task_environment_.RunUntilIdle();
// Verify that the resolver read the subjectAltName email field from the
// cert, and wrote it into the shill service entry.
std::string identity;
GetServiceProperty(shill::kEapIdentityProperty, &identity);
EXPECT_EQ("[email protected]", identity);
EXPECT_EQ(1, network_properties_changed_count_);
// Verify that after changing the ONC policy to request a variant of the
// Microsoft Universal Principal Name field instead, the correct value is
// substituted into the shill service entry.
ASSERT_NO_FATAL_FAILURE(SetupPolicyMatchingIssuerPEM(
::onc::ONC_SOURCE_USER_POLICY, "upn-${CERT_SAN_UPN}-suffix"));
task_environment_.RunUntilIdle();
GetServiceProperty(shill::kEapIdentityProperty, &identity);
EXPECT_EQ("[email protected]", identity);
// Verify that after changing the ONC policy to request the subject CommonName
// field, the correct value is substituted into the shill service entry.
ASSERT_NO_FATAL_FAILURE(SetupPolicyMatchingIssuerPEM(
::onc::ONC_SOURCE_USER_POLICY,
"subject-cn-${CERT_SUBJECT_COMMON_NAME}-suffix"));
task_environment_.RunUntilIdle();
GetServiceProperty(shill::kEapIdentityProperty, &identity);
EXPECT_EQ("subject-cn-Client Cert F-suffix", identity);
}
// Test for crbug.com/781276. A notification which results in no networks to be
// resolved should not alter the state of IsAnyResolveTaskRunning().
TEST_F(ClientCertResolverTest, TestResolveTaskQueued) {
// Set up ClientCertResolver and let it run initially
SetupTestCerts("client_1", true /* import issuer */);
StartNetworkCertLoader();
SetupWifi();
SetupNetworkHandlers();
ASSERT_NO_FATAL_FAILURE(
SetupPolicyMatchingIssuerPEM(::onc::ONC_SOURCE_USER_POLICY, ""));
task_environment_.RunUntilIdle();
// Pretend that policy was applied, this shall queue a resolving task.
static_cast<NetworkPolicyObserver*>(client_cert_resolver_.get())
->PolicyAppliedToNetwork(kWifiStub);
EXPECT_TRUE(client_cert_resolver_->IsAnyResolveTaskRunning());
// Pretend that the network list has changed. One resolving task should still
// be queued.
static_cast<NetworkStateHandlerObserver*>(client_cert_resolver_.get())
->NetworkListChanged();
EXPECT_TRUE(client_cert_resolver_->IsAnyResolveTaskRunning());
// Pretend that certificates have changed. One resolving task should still be
// queued.
static_cast<NetworkCertLoader::Observer*>(client_cert_resolver_.get())
->OnCertificatesLoaded();
EXPECT_TRUE(client_cert_resolver_->IsAnyResolveTaskRunning());
task_environment_.RunUntilIdle();
EXPECT_FALSE(client_cert_resolver_->IsAnyResolveTaskRunning());
// Verify that the resolver positively matched the pattern in the policy with
// the test client cert and configured the network.
std::string pkcs11_id;
GetServiceProperty(shill::kEapCertIdProperty, &pkcs11_id);
EXPECT_EQ(test_cert_id_, pkcs11_id);
}
// Tests that a ClientCertRef reference is resolved by |ClientCertResolver|.
// Uses the |CertificateImporterImpl| to import the client certificate from ONC
// policy, ensuring that setting the cert's key's nickname (in the import step)
// and evaluating it (in the matching step) work well together.
TEST_F(ClientCertResolverTest, ResolveClientCertRef) {
const char* test_policy_network =
R"([ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Identity": "TestIdentity",
"Outer": "EAP-TLS",
"ClientCertType": "Ref",
"ClientCertRef": "{some-unique-guid}"
}
}
} ])";
ResolveTestHelper(test_policy_network, false);
}
// Tests that a ClientCertProvisioningProfileId is resolved by
// |ClientCertResolver|.
// Same test as above except that we search for a different key. Note that
// this is using the Ref type instead of the ProvisioningProfileId type because
// that syntax was chosen by the Android team for this type of match. We also
// support a dedicated syntax which is tested below.
TEST_F(ClientCertResolverTest, ResolveByCertProfileIdInClientCertRef) {
const char* test_policy_network =
R"([ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Identity": "TestIdentity",
"Outer": "EAP-TLS",
"ClientCertType": "Ref",
"ClientCertRef": "{some-provisioning-id}"
}
}
} ])";
// Override the getter for the provisioning id. See
// ClientCertResolver::SetProvisioningIdForCertGetterForTesting for details.
// We know that we only import one cert, so we do not need to check more,
// here.
auto runner = ClientCertResolver::SetProvisioningIdForCertGetterForTesting(
base::BindRepeating([](CERTCertificate* cert) -> std::string {
return "{some-provisioning-id}";
}));
ResolveTestHelper(test_policy_network, false);
}
// Tests that a ClientCertProvisioningProfileId is resolved by
// |ClientCertResolver|.
// Same test as above except that we use the dedicated syntax for a
// ClientCertProvisioningProfileId.
TEST_F(ClientCertResolverTest, ResolveByCertProfileId) {
const char* test_policy_network =
R"([ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Identity": "TestIdentity",
"Outer": "EAP-TLS",
"ClientCertType": "ProvisioningProfileId",
"ClientCertProvisioningProfileId": "{some-provisioning-id}"
}
}
} ])";
// Override the getter for the provisioning id. See
// ClientCertResolver::SetProvisioningIdForCertGetterForTesting for details.
// We know that we only import one cert, so we do not need to check more,
// here.
auto runner = ClientCertResolver::SetProvisioningIdForCertGetterForTesting(
base::BindRepeating([](CERTCertificate* cert) -> std::string {
return "{some-provisioning-id}";
}));
ResolveTestHelper(test_policy_network, false);
}
// Tests that a ClientCertProvisioningProfileId is not resolved by
// |ClientCertResolver| if it has the wrong profile id.
TEST_F(ClientCertResolverTest, ResolveByCertProfileIdFailure) {
const char* test_policy_network =
R"([ { "GUID": "wifi_stub",
"Name": "wifi_stub",
"Type": "WiFi",
"WiFi": {
"Security": "WPA-EAP",
"SSID": "wifi_ssid",
"EAP": {
"Identity": "TestIdentity",
"Outer": "EAP-TLS",
"ClientCertType": "ProvisioningProfileId",
"ClientCertProvisioningProfileId": "{wrong-provisioning-id}"
}
}
} ])";
// Override the getter for the provisioning id. See
// ClientCertResolver::SetProvisioningIdForCertGetterForTesting
// for details. We know that we only import one cert, so we do not need to
// check more, here.
auto runner = ClientCertResolver::SetProvisioningIdForCertGetterForTesting(
base::BindRepeating([](CERTCertificate* cert) -> std::string {
return "{some-provisioning-id}";
}));
ResolveTestHelper(test_policy_network, true);
}
} // namespace ash