blob: 5f7a4bc5bff35a80f1580e8bf0768123fcbc7018 [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 <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/fido/authenticator_get_info_response.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_response_converter.h"
#include "device/fido/fake_fido_discovery.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_device.h"
#include "device/fido/fido_device_authenticator.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/fido_types.h"
#include "device/fido/make_credential_request_handler.h"
#include "device/fido/make_credential_task.h"
#include "device/fido/mock_fido_device.h"
#include "device/fido/test_callback_receiver.h"
#include "device/fido/virtual_ctap2_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithoutArgs;
namespace device {
namespace {
using TestMakeCredentialRequestCallback = test::StatusAndValuesCallbackReceiver<
MakeCredentialStatus,
base::Optional<AuthenticatorMakeCredentialResponse>,
const FidoAuthenticator*>;
} // namespace
class FidoMakeCredentialHandlerTest : public ::testing::Test {
public:
FidoMakeCredentialHandlerTest() {
mock_adapter_ =
base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>();
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_);
}
void ForgeDiscoveries() {
discovery_ = fake_discovery_factory_->ForgeNextHidDiscovery();
nfc_discovery_ = fake_discovery_factory_->ForgeNextNfcDiscovery();
platform_discovery_ = fake_discovery_factory_->ForgeNextPlatformDiscovery();
}
std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria authenticator_selection_criteria = {}) {
ForgeDiscoveries();
PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId);
PublicKeyCredentialUserEntity user(
fido_parsing_utils::Materialize(test_data::kUserId));
PublicKeyCredentialParams credential_params(
std::vector<PublicKeyCredentialParams::CredentialInfo>(1));
auto request_parameter = CtapMakeCredentialRequest(
test_data::kClientDataJson, std::move(rp), std::move(user),
std::move(credential_params));
MakeCredentialRequestHandler::Options options(
authenticator_selection_criteria);
options.allow_skipping_pin_touch = true;
auto handler = std::make_unique<MakeCredentialRequestHandler>(
fake_discovery_factory_.get(), supported_transports_,
std::move(request_parameter), std::move(options), cb_.callback());
if (pending_mock_platform_device_) {
platform_discovery_->AddDevice(std::move(pending_mock_platform_device_));
platform_discovery_->WaitForCallToStartAndSimulateSuccess();
}
return handler;
}
void ExpectAllowedTransportsForRequestAre(
MakeCredentialRequestHandler* request_handler,
base::flat_set<FidoTransportProtocol> transports) {
using Transport = FidoTransportProtocol;
if (base::Contains(transports, Transport::kUsbHumanInterfaceDevice))
discovery()->WaitForCallToStartAndSimulateSuccess();
if (base::Contains(transports, Transport::kNearFieldCommunication))
nfc_discovery()->WaitForCallToStartAndSimulateSuccess();
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
if (!base::Contains(transports, Transport::kUsbHumanInterfaceDevice))
EXPECT_FALSE(discovery()->is_start_requested());
if (!base::Contains(transports, Transport::kNearFieldCommunication))
EXPECT_FALSE(nfc_discovery()->is_start_requested());
EXPECT_THAT(
request_handler->transport_availability_info().available_transports,
::testing::UnorderedElementsAreArray(transports));
}
test::FakeFidoDiscovery* discovery() const { return discovery_; }
test::FakeFidoDiscovery* nfc_discovery() const { return nfc_discovery_; }
TestMakeCredentialRequestCallback& callback() { return cb_; }
void set_mock_platform_device(std::unique_ptr<MockFidoDevice> device) {
pending_mock_platform_device_ = std::move(device);
}
void set_supported_transports(
base::flat_set<FidoTransportProtocol> transports) {
supported_transports_ = std::move(transports);
}
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<test::FakeFidoDiscoveryFactory> fake_discovery_factory_ =
std::make_unique<test::FakeFidoDiscoveryFactory>();
test::FakeFidoDiscovery* discovery_;
test::FakeFidoDiscovery* nfc_discovery_;
test::FakeFidoDiscovery* platform_discovery_;
scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_;
std::unique_ptr<MockFidoDevice> pending_mock_platform_device_;
TestMakeCredentialRequestCallback cb_;
base::flat_set<FidoTransportProtocol> supported_transports_ = {
FidoTransportProtocol::kUsbHumanInterfaceDevice,
FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy,
FidoTransportProtocol::kNearFieldCommunication,
FidoTransportProtocol::kInternal,
};
};
TEST_F(FidoMakeCredentialHandlerTest, TransportAvailabilityInfo) {
auto request_handler = CreateMakeCredentialHandler();
EXPECT_EQ(FidoRequestHandlerBase::RequestType::kMakeCredential,
request_handler->transport_availability_info().request_type);
}
TEST_F(FidoMakeCredentialHandlerTest, TestCtap2MakeCredential) {
auto request_handler = CreateMakeCredentialHandler();
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(device));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
// Test a scenario where the connected authenticator is a U2F device.
TEST_F(FidoMakeCredentialHandlerTest, TestU2fRegister) {
auto request_handler = CreateMakeCredentialHandler();
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
device->ExpectRequestAndRespondWith(
test_data::kU2fRegisterCommandApdu,
test_data::kApduEncodedNoErrorRegisterResponse);
discovery()->AddDevice(std::move(device));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithUserVerificationRequired) {
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
device->ExpectRequestAndRespondWith(
test_data::kU2fBogusRegisterCommandApdu,
test_data::kApduEncodedNoErrorRegisterResponse);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(MakeCredentialStatus::kAuthenticatorMissingUserVerification,
callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) {
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
device->ExpectRequestAndRespondWith(
test_data::kU2fBogusRegisterCommandApdu,
test_data::kApduEncodedNoErrorRegisterResponse);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(MakeCredentialStatus::kAuthenticatorMissingResidentKeys,
callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, UserVerificationRequirementNotMet) {
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
test_data::kTestGetInfoResponseWithoutUvSupport);
device->ExpectRequestAndRespondWith(
MockFidoDevice::EncodeCBORRequest(AsCTAPRequestValuePair(
MakeCredentialTask::GetTouchRequest(device.get()))),
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(MakeCredentialStatus::kAuthenticatorMissingUserVerification,
callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, CrossPlatformAttachment) {
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
// kCloudAssistedBluetoothLowEnergy not yet supported for MakeCredential.
ExpectAllowedTransportsForRequestAre(
request_handler.get(), {FidoTransportProtocol::kNearFieldCommunication,
FidoTransportProtocol::kUsbHumanInterfaceDevice});
}
TEST_F(FidoMakeCredentialHandlerTest, PlatformAttachment) {
// Add a platform device to trigger the transport actually becoming available.
// We don't care about the result of the request.
auto platform_device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
test_data::kTestGetInfoResponsePlatformDevice);
platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal);
platform_device->ExpectCtap2CommandAndDoNotRespond(
CtapRequestCommand::kAuthenticatorMakeCredential);
EXPECT_CALL(*platform_device, Cancel(_));
set_mock_platform_device(std::move(platform_device));
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kRequired));
ExpectAllowedTransportsForRequestAre(request_handler.get(),
{FidoTransportProtocol::kInternal});
}
TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyRequirementNotMet) {
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
test_data::kTestGetInfoResponseWithoutResidentKeySupport);
device->ExpectRequestAndRespondWith(
MockFidoDevice::EncodeCBORRequest(AsCTAPRequestValuePair(
MakeCredentialTask::GetTouchRequest(device.get()))),
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(MakeCredentialStatus::kAuthenticatorMissingResidentKeys,
callback().status());
}
MATCHER(IsResidentKeyRequest, "") {
if (arg.empty() ||
arg[0] != base::strict_cast<uint8_t>(
CtapRequestCommand::kAuthenticatorMakeCredential)) {
return false;
}
base::span<const uint8_t> param_bytes(arg);
param_bytes = param_bytes.subspan(1);
const auto maybe_map = cbor::Reader::Read(param_bytes);
if (!maybe_map || !maybe_map->is_map()) {
return false;
}
const auto& map = maybe_map->GetMap();
const auto options_it = map.find(cbor::Value(7));
if (options_it == map.end() || !options_it->second.is_map()) {
LOG(ERROR) << "options missing or not a map";
return false;
}
const auto& options = options_it->second.GetMap();
const auto rk_it = options.find(cbor::Value("rk"));
if (rk_it == options.end() || !rk_it->second.is_bool() ||
!rk_it->second.GetBool()) {
LOG(ERROR) << "rk option missing, wrong type, or false";
return false;
}
return true;
}
// Matches a CTAP command that is:
// * A valid make credential request,
// * if |is_uv| is true,
// * with an options map present,
// * and options.uv present and true.
// * if |is_uv_| is false,
// * with an options map not present,
// * or options.uv not present or false.
MATCHER_P(IsUvRequest, is_uv, "") {
if (arg.empty() ||
arg[0] != base::strict_cast<uint8_t>(
CtapRequestCommand::kAuthenticatorMakeCredential)) {
*result_listener << "not make credential";
return false;
}
base::span<const uint8_t> param_bytes(arg);
param_bytes = param_bytes.subspan(1);
const auto maybe_map = cbor::Reader::Read(param_bytes);
if (!maybe_map || !maybe_map->is_map()) {
*result_listener << "not a map";
return false;
}
const auto& map = maybe_map->GetMap();
const auto options_it = map.find(cbor::Value(7));
if (options_it == map.end() || !options_it->second.is_map()) {
return is_uv == false;
}
const auto& options = options_it->second.GetMap();
const auto uv_it = options.find(cbor::Value("uv"));
if (uv_it == options.end()) {
return is_uv == false;
}
if (!uv_it->second.is_bool()) {
*result_listener << "'uv' is not a boolean";
return false;
}
return uv_it->second.GetBool() == is_uv;
}
ACTION_P(Reply, reply) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<void(base::Optional<std::vector<uint8_t>>)>
callback,
std::vector<uint8_t> reply) {
std::move(callback).Run(std::move(reply));
},
std::move(arg1), std::vector<uint8_t>(reply.begin(), reply.end())));
}
TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyCancelOtherAuthenticator) {
// Create two internal-UV authenticators and trigger a resident-key
// MakeCredential request which will go to both of them. Ensure that the other
// is canceled when one completes. This is the scenario when cancelation is
// most important: we don't want a stray touch to create a resident credential
// on a second authenticator.
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device1 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
auto device2 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
const FidoDevice::CancelToken token = 10;
EXPECT_CALL(*device1, DeviceTransactPtr(IsResidentKeyRequest(), _))
.WillOnce(Return(token));
// The Cancel call should have the same CancelToken as was returned in the
// previous line.
EXPECT_CALL(*device1, Cancel(token));
EXPECT_CALL(*device2, DeviceTransactPtr(IsResidentKeyRequest(), _))
.WillOnce(DoAll(Reply(base::span<const uint8_t>(
test_data::kTestMakeCredentialResponse)),
Return(token + 1)));
discovery()->AddDevice(std::move(device1));
discovery()->AddDevice(std::move(device2));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyCancel) {
// Create an internal-UV authenticator and trigger a resident-key
// MakeCredential request. Ensure that a cancelation is received if the
// request handler is deleted. When a user cancels, we don't want a stray
// touch creating a resident key.
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired));
auto delete_request_handler = [&request_handler]() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<MakeCredentialRequestHandler>* unique_ptr) {
unique_ptr->reset();
},
&request_handler));
};
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
const FidoDevice::CancelToken token = 10;
EXPECT_CALL(*device, DeviceTransactPtr(IsResidentKeyRequest(), _))
.WillOnce(
DoAll(WithoutArgs(Invoke(delete_request_handler)), Return(token)));
EXPECT_CALL(*device, Cancel(token));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(request_handler);
}
TEST_F(FidoMakeCredentialHandlerTest,
AuthenticatorSelectionCriteriaSatisfiedByCrossPlatformDevice) {
set_supported_transports({FidoTransportProtocol::kUsbHumanInterfaceDevice});
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(device));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
EXPECT_THAT(
request_handler->transport_availability_info().available_transports,
::testing::UnorderedElementsAre(
FidoTransportProtocol::kUsbHumanInterfaceDevice));
}
TEST_F(FidoMakeCredentialHandlerTest,
AuthenticatorSelectionCriteriaSatisfiedByPlatformDevice) {
set_supported_transports({FidoTransportProtocol::kInternal});
auto platform_device = MockFidoDevice::MakeCtap(
ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice));
platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal);
EXPECT_CALL(*platform_device, GetId())
.WillRepeatedly(testing::Return("device0"));
platform_device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestGetInfoResponsePlatformDevice);
platform_device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
set_mock_platform_device(std::move(platform_device));
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kPlatform, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
EXPECT_THAT(
request_handler->transport_availability_info().available_transports,
::testing::UnorderedElementsAre(FidoTransportProtocol::kInternal));
}
// A cross-platform authenticator claiming to be a platform authenticator as per
// its GetInfo response is rejected.
TEST_F(FidoMakeCredentialHandlerTest,
CrossPlatformAuthenticatorPretendingToBePlatform) {
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
test_data::kTestGetInfoResponsePlatformDevice);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
// A platform authenticator claiming to be a cross-platform authenticator as per
// its GetInfo response is rejected.
TEST_F(FidoMakeCredentialHandlerTest,
PlatformAuthenticatorPretendingToBeCrossPlatform) {
auto platform_device = MockFidoDevice::MakeCtap(
ReadCTAPGetInfoResponse(test_data::kTestAuthenticatorGetInfoResponse));
platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal);
EXPECT_CALL(*platform_device, GetId())
.WillRepeatedly(testing::Return("device0"));
platform_device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetInfo,
test_data::kTestAuthenticatorGetInfoResponse);
set_mock_platform_device(std::move(platform_device));
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kPlatform, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kRequired));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
TEST_F(FidoMakeCredentialHandlerTest, SupportedTransportsAreOnlyNfc) {
const base::flat_set<FidoTransportProtocol> kNfc = {
FidoTransportProtocol::kNearFieldCommunication,
};
set_supported_transports(kNfc);
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
ExpectAllowedTransportsForRequestAre(request_handler.get(), kNfc);
}
TEST_F(FidoMakeCredentialHandlerTest, IncorrectRpIdHash) {
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponseWithIncorrectRpIdHash);
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
// Tests that only authenticators with resident key support will successfully
// process MakeCredential request when the relying party requires using resident
// keys in AuthenicatorSelectionCriteria.
TEST_F(FidoMakeCredentialHandlerTest,
SuccessfulMakeCredentialWithResidentKeyOption) {
VirtualCtap2Device::Config config;
config.resident_key_support = true;
config.internal_uv_support = true;
auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
state->fingerprints_enrolled = true;
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
std::move(state), std::move(config)));
task_environment_.FastForwardUntilNoTasksRemain();
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
// Tests that MakeCredential request fails when asking to use resident keys with
// authenticators that do not support resident key.
TEST_F(FidoMakeCredentialHandlerTest,
MakeCredentialFailsForIncompatibleResidentKeyOption) {
auto device = std::make_unique<VirtualCtap2Device>();
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(MakeCredentialStatus::kAuthenticatorMissingResidentKeys,
callback().status());
}
// If a device with transport type kInternal returns a
// CTAP2_ERR_OPERATION_DENIED error, the request should complete with
// MakeCredentialStatus::kUserConsentDenied.
TEST_F(FidoMakeCredentialHandlerTest,
TestRequestWithOperationDeniedErrorPlatform) {
auto platform_device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
test_data::kTestGetInfoResponsePlatformDevice);
platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal);
platform_device->ExpectCtap2CommandAndRespondWithError(
CtapRequestCommand::kAuthenticatorMakeCredential,
CtapDeviceResponseCode::kCtap2ErrOperationDenied);
set_mock_platform_device(std::move(platform_device));
auto request_handler = CreateMakeCredentialHandler(
AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(callback().was_called());
EXPECT_EQ(MakeCredentialStatus::kUserConsentDenied, callback().status());
}
// Like |TestRequestWithOperationDeniedErrorPlatform|, but with a
// cross-platform device.
TEST_F(FidoMakeCredentialHandlerTest,
TestRequestWithOperationDeniedErrorCrossPlatform) {
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
device->ExpectCtap2CommandAndRespondWithError(
CtapRequestCommand::kAuthenticatorMakeCredential,
CtapDeviceResponseCode::kCtap2ErrOperationDenied);
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(callback().was_called());
EXPECT_EQ(MakeCredentialStatus::kUserConsentDenied, callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest,
TestCrossPlatformAuthenticatorsForceUVWhenSupported) {
const auto& test_info_response = test_data::kTestAuthenticatorGetInfoResponse;
ASSERT_EQ(ReadCTAPGetInfoResponse(test_info_response)
->options.user_verification_availability,
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured);
auto device =
MockFidoDevice::MakeCtapWithGetInfoExpectation(test_info_response);
device->SetDeviceTransport(FidoTransportProtocol::kUsbHumanInterfaceDevice);
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse, base::TimeDelta(),
IsUvRequest(true));
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kDiscouraged));
discovery()->AddDevice(std::move(device));
discovery()->WaitForCallToStartAndSimulateSuccess();
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
// If a device returns CTAP2_ERR_PIN_AUTH_INVALID, the request should complete
// with MakeCredentialStatus::kUserConsentDenied.
TEST_F(FidoMakeCredentialHandlerTest, TestRequestWithPinAuthInvalid) {
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
device->ExpectCtap2CommandAndRespondWithError(
CtapRequestCommand::kAuthenticatorMakeCredential,
CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid);
auto request_handler =
CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(callback().was_called());
EXPECT_EQ(MakeCredentialStatus::kUserConsentDenied, callback().status());
}
MATCHER_P(IsCtap2Command, expected_command, "") {
return !arg.empty() && arg[0] == base::strict_cast<uint8_t>(expected_command);
}
TEST_F(FidoMakeCredentialHandlerTest, DeviceFailsImmediately) {
// Test that, when a device immediately returns an unexpected error, the
// request continues and waits for another device.
auto broken_device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
EXPECT_CALL(
*broken_device,
DeviceTransactPtr(
IsCtap2Command(CtapRequestCommand::kAuthenticatorMakeCredential), _))
.WillOnce(::testing::DoAll(
::testing::WithArg<1>(
::testing::Invoke([this](FidoDevice::DeviceCallback& callback) {
std::vector<uint8_t> response = {static_cast<uint8_t>(
CtapDeviceResponseCode::kCtap2ErrInvalidCBOR)};
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), std::move(response)));
auto working_device =
MockFidoDevice::MakeCtapWithGetInfoExpectation();
working_device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorMakeCredential,
test_data::kTestMakeCredentialResponse);
discovery()->AddDevice(std::move(working_device));
})),
::testing::Return(0)));
auto request_handler = CreateMakeCredentialHandler();
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(broken_device));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
}
TEST_F(FidoMakeCredentialHandlerTest, PinUvAuthTokenPreTouchFailure) {
VirtualCtap2Device::Config config;
config.ctap2_versions = {Ctap2Version::kCtap2_1};
config.pin_uv_auth_token_support = true;
config.internal_uv_support = true;
config.override_response_map[CtapRequestCommand::kAuthenticatorClientPin] =
CtapDeviceResponseCode::kCtap2ErrOther;
auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
state->fingerprints_enrolled = true;
auto request_handler = CreateMakeCredentialHandler();
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
std::move(state), std::move(config)));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
}
} // namespace device