blob: f11b09605e7305b920b58f69ea9408eb4eab675e [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/json/json_writer.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/client_certificates/certificate_provisioning_service_factory.h"
#include "chrome/browser/enterprise/test/management_context_mixin.h"
#include "chrome/browser/enterprise/test/test_constants.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#include "components/enterprise/client_certificates/core/certificate_provisioning_service.h"
#include "components/enterprise/client_certificates/core/client_identity.h"
#include "components/enterprise/client_certificates/core/features.h"
#include "components/enterprise/client_certificates/core/scoped_ssl_key_converter.h"
#include "components/policy/core/common/policy_switches.h"
#include "components/policy/policy_constants.h"
#include "components/policy/test_support/client_storage.h"
#include "components/policy/test_support/embedded_policy_test_server.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/cert/x509_certificate.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace client_certificates {
using ManagementContextMixin = enterprise::test::ManagementContextMixin;
using ManagementContext = enterprise::test::ManagementContext;
namespace {
constexpr char kProfileIssuerCommonName[] = "Profile Root CA";
constexpr char kBrowserIssuerCommonName[] = "Browser Root CA";
struct CapturedRequest {
scoped_refptr<net::X509Certificate> client_certificate{};
};
} // namespace
class ClientCertificateBrowserTest : public MixinBasedPlatformBrowserTest,
public testing::WithParamInterface<bool> {
protected:
ClientCertificateBrowserTest() : scoped_key_converter_(true) {
management_mixin_ = ManagementContextMixin::Create(
&mixin_host_, this,
{
.is_cloud_user_managed = is_profile_scenario(),
.is_cloud_machine_managed = !is_profile_scenario(),
.affiliated = false,
});
#if BUILDFLAG(IS_ANDROID)
scoped_feature_list_.InitAndEnableFeature(
client_certificates::features::
kEnableClientCertificateProvisioningOnAndroid);
#endif // BUILDFLAG(IS_ANDROID)
}
void SetUp() override {
embedded_https_test_server().SetCertHostnames({});
net::EmbeddedTestServer::ServerCertificateConfig server_cert_config;
server_cert_config.dns_names = {"mtls.google.com"};
net::SSLServerConfig ssl_config;
ssl_config.client_cert_type =
net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT;
embedded_https_test_server().SetSSLConfig(server_cert_config, ssl_config);
CHECK(embedded_https_test_server().InitializeAndListen());
MixinBasedPlatformBrowserTest::SetUp();
}
void SetUpInProcessBrowserTestFixture() override {
test_dm_server_ = std::make_unique<policy::EmbeddedPolicyTestServer>();
if (!is_profile_scenario()) {
test_dm_server_->client_storage()->RegisterClient(
enterprise::test::CreateBrowserClientInfo());
}
ASSERT_TRUE(test_dm_server_->Start());
policy::ChromeBrowserPolicyConnector::EnableCommandLineSupportForTesting();
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitchASCII(policy::switches::kDeviceManagementUrl,
test_dm_server_->GetServiceURL().spec());
MixinBasedPlatformBrowserTest::SetUpInProcessBrowserTestFixture();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
embedded_https_test_server().RegisterRequestHandler(base::BindRepeating(
&ClientCertificateBrowserTest::HandleRequest, base::Unretained(this)));
embedded_https_test_server().StartAcceptingConnections();
MixinBasedPlatformBrowserTest::SetUpOnMainThread();
}
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
const auto& request_url = request.GetURL();
if (request_url.path() != "/mtls") {
SCOPED_TRACE("Unexpected request.");
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (!request.ssl_info || !request.ssl_info->cert) {
response->set_code(net::HTTP_FORBIDDEN);
return response;
}
captured_request_.emplace();
captured_request_->client_certificate = request.ssl_info->cert;
return response;
}
void SetUserPolicy(bool enable_provisioning) {
base::flat_map<std::string, std::optional<base::Value>> policy_values;
policy_values.insert(
{policy::key::kProvisionManagedClientCertificateForUser,
base::Value(enable_provisioning ? 1 : 0)});
management_mixin()->SetCloudUserPolicies(std::move(policy_values));
}
void SetBrowserPolicy(bool enable_provisioning) {
base::flat_map<std::string, std::optional<base::Value>> policy_values;
policy_values.insert(
{policy::key::kProvisionManagedClientCertificateForBrowser,
base::Value(enable_provisioning ? 1 : 0)});
management_mixin()->SetCloudMachinePolicies(std::move(policy_values));
}
void SetCertificateAutoselectionPolicy() {
std::string policy_value_json;
ASSERT_TRUE(base::JSONWriter::Write(
base::Value::Dict()
.Set("pattern", embedded_https_test_server()
.GetURL("mtls.google.com", "/mtls")
.spec())
.Set("filter", base::Value::Dict().Set(
"ISSUER", base::Value::Dict().Set(
"CN", GetIssuerCommonName()))),
&policy_value_json));
base::flat_map<std::string, std::optional<base::Value>> policy_values;
policy_values.insert(
{policy::key::kAutoSelectCertificateForUrls,
base::Value(base::Value::List().Append(policy_value_json))});
if (is_profile_scenario()) {
management_mixin()->SetCloudUserPolicies(std::move(policy_values));
} else {
management_mixin()->SetCloudMachinePolicies(std::move(policy_values));
}
}
void EnablePolicyAndWaitForIdentity() {
CertificateProvisioningService* provisioning_service;
if (is_profile_scenario()) {
SetUserPolicy(true);
provisioning_service =
CertificateProvisioningServiceFactory::GetForProfile(GetProfile());
} else {
SetBrowserPolicy(true);
provisioning_service = g_browser_process->browser_policy_connector()
->chrome_browser_cloud_management_controller()
->GetCertificateProvisioningService();
}
ASSERT_TRUE(provisioning_service);
base::test::TestFuture<std::optional<ClientIdentity>> test_future;
provisioning_service->GetManagedIdentity(test_future.GetCallback());
VerifyIdentity(test_future.Get());
}
void VerifyIdentity(const std::optional<ClientIdentity>& managed_identity) {
ASSERT_TRUE(managed_identity);
ASSERT_TRUE(managed_identity->is_valid());
VerifyClientCert(managed_identity->certificate);
}
void VerifyClientCert(
const scoped_refptr<net::X509Certificate>& client_certificate) {
// Verify that the right cert was created based on the root CA's CN.
ASSERT_TRUE(client_certificate);
EXPECT_EQ(client_certificate->issuer().common_name, GetIssuerCommonName());
}
std::string_view GetIssuerCommonName() {
return is_profile_scenario() ? kProfileIssuerCommonName
: kBrowserIssuerCommonName;
}
ManagementContextMixin* management_mixin() { return management_mixin_.get(); }
base::HistogramTester& histogram_tester() { return histogram_tester_; }
bool is_profile_scenario() const { return GetParam(); }
std::optional<CapturedRequest> captured_request_;
private:
base::HistogramTester histogram_tester_;
std::unique_ptr<policy::EmbeddedPolicyTestServer> test_dm_server_;
client_certificates::ScopedSSLKeyConverter scoped_key_converter_;
std::unique_ptr<ManagementContextMixin> management_mixin_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(ClientCertificateBrowserTest, CreateNewIdentity) {
EnablePolicyAndWaitForIdentity();
histogram_tester().ExpectUniqueSample(
base::StringPrintf(
"Enterprise.ClientCertificate.%s.Provisioning.CertificateCreation."
"Outcome",
is_profile_scenario() ? "Profile" : "Browser"),
true, 1);
}
// Temporarily disabled on Android due to PRE_ tests not being fully supported.
// See crbug.com/40200835
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_P(ClientCertificateBrowserTest, PRE_LoadExistingIdentity) {
EnablePolicyAndWaitForIdentity();
}
IN_PROC_BROWSER_TEST_P(ClientCertificateBrowserTest, LoadExistingIdentity) {
EnablePolicyAndWaitForIdentity();
histogram_tester().ExpectUniqueSample(
base::StringPrintf(
"Enterprise.ClientCertificate.%s.Provisioning.ExistingIdentity."
"Outcome",
is_profile_scenario() ? "Profile" : "Browser"),
true, 1);
}
#endif // !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_P(ClientCertificateBrowserTest, UseIdentityInMtls) {
// Enable the necessary policies and trigger a navigation.
SetCertificateAutoselectionPolicy();
if (is_profile_scenario()) {
SetUserPolicy(true);
} else {
SetBrowserPolicy(true);
}
ASSERT_TRUE(content::NavigateToURL(
chrome_test_utils::GetActiveWebContents(this),
embedded_https_test_server().GetURL("mtls.google.com", "/mtls")));
ASSERT_TRUE(captured_request_);
VerifyClientCert(captured_request_->client_certificate);
}
INSTANTIATE_TEST_SUITE_P(
/* no prefix */,
ClientCertificateBrowserTest,
testing::Bool());
} // namespace client_certificates