#include "device/fido/make_credential_task.h"
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "device/base/features.h"
#include "device/fido/ctap2_device_operation.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/pin.h"
#include "device/fido/u2f_command_constructor.h"
#include "device/fido/u2f_register_operation.h"
namespace device {
namespace {
bool CtapDeviceShouldUseU2fBecauseClientPinIsSet(
const FidoDevice* device,
const CtapMakeCredentialRequest& request) {
if (!IsConvertibleToU2fRegisterCommand(request) ||
ShouldPreferCTAP2EvenIfItNeedsAPIN(request)) {
return false;
}
DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
if (device->device_info()->options.make_cred_uv_not_required) {
return false;
}
if (request.user_verification == UserVerificationRequirement::kRequired ||
request.pin_auth) {
return false;
}
DCHECK(device && device->device_info());
bool client_pin_set =
device->device_info()->options.client_pin_availability ==
AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet;
bool supports_u2f =
base::Contains(device->device_info()->versions, ProtocolVersion::kU2f);
return client_pin_set && supports_u2f;
}
absl::optional<AuthenticatorMakeCredentialResponse> ConvertCTAPResponse(
FidoDevice* device,
bool resident_key_required,
const absl::optional<cbor::Value>& cbor) {
DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
DCHECK(device->device_info());
absl::optional<AuthenticatorMakeCredentialResponse> response =
ReadCTAPMakeCredentialResponse(device->DeviceTransport(), cbor);
if (!response) {
return absl::nullopt;
}
DCHECK(!response->is_resident_key.has_value());
if (resident_key_required) {
response->is_resident_key = true;
} else {
const bool resident_key_supported =
device->device_info()->options.supports_resident_key;
const base::flat_set<Ctap2Version>& ctap2_versions =
device->device_info()->ctap2_versions;
DCHECK(!ctap2_versions.empty());
const bool is_at_least_ctap2_1 = base::ranges::any_of(
ctap2_versions,
[](Ctap2Version v) { return v > Ctap2Version::kCtap2_0; });
if (!resident_key_supported || is_at_least_ctap2_1) {
response->is_resident_key = false;
}
}
if (device->device_info() && device->device_info()->transports) {
response->transports = *device->device_info()->transports;
}
return response;
}
}
MakeCredentialTask::MakeCredentialTask(FidoDevice* device,
CtapMakeCredentialRequest request,
MakeCredentialOptions options,
MakeCredentialTaskCallback callback)
: FidoTask(device),
request_(std::move(request)),
options_(std::move(options)),
callback_(std::move(callback)) {
DCHECK_NE(request_.user_verification,
UserVerificationRequirement::kPreferred);
}
MakeCredentialTask::~MakeCredentialTask() = default;
CtapMakeCredentialRequest MakeCredentialTask::GetTouchRequest(
const FidoDevice* device) {
PublicKeyCredentialUserEntity user({1} );
user.name = "dummy";
CtapMakeCredentialRequest req(
"" , PublicKeyCredentialRpEntity(kDummyRpID),
std::move(user),
PublicKeyCredentialParams(
{{CredentialType::kPublicKey,
base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256)}}));
if (device->supported_protocol() == ProtocolVersion::kU2f ||
(device->device_info() &&
device->device_info()->options.client_pin_availability !=
AuthenticatorSupportedOptions::ClientPinAvailability::
kNotSupported)) {
req.pin_auth.emplace();
req.pin_protocol = PINUVAuthProtocol::kV1;
}
DCHECK(IsConvertibleToU2fRegisterCommand(req));
return req;
}
bool MakeCredentialTask::WillUseCTAP2(const FidoDevice* device,
const CtapMakeCredentialRequest& request,
const MakeCredentialOptions& options) {
return device->supported_protocol() == ProtocolVersion::kCtap2 &&
!CtapDeviceShouldUseU2fBecauseClientPinIsSet(device, request);
}
void MakeCredentialTask::Cancel() {
canceled_ = true;
if (register_operation_) {
register_operation_->Cancel();
}
if (silent_sign_operation_) {
silent_sign_operation_->Cancel();
}
}
void MakeCredentialTask::StartTask() {
if (WillUseCTAP2(device(), request_, options_)) {
MakeCredential();
} else {
DCHECK_EQ(device()->supported_protocol() == ProtocolVersion::kCtap2,
device()->device_info().has_value());
device()->set_supported_protocol(ProtocolVersion::kU2f);
U2fRegister();
}
}
CtapGetAssertionRequest MakeCredentialTask::NextSilentRequest() {
DCHECK(current_exclude_list_batch_ < exclude_list_batches_.size());
CtapGetAssertionRequest request(request_.rp.id,
"");
request.allow_list = exclude_list_batches_.at(current_exclude_list_batch_);
request.user_presence_required = false;
request.user_verification = UserVerificationRequirement::kDiscouraged;
DCHECK_EQ(request_.pin_auth.has_value(),
request_.pin_token_for_exclude_list_probing.has_value());
if (request_.pin_token_for_exclude_list_probing) {
std::tie(request.pin_protocol, request.pin_auth) =
request_.pin_token_for_exclude_list_probing->PinAuth(
request.client_data_hash);
}
return request;
}
void MakeCredentialTask::MakeCredential() {
DCHECK_EQ(device()->supported_protocol(), ProtocolVersion::kCtap2);
exclude_list_batches_ =
FilterAndBatchCredentialDescriptors(request_.exclude_list, *device());
DCHECK(!exclude_list_batches_.empty());
if (exclude_list_batches_.size() == 1 || device()->NoSilentRequests()) {
auto request = request_;
request.exclude_list = exclude_list_batches_.front();
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
base::BindOnce(&ConvertCTAPResponse, device(),
request_.resident_key_required),
nullptr);
register_operation_->Start();
return;
}
silent_sign_operation_ =
std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest,
AuthenticatorGetAssertionResponse>>(
device(), NextSilentRequest(),
base::BindOnce(&MakeCredentialTask::HandleResponseToSilentSignRequest,
weak_factory_.GetWeakPtr()),
base::BindOnce(&ReadCTAPGetAssertionResponse,
device()->DeviceTransport()),
nullptr);
silent_sign_operation_->Start();
}
void MakeCredentialTask::HandleResponseToSilentSignRequest(
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorGetAssertionResponse> response_data) {
if (canceled_) {
return;
}
if (response_code == CtapDeviceResponseCode::kSuccess) {
CtapMakeCredentialRequest request = request_;
request.exclude_list =
exclude_list_batches_.at(current_exclude_list_batch_);
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
base::BindOnce(&ConvertCTAPResponse, device(),
request_.resident_key_required),
nullptr);
register_operation_->Start();
return;
}
if (!FidoDevice::IsStatusForUnrecognisedCredentialID(response_code)) {
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), GetTouchRequest(device()),
base::BindOnce(&MakeCredentialTask::HandleResponseToDummyTouch,
weak_factory_.GetWeakPtr()),
base::BindOnce(&ConvertCTAPResponse, device(),
false),
nullptr);
register_operation_->Start();
return;
}
current_exclude_list_batch_++;
if (current_exclude_list_batch_ < exclude_list_batches_.size()) {
silent_sign_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
device(), NextSilentRequest(),
base::BindOnce(&MakeCredentialTask::HandleResponseToSilentSignRequest,
weak_factory_.GetWeakPtr()),
base::BindOnce(&ReadCTAPGetAssertionResponse,
device()->DeviceTransport()),
nullptr);
silent_sign_operation_->Start();
return;
}
CtapMakeCredentialRequest request = request_;
request.exclude_list = {};
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
base::BindOnce(&ConvertCTAPResponse, device(),
request_.resident_key_required),
nullptr);
register_operation_->Start();
}
void MakeCredentialTask::HandleResponseToDummyTouch(
CtapDeviceResponseCode response_code,
absl::optional<AuthenticatorMakeCredentialResponse> response_data) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
absl::nullopt);
}
void MakeCredentialTask::U2fRegister() {
if (!IsConvertibleToU2fRegisterCommand(request_)) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
absl::nullopt);
return;
}
DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
register_operation_ = std::make_unique<U2fRegisterOperation>(
device(), std::move(request_),
base::BindOnce(&MakeCredentialTask::MaybeRevertU2fFallback,
weak_factory_.GetWeakPtr()));
register_operation_->Start();
}
void MakeCredentialTask::MaybeRevertU2fFallback(
CtapDeviceResponseCode status,
absl::optional<AuthenticatorMakeCredentialResponse> response) {
DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
if (device()->device_info()) {
device()->set_supported_protocol(ProtocolVersion::kCtap2);
}
DCHECK(!response || *response->is_resident_key == false);
std::move(callback_).Run(status, std::move(response));
}
std::vector<std::vector<PublicKeyCredentialDescriptor>>
FilterAndBatchCredentialDescriptors(
const std::vector<PublicKeyCredentialDescriptor>& in,
const FidoDevice& device) {
DCHECK_EQ(device.supported_protocol(), ProtocolVersion::kCtap2);
DCHECK(device.device_info().has_value());
if (device.NoSilentRequests()) {
return {in};
}
const auto& device_info = *device.device_info();
size_t max_credential_id_length =
device_info.max_credential_id_length.value_or(0);
size_t max_credential_count_in_list =
max_credential_id_length > 0
? std::max(device_info.max_credential_count_in_list.value_or(1), 1u)
: 1;
std::vector<std::vector<PublicKeyCredentialDescriptor>> result;
result.emplace_back();
for (const PublicKeyCredentialDescriptor& credential : in) {
if (0 < max_credential_id_length &&
max_credential_id_length < credential.id.size()) {
continue;
}
if (result.back().size() == max_credential_count_in_list) {
result.emplace_back();
}
result.back().push_back(credential);
}
return result;
}
}