0

fido: initial tests for Windows WebAuthn API integration

This adds a fake implementation of WinWebAuthnApi along with a scoped
override of WinWebAuthnApi::GetDefault, as well as a test to verify USB
authenticator instantiation depending on API availability and feature
flag state.

Bug: 898718
Change-Id: Iff3a317b4540986775a40fc872754ba0571ae88a
Reviewed-on: https://chromium-review.googlesource.com/c/1316839
Commit-Queue: Martin Kreichgauer <martinkr@chromium.org>
Reviewed-by: Jun Choi <hongjunchoi@chromium.org>
Reviewed-by: Adam Langley <agl@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606254}
This commit is contained in:
Martin Kreichgauer
2018-11-08 00:43:36 +00:00
committed by Commit Bot
parent aa8e030a25
commit df06312dad
13 changed files with 306 additions and 55 deletions

@ -143,7 +143,6 @@ component("fido") {
defines = [ "IS_DEVICE_FIDO_IMPL" ]
deps = [
":buildflags",
"//components/apdu",
"//components/cbor",
"//crypto",
@ -156,6 +155,7 @@ component("fido") {
]
public_deps = [
":buildflags",
"//base",
"//device/bluetooth",
"//services/device/public/mojom",
@ -307,6 +307,8 @@ source_set("test_support") {
"//device/fido",
"//mojo/public/cpp/bindings",
"//services/device/public/mojom",
"//services/service_manager/public/cpp",
"//services/service_manager/public/mojom",
"//testing/gmock",
"//testing/gtest",
]
@ -329,4 +331,11 @@ source_set("test_support") {
"mac/scoped_touch_id_test_environment.mm",
]
}
if (is_win && use_win_webauthn_api) {
sources += [
"win/fake_webauthn_api.cc",
"win/fake_webauthn_api.h",
]
}
}

@ -180,6 +180,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase
return transport_availability_info_;
}
const AuthenticatorMap& AuthenticatorsForTesting() {
return active_authenticators_;
}
protected:
// Subclasses implement this method to dispatch their request onto the given
// FidoAuthenticator. The FidoAuthenticator is owned by this

