blob: 6ec80785c15b990f525e02162cac0330979ca492 [file] [log] [blame]
// Copyright 2020 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/fido_device_authenticator.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_types.h"
#include "device/fido/large_blob.h"
#include "device/fido/pin.h"
#include "device/fido/test_callback_receiver.h"
#include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_fido_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace device {
namespace {
using WriteCallback =
device::test::ValueCallbackReceiver<CtapDeviceResponseCode>;
using ReadCallback = device::test::StatusAndValueCallbackReceiver<
CtapDeviceResponseCode,
absl::optional<std::vector<std::pair<LargeBlobKey, std::vector<uint8_t>>>>>;
using PinCallback = device::test::StatusAndValueCallbackReceiver<
CtapDeviceResponseCode,
absl::optional<pin::TokenResponse>>;
using TouchCallback = device::test::TestCallbackReceiver<>;
constexpr LargeBlobKey kDummyKey1 = {{0x01}};
constexpr LargeBlobKey kDummyKey2 = {{0x02}};
constexpr std::array<uint8_t, 4> kSmallBlob1 = {'r', 'o', 's', 'a'};
constexpr std::array<uint8_t, 4> kSmallBlob2 = {'l', 'u', 'm', 'a'};
constexpr std::array<uint8_t, 4> kSmallBlob3 = {'s', 't', 'a', 'r'};
constexpr size_t kLargeBlobStorageSize = 4096;
constexpr char kPin[] = "1234";
class FidoDeviceAuthenticatorTest : public testing::Test {
protected:
void SetUp() override {
VirtualCtap2Device::Config config;
config.pin_support = true;
config.large_blob_support = true;
config.resident_key_support = true;
config.available_large_blob_storage = kLargeBlobStorageSize;
config.pin_uv_auth_token_support = true;
config.ctap2_versions = {Ctap2Version::kCtap2_1};
SetUpAuthenticator(std::move(config));
}
protected:
void SetUpAuthenticator(VirtualCtap2Device::Config config) {
authenticator_state_ = base::MakeRefCounted<VirtualFidoDevice::State>();
auto virtual_device =
std::make_unique<VirtualCtap2Device>(authenticator_state_, config);
virtual_device_ = virtual_device.get();
authenticator_ =
std::make_unique<FidoDeviceAuthenticator>(std::move(virtual_device));
device::test::TestCallbackReceiver<> callback;
authenticator_->InitializeAuthenticator(callback.callback());
callback.WaitForCallback();
}
scoped_refptr<VirtualFidoDevice::State> authenticator_state_;
std::unique_ptr<FidoDeviceAuthenticator> authenticator_;
VirtualCtap2Device* virtual_device_;
private:
base::test::SingleThreadTaskEnvironment task_environment_;
};
TEST_F(FidoDeviceAuthenticatorTest, TestReadEmptyLargeBlob) {
ReadCallback callback;
authenticator_->ReadLargeBlob({kDummyKey1}, absl::nullopt,
callback.callback());
callback.WaitForCallback();
EXPECT_EQ(callback.status(), CtapDeviceResponseCode::kSuccess);
EXPECT_EQ(callback.value()->size(), 0u);
}
TEST_F(FidoDeviceAuthenticatorTest, TestReadInvalidLargeBlob) {
authenticator_state_->large_blob[0] += 1;
ReadCallback callback;
authenticator_->ReadLargeBlob({kDummyKey1}, absl::nullopt,
callback.callback());
callback.WaitForCallback();
EXPECT_EQ(callback.status(),
CtapDeviceResponseCode::kCtap2ErrIntegrityFailure);
EXPECT_FALSE(callback.value());
}
// Test reading and writing a blob that fits in a single fragment.
TEST_F(FidoDeviceAuthenticatorTest, TestWriteSmallBlob) {
std::vector<uint8_t> small_blob =
fido_parsing_utils::Materialize(kSmallBlob1);
WriteCallback write_callback;
authenticator_->WriteLargeBlob(small_blob, {kDummyKey1}, absl::nullopt,
write_callback.callback());
write_callback.WaitForCallback();
ASSERT_EQ(write_callback.value(), CtapDeviceResponseCode::kSuccess);
ReadCallback read_callback;
authenticator_->ReadLargeBlob({kDummyKey1}, absl::nullopt,
read_callback.callback());
read_callback.WaitForCallback();
ASSERT_EQ(read_callback.status(), CtapDeviceResponseCode::kSuccess);
auto large_blob_array = read_callback.value();
ASSERT_TRUE(large_blob_array);
ASSERT_EQ(large_blob_array->size(), 1u);
EXPECT_EQ(large_blob_array->at(0).first, kDummyKey1);
EXPECT_EQ(large_blob_array->at(0).second, small_blob);
}
// Test reading and writing a blob that must fit in multiple fragments.
TEST_F(FidoDeviceAuthenticatorTest, TestWriteLargeBlob) {
std::vector<uint8_t> large_blob;
large_blob.reserve(2048);
for (size_t i = 0; i < large_blob.capacity(); ++i) {
large_blob.emplace_back(i % 0xFF);
}
WriteCallback write_callback;
authenticator_->WriteLargeBlob(large_blob, {kDummyKey1}, absl::nullopt,
write_callback.callback());
write_callback.WaitForCallback();
ASSERT_EQ(write_callback.value(), CtapDeviceResponseCode::kSuccess);
ReadCallback read_callback;
authenticator_->ReadLargeBlob({kDummyKey1}, absl::nullopt,
read_callback.callback());
read_callback.WaitForCallback();
ASSERT_EQ(read_callback.status(), CtapDeviceResponseCode::kSuccess);
auto large_blob_array = read_callback.value();
ASSERT_TRUE(large_blob_array);
ASSERT_EQ(large_blob_array->size(), 1u);
EXPECT_EQ(large_blob_array->at(0).first, kDummyKey1);
EXPECT_EQ(large_blob_array->at(0).second, large_blob);
}
// Test reading and writing a blob using a PinUvAuthToken.
TEST_F(FidoDeviceAuthenticatorTest, TestWriteSmallBlobWithToken) {
virtual_device_->SetPin(kPin);
PinCallback pin_callback;
authenticator_->GetPINToken(kPin, {pin::Permissions::kLargeBlobWrite},
/*rp_id=*/absl::nullopt, pin_callback.callback());
pin_callback.WaitForCallback();
ASSERT_EQ(pin_callback.status(), CtapDeviceResponseCode::kSuccess);
pin::TokenResponse pin_token = *pin_callback.value();
std::vector<uint8_t> small_blob =
fido_parsing_utils::Materialize(kSmallBlob1);
WriteCallback write_callback;
authenticator_->WriteLargeBlob(small_blob, {kDummyKey1}, pin_token,
write_callback.callback());
write_callback.WaitForCallback();
ASSERT_EQ(write_callback.value(), CtapDeviceResponseCode::kSuccess);
ReadCallback read_callback;
authenticator_->ReadLargeBlob({kDummyKey1}, pin_token,
read_callback.callback());
read_callback.WaitForCallback();
ASSERT_EQ(read_callback.status(), CtapDeviceResponseCode::kSuccess);
auto large_blob_array = read_callback.value();
ASSERT_TRUE(large_blob_array);
ASSERT_EQ(large_blob_array->size(), 1u);
EXPECT_EQ(large_blob_array->at(0).first, kDummyKey1);
EXPECT_EQ(large_blob_array->at(0).second, small_blob);
}
// Test updating a large blob in an array with multiple entries corresponding to
// other keys.
TEST_F(FidoDeviceAuthenticatorTest, TestUpdateLargeBlob) {
WriteCallback write_callback1;
authenticator_->WriteLargeBlob(fido_parsing_utils::Materialize(kSmallBlob1),
{kDummyKey1}, absl::nullopt,
write_callback1.callback());
write_callback1.WaitForCallback();
ASSERT_EQ(write_callback1.value(), CtapDeviceResponseCode::kSuccess);
WriteCallback write_callback2;
std::vector<uint8_t> small_blob2 =
fido_parsing_utils::Materialize(kSmallBlob2);
authenticator_->WriteLargeBlob(small_blob2, {kDummyKey2}, absl::nullopt,
write_callback2.callback());
write_callback2.WaitForCallback();
ASSERT_EQ(write_callback2.value(), CtapDeviceResponseCode::kSuccess);
// Update the first entry.
WriteCallback write_callback3;
std::vector<uint8_t> small_blob3 =
fido_parsing_utils::Materialize(kSmallBlob3);
authenticator_->WriteLargeBlob(small_blob3, {kDummyKey1}, absl::nullopt,
write_callback3.callback());
write_callback3.WaitForCallback();
ASSERT_EQ(write_callback3.value(), CtapDeviceResponseCode::kSuccess);
ReadCallback read_callback;
authenticator_->ReadLargeBlob({kDummyKey1, kDummyKey2}, absl::nullopt,
read_callback.callback());
read_callback.WaitForCallback();
ASSERT_EQ(read_callback.status(), CtapDeviceResponseCode::kSuccess);
auto large_blob_array = read_callback.value();
ASSERT_TRUE(large_blob_array);
EXPECT_THAT(*large_blob_array, testing::UnorderedElementsAre(
std::make_pair(kDummyKey1, small_blob3),
std::make_pair(kDummyKey2, small_blob2)));
}
// Test attempting to write a large blob with a serialized size larger than the
// maximum. Chrome should not attempt writing the blob in this case.
TEST_F(FidoDeviceAuthenticatorTest, TestWriteLargeBlobTooLarge) {
// First write a valid blob to make sure it isn't overwritten.
WriteCallback write_callback1;
std::vector<uint8_t> small_blob =
fido_parsing_utils::Materialize(kSmallBlob1);
authenticator_->WriteLargeBlob(small_blob, {kDummyKey1}, absl::nullopt,
write_callback1.callback());
write_callback1.WaitForCallback();
ASSERT_EQ(write_callback1.value(), CtapDeviceResponseCode::kSuccess);
// Then, attempt writing a blob that is too large.
std::vector<uint8_t> large_blob;
large_blob.reserve(kLargeBlobStorageSize + 1);
for (size_t i = 0; i < large_blob.capacity(); ++i) {
large_blob.emplace_back(i % 0xFF);
}
WriteCallback write_callback2;
authenticator_->WriteLargeBlob(large_blob, {kDummyKey1}, absl::nullopt,
write_callback2.callback());
write_callback2.WaitForCallback();
ASSERT_EQ(write_callback2.value(),
CtapDeviceResponseCode::kCtap2ErrRequestTooLarge);
// Make sure the first blob was not overwritten.
ReadCallback read_callback;
authenticator_->ReadLargeBlob({kDummyKey1}, absl::nullopt,
read_callback.callback());
read_callback.WaitForCallback();
ASSERT_EQ(read_callback.status(), CtapDeviceResponseCode::kSuccess);
auto large_blob_array = read_callback.value();
ASSERT_TRUE(large_blob_array);
ASSERT_EQ(large_blob_array->size(), 1u);
EXPECT_EQ(kDummyKey1, large_blob_array->at(0).first);
EXPECT_EQ(small_blob, large_blob_array->at(0).second);
}
// Tests getting a touch.
TEST_F(FidoDeviceAuthenticatorTest, TestGetTouch) {
for (Ctap2Version version :
{Ctap2Version::kCtap2_0, Ctap2Version::kCtap2_1}) {
SCOPED_TRACE(std::string("CTAP ") +
(version == Ctap2Version::kCtap2_0 ? "2.0" : "2.1"));
VirtualCtap2Device::Config config;
config.ctap2_versions = {version};
SetUpAuthenticator(std::move(config));
TouchCallback callback;
bool touch_pressed = false;
authenticator_state_->simulate_press_callback =
base::BindLambdaForTesting([&](VirtualFidoDevice* device) {
touch_pressed = true;
return true;
});
authenticator_->GetTouch(callback.callback());
callback.WaitForCallback();
EXPECT_TRUE(touch_pressed);
}
}
} // namespace
} // namespace device