blob: 7ae630e57701ca8e25b27ba9bda7eddecf4c0bd9 [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 <stdint.h>
#include <vector>
#include "base/command_line.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/webauth/authenticator_impl.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/service_manager_connection.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/shell/browser/shell.h"
#include "device/fido/fake_fido_discovery.h"
#include "device/fido/fake_hid_impl_for_testing.h"
#include "net/dns/mock_host_resolver.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.h"
namespace content {
namespace {
using webauth::mojom::AuthenticatorPtr;
using webauth::mojom::AuthenticatorStatus;
using webauth::mojom::GetAssertionAuthenticatorResponsePtr;
using webauth::mojom::MakeCredentialAuthenticatorResponsePtr;
class MockCreateCallback {
public:
MockCreateCallback() = default;
MOCK_METHOD1_T(Run, void(AuthenticatorStatus));
using MakeCredentialCallback =
base::OnceCallback<void(AuthenticatorStatus,
MakeCredentialAuthenticatorResponsePtr)>;
void RunWrapper(AuthenticatorStatus status,
MakeCredentialAuthenticatorResponsePtr unused_response) {
Run(status);
}
MakeCredentialCallback Get() {
return base::BindOnce(&MockCreateCallback::RunWrapper,
base::Unretained(this));
}
private:
DISALLOW_COPY_AND_ASSIGN(MockCreateCallback);
};
class MockGetCallback {
public:
MockGetCallback() = default;
MOCK_METHOD1_T(Run, void(AuthenticatorStatus));
using GetAssertionCallback =
base::OnceCallback<void(AuthenticatorStatus,
GetAssertionAuthenticatorResponsePtr)>;
void RunWrapper(AuthenticatorStatus status,
GetAssertionAuthenticatorResponsePtr unused_response) {
Run(status);
}
GetAssertionCallback Get() {
return base::BindOnce(&MockGetCallback::RunWrapper, base::Unretained(this));
}
private:
DISALLOW_COPY_AND_ASSIGN(MockGetCallback);
};
// Helper object for common tasks.
class WebAuthBrowserTestBase : public content::ContentBrowserTest {
protected:
WebAuthBrowserTestBase()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
void SetUp() override { content::ContentBrowserTest::SetUp(); }
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server().ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server().Start());
NavigateToURL(shell(), GetHttpsURL("www.example.com", "/title1.html"));
ConnectToAuthenticator(shell()->web_contents());
}
virtual void ConnectToAuthenticator(WebContents* web_contents) {
authenticator_impl_.reset(
new content::AuthenticatorImpl(web_contents->GetMainFrame()));
authenticator_impl_->Bind(mojo::MakeRequest(&authenticator_ptr_));
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
static constexpr int32_t kCOSEAlgorithmIdentifierES256 = -7;
webauth::mojom::PublicKeyCredentialCreationOptionsPtr
BuildBasicCreateOptions() {
auto rp = webauth::mojom::PublicKeyCredentialRpEntity::New(
"example.com", "example.com", base::nullopt);
std::vector<uint8_t> kTestUserId{0, 0, 0};
auto user = webauth::mojom::PublicKeyCredentialUserEntity::New(
kTestUserId, "name", base::nullopt, "displayName");
auto param = webauth::mojom::PublicKeyCredentialParameters::New();
param->type = webauth::mojom::PublicKeyCredentialType::PUBLIC_KEY;
param->algorithm_identifier = kCOSEAlgorithmIdentifierES256;
std::vector<webauth::mojom::PublicKeyCredentialParametersPtr> parameters;
parameters.push_back(std::move(param));
std::vector<uint8_t> kTestChallenge{0, 0, 0};
auto mojo_options = webauth::mojom::PublicKeyCredentialCreationOptions::New(
std::move(rp), std::move(user), kTestChallenge, std::move(parameters),
base::TimeDelta::FromSeconds(30),
std::vector<webauth::mojom::PublicKeyCredentialDescriptorPtr>(),
nullptr, webauth::mojom::AttestationConveyancePreference::NONE);
return mojo_options;
}
webauth::mojom::PublicKeyCredentialRequestOptionsPtr BuildBasicGetOptions() {
std::vector<webauth::mojom::PublicKeyCredentialDescriptorPtr> credentials;
std::vector<webauth::mojom::AuthenticatorTransport> transports;
transports.push_back(webauth::mojom::AuthenticatorTransport::USB);
std::vector<uint8_t> kCredentialId{0, 0, 0};
auto descriptor = webauth::mojom::PublicKeyCredentialDescriptor::New(
webauth::mojom::PublicKeyCredentialType::PUBLIC_KEY, kCredentialId,
transports);
credentials.push_back(std::move(descriptor));
std::vector<uint8_t> kTestChallenge{0, 0, 0};
auto mojo_options = webauth::mojom::PublicKeyCredentialRequestOptions::New(
kTestChallenge, base::TimeDelta::FromSeconds(30), "example.com",
std::move(credentials),
webauth::mojom::UserVerificationRequirement::PREFERRED, base::nullopt);
return mojo_options;
}
void ResetAuthenticatorImplAndWaitForConnectionError() {
EXPECT_TRUE(authenticator_impl_);
EXPECT_TRUE(authenticator_ptr_);
EXPECT_TRUE(authenticator_ptr_.is_bound());
EXPECT_FALSE(authenticator_ptr_.encountered_error());
base::RunLoop run_loop;
authenticator_ptr_.set_connection_error_handler(run_loop.QuitClosure());
authenticator_impl_.reset();
run_loop.Run();
}
GURL GetHttpsURL(const std::string& hostname,
const std::string& relative_url) {
return https_server_.GetURL(hostname, relative_url);
}
AuthenticatorPtr& authenticator() { return authenticator_ptr_; }
net::EmbeddedTestServer& https_server() { return https_server_; }
private:
net::EmbeddedTestServer https_server_;
std::unique_ptr<content::AuthenticatorImpl> authenticator_impl_;
AuthenticatorPtr authenticator_ptr_;
};
} // namespace
class WebAuthBrowserTest : public WebAuthBrowserTestBase {
public:
WebAuthBrowserTest() {
scoped_feature_list_.InitWithFeatures(
{features::kWebAuth, features::kWebAuthBle}, {});
}
// Templates to be used with base::ReplaceStringPlaceholders. Can be
// modified to include up to 9 replacements.
base::StringPiece CREATE_PUBLIC_KEY_TEMPLATE =
"navigator.credentials.create({ publicKey: {"
" challenge: new TextEncoder().encode('climb a mountain'),"
" rp: { id: 'example.com', name: 'Acme' },"
" user: { "
" id: new TextEncoder().encode('1098237235409872'),"
" name: 'avery.a.jones@example.com',"
" displayName: 'Avery A. Jones', "
" icon: 'https://pics.acme.com/00/p/aBjjjpqPb.png'},"
" pubKeyCredParams: [{ type: 'public-key', alg: '-7'}],"
" timeout: 60000,"
" excludeCredentials: [],"
" authenticatorSelection : { "
" requireResidentKey: $1, "
" userVerification: '$2' }}"
"}).catch(c => window.domAutomationController.send(c.toString()));";
base::StringPiece GET_PUBLIC_KEY_TEMPLATE =
"navigator.credentials.get({ publicKey: {"
" challenge: new TextEncoder().encode('climb a mountain'),"
" rp: 'example.com',"
" timeout: 60000,"
" userVerification: '$1',"
" allowCredentials: [{ type: 'public-key',"
" id: new TextEncoder().encode('allowedCredential'),"
" transports: ['usb', 'nfc', 'ble']}] }"
"}).catch(c => window.domAutomationController.send(c.toString()));";
void CreatePublicKeyCredentialWithUserVerificationAndExpectNotSupported(
content::WebContents* web_contents) {
std::string result;
std::vector<std::string> subst;
subst.push_back("false");
subst.push_back("required");
std::string script = base::ReplaceStringPlaceholders(
CREATE_PUBLIC_KEY_TEMPLATE, subst, nullptr);
ASSERT_TRUE(
content::ExecuteScriptAndExtractString(web_contents, script, &result));
ASSERT_EQ(
"NotSupportedError: Parameters for this operation are not supported.",
result);
}
void CreatePublicKeyCredentialWithResidentKeyRequiredAndExpectNotSupported(
content::WebContents* web_contents) {
std::string result;
std::vector<std::string> subst;
subst.push_back("true");
subst.push_back("preferred");
std::string script = base::ReplaceStringPlaceholders(
CREATE_PUBLIC_KEY_TEMPLATE, subst, nullptr);
ASSERT_TRUE(
content::ExecuteScriptAndExtractString(web_contents, script, &result));
ASSERT_EQ(
"NotSupportedError: Parameters for this operation are not supported.",
result);
}
void GetPublicKeyCredentialWithUserVerificationAndExpectNotSupported(
content::WebContents* web_contents) {
std::string result;
std::vector<std::string> subst;
subst.push_back("required");
std::string script = base::ReplaceStringPlaceholders(
GET_PUBLIC_KEY_TEMPLATE, subst, nullptr);
ASSERT_TRUE(
content::ExecuteScriptAndExtractString(web_contents, script, &result));
ASSERT_EQ(
"NotSupportedError: Parameters for this operation are not supported.",
result);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserTest);
};
// A test fixture that does not enable BLE discovery.
class WebAuthBrowserBleDisabledTest : public WebAuthBrowserTestBase {
public:
WebAuthBrowserBleDisabledTest() {
scoped_feature_list_.InitAndEnableFeature(features::kWebAuth);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(WebAuthBrowserBleDisabledTest);
};
// Tests that no crash occurs when the implementation is destroyed with a
// pending create(publicKey) request.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest,
CreatePublicKeyCredentialNavigateAway) {
MockCreateCallback create_callback;
EXPECT_CALL(create_callback, Run(::testing::_)).Times(0);
authenticator()->MakeCredential(BuildBasicCreateOptions(),
create_callback.Get());
authenticator().FlushForTesting();
ResetAuthenticatorImplAndWaitForConnectionError();
}
// Tests that no crash occurs when the implementation is destroyed with a
// pending get(publicKey) request.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest, GetPublicKeyCredentialNavigateAway) {
MockGetCallback get_callback;
EXPECT_CALL(get_callback, Run(::testing::_)).Times(0);
authenticator()->GetAssertion(BuildBasicGetOptions(), get_callback.Get());
authenticator().FlushForTesting();
ResetAuthenticatorImplAndWaitForConnectionError();
}
// Regression test for https://crbug.com/818219.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest,
CreatePublicKeyCredentialTwiceInARow) {
MockCreateCallback callback_1;
MockCreateCallback callback_2;
EXPECT_CALL(callback_1, Run(::testing::_)).Times(0);
EXPECT_CALL(callback_2, Run(AuthenticatorStatus::PENDING_REQUEST)).Times(1);
authenticator()->MakeCredential(BuildBasicCreateOptions(), callback_1.Get());
authenticator()->MakeCredential(BuildBasicCreateOptions(), callback_2.Get());
authenticator().FlushForTesting();
}
// Regression test for https://crbug.com/818219.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest, GetPublicKeyCredentialTwiceInARow) {
MockGetCallback callback_1;
MockGetCallback callback_2;
EXPECT_CALL(callback_1, Run(::testing::_)).Times(0);
EXPECT_CALL(callback_2, Run(AuthenticatorStatus::PENDING_REQUEST)).Times(1);
authenticator()->GetAssertion(BuildBasicGetOptions(), callback_1.Get());
authenticator()->GetAssertion(BuildBasicGetOptions(), callback_2.Get());
authenticator().FlushForTesting();
}
// Tests that when navigator.credentials.create() is called with unsupported
// authenticator selection criteria, we get a NotSupportedError.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest,
CreatePublicKeyCredentialUnsupportedSelectionCriteria) {
ASSERT_NO_FATAL_FAILURE(
CreatePublicKeyCredentialWithResidentKeyRequiredAndExpectNotSupported(
shell()->web_contents()));
ASSERT_NO_FATAL_FAILURE(
CreatePublicKeyCredentialWithUserVerificationAndExpectNotSupported(
shell()->web_contents()));
}
// Tests that when navigator.credentials.get() is called with required
// user verification, we get a NotSupportedError.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserTest,
GetPublicKeyCredentialUserVerification) {
ASSERT_NO_FATAL_FAILURE(
GetPublicKeyCredentialWithUserVerificationAndExpectNotSupported(
shell()->web_contents()));
}
// WebAuthBrowserBleDisabledTest ------------------------------
// Tests that the BLE discovery does not start when the WebAuthnBle feature
// flag is disabled.
IN_PROC_BROWSER_TEST_F(WebAuthBrowserBleDisabledTest, CheckBleDisabled) {
device::test::ScopedFakeFidoDiscoveryFactory factory;
auto* fake_hid_discovery = factory.ForgeNextHidDiscovery();
auto* fake_ble_discovery = factory.ForgeNextBleDiscovery();
// Do something that will start discoveries.
MockCreateCallback create_callback;
authenticator()->MakeCredential(BuildBasicCreateOptions(),
create_callback.Get());
fake_hid_discovery->WaitForCallToStart();
EXPECT_TRUE(fake_hid_discovery->is_start_requested());
EXPECT_FALSE(fake_ble_discovery->is_start_requested());
}
} // namespace content