blob: a44c494f60b8d66ec3694e542057c09b47b75c34 [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 "device/fido/fido_device_authenticator.h"
#include <algorithm>
#include <numeric>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase_vector.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/ranges/algorithm.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/cbor/values.h"
#include "device/fido/appid_exclude_probe_task.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/credential_management.h"
#include "device/fido/ctap2_device_operation.h"
#include "device/fido/ctap_authenticator_selection_request.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_device.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/fido_types.h"
#include "device/fido/get_assertion_task.h"
#include "device/fido/large_blob.h"
#include "device/fido/make_credential_task.h"
#include "device/fido/pin.h"
#include "device/fido/u2f_command_constructor.h"
namespace device {
using ClientPinAvailability =
AuthenticatorSupportedOptions::ClientPinAvailability;
using UserVerificationAvailability =
AuthenticatorSupportedOptions::UserVerificationAvailability;
namespace {
// Helper method for determining correct bio enrollment version.
BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion(
const AuthenticatorSupportedOptions& options) {
DCHECK(options.bio_enrollment_availability_preview !=
AuthenticatorSupportedOptions::BioEnrollmentAvailability::
kNotSupported ||
options.bio_enrollment_availability !=
AuthenticatorSupportedOptions::BioEnrollmentAvailability::
kNotSupported);
return options.bio_enrollment_availability !=
AuthenticatorSupportedOptions::BioEnrollmentAvailability::
kNotSupported
? BioEnrollmentRequest::kDefault
: BioEnrollmentRequest::kPreview;
}
CredentialManagementRequest::Version GetCredentialManagementRequestVersion(
const AuthenticatorSupportedOptions& options) {
DCHECK(options.supports_credential_management_preview ||
options.supports_credential_management);
return options.supports_credential_management
? CredentialManagementRequest::kDefault
: CredentialManagementRequest::kPreview;
}
} // namespace
FidoDeviceAuthenticator::FidoDeviceAuthenticator(
std::unique_ptr<FidoDevice> device)
: device_(std::move(device)) {}
FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default;
void FidoDeviceAuthenticator::InitializeAuthenticator(
base::OnceClosure callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&FidoDevice::DiscoverSupportedProtocolAndDeviceInfo,
device()->GetWeakPtr(),
base::BindOnce(&FidoDeviceAuthenticator::InitializeAuthenticatorDone,
weak_factory_.GetWeakPtr(), std::move(callback))));
}
void FidoDeviceAuthenticator::InitializeAuthenticatorDone(
base::OnceClosure callback) {
DCHECK(!initialized_);
initialized_ = true;
switch (device_->supported_protocol()) {
case ProtocolVersion::kU2f:
// U2F devices always "support" enterprise attestation because it turns
// into a bit in the makeCredential command that is ignored if not
// supported.
options_.enterprise_attestation = true;
break;
case ProtocolVersion::kCtap2:
DCHECK(device_->device_info()) << "uninitialized device";
options_ = device_->device_info()->options;
if (device_->device_info()->pin_protocols) {
DCHECK(!device_->device_info()->pin_protocols->empty());
// Choose the highest supported version.
chosen_pin_uv_auth_protocol_ =
*(device_->device_info()->pin_protocols->end() - 1);
}
// The hmac-secret extension involves encrypting the values passed back
// and forth, thus there must be a valid PIN protocol.
options_.supports_hmac_secret &= chosen_pin_uv_auth_protocol_.has_value();
break;
case ProtocolVersion::kUnknown:
NOTREACHED() << "uninitialized device";
}
std::move(callback).Run();
}
void FidoDeviceAuthenticator::ExcludeAppIdCredentialsBeforeMakeCredential(
CtapMakeCredentialRequest request,
MakeCredentialOptions options,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<bool>)>
callback) {
// If the device (or request) is U2F-only then |MakeCredential| will handle
// the AppID-excluded credentials, if any. There's no interaction with PUATs
// to worry about because U2F doesn't have them.
//
// (If the device is AlwaysUV then it should still support up=false requests
// without a PUAT, so they aren't excluded here.)
if (!MakeCredentialTask::WillUseCTAP2(device_.get(), request, options) ||
device_->NoSilentRequests()) {
std::move(callback).Run(CtapDeviceResponseCode::kSuccess, absl::nullopt);
return;
}
// This is a CTAP2 device. In CTAP 2.1, a PUAT is invalidated if a request is
// made with a different RP ID, even if the PUAT isn't used on that request.
// Therefore appidExclude probing has to happen before the PUAT is obtained.
// For CTAP 2.0 devices we follow the same pattern, even though a PIN token
// doesn't have that issue.
RunTask<AppIdExcludeProbeTask, bool, CtapMakeCredentialRequest,
MakeCredentialOptions>(std::move(request), std::move(options),
std::move(callback));
}
void FidoDeviceAuthenticator::MakeCredential(
CtapMakeCredentialRequest request,
MakeCredentialOptions request_options,
MakeCredentialCallback callback) {
// If the authenticator has UV configured then UV will be required in
// order to create a credential (as specified by CTAP 2.0), even if
// user-verification is "discouraged".
if (!request.pin_auth &&
options_.user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured &&
!options_.make_cred_uv_not_required) {
request.user_verification = UserVerificationRequirement::kRequired;
} else {
request.user_verification = UserVerificationRequirement::kDiscouraged;
}
RunTask<MakeCredentialTask, AuthenticatorMakeCredentialResponse,
CtapMakeCredentialRequest, MakeCredentialOptions>(
std::move(request), std::move(request_options), std::move(callback));
}
void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback) {
if (options.pin_uv_auth_token) {
std::tie(request.pin_protocol, request.pin_auth) =
options.pin_uv_auth_token->PinAuth(request.client_data_hash);
}
large_blob_.reset();
large_blob_read_ = false;
if (options_.large_blob_type == LargeBlobSupportType::kExtension) {
if (options.large_blob_read) {
request.large_blob_extension_read = true;
}
} else if (options.large_blob_read || options.large_blob_write) {
DCHECK(options_.large_blob_type == LargeBlobSupportType::kKey);
request.large_blob_key = true;
large_blob_read_ = options.large_blob_read;
}
if (options.large_blob_write) {
// This copy is done because `options` is also moved in this call. While
// moving the object would hopefully not reallocate member buffers, that's
// not guaranteed and this is simpler than worrying about it.
const std::vector<uint8_t> large_blob_data = *options.large_blob_write;
data_decoder_.Deflate(
large_blob_data,
base::BindOnce(
&FidoDeviceAuthenticator::OnHaveCompressedLargeBlobForGetAssertion,
weak_factory_.GetWeakPtr(), std::move(request), std::move(options),
std::move(callback), large_blob_data.size()));
} else {
MaybeGetEphemeralKeyForGetAssertion(std::move(request), std::move(options),
std::move(callback));
}
}
void FidoDeviceAuthenticator::OnHaveCompressedLargeBlobForGetAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback,
size_t original_size,
base::expected<mojo_base::BigBuffer, std::string> result) {
if (!result.has_value()) {
LogLargeBlobResult(LargeBlobKeyWriteResult::kCompressionError);
FIDO_LOG(ERROR) << "Failed to compress large blob: " << result.error();
} else {
// If the authenticator supports the largeBlob extension then the blob is
// sent directly in the request. Otherwise it's saved in `large_blob_` to
// be written after the request, using the result of the `largeBlobKey`
// extension.
absl::optional<LargeBlob>* destination;
if (options_.large_blob_type == LargeBlobSupportType::kExtension) {
destination = &request.large_blob_extension_write;
} else {
DCHECK(request.large_blob_key);
destination = &large_blob_;
}
destination->emplace(fido_parsing_utils::Materialize(result.value()),
original_size);
}
MaybeGetEphemeralKeyForGetAssertion(std::move(request), std::move(options),
std::move(callback));
}
void FidoDeviceAuthenticator::MaybeGetEphemeralKeyForGetAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback) {
if (!options.prf_inputs.empty()) {
GetEphemeralKey(base::BindOnce(
&FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetAssertion,
weak_factory_.GetWeakPtr(), std::move(request), std::move(options),
std::move(callback)));
return;
}
DoGetAssertion(std::move(request), std::move(options), std::move(callback));
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, {});
return;
}
options.pin_key_agreement = std::move(*key);
if (!request.pin_protocol) {
// If `chosen_pin_uv_auth_protocol_` is `nullopt` then hmac_secret support
// isn't advertised and the caller should never have requested it.
DCHECK(chosen_pin_uv_auth_protocol_);
request.pin_protocol = chosen_pin_uv_auth_protocol_;
}
DoGetAssertion(std::move(request), std::move(options), std::move(callback));
}
void FidoDeviceAuthenticator::DoGetAssertion(CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
GetAssertionCallback callback) {
if (!request.pin_auth &&
options_.user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured &&
request.user_verification != UserVerificationRequirement::kDiscouraged) {
request.user_verification = UserVerificationRequirement::kRequired;
} else {
request.user_verification = UserVerificationRequirement::kDiscouraged;
}
CtapGetAssertionRequest request_copy(request);
CtapGetAssertionOptions options_copy(options);
RunTask<GetAssertionTask, AuthenticatorGetAssertionResponse,
CtapGetAssertionRequest, CtapGetAssertionOptions>(
std::move(request), std::move(options),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveNextAssertion,
weak_factory_.GetWeakPtr(), std::move(request_copy),
std::move(options_copy),
std::vector<AuthenticatorGetAssertionResponse>{},
std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveNextAssertion(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<AuthenticatorGetAssertionResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, {});
return;
}
responses.emplace_back(std::move(*response));
uint8_t num_responses = responses.at(0).num_credentials.value_or(1u);
if (num_responses == 0 ||
(num_responses > 1 && !request.allow_list.empty())) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, {});
return;
}
if (responses.size() >= num_responses) {
PerformGetAssertionLargeBlobOperation(
std::move(request), std::move(options), std::move(responses),
std::move(callback));
return;
}
// Read the next response.
RunOperation<CtapGetNextAssertionRequest, AuthenticatorGetAssertionResponse>(
CtapGetNextAssertionRequest(),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveNextAssertion,
weak_factory_.GetWeakPtr(), std::move(request),
std::move(options), std::move(responses),
std::move(callback)),
base::BindOnce(&ReadCTAPGetAssertionResponse, device_->DeviceTransport()),
GetAssertionTask::StringFixupPredicate);
}
void FidoDeviceAuthenticator::PerformGetAssertionLargeBlobOperation(
CtapGetAssertionRequest request,
CtapGetAssertionOptions options,
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback) {
// Only a single response is supported when using the largeBlob extension
// because we assume that only large authenticators will implement largeBlobs
// that way and they do internal account selection.
if (responses.size() == 1 && responses[0].large_blob_extension) {
if (!request.large_blob_extension_read) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, {});
return;
}
LargeBlob large_blob(std::move(*responses[0].large_blob_extension));
data_decoder_.Inflate(
std::move(large_blob.compressed_data), large_blob.original_size,
base::BindOnce(
&FidoDeviceAuthenticator::OnLargeBlobExtensionUncompressed,
weak_factory_.GetWeakPtr(), std::move(responses),
std::move(callback)));
return;
}
if (responses.size() == 1 && responses[0].large_blob_written &&
!request.large_blob_extension_write) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrInvalidCBOR, {});
return;
}
if (large_blob_) {
DCHECK(options_.large_blob_type == LargeBlobSupportType::kKey);
DCHECK_EQ(responses.size(), 1u);
if (!responses.at(0).large_blob_key) {
LogLargeBlobResult(LargeBlobKeyWriteResult::kCredentialHasNoLargeBlobKey);
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
return;
}
LargeBlobKey large_blob_key = *responses.at(0).large_blob_key;
DCHECK(large_blob_);
FetchLargeBlobArray(
LargeBlobArrayReader(),
base::BindOnce(
&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite,
weak_factory_.GetWeakPtr(), large_blob_key,
options.pin_uv_auth_token,
base::BindOnce(
&FidoDeviceAuthenticator::OnWroteLargeBlobForGetAssertion,
weak_factory_.GetWeakPtr(), std::move(responses),
std::move(callback))));
return;
}
if (large_blob_read_) {
DCHECK(options_.large_blob_type == LargeBlobSupportType::kKey);
std::vector<LargeBlobKey> keys;
for (const auto& assertion : responses) {
if (assertion.large_blob_key) {
keys.emplace_back(*assertion.large_blob_key);
}
}
if (keys.empty()) {
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
return;
}
ReadLargeBlob(
keys,
base::BindOnce(&FidoDeviceAuthenticator::OnReadLargeBlobForGetAssertion,
weak_factory_.GetWeakPtr(), std::move(responses),
std::move(callback)));
return;
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
}
void FidoDeviceAuthenticator::GetTouch(base::OnceClosure callback) {
if (device()->device_info() &&
device()->device_info()->SupportsAtLeast(Ctap2Version::kCtap2_1)) {
RunOperation<CtapAuthenticatorSelectionRequest, pin::EmptyResponse>(
CtapAuthenticatorSelectionRequest(),
base::BindOnce(
[](std::string authenticator_id, base::OnceClosure callback,
CtapDeviceResponseCode status,
absl::optional<pin::EmptyResponse> _) {
if (status == CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run();
return;
}
FIDO_LOG(DEBUG) << "Ignoring status " << static_cast<int>(status)
<< " from " << authenticator_id;
},
GetId(), std::move(callback)),
base::BindOnce(&pin::EmptyResponse::Parse));
return;
}
MakeCredential(
MakeCredentialTask::GetTouchRequest(device()), MakeCredentialOptions(),
base::BindOnce(
[](std::string authenticator_id, base::OnceCallback<void()> callback,
CtapDeviceResponseCode status,
absl::optional<AuthenticatorMakeCredentialResponse>) {
// If the device didn't understand/process the request it may
// fail immediately. Rather than count that as a touch, ignore
// those cases completely.
if (status == CtapDeviceResponseCode::kSuccess ||
status == CtapDeviceResponseCode::kCtap2ErrPinNotSet ||
status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid) {
std::move(callback).Run();
return;
}
FIDO_LOG(DEBUG) << "Ignoring status " << static_cast<int>(status)
<< " from " << authenticator_id;
},
GetId(), std::move(callback)));
}
void FidoDeviceAuthenticator::GetPinRetries(GetRetriesCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported);
DCHECK(chosen_pin_uv_auth_protocol_);
RunOperation<pin::PinRetriesRequest, pin::RetriesResponse>(
pin::PinRetriesRequest{*chosen_pin_uv_auth_protocol_},
std::move(callback),
base::BindOnce(&pin::RetriesResponse::ParsePinRetries));
}
void FidoDeviceAuthenticator::GetEphemeralKey(
GetEphemeralKeyCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported ||
options_.supports_pin_uv_auth_token || options_.supports_hmac_secret);
DCHECK(chosen_pin_uv_auth_protocol_);
RunOperation<pin::KeyAgreementRequest, pin::KeyAgreementResponse>(
pin::KeyAgreementRequest{*chosen_pin_uv_auth_protocol_},
std::move(callback), base::BindOnce(&pin::KeyAgreementResponse::Parse));
}
void FidoDeviceAuthenticator::GetPINToken(
std::string pin,
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
GetTokenCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported);
DCHECK_NE(permissions.size(), 0u);
DCHECK(!((base::Contains(permissions, pin::Permissions::kMakeCredential)) ||
base::Contains(permissions, pin::Permissions::kGetAssertion)) ||
rp_id);
GetEphemeralKey(base::BindOnce(
&FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken,
weak_factory_.GetWeakPtr(), std::move(pin), std::move(permissions),
std::move(rp_id), std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken(
std::string pin,
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
GetTokenCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
if (options_.supports_pin_uv_auth_token) {
pin::PinTokenWithPermissionsRequest request(*chosen_pin_uv_auth_protocol_,
pin, *key, permissions, rp_id);
std::vector<uint8_t> shared_key = request.shared_key();
RunOperation<pin::PinTokenWithPermissionsRequest, pin::TokenResponse>(
std::move(request), std::move(callback),
base::BindOnce(&pin::TokenResponse::Parse,
*chosen_pin_uv_auth_protocol_, std::move(shared_key)));
return;
}
pin::PinTokenRequest request(*chosen_pin_uv_auth_protocol_, pin, *key);
std::vector<uint8_t> shared_key = request.shared_key();
RunOperation<pin::PinTokenRequest, pin::TokenResponse>(
std::move(request), std::move(callback),
base::BindOnce(&pin::TokenResponse::Parse, *chosen_pin_uv_auth_protocol_,
std::move(shared_key)));
}
void FidoDeviceAuthenticator::SetPIN(const std::string& pin,
SetPINCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported);
GetEphemeralKey(base::BindOnce(
&FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN,
weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForSetPIN(
std::string pin,
SetPINCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
RunOperation<pin::SetRequest, pin::EmptyResponse>(
pin::SetRequest(*chosen_pin_uv_auth_protocol_, pin, *key),
std::move(callback), base::BindOnce(&pin::EmptyResponse::Parse));
}
void FidoDeviceAuthenticator::ChangePIN(const std::string& old_pin,
const std::string& new_pin,
SetPINCallback callback) {
DCHECK(options_.client_pin_availability !=
ClientPinAvailability::kNotSupported);
GetEphemeralKey(
base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN,
weak_factory_.GetWeakPtr(), std::move(old_pin),
std::move(new_pin), std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN(
std::string old_pin,
std::string new_pin,
SetPINCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
RunOperation<pin::ChangeRequest, pin::EmptyResponse>(
pin::ChangeRequest(*chosen_pin_uv_auth_protocol_, old_pin, new_pin, *key),
std::move(callback), base::BindOnce(&pin::EmptyResponse::Parse));
}
FidoAuthenticator::PINUVDisposition
FidoDeviceAuthenticator::PINUVDispositionForMakeCredential(
const CtapMakeCredentialRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
DCHECK(initialized_);
const bool can_collect_pin = observer && observer->SupportsPIN();
const bool pin_supported =
options_.client_pin_availability != ClientPinAvailability::kNotSupported;
const bool uv_supported = options_.user_verification_availability !=
UserVerificationAvailability::kNotSupported;
const bool pin_configured = options_.client_pin_availability ==
ClientPinAvailability::kSupportedAndPinSet;
const bool uv_configured =
options_.user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured;
// CTAP 2.0 requires a PIN for credential creation once a PIN has been set.
// Thus, if fallback to U2F isn't possible, a PIN will be needed if set.
const bool u2f_fallback_possible =
device()->device_info() &&
device()->device_info()->versions.contains(ProtocolVersion::kU2f) &&
IsConvertibleToU2fRegisterCommand(request) &&
!ShouldPreferCTAP2EvenIfItNeedsAPIN(request);
// CTAP 2.1 authenticators on the other hand can indicate that they allow
// credential creation with PIN or UV.
const bool can_make_ctap2_credential_without_uv =
request.user_verification == UserVerificationRequirement::kDiscouraged &&
options_.make_cred_uv_not_required;
const UserVerificationRequirement uv_requirement =
(pin_configured && !u2f_fallback_possible &&
!can_make_ctap2_credential_without_uv)
? UserVerificationRequirement::kRequired
: request.user_verification;
if (uv_requirement == UserVerificationRequirement::kDiscouraged ||
(uv_requirement == UserVerificationRequirement::kPreferred &&
((!pin_configured || !can_collect_pin) && !uv_configured &&
// The hmac-secret extension makes uv=preferred "more" preferred so that
// the HMAC output is stable. Otherwise later configuring UV on the
// authenticator could cause the hmac-secret outputs to change as a
// different seed is used for UV and non-UV assertions.
(!request.hmac_secret || !options_.supports_hmac_secret)))) {
if (!pin_supported && !uv_supported) {
return PINUVDisposition::kUVNotSupportedNorRequired;
}
return PINUVDisposition::kNoUVRequired;
}
// Authenticators with built-in UV that don't support UV token should try
// sending the request as-is with uv=true first.
if (uv_configured && !CanGetUvToken()) {
return (can_collect_pin && pin_supported)
? PINUVDisposition::kNoTokenInternalUVPINFallback
: PINUVDisposition::kNoTokenInternalUV;
}
const bool can_get_token =
(can_collect_pin && pin_supported) || CanGetUvToken();
if (can_get_token) {
return PINUVDisposition::kGetToken;
}
if (uv_requirement == UserVerificationRequirement::kPreferred) {
return PINUVDisposition::kNoUVRequired;
}
return PINUVDisposition::kUnsatisfiable;
}
FidoAuthenticator::PINUVDisposition
FidoDeviceAuthenticator::PINUVDispositionForGetAssertion(
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
// TODO(crbug.com/1149405): GetAssertion requests don't allow in-line UV
// enrollment. Perhaps we should change this and align with MakeCredential
// behavior.
const bool can_collect_pin = observer && observer->SupportsPIN();
const bool pin_supported =
options_.client_pin_availability != ClientPinAvailability::kNotSupported;
const bool uv_supported = options_.user_verification_availability !=
UserVerificationAvailability::kNotSupported;
const bool pin_configured = options_.client_pin_availability ==
ClientPinAvailability::kSupportedAndPinSet;
const bool uv_configured =
options_.user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured;
const UserVerificationRequirement uv_requirement =
request.allow_list.empty() ? UserVerificationRequirement::kRequired
: request.user_verification;
if (uv_requirement == UserVerificationRequirement::kDiscouraged ||
(uv_requirement == UserVerificationRequirement::kPreferred &&
((!pin_configured || !can_collect_pin) && !uv_configured))) {
if (!pin_supported && !uv_supported) {
return PINUVDisposition::kUVNotSupportedNorRequired;
}
return PINUVDisposition::kNoUVRequired;
}
// Authenticators with built-in UV that don't support UV token should try
// sending the request as-is with uv=true first.
if (uv_configured && !CanGetUvToken()) {
return (can_collect_pin && pin_configured)
? PINUVDisposition::kNoTokenInternalUVPINFallback
: PINUVDisposition::kNoTokenInternalUV;
}
if ((can_collect_pin && pin_configured) || CanGetUvToken()) {
return PINUVDisposition::kGetToken;
}
return PINUVDisposition::kUnsatisfiable;
}
void FidoDeviceAuthenticator::GetCredentialsMetadata(
const pin::TokenResponse& pin_token,
GetCredentialsMetadataCallback callback) {
DCHECK(options_.supports_credential_management ||
options_.supports_credential_management_preview);
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<CredentialManagementRequest, CredentialsMetadataResponse>(
CredentialManagementRequest::ForGetCredsMetadata(
GetCredentialManagementRequestVersion(options_), pin_token),
std::move(callback), base::BindOnce(&CredentialsMetadataResponse::Parse));
}
struct FidoDeviceAuthenticator::EnumerateCredentialsState {
explicit EnumerateCredentialsState(pin::TokenResponse pin_token_)
: pin_token(pin_token_) {}
EnumerateCredentialsState(EnumerateCredentialsState&&) = default;
EnumerateCredentialsState& operator=(EnumerateCredentialsState&&) = default;
pin::TokenResponse pin_token;
size_t rp_count = 0;
size_t current_rp = 0;
size_t current_rp_credential_count = 0;
FidoDeviceAuthenticator::EnumerateCredentialsCallback callback;
std::vector<AggregatedEnumerateCredentialsResponse> responses;
std::vector<std::array<uint8_t, kRpIdHashLength>> rp_id_hashes;
};
void FidoDeviceAuthenticator::EnumerateCredentials(
const pin::TokenResponse& pin_token,
EnumerateCredentialsCallback callback) {
DCHECK(options_.supports_credential_management ||
options_.supports_credential_management_preview);
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
EnumerateCredentialsState state(pin_token);
state.callback = std::move(callback);
RunOperation<CredentialManagementRequest, EnumerateRPsResponse>(
CredentialManagementRequest::ForEnumerateRPsBegin(
GetCredentialManagementRequestVersion(options_), pin_token),
base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone,
weak_factory_.GetWeakPtr(), std::move(state)),
base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/true),
&EnumerateRPsResponse::StringFixupPredicate);
}
// TaskClearProxy interposes |callback| and resets |task_| before it runs.
template <typename... Args>
void FidoDeviceAuthenticator::TaskClearProxy(
base::OnceCallback<void(Args...)> callback,
Args... args) {
DCHECK(task_);
DCHECK(!operation_);
task_.reset();
std::move(callback).Run(std::forward<Args>(args)...);
}
// OperationClearProxy interposes |callback| and resets |operation_| before it
// runs.
template <typename... Args>
void FidoDeviceAuthenticator::OperationClearProxy(
base::OnceCallback<void(Args...)> callback,
Args... args) {
DCHECK(operation_);
DCHECK(!task_);
operation_.reset();
std::move(callback).Run(std::forward<Args>(args)...);
}
// RunTask starts a |FidoTask| and ensures that |task_| is reset when the given
// callback is called.
template <typename Task, typename Response, typename... RequestArgs>
void FidoDeviceAuthenticator::RunTask(
RequestArgs&&... request_args,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<Response>)>
callback) {
DCHECK(!task_);
DCHECK(!operation_);
DCHECK(initialized_);
task_ = std::make_unique<Task>(
device_.get(), std::forward<RequestArgs>(request_args)...,
base::BindOnce(
&FidoDeviceAuthenticator::TaskClearProxy<CtapDeviceResponseCode,
absl::optional<Response>>,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
// RunOperation starts a |Ctap2DeviceOperation| and ensures that |operation_| is
// reset when the given completion callback is called.
template <typename Request, typename Response>
void FidoDeviceAuthenticator::RunOperation(
Request request,
base::OnceCallback<void(CtapDeviceResponseCode, absl::optional<Response>)>
callback,
base::OnceCallback<
absl::optional<Response>(const absl::optional<cbor::Value>&)> parser,
bool (*string_fixup_predicate)(const std::vector<const cbor::Value*>&)) {
DCHECK(!task_);
DCHECK(!operation_);
DCHECK(initialized_);
operation_ = std::make_unique<Ctap2DeviceOperation<Request, Response>>(
device_.get(), std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OperationClearProxy<
CtapDeviceResponseCode, absl::optional<Response>>,
weak_factory_.GetWeakPtr(), std::move(callback)),
std::move(parser), string_fixup_predicate);
operation_->Start();
}
void FidoDeviceAuthenticator::OnEnumerateRPsDone(
EnumerateCredentialsState state,
CtapDeviceResponseCode status,
absl::optional<EnumerateRPsResponse> response) {
DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size());
DCHECK_LE(state.rp_id_hashes.size(), state.rp_count);
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(state.callback).Run(status, absl::nullopt);
return;
}
if (state.rp_count == 0) {
if (response->rp_count == 0) {
std::move(state.callback).Run(status, std::move(state.responses));
return;
}
state.rp_count = response->rp_count;
}
DCHECK(response->rp);
DCHECK(response->rp_id_hash);
state.rp_id_hashes.push_back(*response->rp_id_hash);
state.responses.emplace_back(*response->rp);
if (state.rp_id_hashes.size() < state.rp_count) {
// Get the next RP.
RunOperation<CredentialManagementRequest, EnumerateRPsResponse>(
CredentialManagementRequest::ForEnumerateRPsGetNext(
GetCredentialManagementRequestVersion(options_)),
base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone,
weak_factory_.GetWeakPtr(), std::move(state)),
base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/false),
&EnumerateRPsResponse::StringFixupPredicate);
return;
}
auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin(
GetCredentialManagementRequestVersion(options_), state.pin_token,
state.rp_id_hashes.front());
RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>(
std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone,
weak_factory_.GetWeakPtr(), std::move(state)),
base::BindOnce(&EnumerateCredentialsResponse::Parse,
/*expect_credential_count=*/true),
&EnumerateCredentialsResponse::StringFixupPredicate);
}
void FidoDeviceAuthenticator::OnEnumerateCredentialsDone(
EnumerateCredentialsState state,
CtapDeviceResponseCode status,
absl::optional<EnumerateCredentialsResponse> response) {
DCHECK_EQ(state.rp_id_hashes.size(), state.responses.size());
DCHECK_EQ(state.rp_id_hashes.size(), state.rp_count);
DCHECK_LT(state.current_rp, state.rp_count);
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(state.callback).Run(status, absl::nullopt);
return;
}
if (state.current_rp_credential_count == 0) {
// First credential for this RP.
DCHECK_GT(response->credential_count, 0u);
state.current_rp_credential_count = response->credential_count;
}
AggregatedEnumerateCredentialsResponse& current_aggregated_response =
state.responses.at(state.current_rp);
current_aggregated_response.credentials.push_back(std::move(*response));
if (current_aggregated_response.credentials.size() <
state.current_rp_credential_count) {
// Fetch the next credential for this RP.
RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>(
CredentialManagementRequest::ForEnumerateCredentialsGetNext(
GetCredentialManagementRequestVersion(options_)),
base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone,
weak_factory_.GetWeakPtr(), std::move(state)),
base::BindOnce(&EnumerateCredentialsResponse::Parse,
/*expect_credential_count=*/false),
&EnumerateCredentialsResponse::StringFixupPredicate);
return;
}
if (++state.current_rp < state.rp_count) {
// Enumerate credentials for the next RP.
state.current_rp_credential_count = 0;
auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin(
GetCredentialManagementRequestVersion(options_), state.pin_token,
state.rp_id_hashes.at(state.current_rp));
RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>(
std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone,
weak_factory_.GetWeakPtr(), std::move(state)),
base::BindOnce(&EnumerateCredentialsResponse::Parse,
/*expect_credential_count=*/true),
&EnumerateCredentialsResponse::StringFixupPredicate);
return;
}
std::move(state.callback)
.Run(CtapDeviceResponseCode::kSuccess, std::move(state.responses));
return;
}
void FidoDeviceAuthenticator::DeleteCredential(
const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
DeleteCredentialCallback callback) {
DCHECK(options_.supports_credential_management ||
options_.supports_credential_management_preview);
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<CredentialManagementRequest, DeleteCredentialResponse>(
CredentialManagementRequest::ForDeleteCredential(
GetCredentialManagementRequestVersion(options_), pin_token,
credential_id),
std::move(callback), base::BindOnce(&DeleteCredentialResponse::Parse),
/*string_fixup_predicate=*/nullptr);
}
bool FidoDeviceAuthenticator::SupportsUpdateUserInformation() const {
return device_->device_info() &&
device_->device_info()->SupportsAtLeast(Ctap2Version::kCtap2_1);
}
void FidoDeviceAuthenticator::UpdateUserInformation(
const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
const PublicKeyCredentialUserEntity& updated_user,
UpdateUserInformationCallback callback) {
DCHECK(options_.supports_credential_management ||
options_.supports_credential_management_preview);
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<CredentialManagementRequest, UpdateUserInformationResponse>(
CredentialManagementRequest::ForUpdateUserInformation(
GetCredentialManagementRequestVersion(options_), pin_token,
credential_id, updated_user),
std::move(callback),
base::BindOnce(&UpdateUserInformationResponse::Parse),
/*string_fixup_predicate=*/nullptr);
}
void FidoDeviceAuthenticator::GetModality(BioEnrollmentCallback callback) {
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForGetModality(
GetBioEnrollmentRequestVersion(options_)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::GetSensorInfo(BioEnrollmentCallback callback) {
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForGetSensorInfo(
GetBioEnrollmentRequestVersion(options_)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::BioEnrollFingerprint(
const pin::TokenResponse& pin_token,
absl::optional<std::vector<uint8_t>> template_id,
BioEnrollmentCallback callback) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
template_id
? BioEnrollmentRequest::ForEnrollNextSample(
GetBioEnrollmentRequestVersion(options_), std::move(pin_token),
std::move(*template_id))
: BioEnrollmentRequest::ForEnrollBegin(
GetBioEnrollmentRequestVersion(options_), std::move(pin_token)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::BioEnrollRename(
const pin::TokenResponse& pin_token,
std::vector<uint8_t> id,
std::string name,
BioEnrollmentCallback callback) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForRename(GetBioEnrollmentRequestVersion(options_),
pin_token, std::move(id),
std::move(name)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::BioEnrollDelete(
const pin::TokenResponse& pin_token,
std::vector<uint8_t> template_id,
BioEnrollmentCallback callback) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForDelete(GetBioEnrollmentRequestVersion(options_),
pin_token, std::move(template_id)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::BioEnrollCancel(BioEnrollmentCallback callback) {
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForCancel(GetBioEnrollmentRequestVersion(options_)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::BioEnrollEnumerate(
const pin::TokenResponse& pin_token,
BioEnrollmentCallback callback) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_token.protocol());
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForEnumerate(
GetBioEnrollmentRequestVersion(options_), std::move(pin_token)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
void FidoDeviceAuthenticator::OnWroteLargeBlobForGetAssertion(
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status) {
switch (status) {
case CtapDeviceResponseCode::kSuccess:
LogLargeBlobResult(LargeBlobKeyWriteResult::kSuccess);
break;
case CtapDeviceResponseCode::kCtap2ErrRequestTooLarge:
LogLargeBlobResult(LargeBlobKeyWriteResult::kNotEnoughSpace);
break;
default:
LogLargeBlobResult(LargeBlobKeyWriteResult::kCtapError);
}
responses.at(0).large_blob_written =
status == CtapDeviceResponseCode::kSuccess;
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
}
void FidoDeviceAuthenticator::OnReadLargeBlobForGetAssertion(
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
CtapDeviceResponseCode status,
absl::optional<std::vector<std::pair<LargeBlobKey, LargeBlob>>> blobs) {
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "Reading large blob failed with code "
<< static_cast<int>(status);
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
return;
}
if (blobs->empty()) {
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
return;
}
std::pair<LargeBlobKey, LargeBlob> next = std::move(blobs->back());
blobs->pop_back();
data_decoder_.Inflate(
std::move(next.second.compressed_data), next.second.original_size,
base::BindOnce(&FidoDeviceAuthenticator::OnBlobUncompressed,
weak_factory_.GetWeakPtr(), std::move(responses),
std::move(*blobs), std::move(next.first),
std::move(callback)));
}
void FidoDeviceAuthenticator::OnBlobUncompressed(
std::vector<AuthenticatorGetAssertionResponse> responses,
std::vector<std::pair<LargeBlobKey, LargeBlob>> blobs,
LargeBlobKey uncompressed_key,
GetAssertionCallback callback,
base::expected<mojo_base::BigBuffer, std::string> result) {
if (result.has_value()) {
bool set_blob = false;
for (auto& response : responses) {
if (response.large_blob_key == uncompressed_key) {
response.large_blob = fido_parsing_utils::Materialize(result.value());
set_blob = true;
break;
}
}
DCHECK(set_blob);
} else {
FIDO_LOG(ERROR) << "Could not uncompress blob: " << result.error();
}
if (blobs.empty()) {
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
return;
}
std::pair<LargeBlobKey, LargeBlob> next = std::move(blobs.back());
blobs.pop_back();
data_decoder_.Inflate(
std::move(next.second.compressed_data), next.second.original_size,
base::BindOnce(&FidoDeviceAuthenticator::OnBlobUncompressed,
weak_factory_.GetWeakPtr(), std::move(responses),
std::move(blobs), std::move(next.first),
std::move(callback)));
}
void FidoDeviceAuthenticator::OnLargeBlobExtensionUncompressed(
std::vector<AuthenticatorGetAssertionResponse> responses,
GetAssertionCallback callback,
base::expected<mojo_base::BigBuffer, std::string> result) {
DCHECK_EQ(responses.size(), 1u);
if (result.has_value()) {
responses.at(0).large_blob =
fido_parsing_utils::Materialize(result.value());
} else {
FIDO_LOG(ERROR) << "Could not uncompress blob: " << result.error();
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(responses));
}
void FidoDeviceAuthenticator::ReadLargeBlob(
const std::vector<LargeBlobKey>& large_blob_keys,
LargeBlobReadCallback callback) {
DCHECK(!large_blob_keys.empty());
FetchLargeBlobArray(
LargeBlobArrayReader(),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead,
weak_factory_.GetWeakPtr(), large_blob_keys,
std::move(callback)));
}
void FidoDeviceAuthenticator::GarbageCollectLargeBlob(
const pin::TokenResponse& pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
EnumerateCredentials(
pin_uv_auth_token,
base::BindOnce(
&FidoDeviceAuthenticator::OnCredentialsEnumeratedForGarbageCollect,
weak_factory_.GetWeakPtr(), pin_uv_auth_token, std::move(callback)));
}
void FidoDeviceAuthenticator::FetchLargeBlobArray(
LargeBlobArrayReader large_blob_array_reader,
base::OnceCallback<void(CtapDeviceResponseCode,
absl::optional<LargeBlobArrayReader>)> callback) {
size_t bytes_to_read = max_large_blob_fragment_length();
LargeBlobsRequest request =
LargeBlobsRequest::ForRead(bytes_to_read, large_blob_array_reader.size());
RunOperation<LargeBlobsRequest, LargeBlobsResponse>(
std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OnReadLargeBlobFragment,
weak_factory_.GetWeakPtr(), bytes_to_read,
std::move(large_blob_array_reader), std::move(callback)),
base::BindOnce(&LargeBlobsResponse::ParseForRead, bytes_to_read));
}
void FidoDeviceAuthenticator::OnReadLargeBlobFragment(
const size_t bytes_requested,
LargeBlobArrayReader large_blob_array_reader,
base::OnceCallback<void(CtapDeviceResponseCode,
absl::optional<LargeBlobArrayReader>)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobsResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
DCHECK(response && response->config());
large_blob_array_reader.Append(*response->config());
if (response->config()->size() == bytes_requested) {
// More data may be available, read the next fragment.
FetchLargeBlobArray(std::move(large_blob_array_reader),
std::move(callback));
return;
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
std::move(large_blob_array_reader));
}
void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite(
const LargeBlobKey& large_blob_key,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
FIDO_LOG(ERROR) << "Large blob array corrupted. Replacing with a new one";
large_blob_array.emplace();
}
auto existing_large_blob = base::ranges::find_if(
*large_blob_array, [&large_blob_key](const cbor::Value& blob_cbor) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
return blob && blob->Decrypt(large_blob_key).has_value();
});
cbor::Value new_blob =
LargeBlobData(large_blob_key, std::move(*large_blob_)).AsCBOR();
large_blob_.reset();
if (existing_large_blob != large_blob_array->end()) {
*existing_large_blob = std::move(new_blob);
} else {
large_blob_array->emplace_back(std::move(new_blob));
}
LargeBlobArrayWriter writer(std::move(*large_blob_array));
if (writer.size() >
device_->device_info()->max_serialized_large_blob_array.value_or(
kMinLargeBlobSize)) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrRequestTooLarge);
return;
}
WriteLargeBlobArray(std::move(pin_uv_auth_token), std::move(writer),
std::move(callback));
}
void FidoDeviceAuthenticator::WriteLargeBlobArray(
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
LargeBlobArrayWriter large_blob_array_writer,
base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
LargeBlobArrayFragment fragment =
large_blob_array_writer.Pop(max_large_blob_fragment_length());
LargeBlobsRequest request = LargeBlobsRequest::ForWrite(
std::move(fragment), large_blob_array_writer.size());
if (pin_uv_auth_token) {
DCHECK(chosen_pin_uv_auth_protocol_ == pin_uv_auth_token->protocol());
request.SetPinParam(*pin_uv_auth_token);
}
RunOperation<LargeBlobsRequest, LargeBlobsResponse>(
std::move(request),
base::BindOnce(&FidoDeviceAuthenticator::OnWriteLargeBlobFragment,
weak_factory_.GetWeakPtr(),
std::move(large_blob_array_writer),
std::move(pin_uv_auth_token), std::move(callback)),
base::BindOnce(&LargeBlobsResponse::ParseForWrite));
}
void FidoDeviceAuthenticator::OnWriteLargeBlobFragment(
LargeBlobArrayWriter large_blob_array_writer,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobsResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
if (large_blob_array_writer.has_remaining_fragments()) {
WriteLargeBlobArray(std::move(pin_uv_auth_token),
std::move(large_blob_array_writer),
std::move(callback));
return;
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess);
}
void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead(
const std::vector<LargeBlobKey>& large_blob_keys,
LargeBlobReadCallback callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrIntegrityFailure,
absl::nullopt);
return;
}
std::vector<std::pair<LargeBlobKey, LargeBlob>> result;
for (const cbor::Value& blob_cbor : *large_blob_array) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
if (!blob.has_value()) {
continue;
}
for (const LargeBlobKey& key : large_blob_keys) {
absl::optional<LargeBlob> plaintext = blob->Decrypt(key);
if (plaintext) {
result.emplace_back(key, std::move(*plaintext));
break;
}
}
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::move(result));
}
void FidoDeviceAuthenticator::OnCredentialsEnumeratedForGarbageCollect(
const pin::TokenResponse& pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<std::vector<AggregatedEnumerateCredentialsResponse>>
credentials) {
if (status == CtapDeviceResponseCode::kCtap2ErrNoCredentials) {
credentials.emplace();
} else if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
FetchLargeBlobArray(
LargeBlobArrayReader(),
base::BindOnce(
&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForGarbageCollect,
weak_factory_.GetWeakPtr(), std::move(*credentials),
pin_uv_auth_token, std::move(callback)));
}
void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForGarbageCollect(
std::vector<AggregatedEnumerateCredentialsResponse> credentials,
const pin::TokenResponse& pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback,
CtapDeviceResponseCode status,
absl::optional<LargeBlobArrayReader> large_blob_array_reader) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status);
return;
}
absl::optional<cbor::Value::ArrayValue> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
FIDO_LOG(ERROR) << "Large blob array corrupted. Replacing with a new one";
WriteLargeBlobArray(std::move(pin_uv_auth_token), LargeBlobArrayWriter({}),
std::move(callback));
return;
}
std::vector<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_keys;
for (const auto& cred_by_rp : credentials) {
for (const auto& credential : cred_by_rp.credentials) {
if (credential.large_blob_key) {
large_blob_keys.push_back(*credential.large_blob_key);
}
}
}
bool did_erase = base::EraseIf(
*large_blob_array, [&large_blob_keys](const cbor::Value& blob_cbor) {
absl::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
return blob &&
base::ranges::none_of(
large_blob_keys,
[&blob](
const std::array<uint8_t, kLargeBlobKeyLength>& key) {
return blob->Decrypt(key);
});
});
if (!did_erase) {
// No need to update the blob.
std::move(callback).Run(CtapDeviceResponseCode::kSuccess);
return;
}
LargeBlobArrayWriter writer(std::move(*large_blob_array));
DCHECK_LE(writer.size(),
device_->device_info()->max_serialized_large_blob_array.value_or(
kMinLargeBlobSize));
WriteLargeBlobArray(std::move(pin_uv_auth_token), std::move(writer),
std::move(callback));
}
void FidoDeviceAuthenticator::LogLargeBlobResult(
LargeBlobKeyWriteResult result) {
if (options_.large_blob_type == LargeBlobSupportType::kKey) {
base::UmaHistogramEnumeration("WebAuthentication.LargeBlobKey.WriteResult",
result);
}
}
absl::optional<base::span<const int32_t>>
FidoDeviceAuthenticator::GetAlgorithms() {
if (device_->supported_protocol() == ProtocolVersion::kU2f) {
static constexpr int32_t kU2fAlgorithms[1] = {
static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256)};
return kU2fAlgorithms;
}
const absl::optional<AuthenticatorGetInfoResponse>& get_info_response =
device_->device_info();
if (get_info_response) {
return get_info_response->algorithms;
}
return absl::nullopt;
}
bool FidoDeviceAuthenticator::DiscoverableCredentialStorageFull() const {
return device_->device_info()->remaining_discoverable_credentials == 0u;
}
void FidoDeviceAuthenticator::Reset(ResetCallback callback) {
DCHECK(initialized_);
RunOperation<pin::ResetRequest, pin::ResetResponse>(
pin::ResetRequest(), std::move(callback),
base::BindOnce(&pin::ResetResponse::Parse));
}
void FidoDeviceAuthenticator::Cancel() {
if (operation_) {
operation_->Cancel();
}
if (task_) {
task_->Cancel();
}
}
std::string FidoDeviceAuthenticator::GetId() const {
return device_->GetId();
}
std::string FidoDeviceAuthenticator::GetDisplayName() const {
return device_->GetDisplayName();
}
ProtocolVersion FidoDeviceAuthenticator::SupportedProtocol() const {
DCHECK(initialized_);
return device_->supported_protocol();
}
const AuthenticatorSupportedOptions& FidoDeviceAuthenticator::Options() const {
return options_;
}
absl::optional<FidoTransportProtocol>
FidoDeviceAuthenticator::AuthenticatorTransport() const {
return device_->DeviceTransport();
}
void FidoDeviceAuthenticator::SetTaskForTesting(
std::unique_ptr<FidoTask> task) {
task_ = std::move(task);
}
void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) {
DCHECK(options_.user_verification_availability !=
UserVerificationAvailability::kNotSupported);
DCHECK(chosen_pin_uv_auth_protocol_);
RunOperation<pin::UvRetriesRequest, pin::RetriesResponse>(
pin::UvRetriesRequest{*chosen_pin_uv_auth_protocol_}, std::move(callback),
base::BindOnce(&pin::RetriesResponse::ParseUvRetries));
}
bool FidoDeviceAuthenticator::CanGetUvToken() {
return options_.user_verification_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured &&
options_.supports_pin_uv_auth_token;
}
void FidoDeviceAuthenticator::GetUvToken(
std::vector<pin::Permissions> permissions,
absl::optional<std::string> rp_id,
GetTokenCallback callback) {
GetEphemeralKey(
base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken,
weak_factory_.GetWeakPtr(), std::move(rp_id),
std::move(permissions), std::move(callback)));
}
uint32_t FidoDeviceAuthenticator::CurrentMinPINLength() {
return ForcePINChange() ? kMinPinLength : NewMinPINLength();
}
uint32_t FidoDeviceAuthenticator::NewMinPINLength() {
return device()->device_info()->min_pin_length.value_or(kMinPinLength);
}
bool FidoDeviceAuthenticator::ForcePINChange() {
return device()->device_info()->force_pin_change.value_or(false);
}
void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken(
absl::optional<std::string> rp_id,
std::vector<pin::Permissions> permissions,
GetTokenCallback callback,
CtapDeviceResponseCode status,
absl::optional<pin::KeyAgreementResponse> key) {
if (status != CtapDeviceResponseCode::kSuccess) {
std::move(callback).Run(status, absl::nullopt);
return;
}
DCHECK(key);
pin::UvTokenRequest request(*chosen_pin_uv_auth_protocol_, *key,
std::move(rp_id), permissions);
std::vector<uint8_t> shared_key = request.shared_key();
RunOperation<pin::UvTokenRequest, pin::TokenResponse>(
std::move(request), std::move(callback),
base::BindOnce(&pin::TokenResponse::Parse, *chosen_pin_uv_auth_protocol_,
std::move(shared_key)));
}
size_t FidoDeviceAuthenticator::max_large_blob_fragment_length() {
return device_->device_info()->max_msg_size
? *device_->device_info()->max_msg_size -
kLargeBlobReadEncodingOverhead
: kLargeBlobDefaultMaxFragmentLength;
}
base::WeakPtr<FidoAuthenticator> FidoDeviceAuthenticator::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device