#include "device/fido/virtual_fido_device.h"
#include <algorithm>
#include <tuple>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
#include "crypto/hash.h"
#include "crypto/keypair.h"
#include "crypto/sign.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/large_blob.h"
#include "device/fido/p256_public_key.h"
#include "device/fido/public_key.h"
#include "net/cert/x509_util.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
namespace device {
namespace {
constexpr uint8_t kAttestationKey[]{
0x30, 0x81, 0x87, 0x02, 0x01, 0x00, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
0x03, 0x01, 0x07, 0x04, 0x6d, 0x30, 0x6b, 0x02, 0x01, 0x01, 0x04, 0x20,
0xf3, 0xfc, 0xcc, 0x0d, 0x00, 0xd8, 0x03, 0x19, 0x54, 0xf9, 0x08, 0x64,
0xd4, 0x3c, 0x24, 0x7f, 0x4b, 0xf5, 0xf0, 0x66, 0x5c, 0x6b, 0x50, 0xcc,
0x17, 0x74, 0x9a, 0x27, 0xd1, 0xcf, 0x76, 0x64, 0xa1, 0x44, 0x03, 0x42,
0x00, 0x04, 0x8d, 0x61, 0x7e, 0x65, 0xc9, 0x50, 0x8e, 0x64, 0xbc, 0xc5,
0x67, 0x3a, 0xc8, 0x2a, 0x67, 0x99, 0xda, 0x3c, 0x14, 0x46, 0x68, 0x2c,
0x25, 0x8c, 0x46, 0x3f, 0xff, 0xdf, 0x58, 0xdf, 0xd2, 0xfa, 0x3e, 0x6c,
0x37, 0x8b, 0x53, 0xd7, 0x95, 0xc4, 0xa4, 0xdf, 0xfb, 0x41, 0x99, 0xed,
0xd7, 0x86, 0x2f, 0x23, 0xab, 0xaf, 0x02, 0x03, 0xb4, 0xb8, 0x91, 0x1b,
0xa0, 0x56, 0x99, 0x94, 0xe1, 0x01};
constexpr std::array<uint8_t, 17> kDefaultLargeBlobArray = {
0x80, 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7,
0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c};
class P256PrivateKey : public VirtualFidoDevice::PrivateKey {
public:
explicit P256PrivateKey(crypto::keypair::PrivateKey key) : PrivateKey(key) {}
~P256PrivateKey() override = default;
std::unique_ptr<PublicKey> GetPublicKey() const override {
return P256PublicKey::ParseX962Uncompressed(
static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256),
GetX962PublicKey());
}
};
class RSAPrivateKey : public VirtualFidoDevice::PrivateKey {
public:
explicit RSAPrivateKey(crypto::keypair::PrivateKey key) : PrivateKey(key) {}
~RSAPrivateKey() override = default;
std::unique_ptr<PublicKey> GetPublicKey() const override {
const RSA* rsa = EVP_PKEY_get0_RSA(key_.key());
const BIGNUM* n = RSA_get0_n(rsa);
const BIGNUM* e = RSA_get0_e(rsa);
std::vector<uint8_t> modulus(BN_num_bytes(n));
BN_bn2bin(n, modulus.data());
std::vector<uint8_t> public_exponent(BN_num_bytes(e));
BN_bn2bin(e, public_exponent.data());
cbor::Value::MapValue map;
map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg),
static_cast<int64_t>(CoseAlgorithmIdentifier::kRs256));
map.emplace(static_cast<int64_t>(CoseKeyKey::kKty),
static_cast<int64_t>(CoseKeyTypes::kRSA));
map.emplace(static_cast<int64_t>(CoseKeyKey::kRSAModulus),
std::move(modulus));
map.emplace(static_cast<int64_t>(CoseKeyKey::kRSAPublicExponent),
std::move(public_exponent));
std::optional<std::vector<uint8_t>> cbor_bytes(
cbor::Writer::Write(cbor::Value(std::move(map))));
return std::make_unique<PublicKey>(
static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), *cbor_bytes,
key_.ToSubjectPublicKeyInfo());
}
};
class Ed25519PrivateKey : public VirtualFidoDevice::PrivateKey {
public:
explicit Ed25519PrivateKey(crypto::keypair::PrivateKey key)
: PrivateKey(key) {}
~Ed25519PrivateKey() override = default;
std::unique_ptr<PublicKey> GetPublicKey() const override {
cbor::Value::MapValue map;
map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg),
static_cast<int64_t>(CoseAlgorithmIdentifier::kEdDSA));
map.emplace(static_cast<int64_t>(CoseKeyKey::kKty),
static_cast<int64_t>(CoseKeyTypes::kOKP));
map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticCurve),
static_cast<int64_t>(CoseCurves::kEd25519));
map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticX),
key_.ToEd25519PublicKey());
std::optional<std::vector<uint8_t>> cbor_bytes(
cbor::Writer::Write(cbor::Value(std::move(map))));
return std::make_unique<PublicKey>(
static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA), *cbor_bytes,
key_.ToSubjectPublicKeyInfo());
}
};
class InvalidForTestingPrivateKey : public VirtualFidoDevice::PrivateKey {
public:
InvalidForTestingPrivateKey()
: PrivateKey(crypto::keypair::PrivateKey::GenerateEcP256()) {}
std::vector<uint8_t> Sign(base::span<const uint8_t> message) override {
return {'s', 'i', 'g'};
}
std::unique_ptr<PublicKey> GetPublicKey() const override {
cbor::Value::MapValue map;
map.emplace(
static_cast<int64_t>(CoseKeyKey::kAlg),
static_cast<int64_t>(CoseAlgorithmIdentifier::kInvalidForTesting));
map.emplace(static_cast<int64_t>(CoseKeyKey::kKty),
static_cast<int64_t>(CoseKeyTypes::kInvalidForTesting));
std::optional<std::vector<uint8_t>> cbor_bytes(
cbor::Writer::Write(cbor::Value(std::move(map))));
return std::make_unique<PublicKey>(
static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting),
*cbor_bytes, std::nullopt);
}
};
crypto::sign::SignatureKind SignatureKindForKey(
const crypto::keypair::PrivateKey& key) {
if (key.IsEc()) {
return crypto::sign::ECDSA_SHA256;
} else if (key.IsRsa()) {
return crypto::sign::RSA_PKCS1_SHA256;
} else if (key.IsEd25519()) {
return crypto::sign::ED25519;
} else {
NOTREACHED();
}
}
}
VirtualFidoDevice::PrivateKey::~PrivateKey() = default;
std::optional<std::unique_ptr<VirtualFidoDevice::PrivateKey>>
VirtualFidoDevice::PrivateKey::FromPKCS8(
base::span<const uint8_t> pkcs8_private_key) {
std::optional<crypto::keypair::PrivateKey> key =
crypto::keypair::PrivateKey::FromPrivateKeyInfo(pkcs8_private_key);
if (!key) {
return std::nullopt;
}
if (key->IsEc()) {
return std::make_unique<P256PrivateKey>(*key);
} else if (key->IsRsa()) {
return std::make_unique<RSAPrivateKey>(*key);
} else if (key->IsEd25519()) {
return std::make_unique<Ed25519PrivateKey>(*key);
} else {
NOTREACHED();
}
}
std::unique_ptr<VirtualFidoDevice::PrivateKey>
VirtualFidoDevice::PrivateKey::FreshP256Key() {
return std::make_unique<P256PrivateKey>(
crypto::keypair::PrivateKey::GenerateEcP256());
}
std::unique_ptr<VirtualFidoDevice::PrivateKey>
VirtualFidoDevice::PrivateKey::FreshRSAKey() {
return std::make_unique<RSAPrivateKey>(
crypto::keypair::PrivateKey::GenerateRsa2048());
}
std::unique_ptr<VirtualFidoDevice::PrivateKey>
VirtualFidoDevice::PrivateKey::FreshEd25519Key() {
return std::make_unique<Ed25519PrivateKey>(
crypto::keypair::PrivateKey::GenerateEd25519());
}
std::unique_ptr<VirtualFidoDevice::PrivateKey>
VirtualFidoDevice::PrivateKey::FreshInvalidForTestingKey() {
return std::make_unique<InvalidForTestingPrivateKey>();
}
std::vector<uint8_t> VirtualFidoDevice::PrivateKey::Sign(
base::span<const uint8_t> message) {
return crypto::sign::Sign(SignatureKindForKey(key_), key_, message);
}
std::vector<uint8_t> VirtualFidoDevice::PrivateKey::GetX962PublicKey() const {
if (key_.IsEc()) {
return key_.ToUncompressedX962Point();
} else {
NOTREACHED();
}
}
std::vector<uint8_t> VirtualFidoDevice::PrivateKey::GetPKCS8PrivateKey() const {
return key_.ToPrivateKeyInfo();
}
VirtualFidoDevice::PrivateKey::PrivateKey(crypto::keypair::PrivateKey key)
: key_(std::move(key)) {}
VirtualFidoDevice::RegistrationData::RegistrationData() = default;
VirtualFidoDevice::RegistrationData::RegistrationData(const std::string& rp_id)
: application_parameter(crypto::hash::Sha256(rp_id)) {}
VirtualFidoDevice::RegistrationData::RegistrationData(
std::unique_ptr<PrivateKey> private_key,
base::span<const uint8_t, kRpIdHashLength> application_parameter,
uint32_t counter)
: private_key(std::move(private_key)),
application_parameter(
fido_parsing_utils::Materialize(application_parameter)),
counter(counter) {}
VirtualFidoDevice::RegistrationData::RegistrationData(RegistrationData&& data) =
default;
VirtualFidoDevice::RegistrationData::~RegistrationData() = default;
VirtualFidoDevice::RegistrationData&
VirtualFidoDevice::RegistrationData::operator=(RegistrationData&& other) =
default;
VirtualFidoDevice::State::State()
: attestation_cert_common_name("Batch Certificate"),
individual_attestation_cert_common_name("Individual Certificate") {
large_blob.assign(kDefaultLargeBlobArray.begin(),
kDefaultLargeBlobArray.end());
}
VirtualFidoDevice::State::~State() = default;
void VirtualFidoDevice::State::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void VirtualFidoDevice::State::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void VirtualFidoDevice::State::NotifyCredentialCreated(
const Credential& credential) {
for (Observer& observer : observers_) {
observer.OnCredentialCreated(credential);
}
}
void VirtualFidoDevice::State::NotifyCredentialDeleted(
base::span<const uint8_t> credential_id) {
for (Observer& observer : observers_) {
observer.OnCredentialDeleted(credential_id);
}
}
void VirtualFidoDevice::State::NotifyCredentialUpdated(
const std::pair<base::span<const uint8_t>, RegistrationData*>& credential) {
for (Observer& observer : observers_) {
observer.OnCredentialUpdated(credential);
}
}
void VirtualFidoDevice::State::NotifyAssertion(const Credential& credential) {
for (Observer& observer : observers_) {
observer.OnAssertion(credential);
}
}
bool VirtualFidoDevice::State::InjectRegistration(
base::span<const uint8_t> credential_id,
RegistrationData registration) {
bool was_inserted;
std::tie(std::ignore, was_inserted) = registrations.emplace(
fido_parsing_utils::Materialize(credential_id), std::move(registration));
return was_inserted;
}
bool VirtualFidoDevice::State::InjectRegistration(
base::span<const uint8_t> credential_id,
const std::string& relying_party_id) {
RegistrationData registration(relying_party_id);
registration.backup_eligible = default_backup_eligibility;
registration.backup_state = default_backup_state;
return InjectRegistration(credential_id, std::move(registration));
}
bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id,
device::PublicKeyCredentialRpEntity rp,
device::PublicKeyCredentialUserEntity user,
int32_t signature_counter,
std::unique_ptr<PrivateKey> private_key) {
auto application_parameter = crypto::hash::Sha256(rp.id);
for (const auto& registration : registrations) {
if (registration.second.is_resident &&
application_parameter == registration.second.application_parameter &&
user.id == registration.second.user->id) {
return false;
}
}
RegistrationData registration(std::move(private_key),
std::move(application_parameter),
signature_counter);
registration.is_resident = true;
registration.rp = std::move(rp);
registration.user = std::move(user);
registration.backup_eligible = default_backup_eligibility;
registration.backup_state = default_backup_state;
bool was_inserted;
std::tie(std::ignore, was_inserted) = registrations.emplace(
fido_parsing_utils::Materialize(credential_id), std::move(registration));
return was_inserted;
}
bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id,
device::PublicKeyCredentialRpEntity rp,
device::PublicKeyCredentialUserEntity user) {
return InjectResidentKey(credential_id, std::move(rp), std::move(user),
0, PrivateKey::FreshP256Key());
}
bool VirtualFidoDevice::State::InjectResidentKey(
base::span<const uint8_t> credential_id,
const std::string& relying_party_id,
base::span<const uint8_t> user_id,
std::optional<std::string> user_name,
std::optional<std::string> user_display_name) {
return InjectResidentKey(
credential_id, PublicKeyCredentialRpEntity(std::move(relying_party_id)),
PublicKeyCredentialUserEntity(fido_parsing_utils::Materialize(user_id),
std::move(user_name),
std::move(user_display_name)));
}
std::optional<LargeBlob> VirtualFidoDevice::State::GetLargeBlob(
const RegistrationData& credential) {
if (credential.large_blob) {
return credential.large_blob;
}
if (!credential.large_blob_key) {
return std::nullopt;
}
LargeBlobArrayReader reader;
reader.Append(large_blob);
std::optional<cbor::Value::ArrayValue> large_blob_array =
reader.Materialize();
if (!large_blob_array) {
return std::nullopt;
}
for (const cbor::Value& blob_cbor : *large_blob_array) {
std::optional<LargeBlobData> data = LargeBlobData::Parse(blob_cbor);
if (!data.has_value()) {
continue;
}
std::optional<LargeBlob> blob = data->Decrypt(*credential.large_blob_key);
if (blob) {
return blob;
}
}
return std::nullopt;
}
void VirtualFidoDevice::State::InjectLargeBlob(RegistrationData* credential,
LargeBlob blob) {
LargeBlobArrayReader reader;
reader.Append(large_blob);
cbor::Value::ArrayValue large_blob_array =
reader.Materialize().value_or(cbor::Value::ArrayValue());
if (credential->large_blob_key) {
std::erase_if(
large_blob_array, [&credential](const cbor::Value& blob_cbor) {
std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
return blob && blob->Decrypt(*credential->large_blob_key).has_value();
});
} else {
credential->large_blob_key.emplace();
base::RandBytes(*credential->large_blob_key);
}
large_blob_array.emplace_back(
LargeBlobData(*credential->large_blob_key, std::move(blob)).AsCBOR());
LargeBlobArrayWriter writer(std::move(large_blob_array));
large_blob = writer.Pop(writer.size()).bytes;
}
void VirtualFidoDevice::State::InjectOpaqueLargeBlob(cbor::Value blob) {
LargeBlobArrayReader reader;
reader.Append(large_blob);
cbor::Value::ArrayValue large_blob_array =
reader.Materialize().value_or(cbor::Value::ArrayValue());
large_blob_array.emplace_back(std::move(blob));
LargeBlobArrayWriter writer(std::move(large_blob_array));
large_blob = writer.Pop(writer.size()).bytes;
}
void VirtualFidoDevice::State::ClearLargeBlobs() {
large_blob.assign(kDefaultLargeBlobArray.begin(),
kDefaultLargeBlobArray.end());
}
VirtualFidoDevice::VirtualFidoDevice() = default;
VirtualFidoDevice::VirtualFidoDevice(scoped_refptr<State> state)
: state_(std::move(state)) {}
VirtualFidoDevice::~VirtualFidoDevice() = default;
std::string VirtualFidoDevice::GetId() const {
return state_->device_id_override.value_or(id_);
}
std::vector<uint8_t> VirtualFidoDevice::GetAttestationKey() {
return fido_parsing_utils::Materialize(kAttestationKey);
}
std::optional<std::vector<uint8_t>>
VirtualFidoDevice::GenerateAttestationCertificate(
bool individual_attestation_requested,
bool include_transports) const {
auto attestation_private_key =
crypto::keypair::PrivateKey::FromPrivateKeyInfo(GetAttestationKey());
constexpr uint32_t kAttestationCertSerialNumber = 1;
static constexpr uint8_t kTransportTypesOID[] = {
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xe5, 0x1c, 0x02, 0x01, 0x01};
uint8_t transport_bit;
switch (DeviceTransport()) {
case FidoTransportProtocol::kBluetoothLowEnergy:
case FidoTransportProtocol::kHybrid:
transport_bit = 1;
break;
case FidoTransportProtocol::kUsbHumanInterfaceDevice:
transport_bit = 2;
break;
case FidoTransportProtocol::kNearFieldCommunication:
transport_bit = 3;
break;
case FidoTransportProtocol::kInternal:
transport_bit = 4;
break;
case FidoTransportProtocol::kDeprecatedAoa:
NOTREACHED();
}
const uint8_t kTransportTypesContents[] = {
3,
2,
static_cast<uint8_t>(8 - transport_bit - 1),
static_cast<uint8_t>(0b10000000 >> transport_bit),
};
static constexpr uint8_t kBasicContraintsOID[] = {0x55, 0x1d, 0x13};
static constexpr uint8_t kBasicContraintsContents[] = {
0x30,
0x00,
};
std::vector<net::x509_util::Extension> extensions = {
{kBasicContraintsOID, true, kBasicContraintsContents},
};
if (include_transports) {
extensions.emplace_back(kTransportTypesOID, false,
kTransportTypesContents);
}
base::Time expiry_date = base::Time::Now() + base::Days(365 * 20);
std::string attestation_cert;
if (!net::x509_util::CreateSelfSignedCert(
attestation_private_key->key(), net::x509_util::DIGEST_SHA256,
"C=US, O=Chromium, OU=Authenticator Attestation, CN=" +
(individual_attestation_requested
? state_->individual_attestation_cert_common_name
: state_->attestation_cert_common_name),
kAttestationCertSerialNumber, base::Time::FromTimeT(1500000000),
expiry_date, extensions, &attestation_cert)) {
DVLOG(2) << "Failed to create attestation certificate";
return std::nullopt;
}
return std::vector<uint8_t>(attestation_cert.begin(), attestation_cert.end());
}
void VirtualFidoDevice::StoreNewKey(
base::span<const uint8_t> key_handle,
VirtualFidoDevice::RegistrationData registration_data) {
if (registration_data.application_parameter == device::kBogusAppParam ||
registration_data.application_parameter ==
crypto::hash::Sha256(kDummyRpID)) {
return;
}
auto result = mutable_state()->registrations.emplace(
fido_parsing_utils::Materialize(key_handle),
std::move(registration_data));
DCHECK(result.second);
mutable_state()->NotifyCredentialCreated(
std::make_pair(key_handle, &result.first->second));
}
VirtualFidoDevice::RegistrationData* VirtualFidoDevice::FindRegistrationData(
base::span<const uint8_t> key_handle,
base::span<const uint8_t, kRpIdHashLength> application_parameter) {
auto it = mutable_state()->registrations.find(key_handle);
if (it == mutable_state()->registrations.end())
return nullptr;
if (!std::ranges::equal(application_parameter,
it->second.application_parameter)) {
return nullptr;
}
return &it->second;
}
bool VirtualFidoDevice::SimulatePress() {
if (!state_->simulate_press_callback)
return true;
auto weak_this = GetWeakPtr();
bool result = state_->simulate_press_callback.Run(this);
return weak_this && result;
}
void VirtualFidoDevice::TryWink(base::OnceClosure cb) {
std::move(cb).Run();
}
FidoTransportProtocol VirtualFidoDevice::DeviceTransport() const {
return state_->transport;
}
std::string VirtualFidoDevice::MakeVirtualFidoDeviceId() {
uint8_t rand_bytes[32];
base::RandBytes(rand_bytes);
return "VirtualFidoDevice-" + base::HexEncode(rand_bytes);
}
}