Commit 77aeb178 authored by Manas Verma's avatar Manas Verma Committed by Commit Bot

Adding ChromeInternalAuthenticator

A new mojom pipe that is meant for internal components to facilitate WebAuthn protocol. This allows the caller to set its own origin, that does not necessarily match the origin of the render frame host

Bug: 945993
Change-Id: I8e553166277821f74f01c45789e58769b605092d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1540310Reviewed-by: default avatarSebastien Lalancette <seblalancette@chromium.org>
Reviewed-by: default avatarFabio Tirelo <ftirelo@chromium.org>
Reviewed-by: default avatarGreg Kerr <kerrnel@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarAdam Langley <agl@chromium.org>
Reviewed-by: default avatarKim Paulhamus <kpaulhamus@chromium.org>
Commit-Queue: Manas Verma <manasverma@google.com>
Cr-Commit-Position: refs/heads/master@{#659201}
parent 28d9b114
......@@ -12,4 +12,8 @@ specific_include_rules = {
'.*_[a-z]*test\.cc': [
"+content/public/test",
],
'.*internal_authenticator_impl\.(cc|h)': [
"+content/browser/webauth/authenticator_common.h",
"+third_party/blink/public/mojom/webauthn/internal_authenticator.mojom.h",
]
}
# Copyright 2019 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.
import("//build/config/jumbo.gni")
jumbo_static_library("webauthn") {
if (is_component_build) {
check_includes = false
}
sources = [
"internal_authenticator_impl.cc",
"internal_authenticator_impl.h",
]
deps = [
"//content/browser:for_internal_webauthn",
"//content/public/browser",
]
}
file://components/autofill/core/browser/payments/OWNERS
// Copyright 2019 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 "components/autofill/content/browser/webauthn/internal_authenticator_impl.h"
#include <string>
#include <utility>
#include "base/timer/timer.h"
#include "content/browser/webauth/authenticator_common.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "url/origin.h"
namespace content {
InternalAuthenticatorImpl::InternalAuthenticatorImpl(
RenderFrameHost* render_frame_host,
url::Origin effective_origin)
: InternalAuthenticatorImpl(render_frame_host,
std::move(effective_origin),
std::make_unique<AuthenticatorCommon>(
render_frame_host,
nullptr /* connector */,
std::make_unique<base::OneShotTimer>())) {}
InternalAuthenticatorImpl::InternalAuthenticatorImpl(
RenderFrameHost* render_frame_host,
url::Origin effective_origin,
std::unique_ptr<AuthenticatorCommon> authenticator_common)
: WebContentsObserver(WebContents::FromRenderFrameHost(render_frame_host)),
render_frame_host_(render_frame_host),
effective_origin_(std::move(effective_origin)),
authenticator_common_(std::move(authenticator_common)),
binding_(this),
weak_factory_(this) {
DCHECK(render_frame_host_);
DCHECK(authenticator_common_);
DCHECK(!effective_origin.opaque());
}
InternalAuthenticatorImpl::~InternalAuthenticatorImpl() {
// This call exists to assert that |render_frame_host_| outlives this object.
// If this is violated, ASAN should notice.
render_frame_host_->GetRoutingID();
}
void InternalAuthenticatorImpl::Bind(
blink::mojom::InternalAuthenticatorRequest request) {
// If |render_frame_host_| is being unloaded then binding requests are
// rejected.
if (!render_frame_host_->IsCurrent()) {
return;
}
DCHECK(!binding_.is_bound());
binding_.Bind(std::move(request));
}
// mojom::InternalAuthenticator
void InternalAuthenticatorImpl::MakeCredential(
blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
MakeCredentialCallback callback) {
authenticator_common_->MakeCredential(effective_origin_, std::move(options),
std::move(callback));
}
// mojom::InternalAuthenticator
void InternalAuthenticatorImpl::GetAssertion(
blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
GetAssertionCallback callback) {
authenticator_common_->GetAssertion(effective_origin_, std::move(options),
std::move(callback));
}
// mojom::InternalAuthenticator
void InternalAuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable(
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) {
authenticator_common_->IsUserVerifyingPlatformAuthenticatorAvailable(
std::move(callback));
}
void InternalAuthenticatorImpl::DidFinishNavigation(
NavigationHandle* navigation_handle) {
// If the RenderFrameHost itself is navigated then this function will cause
// request state to be cleaned up. It's also possible for a navigation in the
// same frame to use a fresh RenderFrameHost. In this case,
// |render_frame_host_->IsCurrent()| will start returning false, causing all
// focus checks to fail if any Mojo requests are made in that state.
if (!navigation_handle->HasCommitted() ||
navigation_handle->IsSameDocument() ||
navigation_handle->GetRenderFrameHost() != render_frame_host_) {
return;
}
binding_.Close();
authenticator_common_->Cleanup();
}
} // namespace content
// Copyright 2019 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.
#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_WEBAUTHN_INTERNAL_AUTHENTICATOR_IMPL_H_
#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WEBAUTHN_INTERNAL_AUTHENTICATOR_IMPL_H_
#include <stdint.h>
#include <memory>
#include <string>
#include "base/macros.h"
#include "content/browser/webauth/authenticator_common.h"
#include "content/public/browser/web_contents_observer.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "third_party/blink/public/mojom/webauthn/internal_authenticator.mojom.h"
namespace url {
class Origin;
}
namespace content {
class RenderFrameHost;
// Implementation of the public InternalAuthenticator interface.
// This class is meant only for trusted and internal components of Chrome to
// use.
class InternalAuthenticatorImpl : public blink::mojom::InternalAuthenticator,
public WebContentsObserver {
public:
InternalAuthenticatorImpl(RenderFrameHost* render_frame_host,
url::Origin effective_origin);
~InternalAuthenticatorImpl() override;
// Creates a binding between this implementation and |request|.
//
// Note that one InternalAuthenticatorImpl instance can be bound to
// exactly one interface connection at a time, and disconnected when the frame
// navigates to a new active document.
void Bind(blink::mojom::InternalAuthenticatorRequest request);
private:
friend class InternalAuthenticatorImplTest;
// By being able to set AuthenticatorCommon, this constructor permits setting
// the connector and timer for testing. Using this constructor will also empty
// out the protocol set, since no device discovery will take place during
// tests.
InternalAuthenticatorImpl(
RenderFrameHost* render_frame_host,
url::Origin effective_origin,
std::unique_ptr<AuthenticatorCommon> authenticator_common);
AuthenticatorCommon* get_authenticator_common_for_testing() {
return authenticator_common_.get();
}
// mojom::InternalAuthenticator
void MakeCredential(
blink::mojom::PublicKeyCredentialCreationOptionsPtr options,
MakeCredentialCallback callback) override;
void GetAssertion(blink::mojom::PublicKeyCredentialRequestOptionsPtr options,
GetAssertionCallback callback) override;
void IsUserVerifyingPlatformAuthenticatorAvailable(
IsUserVerifyingPlatformAuthenticatorAvailableCallback callback) override;
// WebContentsObserver
void DidFinishNavigation(NavigationHandle* navigation_handle) override;
RenderFrameHost* const render_frame_host_;
const url::Origin effective_origin_;
std::unique_ptr<AuthenticatorCommon> authenticator_common_;
// Owns pipes to this Authenticator from |render_frame_host_|.
mojo::Binding<blink::mojom::InternalAuthenticator> binding_;
base::WeakPtrFactory<InternalAuthenticatorImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(InternalAuthenticatorImpl);
};
} // namespace content
#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WEBAUTHN_INTERNAL_AUTHENTICATOR_IMPL_H_
......@@ -23,6 +23,7 @@ jumbo_source_set("browser") {
# internal content ones) should depend on the public one.
visibility = [
":for_content_tests", # See top of //content/BUILD.gn for why.
":for_internal_webauthn",
"//content/app:*",
"//content/public/browser:browser_sources",
"//content/test/fuzzer:appcache_fuzzer",
......@@ -2713,3 +2714,13 @@ group("for_content_tests") {
]
}
}
# Meant for internal components to facilitate WebAuthn.
group("for_internal_webauthn") {
visibility = [ "//components/autofill/content/browser/webauthn" ]
if (!is_component_build) {
public_deps = [
":browser",
]
}
}
include_rules = [
"+device",
"+device",
]
specific_include_rules = {
"authenticator_impl_unittest\.cc": [
"+components/autofill/content/browser/webauthn/internal_authenticator_impl.h"
]
}
......@@ -27,6 +27,7 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/autofill/content/browser/webauthn/internal_authenticator_impl.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "content/browser/webauth/authenticator_common.h"
......@@ -77,6 +78,7 @@ using blink::mojom::AuthenticatorTransport;
using blink::mojom::CableAuthentication;
using blink::mojom::CableAuthenticationPtr;
using blink::mojom::GetAssertionAuthenticatorResponsePtr;
using blink::mojom::InternalAuthenticatorPtr;
using blink::mojom::MakeCredentialAuthenticatorResponsePtr;
using blink::mojom::PublicKeyCredentialCreationOptions;
using blink::mojom::PublicKeyCredentialCreationOptionsPtr;
......@@ -3754,4 +3756,174 @@ TEST_F(ResidentKeyAuthenticatorImplTest, WinCredProtectApiVersion) {
}
#endif // defined(OS_WIN)
class InternalAuthenticatorImplTest
: public content::RenderViewHostTestHarness {
public:
InternalAuthenticatorImplTest() = default;
protected:
void TearDown() override {
// The |RenderFrameHost| must outlive |AuthenticatorImpl|.
internal_authenticator_impl_.reset();
content::RenderViewHostTestHarness::TearDown();
}
void NavigateAndCommit(const GURL& url) {
// The |RenderFrameHost| must outlive |AuthenticatorImpl|.
internal_authenticator_impl_.reset();
content::RenderViewHostTestHarness::NavigateAndCommit(url);
}
InternalAuthenticatorPtr ConnectToAuthenticator(GURL effective_origin_url) {
internal_authenticator_impl_ = std::make_unique<InternalAuthenticatorImpl>(
main_rfh(), url::Origin::Create(effective_origin_url));
InternalAuthenticatorPtr authenticator;
internal_authenticator_impl_->Bind(mojo::MakeRequest(&authenticator));
return authenticator;
}
InternalAuthenticatorPtr ConnectToAuthenticator(
GURL effective_origin_url,
service_manager::Connector* connector,
std::unique_ptr<base::OneShotTimer> timer) {
internal_authenticator_impl_.reset(new InternalAuthenticatorImpl(
main_rfh(), url::Origin::Create(effective_origin_url),
std::make_unique<AuthenticatorCommon>(main_rfh(), connector,
std::move(timer))));
InternalAuthenticatorPtr authenticator;
internal_authenticator_impl_->Bind(mojo::MakeRequest(&authenticator));
return authenticator;
}
InternalAuthenticatorPtr ConstructAuthenticatorWithTimer(
GURL effective_origin_url,
scoped_refptr<base::TestMockTimeTaskRunner> task_runner) {
connector_ = service_manager::Connector::Create(&request_);
fake_hid_manager_ = std::make_unique<device::FakeHidManager>();
connector_->OverrideBinderForTesting(
service_manager::ServiceFilter::ByName(device::mojom::kServiceName),
device::mojom::HidManager::Name_,
base::BindRepeating(&device::FakeHidManager::AddBinding,
base::Unretained(fake_hid_manager_.get())));
// Set up a timer for testing.
auto timer =
std::make_unique<base::OneShotTimer>(task_runner->GetMockTickClock());
timer->SetTaskRunner(task_runner);
return ConnectToAuthenticator(effective_origin_url, connector_.get(),
std::move(timer));
}
protected:
std::unique_ptr<InternalAuthenticatorImpl> internal_authenticator_impl_;
service_manager::mojom::ConnectorRequest request_;
std::unique_ptr<service_manager::Connector> connector_;
std::unique_ptr<device::FakeHidManager> fake_hid_manager_;
};
// Verify behavior for various combinations of origins and RP IDs.
TEST_F(InternalAuthenticatorImplTest, MakeCredentialOriginAndRpIds) {
// These instances should return security errors (for circumstances
// that would normally crash the renderer).
for (auto test_case : kInvalidRelyingPartyTestCases) {
SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
std::string(test_case.origin));
GURL origin = GURL(test_case.origin);
if (url::Origin::Create(origin).opaque()) {
// Opaque origins will cause DCHECK to fail.
continue;
}
NavigateAndCommit(origin);
InternalAuthenticatorPtr authenticator = ConnectToAuthenticator(origin);
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->relying_party->id = test_case.claimed_authority;
TestMakeCredentialCallback callback_receiver;
authenticator->MakeCredential(std::move(options),
callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::INVALID_DOMAIN, callback_receiver.status());
}
// These instances should bypass security errors, by setting the effective
// origin to a valid one.
for (auto test_case : kValidRelyingPartyTestCases) {
SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
std::string(test_case.origin));
NavigateAndCommit(GURL("https://this.isthewrong.origin"));
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
auto authenticator =
ConstructAuthenticatorWithTimer(GURL(test_case.origin), task_runner);
PublicKeyCredentialCreationOptionsPtr options =
GetTestPublicKeyCredentialCreationOptions();
options->relying_party->id = test_case.claimed_authority;
device::test::ScopedVirtualFidoDevice virtual_device;
TestMakeCredentialCallback callback_receiver;
authenticator->MakeCredential(std::move(options),
callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
}
}
// Verify behavior for various combinations of origins and RP IDs.
TEST_F(InternalAuthenticatorImplTest, GetAssertionOriginAndRpIds) {
// These instances should return security errors (for circumstances
// that would normally crash the renderer).
for (const OriginClaimedAuthorityPair& test_case :
kInvalidRelyingPartyTestCases) {
SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
std::string(test_case.origin));
GURL origin = GURL(test_case.origin);
if (url::Origin::Create(origin).opaque()) {
// Opaque origins will cause DCHECK to fail.
continue;
}
NavigateAndCommit(origin);
InternalAuthenticatorPtr authenticator = ConnectToAuthenticator(origin);
PublicKeyCredentialRequestOptionsPtr options =
GetTestPublicKeyCredentialRequestOptions();
options->relying_party_id = test_case.claimed_authority;
TestGetAssertionCallback callback_receiver;
authenticator->GetAssertion(std::move(options),
callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::INVALID_DOMAIN, callback_receiver.status());
}
// These instances should bypass security errors, by setting the effective
// origin to a valid one.
for (const OriginClaimedAuthorityPair& test_case :
kValidRelyingPartyTestCases) {
SCOPED_TRACE(std::string(test_case.claimed_authority) + " " +
std::string(test_case.origin));
NavigateAndCommit(GURL("https://this.isthewrong.origin"));
auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
auto authenticator =
ConstructAuthenticatorWithTimer(GURL(test_case.origin), task_runner);
PublicKeyCredentialRequestOptionsPtr options =
GetTestPublicKeyCredentialRequestOptions();
options->relying_party_id = test_case.claimed_authority;
device::test::ScopedVirtualFidoDevice virtual_device;
ASSERT_TRUE(virtual_device.mutable_state()->InjectRegistration(
options->allow_credentials[0]->id, test_case.claimed_authority));
TestGetAssertionCallback callback_receiver;
authenticator->GetAssertion(std::move(options),
callback_receiver.callback());
callback_receiver.WaitForCallback();
EXPECT_EQ(AuthenticatorStatus::SUCCESS, callback_receiver.status());
}
}
} // namespace content
......@@ -2229,6 +2229,7 @@ test("content_unittests") {
if (!is_linux_without_udev && !is_android) {
sources += [ "../browser/webauth/authenticator_impl_unittest.cc" ]
deps += [
"//components/autofill/content/browser/webauthn",
"//device/base",
"//device/fido",
"//device/fido:mocks",
......
......@@ -204,6 +204,7 @@ mojom("android_mojo_bindings") {
"payments/payment_request.mojom",
"remote_objects/remote_objects.mojom",
"webauthn/authenticator.mojom",
"webauthn/internal_authenticator.mojom",
"webshare/webshare.mojom",
]
if (is_android && notouch_build) {
......
// Copyright 2019 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.
module blink.mojom;
import "third_party/blink/public/mojom/webauthn/authenticator.mojom";
// Interface similar to blink::mojom::Authenticator meant only for internal
// components in Chrome to use in order to direct authenticators to create or
// use a public key credential. Unlike Authenticator, the caller will be
// allowed to set its own effective origin.
interface InternalAuthenticator {
// Gets the credential info for a new public key credential created by an
// authenticator for the given |PublicKeyCredentialCreationOptions|
// [MakeCredentialAuthenticatorResponse] will be set if and only if status == SUCCESS.
MakeCredential(PublicKeyCredentialCreationOptions options)
=> (AuthenticatorStatus status, MakeCredentialAuthenticatorResponse? credential);
// Uses an existing credential to produce an assertion for the given
// |PublicKeyCredentialRequestOptions|.
// |GetAssertionResponse| will be set if and only if status == SUCCESS.
GetAssertion(PublicKeyCredentialRequestOptions options)
=> (AuthenticatorStatus status, GetAssertionAuthenticatorResponse? credential);
// Returns true if the user platform provides an authenticator. Relying
// Parties use this method to determine whether they can create a new
// credential using a user-verifying platform authenticator.
IsUserVerifyingPlatformAuthenticatorAvailable() => (bool available);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment