blob: 8967b05cb58800c50c7335bfbcbeabd48ee792dc [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
#include <algorithm>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate_factory.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/webauthn_pref_names.h"
#include "chrome/browser/webauthn/webauthn_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/web_contents_tester.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_discovery_factory.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/test_callback_receiver.h"
#include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_fido_device_authenticator.h"
#include "net/ssl/ssl_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(IS_WIN)
#include "device/fido/win/authenticator.h"
#include "device/fido/win/fake_webauthn_api.h"
#include "third_party/microsoft_webauthn/webauthn.h"
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_MAC)
#include "device/fido/mac/authenticator_config.h"
#endif // BUILDFLAG(IS_MAC)
namespace {
static constexpr char kRelyingPartyID[] = "example.com";
class ChromeAuthenticatorRequestDelegateTest
: public ChromeRenderViewHostTestHarness {};
class TestAuthenticatorModelObserver final
: public AuthenticatorRequestDialogModel::Observer {
public:
explicit TestAuthenticatorModelObserver(
AuthenticatorRequestDialogModel* model)
: model_(model) {
last_step_ = model_->current_step();
}
~TestAuthenticatorModelObserver() override {
if (model_) {
model_->RemoveObserver(this);
}
}
AuthenticatorRequestDialogModel::Step last_step() { return last_step_; }
// AuthenticatorRequestDialogModel::Observer:
void OnStepTransition() override { last_step_ = model_->current_step(); }
void OnModelDestroyed(AuthenticatorRequestDialogModel* model) override {
model_ = nullptr;
}
private:
raw_ptr<AuthenticatorRequestDialogModel> model_;
AuthenticatorRequestDialogModel::Step last_step_;
};
TEST_F(ChromeAuthenticatorRequestDelegateTest, IndividualAttestation) {
static const struct TestCase {
std::string name;
std::string origin;
std::string rp_id;
std::string enterprise_attestation_switch_value;
std::vector<std::string> permit_attestation_policy_values;
bool expected;
} kTestCases[] = {
{"Basic", "https://login.example.com", "example.com", "", {}, false},
{"Policy permits RP ID",
"https://login.example.com",
"example.com",
"",
{"example.com", "other.com"},
true},
{"Policy doesn't permit RP ID",
"https://login.example.com",
"example.com",
"",
{"other.com", "login.example.com", "https://example.com",
"http://example.com", "https://login.example.com", "com", "*"},
false},
{"Policy doesn't care about the origin",
"https://login.example.com",
"example.com",
"",
{"https://login.example.com", "https://example.com"},
false},
{"Switch permits origin",
"https://login.example.com",
"example.com",
"https://login.example.com,https://other.com,xyz:/invalidorigin",
{},
true},
{"Switch doesn't permit origin",
"https://login.example.com",
"example.com",
"example.com,login.example.com,http://login.example.com,https://"
"example.com,https://a.login.example.com,https://*.example.com",
{},
false},
};
for (const auto& test : kTestCases) {
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitchASCII(
webauthn::switches::kPermitEnterpriseAttestationOriginList,
test.enterprise_attestation_switch_value);
PrefService* prefs =
Profile::FromBrowserContext(GetBrowserContext())->GetPrefs();
if (!test.permit_attestation_policy_values.empty()) {
base::Value::List policy_values;
for (const std::string& v : test.permit_attestation_policy_values)
policy_values.Append(v);
prefs->SetList(prefs::kSecurityKeyPermitAttestation,
std::move(policy_values));
} else {
prefs->ClearPref(prefs::kSecurityKeyPermitAttestation);
}
ChromeWebAuthenticationDelegate delegate;
EXPECT_EQ(delegate.ShouldPermitIndividualAttestation(
GetBrowserContext(), url::Origin::Create(GURL(test.origin)),
test.rp_id),
test.expected)
<< test.name;
}
}
TEST_F(ChromeAuthenticatorRequestDelegateTest, CableConfiguration) {
class DiscoveryFactory : public device::FidoDiscoveryFactory {
public:
void set_cable_data(
device::CableRequestType request_type,
std::vector<device::CableDiscoveryData> data,
const absl::optional<std::array<uint8_t, device::cablev2::kQRKeySize>>&
qr_generator_key,
std::vector<std::unique_ptr<device::cablev2::Pairing>> pairings)
override {
cable_data = std::move(data);
qr_key = qr_generator_key;
v2_pairings = std::move(pairings);
}
void set_android_accessory_params(
mojo::Remote<device::mojom::UsbDeviceManager>,
std::string aoa_request_description) override {
this->aoa_configured = true;
}
std::vector<device::CableDiscoveryData> cable_data;
absl::optional<std::array<uint8_t, device::cablev2::kQRKeySize>> qr_key;
std::vector<std::unique_ptr<device::cablev2::Pairing>> v2_pairings;
bool aoa_configured = false;
};
const std::array<uint8_t, 16> eid = {1, 2, 3, 4};
const std::array<uint8_t, 32> prekey = {5, 6, 7, 8};
const device::CableDiscoveryData v1_extension(
device::CableDiscoveryData::Version::V1, eid, eid, prekey);
device::CableDiscoveryData v2_extension;
v2_extension.version = device::CableDiscoveryData::Version::V2;
v2_extension.v2.emplace(std::vector<uint8_t>(prekey.begin(), prekey.end()),
std::vector<uint8_t>());
enum class Result {
kNone,
kV1,
kServerLink,
k3rdParty,
};
#if BUILDFLAG(IS_LINUX)
// On Linux, some configurations aren't supported because of bluez
// limitations. This macro maps the expected result in that case.
#define NONE_ON_LINUX(r) (Result::kNone)
#else
#define NONE_ON_LINUX(r) (r)
#endif
const struct {
const char* origin;
std::vector<device::CableDiscoveryData> extensions;
device::CableRequestType request_type;
absl::optional<device::ResidentKeyRequirement> resident_key_requirement;
Result expected_result;
} kTests[] = {
{
"https://example.com",
{},
device::CableRequestType::kGetAssertion,
absl::nullopt,
Result::k3rdParty,
},
{
// Extensions should be ignored on a 3rd-party site.
"https://example.com",
{v1_extension},
device::CableRequestType::kGetAssertion,
absl::nullopt,
Result::k3rdParty,
},
{
// Extensions should be ignored on a 3rd-party site.
"https://example.com",
{v2_extension},
device::CableRequestType::kGetAssertion,
absl::nullopt,
Result::k3rdParty,
},
{
// a.g.c should still be able to get 3rd-party caBLE
// if it doesn't send an extension in an assertion request.
"https://accounts.google.com",
{},
device::CableRequestType::kGetAssertion,
absl::nullopt,
Result::k3rdParty,
},
{
// ... but not for non-discoverable registration.
"https://accounts.google.com",
{},
device::CableRequestType::kMakeCredential,
device::ResidentKeyRequirement::kDiscouraged,
Result::kNone,
},
{
// ... but yes for rk=preferred
"https://accounts.google.com",
{},
device::CableRequestType::kMakeCredential,
device::ResidentKeyRequirement::kPreferred,
Result::k3rdParty,
},
{
// ... or rk=required.
"https://accounts.google.com",
{},
device::CableRequestType::kDiscoverableMakeCredential,
device::ResidentKeyRequirement::kRequired,
Result::k3rdParty,
},
{
"https://accounts.google.com",
{v1_extension},
device::CableRequestType::kGetAssertion,
absl::nullopt,
NONE_ON_LINUX(Result::kV1),
},
{
"https://accounts.google.com",
{v2_extension},
device::CableRequestType::kGetAssertion,
absl::nullopt,
Result::kServerLink,
},
};
unsigned test_case = 0;
for (const auto& test : kTests) {
SCOPED_TRACE(test_case);
test_case++;
DiscoveryFactory discovery_factory;
ChromeAuthenticatorRequestDelegate delegate(main_rfh());
delegate.SetRelyingPartyId(/*rp_id=*/"example.com");
delegate.SetPassEmptyUsbDeviceManagerForTesting(true);
delegate.ConfigureCable(url::Origin::Create(GURL(test.origin)),
test.request_type, test.resident_key_requirement,
test.extensions, &discovery_factory);
switch (test.expected_result) {
case Result::kNone:
EXPECT_FALSE(discovery_factory.qr_key.has_value());
EXPECT_TRUE(discovery_factory.v2_pairings.empty());
EXPECT_TRUE(discovery_factory.cable_data.empty());
EXPECT_TRUE(discovery_factory.aoa_configured);
break;
case Result::kV1:
EXPECT_FALSE(discovery_factory.qr_key.has_value());
EXPECT_TRUE(discovery_factory.v2_pairings.empty());
EXPECT_FALSE(discovery_factory.cable_data.empty());
EXPECT_TRUE(discovery_factory.aoa_configured);
EXPECT_EQ(delegate.dialog_model()->cable_ui_type(),
AuthenticatorRequestDialogModel::CableUIType::CABLE_V1);
break;
case Result::kServerLink:
EXPECT_TRUE(discovery_factory.qr_key.has_value());
EXPECT_TRUE(discovery_factory.v2_pairings.empty());
EXPECT_FALSE(discovery_factory.cable_data.empty());
EXPECT_TRUE(discovery_factory.aoa_configured);
EXPECT_EQ(
delegate.dialog_model()->cable_ui_type(),
AuthenticatorRequestDialogModel::CableUIType::CABLE_V2_SERVER_LINK);
break;
case Result::k3rdParty:
EXPECT_TRUE(discovery_factory.qr_key.has_value());
EXPECT_TRUE(discovery_factory.v2_pairings.empty());
EXPECT_TRUE(discovery_factory.cable_data.empty());
EXPECT_TRUE(discovery_factory.aoa_configured);
EXPECT_EQ(
delegate.dialog_model()->cable_ui_type(),
AuthenticatorRequestDialogModel::CableUIType::CABLE_V2_2ND_FACTOR);
break;
}
}
}
TEST_F(ChromeAuthenticatorRequestDelegateTest, ConditionalUI) {
// The RenderFrame has to be live for the ChromeWebAuthnCredentialsDelegate to
// be created.
content::WebContentsTester::For(web_contents())
->NavigateAndCommit(GURL("https://example.com"));
ChromeWebAuthnCredentialsDelegateFactory::CreateForWebContents(
web_contents());
// Enabling conditional mode should cause the modal dialog to stay hidden at
// the beginning of a request. An omnibar icon might be shown instead.
for (bool conditional_ui : {true, false}) {
ChromeAuthenticatorRequestDelegate delegate(main_rfh());
delegate.SetConditionalRequest(conditional_ui);
delegate.SetRelyingPartyId(/*rp_id=*/"example.com");
AuthenticatorRequestDialogModel* model = delegate.dialog_model();
TestAuthenticatorModelObserver observer(model);
model->AddObserver(&observer);
EXPECT_EQ(observer.last_step(),
AuthenticatorRequestDialogModel::Step::kNotStarted);
delegate.OnTransportAvailabilityEnumerated(
AuthenticatorRequestDialogModel::TransportAvailabilityInfo());
EXPECT_EQ(observer.last_step() ==
AuthenticatorRequestDialogModel::Step::kConditionalMediation,
conditional_ui);
}
}
TEST_F(ChromeAuthenticatorRequestDelegateTest,
OverrideValidateDomainAndRelyingPartyIDTest) {
constexpr char kTestExtensionOrigin[] = "chrome-extension://abcdef";
static const struct {
std::string rp_id;
std::string origin;
bool expected;
} kTests[] = {
{"example.com", "https://example.com", false},
{"foo.com", "https://example.com", false},
{"abcdef", kTestExtensionOrigin, true},
{"abcdefg", kTestExtensionOrigin, false},
{"example.com", kTestExtensionOrigin, false},
};
ChromeWebAuthenticationDelegate delegate;
for (const auto& test : kTests) {
EXPECT_EQ(delegate.OverrideCallerOriginAndRelyingPartyIdValidation(
GetBrowserContext(), url::Origin::Create(GURL(test.origin)),
test.rp_id),
test.expected);
}
}
TEST_F(ChromeAuthenticatorRequestDelegateTest, MaybeGetRelyingPartyIdOverride) {
constexpr char kTestExtensionOrigin[] = "chrome-extension://abcdef";
ChromeWebAuthenticationDelegate delegate;
static const struct {
std::string rp_id;
std::string origin;
absl::optional<std::string> expected;
} kTests[] = {
{"example.com", "https://example.com", absl::nullopt},
{"foo.com", "https://example.com", absl::nullopt},
{"abcdef", kTestExtensionOrigin, kTestExtensionOrigin},
{"example.com", kTestExtensionOrigin, kTestExtensionOrigin},
};
for (const auto& test : kTests) {
EXPECT_EQ(delegate.MaybeGetRelyingPartyIdOverride(
test.rp_id, url::Origin::Create(GURL(test.origin))),
test.expected);
}
}
// Tests that attestation is returned if the virtual environment is enabled and
// the UI is disabled.
// Regression test for crbug.com/1342458
TEST_F(ChromeAuthenticatorRequestDelegateTest, VirtualEnvironmentAttestation) {
ChromeAuthenticatorRequestDelegate delegate(main_rfh());
delegate.DisableUI();
delegate.SetVirtualEnvironment(true);
device::VirtualFidoDeviceAuthenticator authenticator(
std::make_unique<device::VirtualCtap2Device>());
device::test::ValueCallbackReceiver<bool> cb;
delegate.ShouldReturnAttestation(kRelyingPartyID, &authenticator,
/*is_enterprise_attestation=*/false,
cb.callback());
EXPECT_TRUE(cb.value());
}
#if BUILDFLAG(IS_MAC)
std::string TouchIdMetadataSecret(ChromeWebAuthenticationDelegate& delegate,
content::BrowserContext* browser_context) {
return delegate.GetTouchIdAuthenticatorConfig(browser_context)
->metadata_secret;
}
TEST_F(ChromeAuthenticatorRequestDelegateTest, TouchIdMetadataSecret) {
ChromeWebAuthenticationDelegate delegate;
std::string secret = TouchIdMetadataSecret(delegate, GetBrowserContext());
EXPECT_EQ(secret.size(), 32u);
// The secret should be stable.
EXPECT_EQ(secret, TouchIdMetadataSecret(delegate, GetBrowserContext()));
}
TEST_F(ChromeAuthenticatorRequestDelegateTest,
TouchIdMetadataSecret_EqualForSameProfile) {
// Different delegates on the same BrowserContext (Profile) should return
// the same secret.
ChromeWebAuthenticationDelegate delegate1;
ChromeWebAuthenticationDelegate delegate2;
EXPECT_EQ(TouchIdMetadataSecret(delegate1, GetBrowserContext()),
TouchIdMetadataSecret(delegate2, GetBrowserContext()));
}
TEST_F(ChromeAuthenticatorRequestDelegateTest,
TouchIdMetadataSecret_NotEqualForDifferentProfiles) {
// Different profiles have different secrets.
auto other_browser_context = CreateBrowserContext();
ChromeWebAuthenticationDelegate delegate;
EXPECT_NE(TouchIdMetadataSecret(delegate, GetBrowserContext()),
TouchIdMetadataSecret(delegate, other_browser_context.get()));
// Ensure this second secret is actually valid.
EXPECT_EQ(
32u, TouchIdMetadataSecret(delegate, other_browser_context.get()).size());
}
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
// Tests that ShouldReturnAttestation() returns with true if |authenticator|
// is the Windows native WebAuthn API with WEBAUTHN_API_VERSION_2 or higher,
// where Windows prompts for attestation in its own native UI.
//
// Ideally, this would also test the inverse case, i.e. that with
// WEBAUTHN_API_VERSION_1 Chrome's own attestation prompt is shown. However,
// there seems to be no good way to test AuthenticatorRequestDialogModel UI.
TEST_F(ChromeAuthenticatorRequestDelegateTest, ShouldPromptForAttestationWin) {
::device::FakeWinWebAuthnApi win_webauthn_api;
win_webauthn_api.set_version(WEBAUTHN_API_VERSION_2);
::device::WinWebAuthnApiAuthenticator authenticator(
/*current_window=*/nullptr, &win_webauthn_api);
::device::test::ValueCallbackReceiver<bool> cb;
ChromeAuthenticatorRequestDelegate delegate(main_rfh());
delegate.ShouldReturnAttestation(kRelyingPartyID, &authenticator,
/*is_enterprise_attestation=*/false,
cb.callback());
cb.WaitForCallback();
EXPECT_EQ(cb.value(), true);
}
class ChromeAuthenticatorRequestDelegateWindowsBehaviorTest
: public ChromeAuthenticatorRequestDelegateTest {
public:
ChromeAuthenticatorRequestDelegateWindowsBehaviorTest() {
scoped_feature_list_.InitAndDisableFeature(
device::kWebAuthnNewDiscoverableCredentialsUi);
}
void CreateObjectsUnderTest() {
delegate_.emplace(main_rfh());
delegate_->SetRelyingPartyId("example.com");
AuthenticatorRequestDialogModel* const model = delegate_->dialog_model();
observer_.emplace(model);
model->AddObserver(&observer_.value());
CHECK_EQ(observer_->last_step(),
AuthenticatorRequestDialogModel::Step::kNotStarted);
}
absl::optional<ChromeAuthenticatorRequestDelegate> delegate_;
absl::optional<TestAuthenticatorModelObserver> observer_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(ChromeAuthenticatorRequestDelegateWindowsBehaviorTest,
CancelAfterMechanismSelection) {
// Test that, on Windows, the `ChromeAuthenticatorRequestDelegate` should
// remember whether the last successful operation was with the native API or
// not and immediately trigger that UI for the next operation accordingly.
// Setup the Windows native authenticator and configure caBLE such that adding
// a phone is an option.
AuthenticatorRequestDialogModel::TransportAvailabilityInfo tai;
tai.has_win_native_api_authenticator = true;
tai.win_native_api_authenticator_id = "ID";
tai.available_transports.insert(device::FidoTransportProtocol::kHybrid);
CreateObjectsUnderTest();
delegate_->dialog_model()->set_cable_transport_info(
absl::nullopt, {}, base::DoNothing(), "fido:/1234");
delegate_->OnTransportAvailabilityEnumerated(tai);
// Since there are two options, the mechanism selection sheet should be shown.
EXPECT_EQ(observer_->last_step(),
AuthenticatorRequestDialogModel::Step::kMechanismSelection);
// Simulate the Windows native API being used successfully.
ChromeWebAuthenticationDelegate non_request_delegate;
non_request_delegate.OperationSucceeded(profile(), /* used_win_api= */ true);
CreateObjectsUnderTest();
delegate_->dialog_model()->set_cable_transport_info(
absl::nullopt, {}, base::DoNothing(), "fido:/1234");
delegate_->OnTransportAvailabilityEnumerated(tai);
// Since the Windows API was used successfully last time, it should jump
// directly to the native UI this time.
EXPECT_EQ(observer_->last_step(),
AuthenticatorRequestDialogModel::Step::kNotStarted);
// Simulate that caBLE was used successfully.
non_request_delegate.OperationSucceeded(profile(), /* used_win_api= */ false);
CreateObjectsUnderTest();
delegate_->dialog_model()->set_cable_transport_info(
absl::nullopt, {}, base::DoNothing(), "fido:/1234");
delegate_->OnTransportAvailabilityEnumerated(tai);
// Should show the mechanism selection sheet again.
EXPECT_EQ(observer_->last_step(),
AuthenticatorRequestDialogModel::Step::kMechanismSelection);
}
#endif // BUILDFLAG(IS_WIN)
class OriginMayUseRemoteDesktopClientOverrideTest
: public ChromeAuthenticatorRequestDelegateTest {
protected:
static constexpr char kCorpCrdOrigin[] =
"https://remotedesktop.corp.google.com";
static constexpr char kExampleOrigin[] = "https://example.com";
base::test::ScopedFeatureList scoped_feature_list_{
device::kWebAuthnGoogleCorpRemoteDesktopClientPrivilege};
};
TEST_F(OriginMayUseRemoteDesktopClientOverrideTest,
RemoteProxiedRequestsAllowedPolicy) {
// The "webauthn.remote_proxied_requests_allowed" policy pref should enable
// Google's internal CRD origin to use the RemoteDesktopClientOverride
// extension.
enum class Policy {
kUnset,
kDisabled,
kEnabled,
};
ChromeWebAuthenticationDelegate delegate;
PrefService* prefs =
Profile::FromBrowserContext(GetBrowserContext())->GetPrefs();
for (auto* origin : {kCorpCrdOrigin, kExampleOrigin}) {
for (const auto policy :
{Policy::kUnset, Policy::kDisabled, Policy::kEnabled}) {
switch (policy) {
case Policy::kUnset:
prefs->ClearPref(webauthn::pref_names::kRemoteProxiedRequestsAllowed);
break;
case Policy::kDisabled:
prefs->SetBoolean(webauthn::pref_names::kRemoteProxiedRequestsAllowed,
false);
break;
case Policy::kEnabled:
prefs->SetBoolean(webauthn::pref_names::kRemoteProxiedRequestsAllowed,
true);
break;
}
EXPECT_EQ(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(), url::Origin::Create(GURL(origin))),
origin == kCorpCrdOrigin && policy == Policy::kEnabled);
}
}
}
TEST_F(OriginMayUseRemoteDesktopClientOverrideTest,
OriginMayUseRemoteDesktopClientOverrideAdditionalOriginSwitch) {
// The --webauthn-remote-proxied-requests-allowed-additional-origin switch
// allows passing an additional origin for testing.
ChromeWebAuthenticationDelegate delegate;
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitchASCII(
webauthn::switches::kRemoteProxiedRequestsAllowedAdditionalOrigin,
kExampleOrigin);
// The flag shouldn't have an effect without the policy enabled.
EXPECT_FALSE(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(), url::Origin::Create(GURL(kExampleOrigin))));
EXPECT_FALSE(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(), url::Origin::Create(GURL(kCorpCrdOrigin))));
// With the policy enabled, both the hard-coded and flag origin should be
// allowed.
PrefService* prefs =
Profile::FromBrowserContext(GetBrowserContext())->GetPrefs();
prefs->SetBoolean(webauthn::pref_names::kRemoteProxiedRequestsAllowed, true);
EXPECT_TRUE(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(), url::Origin::Create(GURL(kExampleOrigin))));
EXPECT_TRUE(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(), url::Origin::Create(GURL(kCorpCrdOrigin))));
// Other origins still shouldn't be permitted.
EXPECT_FALSE(delegate.OriginMayUseRemoteDesktopClientOverride(
browser_context(),
url::Origin::Create(GURL("https://other.example.com"))));
}
class DisableWebAuthnWithBrokenCertsTest
: public ChromeAuthenticatorRequestDelegateTest {
protected:
base::test::ScopedFeatureList scoped_feature_list_{
device::kDisableWebAuthnWithBrokenCerts};
};
TEST_F(DisableWebAuthnWithBrokenCertsTest, SecurityLevelNotAcceptable) {
GURL url("https://doofenshmirtz.evil");
ChromeWebAuthenticationDelegate delegate;
auto simulator =
content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
net::SSLInfo ssl_info;
ssl_info.cert_status = net::CERT_STATUS_DATE_INVALID;
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
simulator->SetSSLInfo(std::move(ssl_info));
simulator->Commit();
EXPECT_FALSE(delegate.IsSecurityLevelAcceptableForWebAuthn(
main_rfh(), url::Origin::Create(url)));
}
TEST_F(DisableWebAuthnWithBrokenCertsTest, ExtensionSupported) {
GURL url("chrome-extension://extensionid");
ChromeWebAuthenticationDelegate delegate;
auto simulator =
content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
net::SSLInfo ssl_info;
ssl_info.cert_status = net::CERT_STATUS_DATE_INVALID;
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
simulator->SetSSLInfo(std::move(ssl_info));
simulator->Commit();
EXPECT_TRUE(delegate.IsSecurityLevelAcceptableForWebAuthn(
main_rfh(), url::Origin::Create(url)));
}
TEST_F(DisableWebAuthnWithBrokenCertsTest, EnterpriseOverride) {
PrefService* prefs =
Profile::FromBrowserContext(GetBrowserContext())->GetPrefs();
prefs->SetBoolean(webauthn::pref_names::kAllowWithBrokenCerts, true);
GURL url("https://doofenshmirtz.evil");
ChromeWebAuthenticationDelegate delegate;
auto simulator =
content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
net::SSLInfo ssl_info;
ssl_info.cert_status = net::CERT_STATUS_DATE_INVALID;
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
simulator->SetSSLInfo(std::move(ssl_info));
simulator->Commit();
EXPECT_TRUE(delegate.IsSecurityLevelAcceptableForWebAuthn(
main_rfh(), url::Origin::Create(url)));
}
TEST_F(DisableWebAuthnWithBrokenCertsTest, Localhost) {
GURL url("http://localhost");
ChromeWebAuthenticationDelegate delegate;
auto simulator =
content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
EXPECT_TRUE(delegate.IsSecurityLevelAcceptableForWebAuthn(
main_rfh(), url::Origin::Create(url)));
}
TEST_F(DisableWebAuthnWithBrokenCertsTest, SecurityLevelAcceptable) {
GURL url("https://owca.org");
ChromeWebAuthenticationDelegate delegate;
auto simulator =
content::NavigationSimulator::CreateBrowserInitiated(url, web_contents());
net::SSLInfo ssl_info;
ssl_info.cert_status = 0; // ok.
ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
simulator->SetSSLInfo(std::move(ssl_info));
simulator->Commit();
EXPECT_TRUE(delegate.IsSecurityLevelAcceptableForWebAuthn(
main_rfh(), url::Origin::Create(url)));
}
} // namespace