Commit 173b8417 authored by Martin Kreichgauer's avatar Martin Kreichgauer Committed by Commit Bot

fido: make GetAssertionRequestHandler use AuthTokenRequester

This replaces the bits of GetAssertionRequestHandler that request a
PIN/UV Auth Token by calling out to AuthTokenRequester instead. See
CL:2469445 for the equivalent CL for MakeCredential.

Bug: 1139111
Change-Id: I1669258ffabc679a5b6d0592ed14fbdaa710dc3f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2536930Reviewed-by: default avatarNina Satragno <nsatragno@chromium.org>
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#829271}
parent 93bac529
......@@ -73,18 +73,18 @@ void FidoAuthenticator::ChangePIN(const std::string& old_pin,
NOTREACHED();
}
FidoAuthenticator::MakeCredentialPINUVDisposition
FidoAuthenticator::PINUVDisposition
FidoAuthenticator::PINUVDispositionForMakeCredential(
const CtapMakeCredentialRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
return MakeCredentialPINUVDisposition::kNoUV;
return PINUVDisposition::kNoUV;
}
FidoAuthenticator::GetAssertionPINDisposition
FidoAuthenticator::WillNeedPINToGetAssertion(
FidoAuthenticator::PINUVDisposition
FidoAuthenticator::PINUVDispositionForGetAssertion(
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
return GetAssertionPINDisposition::kNoPIN;
return PINUVDisposition::kNoUV;
}
void FidoAuthenticator::GetCredentialsMetadata(
......
......@@ -146,9 +146,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
const std::string& new_pin,
SetPINCallback callback);
// MakeCredentialPINUVDisposition enumerates the possible options for
// obtaining user verification when making a credential.
enum class MakeCredentialPINUVDisposition {
// PINUVDisposition enumerates the possible options for obtaining user
// verification when making a CTAP2 request.
enum class PINUVDisposition {
// No UV (neither clientPIN nor internal) is needed to make this
// credential.
kNoUV,
......@@ -165,31 +165,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
// The request cannot be satisfied by this authenticator.
kUnsatisfiable,
};
// PINUVDispositionForMakeCredential returns whether and how user verification
// PINUVDisposition returns whether and how user verification
// should be obtained in order to serve the given request on this
// authenticator.
virtual MakeCredentialPINUVDisposition PINUVDispositionForMakeCredential(
virtual PINUVDisposition PINUVDispositionForMakeCredential(
const CtapMakeCredentialRequest& request,
const FidoRequestHandlerBase::Observer* observer);
// GetAssertionPINDisposition enumerates the possible interactions between
// a user-verification level and the PIN support of an authenticator when
// getting an assertion.
enum class GetAssertionPINDisposition {
// kNoPIN means that a PIN will not be needed for this assertion.
kNoPIN,
// kUsePIN means that a PIN must be gathered and used for this assertion.
kUsePIN,
// kUsePINForFallback means that a PIN may be used for fallback if internal
// user verification fails.
kUsePINForFallback,
// kUnsatisfiable means that the request cannot be satisfied by this
// authenticator.
kUnsatisfiable,
};
// WillNeedPINToGetAssertion returns whether a PIN prompt will be needed to
// serve the given request on this authenticator.
virtual GetAssertionPINDisposition WillNeedPINToGetAssertion(
virtual PINUVDisposition PINUVDispositionForGetAssertion(
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer);
......
......@@ -349,7 +349,7 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForChangePIN(
std::move(callback), base::BindOnce(&pin::EmptyResponse::Parse));
}
FidoAuthenticator::MakeCredentialPINUVDisposition
FidoAuthenticator::PINUVDisposition
FidoDeviceAuthenticator::PINUVDispositionForMakeCredential(
const CtapMakeCredentialRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
......@@ -363,9 +363,6 @@ FidoDeviceAuthenticator::PINUVDispositionForMakeCredential(
Options()->user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured;
const bool can_get_token =
(can_collect_pin && pin_supported) || CanGetUvToken();
// 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 =
......@@ -373,73 +370,74 @@ FidoDeviceAuthenticator::PINUVDispositionForMakeCredential(
device()->device_info()->versions.contains(ProtocolVersion::kU2f) &&
IsConvertibleToU2fRegisterCommand(request) &&
!ShouldPreferCTAP2EvenIfItNeedsAPIN(request);
const bool uv_required =
request.user_verification == UserVerificationRequirement::kRequired ||
(pin_configured && !u2f_fallback_possible);
const bool uv_preferred =
request.user_verification == UserVerificationRequirement::kPreferred;
if (!uv_required && !(uv_preferred && (pin_configured || uv_configured))) {
return MakeCredentialPINUVDisposition::kNoUV;
const UserVerificationRequirement uv_requirement =
(pin_configured && !u2f_fallback_possible)
? 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)
? MakeCredentialPINUVDisposition::kNoTokenInternalUVPINFallback
: MakeCredentialPINUVDisposition::kNoTokenInternalUV;
? PINUVDisposition::kNoTokenInternalUVPINFallback
: PINUVDisposition::kNoTokenInternalUV;
}
const bool can_get_token =
(can_collect_pin && pin_supported) || CanGetUvToken();
if (can_get_token) {
return MakeCredentialPINUVDisposition::kGetToken;
return PINUVDisposition::kGetToken;
}
return MakeCredentialPINUVDisposition::kUnsatisfiable;
return PINUVDisposition::kUnsatisfiable;
}
FidoAuthenticator::GetAssertionPINDisposition
FidoDeviceAuthenticator::WillNeedPINToGetAssertion(
FidoAuthenticator::PINUVDisposition
FidoDeviceAuthenticator::PINUVDispositionForGetAssertion(
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer) {
const bool can_use_pin = (Options()->client_pin_availability ==
AuthenticatorSupportedOptions::
ClientPinAvailability::kSupportedAndPinSet) &&
// The PIN is effectively unavailable if there's no
// UI support for collecting it.
observer && observer->SupportsPIN();
// Authenticators with built-in UV can use that.
if (Options()->user_verification_availability ==
UserVerificationAvailability::kSupportedAndConfigured) {
return can_use_pin ? GetAssertionPINDisposition::kUsePINForFallback
: GetAssertionPINDisposition::kNoPIN;
}
// 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 bool resident_key_request = request.allow_list.empty();
const UserVerificationRequirement uv_requirement =
request.allow_list.empty() ? UserVerificationRequirement::kRequired
: request.user_verification;
if (resident_key_request) {
if (can_use_pin) {
return GetAssertionPINDisposition::kUsePIN;
}
return GetAssertionPINDisposition::kUnsatisfiable;
if (uv_requirement == UserVerificationRequirement::kDiscouraged ||
(uv_requirement == UserVerificationRequirement::kPreferred &&
((!pin_configured || !can_collect_pin) && !uv_configured))) {
return PINUVDisposition::kNoUV;
}
// If UV is required then the PIN must be used if set, or else this request
// cannot be satisfied.
if (request.user_verification == UserVerificationRequirement::kRequired) {
if (can_use_pin) {
return GetAssertionPINDisposition::kUsePIN;
}
return GetAssertionPINDisposition::kUnsatisfiable;
// 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 UV is preferred and a PIN is set, use it.
if (request.user_verification == UserVerificationRequirement::kPreferred &&
can_use_pin) {
return GetAssertionPINDisposition::kUsePIN;
if ((can_collect_pin && pin_configured) || CanGetUvToken()) {
return PINUVDisposition::kGetToken;
}
return GetAssertionPINDisposition::kNoPIN;
return PINUVDisposition::kUnsatisfiable;
}
void FidoDeviceAuthenticator::GetCredentialsMetadata(
......
......@@ -67,13 +67,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
void ChangePIN(const std::string& old_pin,
const std::string& new_pin,
SetPINCallback callback) override;
MakeCredentialPINUVDisposition PINUVDispositionForMakeCredential(
PINUVDisposition PINUVDispositionForMakeCredential(
const CtapMakeCredentialRequest& request,
const FidoRequestHandlerBase::Observer* observer) override;
// WillNeedPINToGetAssertion returns whether a PIN prompt will be needed to
// serve the given request on this authenticator.
GetAssertionPINDisposition WillNeedPINToGetAssertion(
PINUVDisposition PINUVDispositionForGetAssertion(
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer) override;
......
......@@ -44,15 +44,15 @@ namespace device {
namespace {
using PINDisposition = FidoAuthenticator::GetAssertionPINDisposition;
using PINUVDisposition = FidoAuthenticator::PINUVDisposition;
const std::vector<pin::Permissions> GetPinTokenPermissionsFor(
const std::set<pin::Permissions> GetPinTokenPermissionsFor(
const FidoAuthenticator& authenticator,
const CtapGetAssertionRequest& request) {
std::vector<pin::Permissions> permissions = {pin::Permissions::kGetAssertion};
std::set<pin::Permissions> permissions = {pin::Permissions::kGetAssertion};
if (request.large_blob_write && authenticator.Options() &&
authenticator.Options()->supports_large_blobs) {
permissions.emplace_back(pin::Permissions::kLargeBlobWrite);
permissions.emplace(pin::Permissions::kLargeBlobWrite);
}
return permissions;
}
......@@ -75,8 +75,7 @@ base::Optional<GetAssertionStatus> ConvertDeviceResponseCode(
return GetAssertionStatus::kUserConsentDenied;
// External authenticators may return this error if internal user
// verification fails for a make credential request or if the pin token is
// not valid.
// verification fails or if the pin token is not valid.
case CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid:
return GetAssertionStatus::kUserConsentDenied;
......@@ -342,23 +341,20 @@ void GetAssertionRequestHandler::DispatchRequest(
CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator);
switch (authenticator->WillNeedPINToGetAssertion(request, observer())) {
case PINDisposition::kUsePIN:
// Skip asking for touch if this is the only available authenticator.
if (active_authenticators().size() == 1 && allow_skipping_pin_touch_) {
CollectPINThenSendRequest(authenticator);
return;
}
// A PIN will be needed. Just request a touch to let the user select
// this authenticator if they wish.
FIDO_LOG(DEBUG) << "Asking for touch from "
<< authenticator->GetDisplayName()
<< " because a PIN will be required";
authenticator->GetTouch(
base::BindOnce(&GetAssertionRequestHandler::CollectPINThenSendRequest,
weak_factory_.GetWeakPtr(), authenticator));
PINUVDisposition uv_disposition =
authenticator->PINUVDispositionForGetAssertion(request, observer());
switch (uv_disposition) {
case PINUVDisposition::kNoUV:
case PINUVDisposition::kNoTokenInternalUV:
case PINUVDisposition::kNoTokenInternalUVPINFallback:
// Proceed without a token.
break;
case PINUVDisposition::kGetToken:
ObtainPINUVAuthToken(authenticator,
GetPinTokenPermissionsFor(*authenticator, request),
allow_skipping_pin_touch_);
return;
case PINDisposition::kUnsatisfiable:
case PINUVDisposition::kUnsatisfiable:
FIDO_LOG(DEBUG) << authenticator->GetDisplayName()
<< " cannot satisfy assertion request. Requesting "
"touch in order to handle error case.";
......@@ -366,23 +362,10 @@ void GetAssertionRequestHandler::DispatchRequest(
&GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch,
weak_factory_.GetWeakPtr(), authenticator));
return;
case PINDisposition::kNoPIN:
case PINDisposition::kUsePINForFallback:
break;
}
if (request.user_verification != UserVerificationRequirement::kDiscouraged &&
authenticator->CanGetUvToken()) {
authenticator->GetUvRetries(
base::BindOnce(&GetAssertionRequestHandler::OnStartUvTokenOrFallback,
weak_factory_.GetWeakPtr(), authenticator));
return;
}
ReportGetAssertionRequestTransport(authenticator);
FIDO_LOG(DEBUG) << "Asking for assertion from "
<< authenticator->GetDisplayName();
CtapGetAssertionRequest request_copy(request);
authenticator->GetAssertion(
std::move(request_copy), options_,
......@@ -425,12 +408,15 @@ void GetAssertionRequestHandler::AuthenticatorRemoved(
FidoAuthenticator* authenticator) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
auth_token_requester_map_.erase(authenticator);
FidoRequestHandlerBase::AuthenticatorRemoved(discovery, authenticator);
if (authenticator == authenticator_) {
authenticator_ = nullptr;
if (state_ == State::kWaitingForPIN ||
state_ == State::kWaitingForSecondTouch) {
if (authenticator == selected_authenticator_for_pin_uv_auth_token_) {
selected_authenticator_for_pin_uv_auth_token_ = nullptr;
// Authenticator could have been removed during PIN entry or PIN fallback
// after failed internal UV. Bail and show an error.
if (state_ != State::kFinished) {
state_ = State::kFinished;
std::move(completion_callback_)
.Run(GetAssertionStatus::kAuthenticatorRemovedDuringPINEntry,
......@@ -439,6 +425,107 @@ void GetAssertionRequestHandler::AuthenticatorRemoved(
}
}
void GetAssertionRequestHandler::AuthenticatorSelectedForPINUVAuthToken(
FidoAuthenticator* authenticator) {
DCHECK_EQ(state_, State::kWaitingForTouch);
state_ = State::kWaitingForToken;
selected_authenticator_for_pin_uv_auth_token_ = authenticator;
base::EraseIf(auth_token_requester_map_, [authenticator](auto& entry) {
return entry.first != authenticator;
});
CancelActiveAuthenticators(authenticator->GetId());
}
void GetAssertionRequestHandler::CollectNewPIN(
uint32_t min_pin_length,
ProvidePINCallback provide_pin_cb) {
DCHECK_EQ(state_, State::kWaitingForToken);
observer()->CollectPIN(min_pin_length, /*attempts=*/base::nullopt,
std::move(provide_pin_cb));
}
void GetAssertionRequestHandler::CollectExistingPIN(
int attempts,
uint32_t min_pin_length,
ProvidePINCallback provide_pin_cb) {
DCHECK_EQ(state_, State::kWaitingForToken);
observer()->CollectPIN(min_pin_length, attempts, std::move(provide_pin_cb));
}
void GetAssertionRequestHandler::PromptForInternalUVRetry(int attempts) {
DCHECK(state_ == State::kWaitingForTouch ||
state_ == State::kWaitingForToken);
observer()->OnRetryUserVerification(attempts);
}
void GetAssertionRequestHandler::InternalUVLockedForAuthToken() {
DCHECK(state_ == State::kWaitingForTouch ||
state_ == State::kWaitingForToken);
observer()->OnInternalUserVerificationLocked();
}
void GetAssertionRequestHandler::HavePINUVAuthTokenResultForAuthenticator(
FidoAuthenticator* authenticator,
AuthTokenRequester::Result result,
base::Optional<pin::TokenResponse> token_response) {
DCHECK_EQ(state_, State::kWaitingForToken);
DCHECK_EQ(selected_authenticator_for_pin_uv_auth_token_, authenticator);
base::Optional<GetAssertionStatus> error;
switch (result) {
case AuthTokenRequester::Result::kPreTouchUnsatisfiableRequest:
case AuthTokenRequester::Result::kPreTouchAuthenticatorResponseInvalid:
FIDO_LOG(ERROR) << "Ignoring AuthTokenRequester::Result="
<< static_cast<int>(result) << " from "
<< authenticator->GetId();
return;
case AuthTokenRequester::Result::kPostTouchAuthenticatorInternalUVLock:
error = GetAssertionStatus::kAuthenticatorMissingUserVerification;
break;
case AuthTokenRequester::Result::kPostTouchAuthenticatorResponseInvalid:
error = GetAssertionStatus::kAuthenticatorResponseInvalid;
break;
case AuthTokenRequester::Result::kPostTouchAuthenticatorOperationDenied:
error = GetAssertionStatus::kUserConsentDenied;
break;
case AuthTokenRequester::Result::kPostTouchAuthenticatorPINSoftLock:
error = GetAssertionStatus::kSoftPINBlock;
break;
case AuthTokenRequester::Result::kPostTouchAuthenticatorPINHardLock:
error = GetAssertionStatus::kHardPINBlock;
break;
case AuthTokenRequester::Result::kSuccess:
break;
}
if (error) {
state_ = State::kFinished;
std::move(completion_callback_).Run(*error, base::nullopt, authenticator);
return;
}
DCHECK_EQ(result, AuthTokenRequester::Result::kSuccess);
auto request = std::make_unique<CtapGetAssertionRequest>(request_);
SpecializeRequestForAuthenticator(*request, *authenticator);
DispatchRequestWithToken(std::move(*token_response));
}
void GetAssertionRequestHandler::ObtainPINUVAuthToken(
FidoAuthenticator* authenticator,
std::set<pin::Permissions> permissions,
bool skip_pin_touch) {
AuthTokenRequester::Options options;
options.token_permissions = std::move(permissions);
options.rp_id = request_.rp_id;
options.skip_pin_touch = skip_pin_touch;
auth_token_requester_map_.insert(
{authenticator, std::make_unique<AuthTokenRequester>(
this, authenticator, std::move(options))});
auth_token_requester_map_.at(authenticator)->ObtainPINUVAuthToken();
}
void GetAssertionRequestHandler::HandleResponse(
FidoAuthenticator* authenticator,
CtapGetAssertionRequest request,
......@@ -448,7 +535,7 @@ void GetAssertionRequestHandler::HandleResponse(
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (state_ != State::kWaitingForTouch &&
state_ != State::kWaitingForSecondTouch) {
state_ != State::kWaitingForResponseWithToken) {
FIDO_LOG(DEBUG) << "Ignoring response from "
<< authenticator->GetDisplayName()
<< " because no longer waiting for touch";
......@@ -483,34 +570,36 @@ void GetAssertionRequestHandler::HandleResponse(
}
#endif
// Requests that require a PIN should follow the |GetTouch| path initially.
DCHECK(state_ == State::kWaitingForSecondTouch ||
authenticator->WillNeedPINToGetAssertion(request, observer()) !=
PINDisposition::kUsePIN);
if ((status == CtapDeviceResponseCode::kCtap2ErrPinRequired ||
// If we requested UV from an authentiator without uvToken support, UV failed,
// and the authenticator supports PIN, fall back to that.
if (request.user_verification != UserVerificationRequirement::kDiscouraged &&
!request.pin_auth &&
(status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrPinRequired ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied) &&
authenticator->WillNeedPINToGetAssertion(request, observer()) ==
PINDisposition::kUsePINForFallback) {
authenticator->PINUVDispositionForGetAssertion(request, observer()) ==
PINUVDisposition::kNoTokenInternalUVPINFallback) {
// Authenticators without uvToken support will return this error immediately
// without user interaction when internal UV is locked.
const base::TimeDelta response_time = request_timer.Elapsed();
if (response_time < kMinExpectedAuthenticatorResponseTime) {
FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time="
<< response_time;
authenticator->GetTouch(base::BindOnce(
&GetAssertionRequestHandler::StartPINFallbackForInternalUv,
weak_factory_.GetWeakPtr(), authenticator));
ObtainPINUVAuthToken(authenticator,
GetPinTokenPermissionsFor(*authenticator, request),
/*skip_pin_touch=*/false);
return;
}
StartPINFallbackForInternalUv(authenticator);
ObtainPINUVAuthToken(authenticator,
GetPinTokenPermissionsFor(*authenticator, request),
/*skip_pin_touch=*/true);
return;
}
const base::Optional<GetAssertionStatus> maybe_result =
ConvertDeviceResponseCode(status);
if (!maybe_result) {
if (state_ == State::kWaitingForSecondTouch) {
if (state_ == State::kWaitingForResponseWithToken) {
std::move(completion_callback_)
.Run(GetAssertionStatus::kAuthenticatorResponseInvalid, base::nullopt,
authenticator);
......@@ -613,34 +702,6 @@ void GetAssertionRequestHandler::HandleNextResponse(
OnGetAssertionSuccess(authenticator, std::move(request));
}
void GetAssertionRequestHandler::CollectPINThenSendRequest(
FidoAuthenticator* authenticator) {
if (state_ != State::kWaitingForTouch) {
return;
}
DCHECK_NE(authenticator->WillNeedPINToGetAssertion(
SpecializeRequestForAuthenticator(request_, *authenticator),
observer()),
PINDisposition::kNoPIN);
DCHECK(observer());
state_ = State::kGettingRetries;
CancelActiveAuthenticators(authenticator->GetId());
authenticator_ = authenticator;
authenticator_->GetPinRetries(
base::BindOnce(&GetAssertionRequestHandler::OnPinRetriesResponse,
weak_factory_.GetWeakPtr()));
}
void GetAssertionRequestHandler::StartPINFallbackForInternalUv(
FidoAuthenticator* authenticator) {
DCHECK_EQ(authenticator->WillNeedPINToGetAssertion(
SpecializeRequestForAuthenticator(request_, *authenticator),
observer()),
PINDisposition::kUsePINForFallback);
observer()->OnInternalUserVerificationLocked();
CollectPINThenSendRequest(authenticator);
}
void GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch(
FidoAuthenticator* authenticator) {
// User touched an authenticator that cannot handle this request or internal
......@@ -653,220 +714,27 @@ void GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch(
base::nullopt, nullptr);
}
void GetAssertionRequestHandler::OnPinRetriesResponse(
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
DCHECK_EQ(state_, State::kGettingRetries);
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "OnPinRetriesResponse() failed for "
<< authenticator_->GetDisplayName();
state_ = State::kFinished;
std::move(completion_callback_)
.Run(GetAssertionStatus::kAuthenticatorResponseInvalid, base::nullopt,
nullptr);
return;
}
if (response->retries == 0) {
state_ = State::kFinished;
std::move(completion_callback_)
.Run(GetAssertionStatus::kHardPINBlock, base::nullopt, nullptr);
return;
}
state_ = State::kWaitingForPIN;
observer()->CollectPIN(authenticator_->CurrentMinPINLength(),
response->retries,
base::BindOnce(&GetAssertionRequestHandler::OnHavePIN,
weak_factory_.GetWeakPtr()));
}
void GetAssertionRequestHandler::OnHavePIN(std::string pin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
DCHECK_EQ(State::kWaitingForPIN, state_);
DCHECK(pin::IsValid(pin));
if (authenticator_ == nullptr) {
// Authenticator was detached. The request will already have been canceled
// but this callback may have been waiting in a queue.
return;
}
state_ = State::kRequestWithPIN;
authenticator_->GetPINToken(
std::move(pin), GetPinTokenPermissionsFor(*authenticator_, request_),
request_.rp_id,
base::BindOnce(&GetAssertionRequestHandler::OnHavePINToken,
weak_factory_.GetWeakPtr()));
}
void GetAssertionRequestHandler::OnHavePINToken(
CtapDeviceResponseCode status,
base::Optional<pin::TokenResponse> response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
DCHECK_EQ(state_, State::kRequestWithPIN);
if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
state_ = State::kGettingRetries;
authenticator_->GetPinRetries(
base::BindOnce(&GetAssertionRequestHandler::OnPinRetriesResponse,
weak_factory_.GetWeakPtr()));
return;
}
if (status != CtapDeviceResponseCode::kSuccess) {
state_ = State::kFinished;
GetAssertionStatus ret;
switch (status) {
case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
ret = GetAssertionStatus::kSoftPINBlock;
break;
case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
ret = GetAssertionStatus::kHardPINBlock;
break;
default:
ret = GetAssertionStatus::kAuthenticatorResponseInvalid;
break;
}
std::move(completion_callback_).Run(ret, base::nullopt, nullptr);
return;
}
DispatchRequestWithToken(std::move(*response));
}
void GetAssertionRequestHandler::OnStartUvTokenOrFallback(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response) {
size_t retries;
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "OnStartUvTokenOrFallback() failed for "
<< authenticator_->GetDisplayName()
<< ", assuming authenticator locked.";
retries = 0;
} else {
retries = response->retries;
}
if (retries == 0) {
CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator);
if (authenticator->WillNeedPINToGetAssertion(request, observer()) ==
PINDisposition::kUsePINForFallback) {
authenticator->GetTouch(base::BindOnce(
&GetAssertionRequestHandler::StartPINFallbackForInternalUv,
weak_factory_.GetWeakPtr(), authenticator));
return;
}
authenticator->GetTouch(base::BindOnce(
&GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch,
weak_factory_.GetWeakPtr(), authenticator));
}
authenticator->GetUvToken(
GetPinTokenPermissionsFor(*authenticator, request_), request_.rp_id,
base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator));
}
void GetAssertionRequestHandler::OnUvRetriesResponse(
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "OnUvRetriesResponse() failed for "
<< authenticator_->GetDisplayName();
state_ = State::kFinished;
std::move(completion_callback_)
.Run(GetAssertionStatus::kAuthenticatorResponseInvalid, base::nullopt,
nullptr);
return;
}
state_ = State::kWaitingForTouch;
if (response->retries == 0) {
CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator_);
if (authenticator_->WillNeedPINToGetAssertion(request, observer()) ==
PINDisposition::kUsePINForFallback) {
// Fall back to PIN.
StartPINFallbackForInternalUv(authenticator_);
return;
}
// Device does not support fallback to PIN, terminate the request instead.
TerminateUnsatisfiableRequestPostTouch(authenticator_);
return;
}
observer()->OnRetryUserVerification(response->retries);
authenticator_->GetUvToken(
GetPinTokenPermissionsFor(*authenticator_, request_), request_.rp_id,
base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken,
weak_factory_.GetWeakPtr(), authenticator_));
}
void GetAssertionRequestHandler::OnHaveUvToken(
FidoAuthenticator* authenticator,
CtapDeviceResponseCode status,
base::Optional<pin::TokenResponse> response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_);
if (state_ != State::kWaitingForTouch) {
FIDO_LOG(DEBUG) << "Ignoring uv token response from "
<< authenticator->GetDisplayName()
<< " because no longer waiting for touch";
return;
}
if (status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied ||
status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator_);
if (authenticator->WillNeedPINToGetAssertion(request, observer()) ==
PINDisposition::kUsePINForFallback) {
StartPINFallbackForInternalUv(authenticator);
return;
}
TerminateUnsatisfiableRequestPostTouch(authenticator);
return;
}
DCHECK(status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied);
CancelActiveAuthenticators(authenticator->GetId());
authenticator_ = authenticator;
state_ = State::kGettingRetries;
authenticator->GetUvRetries(
base::BindOnce(&GetAssertionRequestHandler::OnUvRetriesResponse,
weak_factory_.GetWeakPtr()));
return;
}
if (status != CtapDeviceResponseCode::kSuccess) {
FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
<< " from " << authenticator->GetDisplayName();
return;
}
CancelActiveAuthenticators(authenticator->GetId());
authenticator_ = authenticator;
DispatchRequestWithToken(std::move(*response));
}
void GetAssertionRequestHandler::DispatchRequestWithToken(
pin::TokenResponse token) {
DCHECK(selected_authenticator_for_pin_uv_auth_token_);
observer()->FinishCollectToken();
pin_token_ = std::move(token);
state_ = State::kWaitingForSecondTouch;
CtapGetAssertionRequest request =
SpecializeRequestForAuthenticator(request_, *authenticator_);
state_ = State::kWaitingForResponseWithToken;
CtapGetAssertionRequest request = SpecializeRequestForAuthenticator(
request_, *selected_authenticator_for_pin_uv_auth_token_);
std::tie(request.pin_protocol, request.pin_auth) =
pin_token_->PinAuth(request.client_data_hash);
ReportGetAssertionRequestTransport(authenticator_);
ReportGetAssertionRequestTransport(
selected_authenticator_for_pin_uv_auth_token_);
auto request_copy(request);
authenticator_->GetAssertion(
selected_authenticator_for_pin_uv_auth_token_->GetAssertion(
std::move(request_copy), options_,
base::BindOnce(&GetAssertionRequestHandler::HandleResponse,
weak_factory_.GetWeakPtr(), authenticator_,
weak_factory_.GetWeakPtr(),
selected_authenticator_for_pin_uv_auth_token_,
std::move(request), base::ElapsedTimer()));
}
......
......@@ -6,6 +6,7 @@
#define DEVICE_FIDO_GET_ASSERTION_REQUEST_HANDLER_H_
#include <memory>
#include <set>
#include <string>
#include <vector>
......@@ -13,6 +14,7 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "device/fido/auth_token_requester.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/fido_constants.h"
......@@ -29,7 +31,6 @@ class FidoAuthenticator;
class FidoDiscoveryFactory;
namespace pin {
struct RetriesResponse;
class TokenResponse;
} // namespace pin
......@@ -51,7 +52,8 @@ enum class GetAssertionStatus {
};
class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
: public FidoRequestHandlerBase {
: public FidoRequestHandlerBase,
public AuthTokenRequester::Delegate {
public:
using CompletionCallback = base::OnceCallback<void(
GetAssertionStatus,
......@@ -70,10 +72,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
private:
enum class State {
kWaitingForTouch,
kWaitingForSecondTouch,
kGettingRetries,
kWaitingForPIN,
kRequestWithPIN,
kWaitingForToken,
kWaitingForResponseWithToken,
kReadingMultipleResponses,
kFinished,
};
......@@ -89,6 +89,24 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
void AuthenticatorRemoved(FidoDiscoveryBase* discovery,
FidoAuthenticator* authenticator) override;
// AuthTokenRequester::Delegate:
void AuthenticatorSelectedForPINUVAuthToken(
FidoAuthenticator* authenticator) override;
void CollectNewPIN(uint32_t min_pin_length,
ProvidePINCallback provide_pin_cb) override;
void CollectExistingPIN(int attempts,
uint32_t min_pin_length,
ProvidePINCallback provide_pin_cb) override;
void PromptForInternalUVRetry(int attempts) override;
void InternalUVLockedForAuthToken() override;
void HavePINUVAuthTokenResultForAuthenticator(
FidoAuthenticator* authenticator,
AuthTokenRequester::Result result,
base::Optional<pin::TokenResponse> response) override;
void ObtainPINUVAuthToken(FidoAuthenticator* authenticator,
std::set<pin::Permissions> permissions,
bool skip_pin_touch);
void HandleResponse(
FidoAuthenticator* authenticator,
CtapGetAssertionRequest request,
......@@ -100,22 +118,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
CtapGetAssertionRequest request,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorGetAssertionResponse> response);
void CollectPINThenSendRequest(FidoAuthenticator* authenticator);
void StartPINFallbackForInternalUv(FidoAuthenticator* authenticator);
void TerminateUnsatisfiableRequestPostTouch(FidoAuthenticator* authenticator);
void OnPinRetriesResponse(CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response);
void OnHavePIN(std::string pin);
void OnHavePINToken(CtapDeviceResponseCode status,
base::Optional<pin::TokenResponse> response);
void OnStartUvTokenOrFallback(FidoAuthenticator* authenticator,
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response);
void OnUvRetriesResponse(CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response);
void OnHaveUvToken(FidoAuthenticator* authenticator,
CtapDeviceResponseCode status,
base::Optional<pin::TokenResponse> response);
void DispatchRequestWithToken(pin::TokenResponse token);
void OnGetAssertionSuccess(FidoAuthenticator* authenticator,
CtapGetAssertionRequest request);
......@@ -132,23 +135,32 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
CtapGetAssertionRequest request_;
CtapGetAssertionOptions options_;
base::Optional<pin::TokenResponse> pin_token_;
// If true, and if at the time the request is dispatched to the first
// authenticator no other authenticators are available, the request handler
// will skip the initial touch that is usually required to select a PIN
// protected authenticator.
bool allow_skipping_pin_touch_;
// authenticator_ points to the authenticator that will be used for this
// operation. It's only set after the user touches an authenticator to select
// it, after which point that authenticator will be used exclusively through
// requesting PIN etc. The object is owned by the underlying discovery object
// and this pointer is cleared if it's removed during processing.
FidoAuthenticator* authenticator_ = nullptr;
// selected_authenticator_for_pin_uv_auth_token_ points to the authenticator
// that was tapped by the user while requesting a pinUvAuthToken from
// connected authenticators. The object is owned by the underlying discovery
// object and this pointer is cleared if it's removed during processing.
FidoAuthenticator* selected_authenticator_for_pin_uv_auth_token_ = nullptr;
// responses_ holds the set of responses while they are incrementally read
// from the device. Only used when more than one response is returned.
std::vector<AuthenticatorGetAssertionResponse> responses_;
// remaining_responses_ contains the number of responses that remain to be
// read when multiple responses are returned.
size_t remaining_responses_ = 0;
// auth_token_requester_map_ holds active AuthTokenRequesters for
// authenticators that need a pinUvAuthToken to service the request.
std::map<FidoAuthenticator*, std::unique_ptr<AuthTokenRequester>>
auth_token_requester_map_;
SEQUENCE_CHECKER(my_sequence_checker_);
base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_{this};
......
......@@ -27,8 +27,7 @@
namespace device {
using MakeCredentialPINUVDisposition =
FidoAuthenticator::MakeCredentialPINUVDisposition;
using PINUVDisposition = FidoAuthenticator::PINUVDisposition;
using BioEnrollmentAvailability =
AuthenticatorSupportedOptions::BioEnrollmentAvailability;
......@@ -130,7 +129,7 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
}
if (authenticator->PINUVDispositionForMakeCredential(request, observer) ==
MakeCredentialPINUVDisposition::kUnsatisfiable) {
PINUVDisposition::kUnsatisfiable) {
return MakeCredentialStatus::kAuthenticatorMissingUserVerification;
}
......@@ -428,14 +427,14 @@ void MakeCredentialRequestHandler::DispatchRequest(
auto uv_disposition = authenticator->PINUVDispositionForMakeCredential(
*request.get(), observer());
switch (uv_disposition) {
case MakeCredentialPINUVDisposition::kNoUV:
case MakeCredentialPINUVDisposition::kNoTokenInternalUV:
case MakeCredentialPINUVDisposition::kNoTokenInternalUVPINFallback:
case PINUVDisposition::kNoUV:
case PINUVDisposition::kNoTokenInternalUV:
case PINUVDisposition::kNoTokenInternalUVPINFallback:
break;
case MakeCredentialPINUVDisposition::kGetToken:
case PINUVDisposition::kGetToken:
ObtainPINUVAuthToken(authenticator, skip_pin_touch);
return;
case MakeCredentialPINUVDisposition::kUnsatisfiable:
case PINUVDisposition::kUnsatisfiable:
// |IsCandidateAuthenticatorPostTouch| should have handled this case.
NOTREACHED();
return;
......@@ -529,8 +528,8 @@ void MakeCredentialRequestHandler::HavePINUVAuthTokenResultForAuthenticator(
<< authenticator->GetId();
return;
case AuthTokenRequester::Result::kPostTouchAuthenticatorInternalUVLock:
HandleInternalUvLocked(authenticator);
return;
error = MakeCredentialStatus::kAuthenticatorMissingUserVerification;
break;
case AuthTokenRequester::Result::kPostTouchAuthenticatorResponseInvalid:
error = MakeCredentialStatus::kAuthenticatorResponseInvalid;
break;
......@@ -641,7 +640,7 @@ void MakeCredentialRequestHandler::HandleResponse(
(status == CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrPinRequired) &&
authenticator->PINUVDispositionForMakeCredential(*request, observer()) ==
MakeCredentialPINUVDisposition::kNoTokenInternalUVPINFallback) {
PINUVDisposition::kNoTokenInternalUVPINFallback) {
// Authenticators without uvToken support will return this error immediately
// without user interaction when internal UV is locked.
const base::TimeDelta response_time = request_timer.Elapsed();
......@@ -718,15 +717,6 @@ void MakeCredentialRequestHandler::HandleResponse(
.Run(MakeCredentialStatus::kSuccess, std::move(*response), authenticator);
}
void MakeCredentialRequestHandler::HandleInternalUvLocked(
FidoAuthenticator* authenticator) {
state_ = State::kFinished;
CancelActiveAuthenticators(authenticator->GetId());
std::move(completion_callback_)
.Run(MakeCredentialStatus::kAuthenticatorMissingUserVerification,
base::nullopt, nullptr);
}
void MakeCredentialRequestHandler::HandleInapplicableAuthenticator(
FidoAuthenticator* authenticator,
std::unique_ptr<CtapMakeCredentialRequest> request) {
......
......@@ -178,7 +178,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
base::ElapsedTimer request_timer,
CtapDeviceResponseCode response_code,
base::Optional<AuthenticatorMakeCredentialResponse> response);
void HandleInternalUvLocked(FidoAuthenticator* authenticator);
void HandleInapplicableAuthenticator(
FidoAuthenticator* authenticator,
std::unique_ptr<CtapMakeCredentialRequest> request);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment