blob: ed3fdbb866e27c26a56bec261905da62627504a4 [file] [log] [blame]
// Copyright 2018 The Chromium 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 "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
#include <utility>
#include "build/build_config.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/web_contents_tester.h"
#include "device/fido/fido_device_authenticator.h"
#include "device/fido/fido_discovery_factory.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN)
#include "device/fido/win/authenticator.h"
#include "device/fido/win/fake_webauthn_api.h"
#include "third_party/microsoft_webauthn/webauthn.h"
#endif // defined(OS_WIN)
#if defined(OS_MAC)
#include "device/fido/mac/authenticator_config.h"
#endif // defined(OS_MAC)
class ChromeAuthenticatorRequestDelegateTest
: public ChromeRenderViewHostTestHarness {};
class TestAuthenticatorModelObserver
: public AuthenticatorRequestDialogModel::Observer {
public:
explicit TestAuthenticatorModelObserver(
AuthenticatorRequestDialogModel* model)
: model_(model) {
last_step_ = model_->current_step();
}
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:
AuthenticatorRequestDialogModel* model_;
AuthenticatorRequestDialogModel::Step last_step_;
};
TEST_F(ChromeAuthenticatorRequestDelegateTest, ConditionalUI) {
// 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::kLocationBarBubble,
conditional_ui);
}
}
#if defined(OS_MAC)
API_AVAILABLE(macos(10.12.2))
std::string TouchIdMetadataSecret(ChromeWebAuthenticationDelegate& delegate,
content::BrowserContext* browser_context) {
return delegate.GetTouchIdAuthenticatorConfig(browser_context)
->metadata_secret;
}
TEST_F(ChromeAuthenticatorRequestDelegateTest, TouchIdMetadataSecret) {
if (__builtin_available(macOS 10.12.2, *)) {
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) {
if (__builtin_available(macOS 10.12.2, *)) {
// 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) {
if (__builtin_available(macOS 10.12.2, *)) {
// 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 // defined(OS_MAC)
#if defined(OS_WIN)
static constexpr char kRelyingPartyID[] = "example.com";
// 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);
}
#endif // defined(OS_WIN)