blob: 200dfec6055f9f6ea4fb727571032de9cb9d9a63 [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 "device/fido/get_assertion_task.h"
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/test/scoped_task_environment.h"
#include "crypto/ec_private_key.h"
#include "device/base/features.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/device_response_converter.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_test_data.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::_;
namespace device {
namespace {
using TestGetAssertionTaskCallbackReceiver =
::device::test::StatusAndValueCallbackReceiver<
CtapDeviceResponseCode,
base::Optional<AuthenticatorGetAssertionResponse>>;
class FidoGetAssertionTaskTest : public testing::Test {
public:
FidoGetAssertionTaskTest() {}
TestGetAssertionTaskCallbackReceiver& get_assertion_callback_receiver() {
return cb_;
}
private:
base::test::TaskEnvironment task_environment_;
TestGetAssertionTaskCallbackReceiver cb_;
};
TEST_F(FidoGetAssertionTaskTest, TestGetAssertionSuccess) {
auto device = MockFidoDevice::MakeCtap();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetAssertion,
test_data::kTestGetAssertionResponse);
CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
test_data::kClientDataJson);
request_param.allow_list.emplace_back(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
fido_parsing_utils::Materialize(
test_data::kTestGetAssertionCredentialId)));
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request_param),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
EXPECT_TRUE(get_assertion_callback_receiver().value());
}
TEST_F(FidoGetAssertionTaskTest, TestU2fSignSuccess) {
auto device = MockFidoDevice::MakeU2f();
device->ExpectWinkedAtLeastOnce();
device->ExpectRequestAndRespondWith(
test_data::kU2fSignCommandApdu,
test_data::kApduEncodedNoErrorSignResponse);
CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
test_data::kClientDataJson);
request_param.allow_list.emplace_back(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request_param),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
EXPECT_TRUE(get_assertion_callback_receiver().value());
}
TEST_F(FidoGetAssertionTaskTest, TestSignSuccessWithFake) {
auto private_key = crypto::ECPrivateKey::Create();
std::string public_key;
private_key->ExportRawPublicKey(&public_key);
auto hash = fido_parsing_utils::CreateSHA256Hash(public_key);
std::vector<uint8_t> key_handle(hash.begin(), hash.end());
CtapGetAssertionRequest request_param(test_data::kRelyingPartyId,
test_data::kClientDataJson);
request_param.allow_list.emplace_back(
PublicKeyCredentialDescriptor(CredentialType::kPublicKey, key_handle));
;
auto device = std::make_unique<VirtualCtap2Device>();
device->mutable_state()->registrations.emplace(
key_handle,
VirtualFidoDevice::RegistrationData(
std::move(private_key),
fido_parsing_utils::CreateSHA256Hash(test_data::kRelyingPartyId),
42 /* counter */));
test::TestCallbackReceiver<> done;
device->DiscoverSupportedProtocolAndDeviceInfo(done.callback());
done.WaitForCallback();
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request_param),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
// Just a sanity check, we don't verify the actual signature.
ASSERT_GE(32u + 1u + 4u + 8u, // Minimal ECDSA signature is 8 bytes
get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()
.size());
EXPECT_EQ(0x01,
get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()[32]); // UP flag
// Counter is incremented for every sign request.
EXPECT_EQ(43, get_assertion_callback_receiver()
.value()
->auth_data()
.SerializeToByteArray()[36]); // counter
}
TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) {
auto device = MockFidoDevice::MakeCtap();
device->ExpectCtap2CommandAndRespondWith(
CtapRequestCommand::kAuthenticatorGetAssertion, base::nullopt);
auto task = std::make_unique<GetAssertionTask>(
device.get(),
CtapGetAssertionRequest(test_data::kRelyingPartyId,
test_data::kClientDataJson),
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::kClientDataJson);
auto device = MockFidoDevice::MakeU2f();
device->ExpectWinkedAtLeastOnce();
device->ExpectRequestAndRespondWith(
test_data::kU2fFakeRegisterCommand,
test_data::kApduEncodedNoErrorSignResponse);
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
get_assertion_callback_receiver().status());
EXPECT_FALSE(get_assertion_callback_receiver().value());
}
// Checks that when device supports both CTAP2 and U2F protocol and when
// appId extension parameter is present, the browser first checks presence
// of valid credentials via silent authentication.
TEST_F(FidoGetAssertionTaskTest, TestSilentSignInWhenAppIdExtensionPresent) {
CtapGetAssertionRequest request(test_data::kRelyingPartyId,
test_data::kClientDataJson);
std::vector<PublicKeyCredentialDescriptor> allowed_list;
allowed_list.push_back(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
request.app_id = test_data::kAppId;
request.alternative_application_parameter =
fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
test_data::kAlternativeApplicationParameter));
request.allow_list = std::move(allowed_list);
auto device = MockFidoDevice::MakeCtap();
device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest,
test_data::kTestGetAssertionResponse);
device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest,
test_data::kTestGetAssertionResponse);
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
}
TEST_F(FidoGetAssertionTaskTest, TestU2fFallbackForAppIdExtension) {
CtapGetAssertionRequest request(test_data::kRelyingPartyId,
test_data::kClientDataJson);
std::vector<PublicKeyCredentialDescriptor> allowed_list;
allowed_list.push_back(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
request.app_id = test_data::kAppId;
request.alternative_application_parameter =
fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
test_data::kAlternativeApplicationParameter));
request.allow_list = std::move(allowed_list);
::testing::InSequence s;
auto device = MockFidoDevice::MakeCtap();
std::array<uint8_t, 1> error{{base::strict_cast<uint8_t>(
CtapDeviceResponseCode::kCtap2ErrNoCredentials)}};
// First, as the device supports both CTAP2 and U2F, the browser will attempt
// a CTAP2 GetAssertion.
device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest,
error);
// After falling back to U2F the request will use the alternative app_param,
// which will be rejected.
device->ExpectWinkedAtLeastOnce();
device->ExpectRequestAndRespondWith(
test_data::kU2fSignCommandApduWithAlternativeApplicationParameter,
test_data::kU2fWrongDataApduResponse);
// After the rejection, the U2F sign request with the primary application
// parameter should be tried.
device->ExpectWinkedAtLeastOnce();
device->ExpectRequestAndRespondWith(
test_data::kU2fSignCommandApdu,
test_data::kApduEncodedNoErrorSignResponse);
auto task = std::make_unique<GetAssertionTask>(
device.get(), std::move(request),
get_assertion_callback_receiver().callback());
get_assertion_callback_receiver().WaitForCallback();
EXPECT_EQ(CtapDeviceResponseCode::kSuccess,
get_assertion_callback_receiver().status());
}
TEST_F(FidoGetAssertionTaskTest, TestAvoidSilentSignInForCtapOnlyDevice) {
CtapGetAssertionRequest request(test_data::kRelyingPartyId,
test_data::kClientDataJson);
std::vector<PublicKeyCredentialDescriptor> allowed_list;
allowed_list.push_back(PublicKeyCredentialDescriptor(
CredentialType::kPublicKey,
fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)));
request.app_id = test_data::kAppId;
request.alternative_application_parameter =
fido_parsing_utils::Materialize(base::span<const uint8_t, 32>(
test_data::kAlternativeApplicationParameter));
request.allow_list = std::move(allowed_list);
auto device = MockFidoDevice::MakeCtap(ReadCTAPGetInfoResponse(
test_data::kTestCtap2OnlyAuthenticatorGetInfoResponse));
std::array<uint8_t, 1> error{
{base::strict_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrOther)}};
device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest,
error);
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());
}
} // namespace
} // namespace device