Commit 2b753726 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

device/fido: move request precondition checks into request handlers

This moves CheckUserVerificationCompatible() from GetAssertionTask into
GetAssertionRequestHandler, and
CheckIfAuthenticatorSelectionCriteriaSatisfied() from MakeCredentialTask
into MakeCredentialRequestHandler.

These preconditions are now checked before dispatching a request onto a
FidoAuthenticator. This means, precondition checks now cover all types
of authenticators, not just FidoDeviceAuthenticator subtypes. The
FidoAuthenticator interface gets a new Options() method to support the
preconditions check (and an implementation for Touch ID is added).

Corresponding unit tests are moved from the FidoTask test into the
matching FidoRequestHandler test.

Also, remove AuthenticatorSelectionCriteria from the FidoTask class and
from the FidoAuthenticator::MakeCredential() signature, where it has
become superfluous due to this change.

Bug: 678128

Change-Id: I75259e63a8ef4fda4ecb0d8c9f8ff25f10c077a6
Reviewed-on: https://chromium-review.googlesource.com/1135601
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Reviewed-by: default avatarJun Choi <hongjunchoi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#575505}
parent f935630e
......@@ -16,7 +16,7 @@
namespace device {
class AuthenticatorSelectionCriteria;
class AuthenticatorSupportedOptions;
class CtapGetAssertionRequest;
class CtapMakeCredentialRequest;
......@@ -36,13 +36,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
virtual ~FidoAuthenticator() = default;
virtual void MakeCredential(
AuthenticatorSelectionCriteria authenticator_selection_criteria,
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) = 0;
virtual void GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) = 0;
virtual void Cancel() = 0;
virtual std::string GetId() const = 0;
virtual const AuthenticatorSupportedOptions& Options() const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(FidoAuthenticator);
......
......@@ -34,6 +34,11 @@ void FidoDevice::DiscoverSupportedProtocolAndDeviceInfo(
}
}
bool FidoDevice::SupportedProtocolIsInitialized() {
return (supported_protocol_ == ProtocolVersion::kU2f && !device_info_) ||
(supported_protocol_ == ProtocolVersion::kCtap && device_info_);
}
void FidoDevice::OnDeviceInfoReceived(
base::OnceClosure done,
base::Optional<std::vector<uint8_t>> response) {
......
......@@ -51,7 +51,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice {
// and device_info_ according to the result (unless the
// device::kNewCtap2Device feature is off, in which case U2F is assumed).
void DiscoverSupportedProtocolAndDeviceInfo(base::OnceClosure done);
// Returns whether supported_protocol has been correctly initialized (usually
// by calling DiscoverSupportedProtocolAndDeviceInfo).
bool SupportedProtocolIsInitialized();
// TODO(martinkr): Rename to "SetSupportedProtocolForTesting".
void set_supported_protocol(ProtocolVersion supported_protocol) {
supported_protocol_ = supported_protocol;
......
......@@ -6,7 +6,8 @@
#include <utility>
#include "device/fido/authenticator_selection_criteria.h"
#include "base/logging.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_device.h"
......@@ -16,19 +17,18 @@
namespace device {
FidoDeviceAuthenticator::FidoDeviceAuthenticator(FidoDevice* device)
: device_(device) {}
: device_(device) {
DCHECK(device_->SupportedProtocolIsInitialized());
}
FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default;
void FidoDeviceAuthenticator::MakeCredential(
AuthenticatorSelectionCriteria authenticator_selection_criteria,
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
DCHECK(!task_);
// TODO(martinkr): Change FidoTasks to take all request parameters by const
// reference, so we can avoid copying these from the RequestHandler.
task_ = std::make_unique<MakeCredentialTask>(
device_, std::move(request), std::move(authenticator_selection_criteria),
std::move(callback));
task_ = std::make_unique<MakeCredentialTask>(device_, std::move(request),
std::move(callback));
}
void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request,
......@@ -48,6 +48,21 @@ std::string FidoDeviceAuthenticator::GetId() const {
return device_->GetId();
}
const AuthenticatorSupportedOptions& FidoDeviceAuthenticator::Options() const {
static const AuthenticatorSupportedOptions default_options;
switch (device_->supported_protocol()) {
case ProtocolVersion::kU2f:
return default_options;
case ProtocolVersion::kCtap:
DCHECK(device_->device_info()) << "uninitialized device";
return device_->device_info()->options();
case ProtocolVersion::kUnknown:
NOTREACHED() << "uninitialized device";
}
NOTREACHED();
return default_options;
}
void FidoDeviceAuthenticator::SetTaskForTesting(
std::unique_ptr<FidoTask> task) {
task_ = std::move(task);
......
......@@ -15,7 +15,6 @@
namespace device {
class AuthenticatorSelectionCriteria;
class CtapGetAssertionRequest;
class CtapMakeCredentialRequest;
class FidoDevice;
......@@ -32,13 +31,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
// FidoAuthenticator:
void MakeCredential(
AuthenticatorSelectionCriteria authenticator_selection_criteria,
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) override;
void GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) override;
void Cancel() override;
std::string GetId() const override;
const AuthenticatorSupportedOptions& Options() const override;
protected:
void OnCtapMakeCredentialResponseReceived(
......
......@@ -15,10 +15,7 @@ namespace device {
FidoTask::FidoTask(FidoDevice* device) : device_(device), weak_factory_(this) {
DCHECK(device_);
DCHECK((device_->supported_protocol() == ProtocolVersion::kCtap &&
device_->device_info()) ||
device_->supported_protocol() == ProtocolVersion::kU2f)
<< "FidoDevice protocol version initialized incorrectly";
DCHECK(device_->SupportedProtocolIsInitialized());
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&FidoTask::StartTask, weak_factory_.GetWeakPtr()));
......
......@@ -38,19 +38,24 @@ class FidoGetAssertionHandlerTest : public ::testing::Test {
}
std::unique_ptr<GetAssertionRequestHandler> CreateGetAssertionHandler() {
ForgeNextHidDiscovery();
CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request_param.SetAllowList(
CtapGetAssertionRequest request(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request.SetAllowList(
{{CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)}});
return CreateGetAssertionHandlerWithRequest(std::move(request));
}
std::unique_ptr<GetAssertionRequestHandler>
CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest request) {
ForgeNextHidDiscovery();
return std::make_unique<GetAssertionRequestHandler>(
nullptr /* connector */,
base::flat_set<FidoTransportProtocol>(
{FidoTransportProtocol::kUsbHumanInterfaceDevice}),
std::move(request_param), get_assertion_cb_.callback());
std::move(request), get_assertion_cb_.callback());
}
void InitFeatureListAndDisableCtapFlag() {
......@@ -138,4 +143,47 @@ TEST_F(FidoGetAssertionHandlerTest, TestU2fSignWithoutCtapFlag) {
EXPECT_TRUE(request_handler->is_complete());
}
TEST_F(FidoGetAssertionHandlerTest, TestIncompatibleUserVerificationSetting) {
auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request.SetUserVerification(UserVerificationRequirement::kRequired);
auto request_handler =
CreateGetAssertionHandlerWithRequest(std::move(request));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponseWithoutUvSupport);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(get_assertion_callback().was_called());
}
TEST_F(FidoGetAssertionHandlerTest,
TestU2fSignRequestWithUserVerificationRequired) {
auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request.SetAllowList(
{{CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)}});
request.SetUserVerification(UserVerificationRequirement::kRequired);
auto request_handler =
CreateGetAssertionHandlerWithRequest(std::move(request));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(get_assertion_callback().was_called());
}
} // namespace device
......@@ -51,10 +51,54 @@ GetAssertionRequestHandler::GetAssertionRequestHandler(
GetAssertionRequestHandler::~GetAssertionRequestHandler() = default;
namespace {
// Checks UserVerificationRequirement enum passed from the relying party is
// compatible with the authenticator, and updates the request to the
// "effective" user verification requirement.
// https://w3c.github.io/webauthn/#effective-user-verification-requirement-for-assertion
bool CheckUserVerificationCompatible(FidoAuthenticator* authenticator,
CtapGetAssertionRequest* request) {
const auto uv_availability =
authenticator->Options().user_verification_availability();
switch (request->user_verification()) {
case UserVerificationRequirement::kRequired:
return uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured;
case UserVerificationRequirement::kDiscouraged:
return true;
case UserVerificationRequirement::kPreferred:
if (uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured) {
request->SetUserVerification(UserVerificationRequirement::kRequired);
} else {
request->SetUserVerification(UserVerificationRequirement::kDiscouraged);
}
return true;
}
NOTREACHED();
return false;
}
} // namespace
void GetAssertionRequestHandler::DispatchRequest(
FidoAuthenticator* authenticator) {
// The user verification field of the request may be adjusted to the
// authenticator, so we need to make a copy.
CtapGetAssertionRequest request_copy = request_;
if (!CheckUserVerificationCompatible(authenticator, &request_copy)) {
return;
}
authenticator->GetAssertion(
request_,
std::move(request_copy),
base::BindOnce(&GetAssertionRequestHandler::OnAuthenticatorResponse,
weak_factory_.GetWeakPtr(), authenticator));
}
......
......@@ -52,12 +52,6 @@ void GetAssertionTask::StartTask() {
}
void GetAssertionTask::GetAssertion() {
if (!CheckUserVerificationCompatible()) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
sign_operation_ =
std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest,
AuthenticatorGetAssertionResponse>>(
......@@ -70,13 +64,7 @@ void GetAssertionTask::GetAssertion() {
void GetAssertionTask::U2fSign() {
DCHECK(!device()->device_info());
device()->set_supported_protocol(ProtocolVersion::kU2f);
if (!CheckUserVerificationCompatible()) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
sign_operation_ = std::make_unique<U2fSignOperation>(
device(), request_,
......@@ -132,6 +120,8 @@ void GetAssertionTask::OnCtapGetAssertionResponseReceived(
return;
}
// TODO(martinkr): CheckRpIdHash invocation needs to move into the Request
// handler. See https://crbug.com/863988.
if (!device_response || !device_response->CheckRpIdHash(request_.rp_id()) ||
!CheckRequirementsOnReturnedCredentialId(*device_response) ||
!CheckRequirementsOnReturnedUserEntities(*device_response)) {
......@@ -143,37 +133,4 @@ void GetAssertionTask::OnCtapGetAssertionResponseReceived(
std::move(callback_).Run(response_code, std::move(device_response));
}
bool GetAssertionTask::CheckUserVerificationCompatible() {
if (!device()->device_info()) {
return request_.user_verification() !=
UserVerificationRequirement::kRequired;
}
const auto uv_availability =
device()->device_info()->options().user_verification_availability();
switch (request_.user_verification()) {
case UserVerificationRequirement::kRequired:
return uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured;
case UserVerificationRequirement::kDiscouraged:
return true;
case UserVerificationRequirement::kPreferred:
if (uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured) {
request_.SetUserVerification(UserVerificationRequirement::kRequired);
} else {
request_.SetUserVerification(UserVerificationRequirement::kDiscouraged);
}
return true;
}
NOTREACHED();
return false;
}
} // namespace device
......@@ -67,17 +67,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask {
bool CheckRequirementsOnReturnedCredentialId(
const AuthenticatorGetAssertionResponse& response);
// Checks UserVerificationRequirement enum passed from the relying party
// is compatible with the authenticator using the following logic:
// - If UserVerificationRequirement is set to kRequired, user verification
// option parameter should be set to true.
// - If UserVerificationRequirement is set to kPreferred, user verification
// option is set to true only if the authenticator supports UV.
// - If UserVerificationRequirement is set to kDiscouraged, user verification
// is set to false.
// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement
bool CheckUserVerificationCompatible();
void OnCtapGetAssertionResponseReceived(
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> device_response);
......
......@@ -252,43 +252,6 @@ TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) {
EXPECT_FALSE(get_assertion_callback_receiver().value());
}
TEST_F(FidoGetAssertionTaskTest, TestIncompatibleUserVerificationSetting) {
auto device = MockFidoDevice::MakeCtap(*ReadCTAPGetInfoResponse(
test_data::kTestGetInfoResponseWithoutUvSupport));
auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request.SetUserVerification(UserVerificationRequirement::kRequired);
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
get_assertion_callback_receiver().status());
EXPECT_FALSE(get_assertion_callback_receiver().value());
}
TEST_F(FidoGetAssertionTaskTest,
TestU2fSignRequestWithUserVerificationRequired) {
auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataHash);
request.SetAllowList(
{{CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)}});
request.SetUserVerification(UserVerificationRequirement::kRequired);
auto device = MockFidoDevice::MakeU2f();
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
get_assertion_callback_receiver().status());
EXPECT_FALSE(get_assertion_callback_receiver().value());
}
TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) {
auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataHash);
......
......@@ -37,13 +37,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) TouchIdAuthenticator
// FidoAuthenticator
void MakeCredential(
AuthenticatorSelectionCriteria authenticator_selection_criteria,
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) override;
void GetAssertion(CtapGetAssertionRequest request,
GetAssertionCallback callback) override;
void Cancel() override;
std::string GetId() const override;
const AuthenticatorSupportedOptions& Options() const override;
private:
TouchIdAuthenticator(std::string keychain_access_group,
......
......@@ -7,11 +7,12 @@
#import <LocalAuthentication/LocalAuthentication.h>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
#include "device/base/features.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
......@@ -57,10 +58,8 @@ std::unique_ptr<TouchIdAuthenticator> TouchIdAuthenticator::CreateForTesting(
TouchIdAuthenticator::~TouchIdAuthenticator() = default;
void TouchIdAuthenticator::MakeCredential(
AuthenticatorSelectionCriteria authenticator_selection_criteria,
CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
void TouchIdAuthenticator::MakeCredential(CtapMakeCredentialRequest request,
MakeCredentialCallback callback) {
if (__builtin_available(macOS 10.12.2, *)) {
DCHECK(!operation_);
operation_ = std::make_unique<MakeCredentialOperation>(
......@@ -97,6 +96,27 @@ std::string TouchIdAuthenticator::GetId() const {
return "TouchIdAuthenticator";
}
namespace {
AuthenticatorSupportedOptions TouchIdAuthenticatorOptions() {
AuthenticatorSupportedOptions options;
options.SetIsPlatformDevice(true);
options.SetSupportsResidentKey(true);
options.SetUserVerificationAvailability(
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured);
options.SetUserPresenceRequired(true);
return options;
}
} // namespace
const AuthenticatorSupportedOptions& TouchIdAuthenticator::Options() const {
static const AuthenticatorSupportedOptions options =
TouchIdAuthenticatorOptions();
return options;
}
TouchIdAuthenticator::TouchIdAuthenticator(std::string keychain_access_group,
std::string metadata_secret)
: keychain_access_group_(std::move(keychain_access_group)),
......
......@@ -14,7 +14,6 @@
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "device/base/features.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
#include "device/fido/mac/authenticator.h"
......@@ -141,8 +140,7 @@ class BrowsingDataDeletionTest : public testing::Test {
TestCallbackReceiver<CtapDeviceResponseCode,
base::Optional<AuthenticatorMakeCredentialResponse>>
callback_receiver;
authenticator->MakeCredential(AuthenticatorSelectionCriteria(),
MakeRequest(), callback_receiver.callback());
authenticator->MakeCredential(MakeRequest(), callback_receiver.callback());
callback_receiver.WaitForCallback();
auto result = callback_receiver.TakeResult();
return std::get<0>(result) == CtapDeviceResponseCode::kSuccess;
......
......@@ -42,6 +42,13 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
}
std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler() {
return CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria());
}
std::unique_ptr<MakeCredentialRequestHandler>
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria authenticator_selection_criteria) {
ForgeNextHidDiscovery();
PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId);
PublicKeyCredentialUserEntity user(
......@@ -57,8 +64,8 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
nullptr,
base::flat_set<FidoTransportProtocol>(
{FidoTransportProtocol::kUsbHumanInterfaceDevice}),
std::move(request_parameter), AuthenticatorSelectionCriteria(),
cb_.callback());
std::move(request_parameter),
std::move(authenticator_selection_criteria), cb_.callback());
}
void InitFeatureListAndDisableCtapFlag() {
......@@ -134,4 +141,181 @@ TEST_F(FidoMakeCredentialHandlerTest, TestU2fRegisterWithoutFlagEnabled) {
EXPECT_TRUE(request_handler->is_complete());
}
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithUserVerificationRequired) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest,
U2fRegisterWithPlatformDeviceRequirement) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::
kPlatform,
false /* require_resident_key */,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
true /* require_resident_key */,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest,
UserVerificationAuthenticatorSelectionCriteria) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponseWithoutUvSupport);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest,
PlatformDeviceAuthenticatorSelectionCriteria) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::
kPlatform,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponseCrossPlatformDevice);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest,
ResidentKeyAuthenticatorSelectionCriteria) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
true /* require_resident_key */,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponseWithoutResidentKeySupport);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest,
SatisfyAllAuthenticatorSelectionCriteria) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::
kPlatform,
true /* require_resident_key */,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestAuthenticatorGetInfoResponse);
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
callback().WaitForCallback();
EXPECT_EQ(FidoReturnCode::kSuccess, callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, IncompatibleUserVerificationSetting) {
auto request_handler =
CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = std::make_unique<MockFidoDevice>();
EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0"));
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponseWithoutUvSupport);
discovery()->AddDevice(std::move(device));
scoped_task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
} // namespace device
......@@ -47,10 +47,58 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default;
namespace {
bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied(
FidoAuthenticator* authenticator,
const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
CtapMakeCredentialRequest* request) {
using AuthenticatorAttachment =
AuthenticatorSelectionCriteria::AuthenticatorAttachment;
using UvAvailability =
AuthenticatorSupportedOptions::UserVerificationAvailability;
const auto& options = authenticator->Options();
if ((authenticator_selection_criteria.authenticator_attachement() ==
AuthenticatorAttachment::kPlatform &&
!options.is_platform_device()) ||
(authenticator_selection_criteria.authenticator_attachement() ==
AuthenticatorAttachment::kCrossPlatform &&
options.is_platform_device())) {
return false;
}
if (authenticator_selection_criteria.require_resident_key() &&
!options.supports_resident_key()) {
return false;
}
const auto& user_verification_requirement =
authenticator_selection_criteria.user_verification_requirement();
if (user_verification_requirement == UserVerificationRequirement::kRequired) {
request->SetUserVerificationRequired(true);
}
return user_verification_requirement !=
UserVerificationRequirement::kRequired ||
options.user_verification_availability() ==
UvAvailability::kSupportedAndConfigured;
}
} // namespace
void MakeCredentialRequestHandler::DispatchRequest(
FidoAuthenticator* authenticator) {
return authenticator->MakeCredential(
authenticator_selection_criteria_, request_parameter_,
// The user verification field of the request may be adjusted to the
// authenticator, so we need to make a copy.
CtapMakeCredentialRequest request_copy = request_parameter_;
if (!CheckIfAuthenticatorSelectionCriteriaAreSatisfied(
authenticator, authenticator_selection_criteria_, &request_copy)) {
return;
}
authenticator->MakeCredential(
std::move(request_copy),
base::BindOnce(&MakeCredentialRequestHandler::OnAuthenticatorResponse,
weak_factory_.GetWeakPtr(), authenticator));
}
......
......@@ -19,12 +19,9 @@ namespace device {
MakeCredentialTask::MakeCredentialTask(
FidoDevice* device,
CtapMakeCredentialRequest request_parameter,
AuthenticatorSelectionCriteria authenticator_selection_criteria,
MakeCredentialTaskCallback callback)
: FidoTask(device),
request_parameter_(std::move(request_parameter)),
authenticator_selection_criteria_(
std::move(authenticator_selection_criteria)),
callback_(std::move(callback)),
weak_factory_(this) {}
......@@ -40,12 +37,6 @@ void MakeCredentialTask::StartTask() {
}
void MakeCredentialTask::MakeCredential() {
if (!CheckIfAuthenticatorSelectionCriteriaAreSatisfied()) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), request_parameter_,
......@@ -56,10 +47,7 @@ void MakeCredentialTask::MakeCredential() {
}
void MakeCredentialTask::U2fRegister() {
device()->set_supported_protocol(ProtocolVersion::kU2f);
if (!CheckIfAuthenticatorSelectionCriteriaAreSatisfied() ||
!IsConvertibleToU2fRegisterCommand(request_parameter_)) {
if (!IsConvertibleToU2fRegisterCommand(request_parameter_)) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
......@@ -80,6 +68,8 @@ void MakeCredentialTask::OnCtapMakeCredentialResponseReceived(
return;
}
// TODO(martinkr): CheckRpIdHash invocation needs to move into the Request
// handler. See https://crbug.com/863988.
if (!response_data ||
!response_data->CheckRpIdHash(request_parameter_.rp().rp_id())) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
......@@ -90,48 +80,4 @@ void MakeCredentialTask::OnCtapMakeCredentialResponseReceived(
std::move(callback_).Run(return_code, std::move(response_data));
}
bool MakeCredentialTask::CheckIfAuthenticatorSelectionCriteriaAreSatisfied() {
using AuthenticatorAttachment =
AuthenticatorSelectionCriteria::AuthenticatorAttachment;
using UvAvailability =
AuthenticatorSupportedOptions::UserVerificationAvailability;
// U2F authenticators are non-platform devices that do not support resident
// key or user verification.
const auto& device_info = device()->device_info();
if (!device_info) {
return !authenticator_selection_criteria_.require_resident_key() &&
authenticator_selection_criteria_.user_verification_requirement() !=
UserVerificationRequirement::kRequired &&
authenticator_selection_criteria_.authenticator_attachement() !=
AuthenticatorAttachment::kPlatform;
}
const auto& options = device_info->options();
if ((authenticator_selection_criteria_.authenticator_attachement() ==
AuthenticatorAttachment::kPlatform &&
!options.is_platform_device()) ||
(authenticator_selection_criteria_.authenticator_attachement() ==
AuthenticatorAttachment::kCrossPlatform &&
options.is_platform_device())) {
return false;
}
if (authenticator_selection_criteria_.require_resident_key() &&
!options.supports_resident_key()) {
return false;
}
const auto user_verification_requirement =
authenticator_selection_criteria_.user_verification_requirement();
if (user_verification_requirement == UserVerificationRequirement::kRequired) {
request_parameter_.SetUserVerificationRequired(true);
}
return user_verification_requirement !=
UserVerificationRequirement::kRequired ||
options.user_verification_availability() ==
UvAvailability::kSupportedAndConfigured;
}
} // namespace device
......@@ -16,7 +16,6 @@
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "device/fido/authenticator_make_credential_response.h"
#include "device/fido/authenticator_selection_criteria.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/device_operation.h"
#include "device/fido/fido_constants.h"
......@@ -37,7 +36,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask {
MakeCredentialTask(FidoDevice* device,
CtapMakeCredentialRequest request_parameter,
AuthenticatorSelectionCriteria authenticator_criteria,
MakeCredentialTaskCallback callback);
~MakeCredentialTask() override;
......@@ -51,14 +49,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask {
CtapDeviceResponseCode return_code,
base::Optional<AuthenticatorMakeCredentialResponse> response_data);
// Invoked after retrieving response to AuthenticatorGetInfo request. Filters
// out authenticators based on |authenticator_selection_criteria_| constraints
// provided by the relying party. If |device_| does not satisfy the
// constraints, then this request is silently dropped.
bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied();
CtapMakeCredentialRequest request_parameter_;
AuthenticatorSelectionCriteria authenticator_selection_criteria_;
std::unique_ptr<RegisterOperation> register_operation_;
MakeCredentialTaskCallback callback_;
base::WeakPtrFactory<MakeCredentialTask> weak_factory_;
......
......@@ -51,23 +51,7 @@ class FidoMakeCredentialTaskTest : public testing::Test {
test_data::kClientDataHash, std::move(rp), std::move(user),
PublicKeyCredentialParams(
std::vector<PublicKeyCredentialParams::CredentialInfo>(1))),
AuthenticatorSelectionCriteria(), callback_receiver_.callback());
}
std::unique_ptr<MakeCredentialTask>
CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
FidoDevice* device,
AuthenticatorSelectionCriteria criteria) {
PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId);
PublicKeyCredentialUserEntity user(
fido_parsing_utils::Materialize(test_data::kUserId));
return std::make_unique<MakeCredentialTask>(
device,
CtapMakeCredentialRequest(
test_data::kClientDataHash, std::move(rp), std::move(user),
PublicKeyCredentialParams(
std::vector<PublicKeyCredentialParams::CredentialInfo>(1))),
std::move(criteria), callback_receiver_.callback());
callback_receiver_.callback());
}
void RemoveCtapFlag() {
......@@ -160,151 +144,5 @@ TEST_F(FidoMakeCredentialTaskTest, TestDefaultU2fRegisterOperationWithoutFlag) {
make_credential_callback_receiver().status());
}
TEST_F(FidoMakeCredentialTaskTest, U2fRegisterWithUserVerificationRequired) {
auto device = MockFidoDevice::MakeU2f();
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(ProtocolVersion::kU2f, device->supported_protocol());
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
}
TEST_F(FidoMakeCredentialTaskTest, U2fRegisterWithPlatformDeviceRequirement) {
auto device = MockFidoDevice::MakeU2f();
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kPlatform,
false /* require_resident_key */,
UserVerificationRequirement::kPreferred));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(ProtocolVersion::kU2f, device->supported_protocol());
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
}
TEST_F(FidoMakeCredentialTaskTest, U2fRegisterWithResidentKeyRequirement) {
auto device = MockFidoDevice::MakeU2f();
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
true /* require_resident_key */,
UserVerificationRequirement::kPreferred));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(ProtocolVersion::kU2f, device->supported_protocol());
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
}
TEST_F(FidoMakeCredentialTaskTest,
UserVerificationAuthenticatorSelectionCriteria) {
auto device = MockFidoDevice::MakeCtap(*ReadCTAPGetInfoResponse(
test_data::kTestGetInfoResponseWithoutUvSupport));
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
EXPECT_EQ(ProtocolVersion::kCtap, device->supported_protocol());
EXPECT_TRUE(device->device_info());
EXPECT_EQ(AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedButNotConfigured,
device->device_info()->options().user_verification_availability());
}
TEST_F(FidoMakeCredentialTaskTest,
PlatformDeviceAuthenticatorSelectionCriteria) {
auto device = MockFidoDevice::MakeCtap(*ReadCTAPGetInfoResponse(
test_data::kTestGetInfoResponseCrossPlatformDevice));
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kPlatform,
false /* require_resident_key */,
UserVerificationRequirement::kPreferred));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
EXPECT_EQ(ProtocolVersion::kCtap, device->supported_protocol());
EXPECT_TRUE(device->device_info());
EXPECT_FALSE(device->device_info()->options().is_platform_device());
}
TEST_F(FidoMakeCredentialTaskTest, ResidentKeyAuthenticatorSelectionCriteria) {
auto device = MockFidoDevice::MakeCtap(*ReadCTAPGetInfoResponse(
test_data::kTestGetInfoResponseWithoutResidentKeySupport));
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
true /* require_resident_key */,
UserVerificationRequirement::kPreferred));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
EXPECT_EQ(ProtocolVersion::kCtap, device->supported_protocol());
EXPECT_TRUE(device->device_info());
EXPECT_FALSE(device->device_info()->options().supports_resident_key());
}
TEST_F(FidoMakeCredentialTaskTest, SatisfyAllAuthenticatorSelectionCriteria) {
auto device = MockFidoDevice::MakeCtap();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kPlatform,
true /* require_resident_key */,
UserVerificationRequirement::kRequired));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
make_credential_callback_receiver().status());
EXPECT_TRUE(make_credential_callback_receiver().value());
EXPECT_EQ(ProtocolVersion::kCtap, device->supported_protocol());
EXPECT_TRUE(device->device_info());
const auto& device_options = device->device_info()->options();
EXPECT_TRUE(device_options.is_platform_device());
EXPECT_TRUE(device_options.supports_resident_key());
EXPECT_EQ(AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured,
device_options.user_verification_availability());
}
TEST_F(FidoMakeCredentialTaskTest, IncompatibleUserVerificationSetting) {
auto device = MockFidoDevice::MakeCtap(*ReadCTAPGetInfoResponse(
test_data::kTestGetInfoResponseWithoutUvSupport));
const auto task = CreateMakeCredentialTaskWithAuthenticatorSelectionCriteria(
device.get(),
AuthenticatorSelectionCriteria(
AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny,
false /* require_resident_key */,
UserVerificationRequirement::kRequired));
make_credential_callback_receiver().WaitForCallback();
EXPECT_EQ(ProtocolVersion::kCtap, device->supported_protocol());
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther,
make_credential_callback_receiver().status());
EXPECT_FALSE(make_credential_callback_receiver().value());
}
} // namespace
} // namespace device
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