blob: 8bab3a648cfa27b146bb064f75064f548ec6d13b [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/webauth/virtual_authenticator.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/uuid.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/public_key_credential_rp_entity.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "device/fido/virtual_ctap2_device.h"
#include "device/fido/virtual_u2f_device.h"
#include "mojo/public/cpp/base/big_buffer.h"
namespace content {
VirtualAuthenticator::Options::Options() = default;
VirtualAuthenticator::Options::~Options() = default;
VirtualAuthenticator::VirtualAuthenticator(const Options& options)
: protocol_(options.protocol),
ctap2_version_(options.ctap2_version),
attachment_(options.attachment),
has_resident_key_(options.has_resident_key),
has_user_verification_(options.has_user_verification),
has_large_blob_(options.has_large_blob),
has_cred_blob_(options.has_cred_blob),
has_min_pin_length_(options.has_min_pin_length),
has_prf_(options.has_prf),
unique_id_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
state_(base::MakeRefCounted<device::VirtualFidoDevice::State>()) {
state_->transport = options.transport;
// If the authenticator has user verification, simulate having set it up
// already.
state_->fingerprints_enrolled = has_user_verification_;
state_->default_backup_eligibility = options.default_backup_eligibility;
state_->default_backup_state = options.default_backup_state;
observation_.Observe(state_.get());
SetUserPresence(true);
}
VirtualAuthenticator::~VirtualAuthenticator() {
for (Observer& observer : observers_) {
observer.OnAuthenticatorWillBeDestroyed(this);
}
}
bool VirtualAuthenticator::AddRegistration(
std::vector<uint8_t> key_handle,
const std::string& rp_id,
base::span<const uint8_t> private_key,
int32_t counter) {
std::optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
fido_private_key =
device::VirtualFidoDevice::PrivateKey::FromPKCS8(private_key);
if (!fido_private_key) {
return false;
}
return state_->registrations
.emplace(
std::move(key_handle),
device::VirtualFidoDevice::RegistrationData(
std::move(*fido_private_key),
device::fido_parsing_utils::CreateSHA256Hash(rp_id), counter))
.second;
}
bool VirtualAuthenticator::AddResidentRegistration(
std::vector<uint8_t> key_handle,
std::string rp_id,
base::span<const uint8_t> private_key,
int32_t counter,
std::vector<uint8_t> user_handle,
std::optional<std::string> user_name,
std::optional<std::string> user_display_name) {
std::optional<std::unique_ptr<device::VirtualFidoDevice::PrivateKey>>
fido_private_key =
device::VirtualFidoDevice::PrivateKey::FromPKCS8(private_key);
if (!fido_private_key) {
return false;
}
return state_->InjectResidentKey(
std::move(key_handle),
device::PublicKeyCredentialRpEntity(std::move(rp_id)),
device::PublicKeyCredentialUserEntity(std::move(user_handle),
std::move(user_name),
std::move(user_display_name)),
counter, std::move(*fido_private_key));
}
void VirtualAuthenticator::ClearRegistrations() {
device::VirtualFidoDevice::State::RegistrationsMap erased;
state_->registrations.swap(erased);
for (const auto& registration : erased) {
state_->NotifyCredentialDeleted(registration.first);
}
}
bool VirtualAuthenticator::RemoveRegistration(
const std::vector<uint8_t>& key_handle) {
bool removed = state_->registrations.erase(key_handle) != 0;
if (removed) {
state_->NotifyCredentialDeleted(key_handle);
}
return removed;
}
void VirtualAuthenticator::UpdateUserDetails(std::string_view relying_party_id,
base::span<const uint8_t> user_id,
std::string_view name,
std::string_view display_name) {
for (auto& registration : state_->registrations) {
if (registration.second.user && registration.second.rp &&
registration.second.rp->id == relying_party_id &&
registration.second.user->id == user_id) {
registration.second.user->name = name;
registration.second.user->display_name = display_name;
state_->NotifyCredentialUpdated(
std::make_pair(registration.first, &registration.second));
}
}
}
void VirtualAuthenticator::SetUserPresence(bool is_user_present) {
is_user_present_ = is_user_present;
state_->simulate_press_callback = base::BindRepeating(
[](bool is_user_present, device::VirtualFidoDevice* device) {
return is_user_present;
},
is_user_present);
}
std::unique_ptr<device::VirtualFidoDevice>
VirtualAuthenticator::ConstructDevice() {
switch (protocol_) {
case device::ProtocolVersion::kU2f:
return std::make_unique<device::VirtualU2fDevice>(state_);
case device::ProtocolVersion::kCtap2: {
device::VirtualCtap2Device::Config config;
switch (ctap2_version_) {
case device::Ctap2Version::kCtap2_0:
config.ctap2_versions = {std::begin(device::kCtap2Versions2_0),
std::end(device::kCtap2Versions2_0)};
break;
case device::Ctap2Version::kCtap2_1:
config.ctap2_versions = {std::begin(device::kCtap2Versions2_1),
std::end(device::kCtap2Versions2_1)};
break;
}
config.resident_key_support = has_resident_key_;
config.large_blob_support = has_large_blob_;
config.cred_protect_support = config.cred_blob_support = has_cred_blob_;
config.min_pin_length_extension_support = has_min_pin_length_;
if (has_prf_) {
config.prf_support = true;
// This is required when `prf_support` is set.
config.internal_account_chooser = true;
}
if (
// Writing a large blob requires obtaining a PinUvAuthToken with
// permissions if the authenticator is protected by user verification.
(has_large_blob_ && has_user_verification_) ||
// PRF support always requires PIN support because the exchange is
// encrypted.
has_prf_) {
config.pin_uv_auth_token_support = true;
}
config.internal_uv_support = has_user_verification_;
config.is_platform_authenticator =
attachment_ == device::AuthenticatorAttachment::kPlatform;
config.user_verification_succeeds = is_user_verified_;
config.advertised_algorithms = {
device::CoseAlgorithmIdentifier::kEdDSA,
device::CoseAlgorithmIdentifier::kEs256,
device::CoseAlgorithmIdentifier::kRs256,
};
return std::make_unique<device::VirtualCtap2Device>(state_, config);
}
default:
NOTREACHED();
}
}
void VirtualAuthenticator::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void VirtualAuthenticator::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool VirtualAuthenticator::HasObserversForTest() {
return !observers_.empty();
}
void VirtualAuthenticator::SetBackupEligibility(
const std::vector<uint8_t>& key_handle,
bool backup_eligibility) {
state_->registrations.at(key_handle).backup_eligible = backup_eligibility;
}
void VirtualAuthenticator::SetBackupState(
const std::vector<uint8_t>& key_handle,
bool backup_state) {
state_->registrations.at(key_handle).backup_state = backup_state;
}
void VirtualAuthenticator::GetLargeBlob(const std::vector<uint8_t>& key_handle,
GetLargeBlobCallback callback) {
auto registration = state_->registrations.find(key_handle);
if (registration == state_->registrations.end()) {
std::move(callback).Run(std::nullopt);
return;
}
std::optional<device::LargeBlob> blob =
state_->GetLargeBlob(registration->second);
if (!blob) {
std::move(callback).Run(std::nullopt);
return;
}
data_decoder_.Inflate(
std::move(blob->compressed_data), blob->original_size,
base::BindOnce(&VirtualAuthenticator::OnLargeBlobUncompressed,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void VirtualAuthenticator::SetLargeBlob(const std::vector<uint8_t>& key_handle,
const std::vector<uint8_t>& blob,
SetLargeBlobCallback callback) {
data_decoder_.Deflate(
blob, base::BindOnce(&VirtualAuthenticator::OnLargeBlobCompressed,
weak_factory_.GetWeakPtr(), key_handle, blob.size(),
std::move(callback)));
}
void VirtualAuthenticator::OnCredentialCreated(
const device::VirtualFidoDevice::Credential& credential) {
for (Observer& observer : observers_) {
observer.OnCredentialCreated(this, credential);
}
}
void VirtualAuthenticator::OnCredentialDeleted(
base::span<const uint8_t> credential_id) {
for (Observer& observer : observers_) {
observer.OnCredentialDeleted(this, credential_id);
}
}
void VirtualAuthenticator::OnCredentialUpdated(
const device::VirtualFidoDevice::Credential& credential) {
for (Observer& observer : observers_) {
observer.OnCredentialUpdated(this, credential);
}
}
void VirtualAuthenticator::OnAssertion(
const device::VirtualFidoDevice::Credential& credential) {
for (Observer& observer : observers_) {
observer.OnAssertion(this, credential);
}
}
void VirtualAuthenticator::OnLargeBlobUncompressed(
GetLargeBlobCallback callback,
base::expected<mojo_base::BigBuffer, std::string> result) {
std::optional<mojo_base::BigBuffer> value;
if (result.has_value())
value = std::move(*result);
std::move(callback).Run(device::fido_parsing_utils::MaterializeOrNull(value));
}
void VirtualAuthenticator::OnLargeBlobCompressed(
base::span<const uint8_t> key_handle,
uint64_t original_size,
SetLargeBlobCallback callback,
base::expected<mojo_base::BigBuffer, std::string> result) {
auto registration = state_->registrations.find(key_handle);
if (registration == state_->registrations.end()) {
std::move(callback).Run(false);
return;
}
if (result.has_value()) {
state_->InjectLargeBlob(
&registration->second,
device::LargeBlob(device::fido_parsing_utils::Materialize(*result),
original_size));
}
std::move(callback).Run(result.has_value());
}
} // namespace content