@ -12,6 +12,7 @@
#include "base/test/scoped_task_environment.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/buildflags.h"
#include "device/fido/fake_fido_discovery.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_device.h"
@ -174,14 +175,18 @@ class FakeFidoAuthenticator : public FidoDeviceAuthenticator {
class FakeFidoRequestHandler : public FidoRequestHandler<std::vector<uint8_t>> {
public:
FakeFidoRequestHandler(const base::flat_set<FidoTransportProtocol>& protocols,
FakeFidoRequestHandler(service_manager::Connector* connector,
const base::flat_set<FidoTransportProtocol>& protocols,
FakeHandlerCallback callback)
: FidoRequestHandler(nullptr /* connector */,
protocols,
std::move(callback)),
: FidoRequestHandler(connector, protocols, std::move(callback)),
weak_factory_(this) {
Start();
}
FakeFidoRequestHandler(const base::flat_set<FidoTransportProtocol>& protocols,
FakeHandlerCallback callback)
: FakeFidoRequestHandler(nullptr /* connector */,
protocols,
std::move(callback)) {}
~FakeFidoRequestHandler() override = default;
void DispatchRequest(FidoAuthenticator* authenticator) override {

@ -7,25 +7,34 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/stl_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "build/build_config.h"
#include "device/base/features.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/buildflags.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/device_response_converter.h"
#include "device/fido/fake_fido_discovery.h"
#include "device/fido/features.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_test_data.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/get_assertion_request_handler.h"
#include "device/fido/hid/fake_hid_impl_for_testing.h"
#include "device/fido/mock_fido_device.h"
#include "device/fido/test_callback_receiver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN) && BUILDFLAG(USE_WIN_WEBAUTHN_API)
#include "device/fido/win/fake_webauthn_api.h"
#endif
namespace device {
namespace {
@ -711,4 +720,63 @@ TEST_F(FidoGetAssertionHandlerTest,
get_assertion_callback().status());
}
#if defined(OS_WIN) && BUILDFLAG(USE_WIN_WEBAUTHN_API)
class GetAssertionRequestHandlerWinTest : public ::testing::Test {
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
ScopedFakeWinWebAuthnApi scoped_fake_win_webauthn_api_;
};
// Verify that the request handler instantiates a HID device backed
// FidoDeviceAuthenticator or a WinNativeCrossPlatformAuthenticator, depending
// on feature flag and API availability.
TEST_F(GetAssertionRequestHandlerWinTest, TestWinUsbDiscovery) {
enum class DeviceType {
kHid,
kWinNative,
};
const struct TestCase {
bool enable_win_webauthn_api;
bool enable_feature_flag;
DeviceType expect_device_type;
} test_cases[] = {
{false, false, DeviceType::kHid},
{false, true, DeviceType::kHid},
{true, false, DeviceType::kHid},
{true, true, DeviceType::kWinNative},
};
size_t i = 0;
for (const auto& test : test_cases) {
SCOPED_TRACE(i++);
scoped_fake_win_webauthn_api_.set_available(test.enable_win_webauthn_api);
base::test::ScopedFeatureList scoped_feature_list;
// Feature is default off (even with API present).
if (test.enable_feature_flag)
scoped_feature_list.InitAndEnableFeature(kWebAuthUseNativeWinApi);
TestGetAssertionRequestCallback cb;
ScopedFakeHidManager fake_hid_manager_;
auto handler = std::make_unique<GetAssertionRequestHandler>(
fake_hid_manager_.service_manager_connector(),
base::flat_set<FidoTransportProtocol>(
{FidoTransportProtocol::kUsbHumanInterfaceDevice}),
CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataJson),
cb.callback());
scoped_task_environment_.RunUntilIdle();
fake_hid_manager_.AddFidoHidDevice("guid");
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(1u, handler->AuthenticatorsForTesting().size());
// Crudely distinguish authenticator type by FidoAuthenticator::GetId.
EXPECT_EQ(test.expect_device_type == DeviceType::kHid
? "hid:guid"
: "WinNativeCrossPlatformAuthenticator",
handler->AuthenticatorsForTesting().begin()->second->GetId());
}
}
#endif // defined(OS_WIN) && BUILDFLAG(USE_WIN_WEBAUTHN_API)
} // namespace device

@ -7,6 +7,10 @@
#include <utility>
#include "device/fido/fido_parsing_utils.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/mojom/connector.mojom.h"
namespace device {
@ -126,6 +130,20 @@ void FakeHidManager::AddBinding2(device::mojom::HidManagerRequest request) {
bindings_.AddBinding(this, std::move(request));
}
void FakeHidManager::AddFidoHidDevice(std::string guid) {
auto c_info = device::mojom::HidCollectionInfo::New();
c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
auto device = device::mojom::HidDeviceInfo::New();
device->guid = std::move(guid);
device->product_name = "Test Fido Device";
device->serial_number = "123FIDO";
device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
device->collections.push_back(std::move(c_info));
device->max_input_report_size = 64;
device->max_output_report_size = 64;
AddDevice(std::move(device));
}
void FakeHidManager::GetDevicesAndSetClient(
device::mojom::HidManagerClientAssociatedPtrInfo client,
GetDevicesCallback callback) {
@ -184,4 +202,16 @@ void FakeHidManager::RemoveDevice(const std::string device_guid) {
devices_.erase(it);
}
ScopedFakeHidManager::ScopedFakeHidManager() {
service_manager::mojom::ConnectorRequest request;
connector_ = service_manager::Connector::Create(&request);
service_manager::Connector::TestApi test_api(connector_.get());
test_api.OverrideBinderForTesting(
service_manager::Identity(device::mojom::kServiceName),
device::mojom::HidManager::Name_,
base::BindRepeating(&FakeHidManager::AddBinding, base::Unretained(this)));
}
ScopedFakeHidManager::~ScopedFakeHidManager() = default;
} // namespace device

@ -20,6 +20,10 @@
#include "services/device/public/mojom/hid.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace service_manager {
class Connector;
}
namespace device {
class MockHidConnection : public device::mojom::HidConnection {
@ -95,6 +99,9 @@ class FakeHidManager : public device::mojom::HidManager {
FakeHidManager();
~FakeHidManager() override;
// Invoke AddDevice with a device info struct that mirrors a FIDO USB device.
void AddFidoHidDevice(std::string guid);
// device::mojom::HidManager implementation:
void GetDevicesAndSetClient(
device::mojom::HidManagerClientAssociatedPtrInfo client,
@ -118,6 +125,23 @@ class FakeHidManager : public device::mojom::HidManager {
DISALLOW_COPY_AND_ASSIGN(FakeHidManager);
};
// ScopedFakeHidManager automatically binds itself to the device service for the
// duration of its lifetime.
class ScopedFakeHidManager : public FakeHidManager {
public:
ScopedFakeHidManager();
~ScopedFakeHidManager() override;
service_manager::Connector* service_manager_connector() {
return connector_.get();
}
private:
std::unique_ptr<service_manager::Connector> connector_;
DISALLOW_COPY_AND_ASSIGN(ScopedFakeHidManager);
};
} // namespace device
#endif // DEVICE_FIDO_HID_FAKE_HID_IMPL_FOR_TESTING_H_

@ -25,21 +25,6 @@ using ::testing::_;
namespace {
device::mojom::HidDeviceInfoPtr MakeFidoHidDevice(std::string guid) {
auto c_info = device::mojom::HidCollectionInfo::New();
c_info->usage = device::mojom::HidUsageAndPage::New(1, 0xf1d0);
auto u2f_device = device::mojom::HidDeviceInfo::New();
u2f_device->guid = std::move(guid);
u2f_device->product_name = "Test Fido Device";
u2f_device->serial_number = "123FIDO";
u2f_device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
u2f_device->collections.push_back(std::move(c_info));
u2f_device->max_input_report_size = 64;
u2f_device->max_output_report_size = 64;
return u2f_device;
}
device::mojom::HidDeviceInfoPtr MakeOtherDevice(std::string guid) {
auto other_device = device::mojom::HidDeviceInfo::New();
other_device->guid = std::move(guid);
@ -56,35 +41,16 @@ MATCHER_P(IdMatches, id, "") {
} // namespace
class FidoHidDiscoveryTest : public ::testing::Test {
public:
base::test::ScopedTaskEnvironment& scoped_task_environment() {
return scoped_task_environment_;
}
void SetUp() override {
fake_hid_manager_ = std::make_unique<FakeHidManager>();
service_manager::mojom::ConnectorRequest request;
connector_ = service_manager::Connector::Create(&request);
service_manager::Connector::TestApi test_api(connector_.get());
test_api.OverrideBinderForTesting(
service_manager::Identity(device::mojom::kServiceName),
device::mojom::HidManager::Name_,
base::Bind(&FakeHidManager::AddBinding,
base::Unretained(fake_hid_manager_.get())));
}
protected:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<service_manager::Connector> connector_;
std::unique_ptr<FakeHidManager> fake_hid_manager_;
ScopedFakeHidManager fake_hid_manager_;
};
TEST_F(FidoHidDiscoveryTest, TestAddRemoveDevice) {
FidoHidDiscovery discovery(connector_.get());
FidoHidDiscovery discovery(fake_hid_manager_.service_manager_connector());
MockFidoDiscoveryObserver observer;
fake_hid_manager_->AddDevice(MakeFidoHidDevice("known"));
fake_hid_manager_.AddFidoHidDevice("known");
EXPECT_CALL(observer, DiscoveryStarted(&discovery, true));
discovery.set_observer(&observer);
@ -93,28 +59,28 @@ TEST_F(FidoHidDiscoveryTest, TestAddRemoveDevice) {
// Devices initially known to the service before discovery started should be
// reported as KNOWN.
EXPECT_CALL(observer, AuthenticatorAdded(&discovery, IdMatches("known")));
scoped_task_environment().RunUntilIdle();
scoped_task_environment_.RunUntilIdle();
// Devices added during the discovery should be reported as ADDED.
EXPECT_CALL(observer, AuthenticatorAdded(&discovery, IdMatches("added")));
fake_hid_manager_->AddDevice(MakeFidoHidDevice("added"));
scoped_task_environment().RunUntilIdle();
fake_hid_manager_.AddFidoHidDevice("added");
scoped_task_environment_.RunUntilIdle();
// Added non-U2F devices should not be reported at all.
EXPECT_CALL(observer, AuthenticatorAdded(_, _)).Times(0);
fake_hid_manager_->AddDevice(MakeOtherDevice("other"));
fake_hid_manager_.AddDevice(MakeOtherDevice("other"));
// Removed non-U2F devices should not be reported at all.
EXPECT_CALL(observer, AuthenticatorRemoved(_, _)).Times(0);
fake_hid_manager_->RemoveDevice("other");
scoped_task_environment().RunUntilIdle();
fake_hid_manager_.RemoveDevice("other");
scoped_task_environment_.RunUntilIdle();
// Removed U2F devices should be reported as REMOVED.
EXPECT_CALL(observer, AuthenticatorRemoved(&discovery, IdMatches("known")));
EXPECT_CALL(observer, AuthenticatorRemoved(&discovery, IdMatches("added")));
fake_hid_manager_->RemoveDevice("known");
fake_hid_manager_->RemoveDevice("added");
scoped_task_environment().RunUntilIdle();
fake_hid_manager_.RemoveDevice("known");
fake_hid_manager_.RemoveDevice("added");
scoped_task_environment_.RunUntilIdle();
}
} // namespace device

@ -18,6 +18,7 @@ WinNativeCrossPlatformAuthenticatorDiscovery::
~WinNativeCrossPlatformAuthenticatorDiscovery() = default;
void WinNativeCrossPlatformAuthenticatorDiscovery::Start() {
DCHECK(!authenticator_);
if (!observer()) {
return;
}

@ -16,7 +16,8 @@ namespace device {
// Instantiates the authenticator subclass for forwarding requests to external
// authenticators via the Windows WebAuthn API.
class WinNativeCrossPlatformAuthenticatorDiscovery : public FidoDiscoveryBase {
class COMPONENT_EXPORT(DEVICE_FIDO) WinNativeCrossPlatformAuthenticatorDiscovery
: public FidoDiscoveryBase {
public:
WinNativeCrossPlatformAuthenticatorDiscovery(
WinWebAuthnApi* const win_webauthn_api,

@ -0,0 +1,65 @@
// 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 "device/fido/win/fake_webauthn_api.h"
#include "base/logging.h"
namespace device {
FakeWinWebAuthnApi::FakeWinWebAuthnApi() = default;
FakeWinWebAuthnApi::~FakeWinWebAuthnApi() = default;
bool FakeWinWebAuthnApi::IsAvailable() const {
return is_available_;
}
HRESULT FakeWinWebAuthnApi::IsUserVerifyingPlatformAuthenticatorAvailable(
BOOL* result) {
*result = false;
DCHECK(is_available_);
return E_NOTIMPL;
}
HRESULT FakeWinWebAuthnApi::AuthenticatorMakeCredential(
HWND h_wnd,
const WEBAUTHN_RP_ENTITY_INFORMATION* rp_information,
const WEBAUTHN_USER_ENTITY_INFORMATION* user_information,
const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS* pub_key_cred_params,
const WEBAUTHN_CLIENT_DATA* client_data,
const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS* options,
ScopedCredentialAttestation* credential_attestation) {
DCHECK(is_available_);
return E_NOTIMPL;
}
HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion(
HWND h_wnd,
const wchar_t* rp_id_utf16,
const WEBAUTHN_CLIENT_DATA* client_data,
const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS* options,
ScopedAssertion* assertion) {
DCHECK(is_available_);
return E_NOTIMPL;
}
HRESULT FakeWinWebAuthnApi::CancelCurrentOperation(GUID* cancellation_id) {
DCHECK(is_available_);
return E_NOTIMPL;
}
const wchar_t* FakeWinWebAuthnApi::GetErrorName(HRESULT hr) {
DCHECK(is_available_);
return L"not implemented";
};
ScopedFakeWinWebAuthnApi::ScopedFakeWinWebAuthnApi() : FakeWinWebAuthnApi() {
WinWebAuthnApi::SetDefaultForTesting(this);
}
ScopedFakeWinWebAuthnApi::~ScopedFakeWinWebAuthnApi() {
WinWebAuthnApi::ClearDefaultForTesting();
}
} // namespace device

@ -0,0 +1,58 @@
// 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.
#ifndef DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_
#define DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_
#include "base/macros.h"
#include "device/fido/win/webauthn_api.h"
namespace device {
class FakeWinWebAuthnApi : public WinWebAuthnApi {
public:
FakeWinWebAuthnApi();
~FakeWinWebAuthnApi() override;
// Inject the return value for WinWebAuthnApi::IsAvailable().
void set_available(bool available) { is_available_ = available; }
// WinWebAuthnApi:
bool IsAvailable() const override;
// The following methods all return E_NOTIMPL immediately.
HRESULT IsUserVerifyingPlatformAuthenticatorAvailable(
BOOL* available) override;
HRESULT AuthenticatorMakeCredential(
HWND h_wnd,
const WEBAUTHN_RP_ENTITY_INFORMATION* rp_information,
const WEBAUTHN_USER_ENTITY_INFORMATION* user_information,
const WEBAUTHN_COSE_CREDENTIAL_PARAMETERS* pub_key_cred_params,
const WEBAUTHN_CLIENT_DATA* client_data,
const WEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS* options,
ScopedCredentialAttestation* credential_attestation) override;
HRESULT AuthenticatorGetAssertion(
HWND h_wnd,
const wchar_t* rp_id_utf16,
const WEBAUTHN_CLIENT_DATA* client_data,
const WEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS* options,
ScopedAssertion* assertion) override;
HRESULT CancelCurrentOperation(GUID* cancellation_id) override;
// Returns L"not implemented".
const wchar_t* GetErrorName(HRESULT hr) override;
private:
bool is_available_ = true;
};
// ScopedFakeWinWebAuthnApi overrides the value returned
// by WinWebAuthnApi::GetDefault with itself for the duration of its
// lifetime.
class ScopedFakeWinWebAuthnApi : public FakeWinWebAuthnApi {
public:
ScopedFakeWinWebAuthnApi();
~ScopedFakeWinWebAuthnApi() override;
};
} // namespace device
#endif // DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_

@ -116,12 +116,30 @@ class WinWebAuthnApiImpl : public WinWebAuthnApi {
bool is_bound_ = false;
};
static WinWebAuthnApi* kDefaultForTesting = nullptr;
// static
WinWebAuthnApi* WinWebAuthnApi::GetDefault() {
if (kDefaultForTesting) {
return kDefaultForTesting;
}
static base::NoDestructor<WinWebAuthnApiImpl> api;
return api.get();
}
// static
void WinWebAuthnApi::SetDefaultForTesting(WinWebAuthnApi* api) {
DCHECK(!kDefaultForTesting);
kDefaultForTesting = api;
}
// static
void WinWebAuthnApi::ClearDefaultForTesting() {
DCHECK(kDefaultForTesting);
kDefaultForTesting = nullptr;
}
WinWebAuthnApi::~WinWebAuthnApi() = default;
} // namespace device

@ -21,9 +21,6 @@ namespace device {
// Users must check the result of |IsAvailable| on the instance to verify that
// the native library was loaded successfully before invoking any of the other
// methods.
//
// TODO(martinkr): Add a ScopedFakeWinWebAuthnApi that overrides
// |GetDefault| for testing.
class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApi {
public:
// ScopedCredentialAttestation is a scoped deleter for a
@ -84,6 +81,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApi {
// See WebAuthNGetErrorName in <webauthn.h>.
virtual const wchar_t* GetErrorName(HRESULT hr) = 0;
private:
friend class ScopedFakeWinWebAuthnApi;
static void SetDefaultForTesting(WinWebAuthnApi* api);
static void ClearDefaultForTesting();
};
} // namespace device