blob: e1d431cbcb5b0718a2795eff8b91009606f8c0bd [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/bind.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "device/fido/appid_exclude_probe_task.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/credential_management.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/features.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"
#include "device/fido/virtual_fido_device.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::SequencedTaskRunnerHandle::Get()->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(!options_);
switch (device_->supported_protocol()) {
case ProtocolVersion::kU2f:
options_ = AuthenticatorSupportedOptions();
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);
}
break;
case ProtocolVersion::kUnknown:
NOTREACHED() << "uninitialized device";
options_ = AuthenticatorSupportedOptions();
}
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". However, if the request is U2F-only
// then that doesn't apply and UV must be set to discouraged so that the
// request can be translated to U2F.
if (!request.pin_auth &&
options_->user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured &&
!options_->make_cred_uv_not_required &&
!request_options.make_u2f_api_credential) {
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.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, absl::nullopt);
return;
}
options.pin_key_agreement = std::move(*key);
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;
}
RunTask<GetAssertionTask, AuthenticatorGetAssertionResponse,
CtapGetAssertionRequest, CtapGetAssertionOptions>(
std::move(request), std::move(options), std::move(callback));
}
void FidoDeviceAuthenticator::GetNextAssertion(GetAssertionCallback callback) {
RunOperation<CtapGetNextAssertionRequest, AuthenticatorGetAssertionResponse>(
CtapGetNextAssertionRequest(), std::move(callback),
base::BindOnce(&ReadCTAPGetAssertionResponse, device_->DeviceTransport()),
GetAssertionTask::StringFixupPredicate);
}
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());
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());
DCHECK(Options()->client_pin_availability !=
ClientPinAvailability::kNotSupported ||
Options()->supports_pin_uv_auth_token ||
SupportsHMACSecretExtension());
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());
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());
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());
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(device_->SupportedProtocolIsInitialized());
DCHECK(options_);
const bool can_collect_pin = observer && observer->SupportsPIN();
const bool pin_supported = Options()->client_pin_availability !=
ClientPinAvailability::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))) {
return PINUVDisposition::kNoUV;
}
// 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;
}
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_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))) {
return PINUVDisposition::kNoUV;
}
// 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(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
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(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
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::WriteLargeBlob(
LargeBlob large_blob,
const LargeBlobKey& large_blob_key,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
auto pin_uv_auth_token_copy = pin_uv_auth_token;
FetchLargeBlobArray(
pin_uv_auth_token_copy, LargeBlobArrayReader(),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite,
weak_factory_.GetWeakPtr(), large_blob, large_blob_key,
std::move(pin_uv_auth_token), std::move(callback)));
}
void FidoDeviceAuthenticator::ReadLargeBlob(
const std::vector<LargeBlobKey>& large_blob_keys,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
LargeBlobReadCallback callback) {
DCHECK(!large_blob_keys.empty());
FetchLargeBlobArray(
std::move(pin_uv_auth_token), LargeBlobArrayReader(),
base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead,
weak_factory_.GetWeakPtr(), large_blob_keys,
std::move(callback)));
}
void FidoDeviceAuthenticator::FetchLargeBlobArray(
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
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(pin_uv_auth_token), std::move(callback)),
base::BindOnce(&LargeBlobsResponse::ParseForRead, bytes_to_read));
}
void FidoDeviceAuthenticator::OnReadLargeBlobFragment(
const size_t bytes_requested,
LargeBlobArrayReader large_blob_array_reader,
const absl::optional<pin::TokenResponse> pin_uv_auth_token,
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(pin_uv_auth_token),
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(
LargeBlob large_blob,
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<std::vector<LargeBlobData>> large_blob_array =
large_blob_array_reader->Materialize();
if (!large_blob_array) {
// The large blob array is corrupted. Replace it completely with a new one.
// TODO(nsatragno): but maybe we want to do something else like trying
// again? It might have been corrupted while transported. Decide when we
// have hardware to test.
large_blob_array.emplace();
return;
}
auto existing_large_blob =
std::find_if(large_blob_array->begin(), large_blob_array->end(),
[&large_blob_key](const LargeBlobData& blob) {
return blob.Decrypt(large_blob_key);
});
LargeBlobData new_large_blob_data(large_blob_key, std::move(large_blob));
if (existing_large_blob != large_blob_array->end()) {
*existing_large_blob = std::move(new_large_blob_data);
} else {
large_blob_array->emplace_back(std::move(new_large_blob_data));
}
LargeBlobArrayWriter writer(*large_blob_array);
if (writer.size() >
*device_->device_info()->max_serialized_large_blob_array) {
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<std::vector<LargeBlobData>> 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 LargeBlobData& blob : *large_blob_array) {
for (const LargeBlobKey& key : large_blob_keys) {
absl::optional<LargeBlob> plaintext = blob.Decrypt(key);
if (plaintext) {
result.emplace_back(std::make_pair(key, std::move(*plaintext)));
break;
}
}
}
std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::move(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(device_->SupportedProtocolIsInitialized())
<< "InitializeAuthenticator() must be called first.";
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(device_->SupportedProtocolIsInitialized());
return device_->supported_protocol();
}
bool FidoDeviceAuthenticator::SupportsHMACSecretExtension() const {
const absl::optional<AuthenticatorGetInfoResponse>& get_info_response =
device_->device_info();
return get_info_response && get_info_response->extensions &&
base::Contains(*get_info_response->extensions, kExtensionHmacSecret);
}
bool FidoDeviceAuthenticator::SupportsEnterpriseAttestation() const {
DCHECK(device_->SupportedProtocolIsInitialized());
if (device_->supported_protocol() == ProtocolVersion::kU2f) {
// U2F devices always "support" enterprise attestation because it turns into
// a bit in the makeCredential command that is ignored if not supported.
return true;
}
return options_ && options_->enterprise_attestation;
}
bool FidoDeviceAuthenticator::SupportsCredBlobOfSize(size_t num_bytes) const {
const absl::optional<AuthenticatorGetInfoResponse>& get_info_response =
device_->device_info();
return get_info_response && get_info_response->max_cred_blob_length &&
num_bytes <= get_info_response->max_cred_blob_length.value();
}
const absl::optional<AuthenticatorSupportedOptions>&
FidoDeviceAuthenticator::Options() const {
return options_;
}
absl::optional<FidoTransportProtocol>
FidoDeviceAuthenticator::AuthenticatorTransport() const {
return device_->DeviceTransport();
}
bool FidoDeviceAuthenticator::IsInPairingMode() const {
return device_->IsInPairingMode();
}
bool FidoDeviceAuthenticator::IsPaired() const {
return device_->IsPaired();
}
bool FidoDeviceAuthenticator::RequiresBlePairingPin() const {
return device_->RequiresBlePairingPin();
}
void FidoDeviceAuthenticator::SetTaskForTesting(
std::unique_ptr<FidoTask> task) {
task_ = std::move(task);
}
void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) {
DCHECK(Options());
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