#include "device/fido/virtual_u2f_device.h"
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "components/apdu/apdu_command.h"
#include "components/apdu/apdu_response.h"
#include "crypto/keypair.h"
#include "crypto/sign.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
using fido_parsing_utils::Append;
namespace {
constexpr uint8_t kU2fRegistrationResponseHeader = 0x05;
std::optional<std::vector<uint8_t>> ErrorStatus(
apdu::ApduResponse::Status status) {
return apdu::ApduResponse(std::vector<uint8_t>(), status)
.GetEncodedResponse();
}
}
bool VirtualU2fDevice::IsTransportSupported(FidoTransportProtocol transport) {
return base::Contains(base::flat_set<FidoTransportProtocol>(
{FidoTransportProtocol::kUsbHumanInterfaceDevice,
FidoTransportProtocol::kBluetoothLowEnergy,
FidoTransportProtocol::kNearFieldCommunication}),
transport);
}
VirtualU2fDevice::VirtualU2fDevice() = default;
VirtualU2fDevice::VirtualU2fDevice(scoped_refptr<State> state)
: VirtualFidoDevice(std::move(state)) {
DCHECK(IsTransportSupported(mutable_state()->transport));
}
VirtualU2fDevice::~VirtualU2fDevice() = default;
void VirtualU2fDevice::Cancel(CancelToken) {}
FidoDevice::CancelToken VirtualU2fDevice::DeviceTransact(
std::vector<uint8_t> command,
DeviceCallback cb) {
auto parsed_command = apdu::ApduCommand::CreateFromMessage(command);
if (!parsed_command) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(cb),
ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED)));
return 0;
}
if (mutable_state()->simulate_invalid_response) {
std::vector<uint8_t> nonsense = {1, 2, 3};
auto response = apdu::ApduResponse(std::move(nonsense),
apdu::ApduResponse::Status::SW_NO_ERROR)
.GetEncodedResponse();
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), std::move(response)));
return 0;
}
std::optional<std::vector<uint8_t>> response;
switch (parsed_command->ins()) {
case base::strict_cast<uint8_t>(U2fApduInstruction::kVersion):
break;
case base::strict_cast<uint8_t>(U2fApduInstruction::kRegister):
response = DoRegister(parsed_command->ins(), parsed_command->p1(),
parsed_command->p2(), parsed_command->data());
break;
case base::strict_cast<uint8_t>(U2fApduInstruction::kSign):
response = DoSign(parsed_command->ins(), parsed_command->p1(),
parsed_command->p2(), parsed_command->data());
break;
default:
response = ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED);
}
if (response) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb), std::move(response)));
}
return 0;
}
base::WeakPtr<FidoDevice> VirtualU2fDevice::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
std::optional<std::vector<uint8_t>> VirtualU2fDevice::DoRegister(
uint8_t ins,
uint8_t p1,
uint8_t p2,
base::span<const uint8_t> data) {
if (data.size() != 64) {
return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH);
}
if (!SimulatePress()) {
return std::nullopt;
}
auto challenge_param = data.first<32>();
auto application_parameter = data.last<32>();
std::unique_ptr<PrivateKey> private_key(PrivateKey::FreshP256Key());
std::vector<uint8_t> x962 = private_key->GetX962PublicKey();
if (mutable_state()->u2f_invalid_public_key) {
x962[10] ^= 1;
}
const auto key_handle = crypto::SHA256Hash(x962);
std::vector<uint8_t> sign_buffer;
sign_buffer.reserve(1 + application_parameter.size() +
challenge_param.size() + key_handle.size() + x962.size());
sign_buffer.push_back(0x00);
Append(&sign_buffer, application_parameter);
Append(&sign_buffer, challenge_param);
Append(&sign_buffer, key_handle);
Append(&sign_buffer, x962);
auto key =
crypto::keypair::PrivateKey::FromPrivateKeyInfo(GetAttestationKey());
CHECK(key && key->IsEc());
std::vector<uint8_t> sig = crypto::sign::Sign(
crypto::sign::SignatureKind::ECDSA_SHA256, *key, sign_buffer);
bool individual_attestation_requested = p1 & kP1IndividualAttestation;
const auto attestation_cert = GenerateAttestationCertificate(
individual_attestation_requested, true);
if (!attestation_cert)
return ErrorStatus(apdu::ApduResponse::Status::SW_INS_NOT_SUPPORTED);
std::vector<uint8_t> response;
response.reserve(1 + x962.size() + 1 + key_handle.size() +
attestation_cert->size() + sig.size());
response.push_back(kU2fRegistrationResponseHeader);
Append(&response, base::as_byte_span(x962));
response.push_back(key_handle.size());
Append(&response, key_handle);
Append(&response, *attestation_cert);
Append(&response, sig);
RegistrationData registration_data(
std::move(private_key), application_parameter, 1 );
registration_data.is_u2f = true;
StoreNewKey(key_handle, std::move(registration_data));
return apdu::ApduResponse(std::move(response),
apdu::ApduResponse::Status::SW_NO_ERROR)
.GetEncodedResponse();
}
std::optional<std::vector<uint8_t>> VirtualU2fDevice::DoSign(
uint8_t ins,
uint8_t p1,
uint8_t p2,
base::span<const uint8_t> data) {
if (!(p1 == kP1CheckOnly || p1 == kP1TupRequiredConsumed ||
p1 == kP1IndividualAttestation) ||
p2 != 0) {
return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA);
}
if (!SimulatePress()) {
return std::nullopt;
}
if (data.size() < 32 + 32 + 1)
return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH);
const auto [challenge_param, after_challenge] = data.split_at<32>();
const auto [application_parameter, after_application] =
after_challenge.split_at<32>();
const auto [key_handle_length, key_handle] = after_application.split_at<1>();
if (key_handle.size() != key_handle_length[0]) {
return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_LENGTH);
}
auto* registration = FindRegistrationData(key_handle, application_parameter);
if (!registration)
return ErrorStatus(apdu::ApduResponse::Status::SW_WRONG_DATA);
++registration->counter;
std::vector<uint8_t> response;
response.push_back(0x01);
response.push_back(registration->counter >> 24);
response.push_back(registration->counter >> 16);
response.push_back(registration->counter >> 8);
response.push_back(registration->counter);
std::vector<uint8_t> sign_buffer;
sign_buffer.reserve(application_parameter.size() + response.size() +
challenge_param.size());
Append(&sign_buffer, application_parameter);
Append(&sign_buffer, response);
Append(&sign_buffer, challenge_param);
std::vector<uint8_t> sig = registration->private_key->Sign(sign_buffer);
if (mutable_state()->u2f_invalid_signature) {
sig[0] ^= 1;
}
Append(&response, sig);
mutable_state()->NotifyAssertion(std::make_pair(key_handle, registration));
return apdu::ApduResponse(std::move(response),
apdu::ApduResponse::Status::SW_NO_ERROR)
.GetEncodedResponse();
}
}