blob: 66f2310ec0f3c4737a68f1e81e8b753137c2bc38 [file] [log] [blame]
// Copyright 2020 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/auth_token_requester.h"
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/fido_authenticator.h"
#include "device/fido/fido_constants.h"
namespace device {
using ClientPinAvailability =
AuthenticatorSupportedOptions::ClientPinAvailability;
using UserVerificationAvailability =
AuthenticatorSupportedOptions::UserVerificationAvailability;
using BioEnrollmentAvailability =
AuthenticatorSupportedOptions::BioEnrollmentAvailability;
AuthTokenRequester::Delegate::~Delegate() = default;
AuthTokenRequester::Options::Options() = default;
AuthTokenRequester::Options::Options(Options&&) = default;
AuthTokenRequester::Options& AuthTokenRequester::Options::operator=(Options&&) =
default;
AuthTokenRequester::Options::~Options() = default;
AuthTokenRequester::AuthTokenRequester(Delegate* delegate,
FidoAuthenticator* authenticator,
Options options)
: delegate_(delegate),
authenticator_(authenticator),
options_(std::move(options)),
internal_uv_locked_(options_.internal_uv_locked) {
DCHECK(delegate_);
DCHECK(authenticator_);
DCHECK(authenticator_->Options());
DCHECK(!options_.token_permissions.empty());
DCHECK(!options_.rp_id || !options_.rp_id->empty());
// Authenticators with CTAP2.0-style pinToken support only support certain
// default permissions.
DCHECK(
authenticator_->Options()->supports_pin_uv_auth_token ||
base::STLSetDifference<std::set<pin::Permissions>>(
options_.token_permissions,
std::set<pin::Permissions>{pin::Permissions::kMakeCredential,
pin::Permissions::kGetAssertion,
pin::Permissions::kBioEnrollment,
pin::Permissions::kCredentialManagement})
.empty());
}
AuthTokenRequester::~AuthTokenRequester() = default;
void AuthTokenRequester::ObtainPINUVAuthToken() {
if (authenticator_->Options()->supports_pin_uv_auth_token) {
// Only attempt to obtain a token through internal UV if the authenticator
// supports CTAP 2.1 pinUvAuthTokens. If it does not, it could be a 2.0
// authenticator that supports UV without any sort of token.
const UserVerificationAvailability user_verification_availability =
authenticator_->Options()->user_verification_availability;
switch (user_verification_availability) {
case UserVerificationAvailability::kNotSupported:
case UserVerificationAvailability::kSupportedButNotConfigured:
// Try PIN first.
break;
case UserVerificationAvailability::kSupportedAndConfigured:
ObtainTokenFromInternalUV();
return;
}
}
const ClientPinAvailability client_pin_availability =
authenticator_->Options()->client_pin_availability;
switch (client_pin_availability) {
case ClientPinAvailability::kNotSupported:
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPreTouchUnsatisfiableRequest, absl::nullopt);
return;
case ClientPinAvailability::kSupportedAndPinSet:
if (options_.skip_pin_touch) {
ObtainTokenFromPIN();
return;
}
authenticator_->GetTouch(base::BindOnce(
&AuthTokenRequester::ObtainTokenFromPIN, weak_factory_.GetWeakPtr()));
return;
case ClientPinAvailability::kSupportedButPinNotSet:
if (options_.skip_pin_touch) {
ObtainTokenFromNewPIN();
return;
}
authenticator_->GetTouch(
base::BindOnce(&AuthTokenRequester::ObtainTokenFromNewPIN,
weak_factory_.GetWeakPtr()));
return;
}
}
void AuthTokenRequester::ObtainTokenFromInternalUV() {
authenticator_->GetUvRetries(base::BindOnce(
&AuthTokenRequester::OnGetUVRetries, weak_factory_.GetWeakPtr()));
}
void AuthTokenRequester::OnGetUVRetries(
CtapDeviceResponseCode status,
absl::optional<pin::RetriesResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPreTouchAuthenticatorResponseInvalid,
absl::nullopt);
return;
}
internal_uv_locked_ = response->retries == 0;
if (response->retries == 0) {
// The authenticator was locked prior to calling
// ObtainTokenFromInternalUV(). Fall back to PIN if able.
if (authenticator_->Options()->client_pin_availability ==
ClientPinAvailability::kSupportedAndPinSet) {
if (options_.skip_pin_touch) {
ObtainTokenFromPIN();
return;
}
authenticator_->GetTouch(base::BindOnce(
&AuthTokenRequester::ObtainTokenFromPIN, weak_factory_.GetWeakPtr()));
return;
}
authenticator_->GetTouch(base::BindOnce(
&AuthTokenRequester::NotifyAuthenticatorSelectedAndFailWithResult,
weak_factory_.GetWeakPtr(),
Result::kPostTouchAuthenticatorInternalUVLock));
return;
}
if (is_internal_uv_retry_) {
delegate_->PromptForInternalUVRetry(response->retries);
}
authenticator_->GetUvToken({std::begin(options_.token_permissions),
std::end(options_.token_permissions)},
options_.rp_id,
base::BindOnce(&AuthTokenRequester::OnGetUVToken,
weak_factory_.GetWeakPtr()));
}
void AuthTokenRequester::OnGetUVToken(
CtapDeviceResponseCode status,
absl::optional<pin::TokenResponse> response) {
if (!base::Contains(
std::set<CtapDeviceResponseCode>{
CtapDeviceResponseCode::kCtap2ErrUvInvalid,
CtapDeviceResponseCode::kCtap2ErrOperationDenied,
CtapDeviceResponseCode::kCtap2ErrUvBlocked,
CtapDeviceResponseCode::kSuccess},
status)) {
// The request was rejected outright, no touch occurred.
FIDO_LOG(ERROR) << "Ignoring status " << static_cast<int>(status)
<< " from " << authenticator_->GetDisplayName();
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPreTouchAuthenticatorResponseInvalid,
absl::nullopt);
return;
}
if (!NotifyAuthenticatorSelected()) {
return;
}
if (status == CtapDeviceResponseCode::kCtap2ErrOperationDenied) {
// The user explicitly denied to the operation on an authenticator with
// a display.
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPostTouchAuthenticatorOperationDenied,
absl::nullopt);
return;
}
if (status == CtapDeviceResponseCode::kCtap2ErrUvInvalid) {
// The attempt failed, but a retry is possible.
is_internal_uv_retry_ = true;
ObtainTokenFromInternalUV();
return;
}
if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
// Fall back to PIN if able.
if (authenticator_->Options()->client_pin_availability ==
ClientPinAvailability::kSupportedAndPinSet) {
internal_uv_locked_ = true;
ObtainTokenFromPIN();
return;
}
// This can be returned pre-touch if the authenticator was already locked at
// the time GetUvToken() was called. However, we checked the number of
// remaining retries just before that to handle that case.
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPostTouchAuthenticatorInternalUVLock,
absl::nullopt);
return;
}
DCHECK_EQ(status, CtapDeviceResponseCode::kSuccess);
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kSuccess, *response);
}
void AuthTokenRequester::ObtainTokenFromPIN() {
if (NotifyAuthenticatorSelected()) {
authenticator_->GetPinRetries(base::BindOnce(
&AuthTokenRequester::OnGetPINRetries, weak_factory_.GetWeakPtr()));
}
}
void AuthTokenRequester::OnGetPINRetries(
CtapDeviceResponseCode status,
absl::optional<pin::RetriesResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPostTouchAuthenticatorResponseInvalid,
absl::nullopt);
return;
}
if (response->retries == 0) {
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPostTouchAuthenticatorPINHardLock,
absl::nullopt);
return;
}
pin_retries_ = response->retries;
pin::PINEntryError error;
if (pin_invalid_) {
pin_invalid_ = false;
error = pin::PINEntryError::kWrongPIN;
} else if (internal_uv_locked_) {
error = pin::PINEntryError::kInternalUvLocked;
} else {
error = pin::PINEntryError::kNoError;
}
delegate_->CollectPIN(
pin::PINEntryReason::kChallenge, error,
authenticator_->CurrentMinPINLength(), pin_retries_,
base::BindOnce(&AuthTokenRequester::HavePIN, weak_factory_.GetWeakPtr()));
}
void AuthTokenRequester::HavePIN(std::u16string pin16) {
pin::PINEntryError error = pin::ValidatePIN(
pin16, authenticator_->CurrentMinPINLength(), current_pin_);
if (error != pin::PINEntryError::kNoError) {
delegate_->CollectPIN(pin::PINEntryReason::kChallenge, error,
authenticator_->CurrentMinPINLength(), pin_retries_,
base::BindOnce(&AuthTokenRequester::HavePIN,
weak_factory_.GetWeakPtr()));
return;
}
std::string pin = base::UTF16ToUTF8(pin16);
authenticator_->GetPINToken(pin,
{std::begin(options_.token_permissions),
std::end(options_.token_permissions)},
options_.rp_id,
base::BindOnce(&AuthTokenRequester::OnGetPINToken,
weak_factory_.GetWeakPtr(), pin));
return;
}
void AuthTokenRequester::OnGetPINToken(
std::string pin,
CtapDeviceResponseCode status,
absl::optional<pin::TokenResponse> response) {
if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid) {
pin_invalid_ = true;
ObtainTokenFromPIN();
return;
}
if (status != CtapDeviceResponseCode::kSuccess) {
Result ret;
switch (status) {
case CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation:
// The user needs to set a new PIN before they can use the device.
current_pin_ = pin;
delegate_->CollectPIN(pin::PINEntryReason::kChange,
pin::PINEntryError::kNoError,
authenticator_->NewMinPINLength(),
/*attempts=*/0,
base::BindOnce(&AuthTokenRequester::HaveNewPIN,
weak_factory_.GetWeakPtr()));
return;
case CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked:
ret = Result::kPostTouchAuthenticatorPINSoftLock;
break;
case CtapDeviceResponseCode::kCtap2ErrPinBlocked:
ret = Result::kPostTouchAuthenticatorPINHardLock;
break;
default:
ret = Result::kPostTouchAuthenticatorResponseInvalid;
break;
}
delegate_->HavePINUVAuthTokenResultForAuthenticator(authenticator_, ret,
absl::nullopt);
return;
}
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kSuccess, std::move(*response));
}
void AuthTokenRequester::ObtainTokenFromNewPIN() {
if (NotifyAuthenticatorSelected()) {
delegate_->CollectPIN(pin::PINEntryReason::kSet,
pin::PINEntryError::kNoError,
authenticator_->NewMinPINLength(),
/*attempts=*/0,
base::BindOnce(&AuthTokenRequester::HaveNewPIN,
weak_factory_.GetWeakPtr()));
}
}
void AuthTokenRequester::HaveNewPIN(std::u16string pin16) {
pin::PINEntryError error =
pin::ValidatePIN(pin16, authenticator_->NewMinPINLength(), current_pin_);
if (error != pin::PINEntryError::kNoError) {
delegate_->CollectPIN(
current_pin_ ? pin::PINEntryReason::kChange : pin::PINEntryReason::kSet,
error, authenticator_->NewMinPINLength(),
/*attempts=*/0,
base::BindOnce(&AuthTokenRequester::HaveNewPIN,
weak_factory_.GetWeakPtr()));
return;
}
std::string pin = base::UTF16ToUTF8(pin16);
if (current_pin_) {
authenticator_->ChangePIN(*current_pin_, pin,
base::BindOnce(&AuthTokenRequester::OnSetPIN,
weak_factory_.GetWeakPtr(), pin));
return;
}
authenticator_->SetPIN(pin, base::BindOnce(&AuthTokenRequester::OnSetPIN,
weak_factory_.GetWeakPtr(), pin));
return;
}
void AuthTokenRequester::OnSetPIN(std::string pin,
CtapDeviceResponseCode status,
absl::optional<pin::EmptyResponse> response) {
if (status != CtapDeviceResponseCode::kSuccess) {
delegate_->HavePINUVAuthTokenResultForAuthenticator(
authenticator_, Result::kPostTouchAuthenticatorResponseInvalid,
absl::nullopt);
return;
}
// Having just set the PIN, we need to immediately turn around and use it to
// get a PIN token.
authenticator_->GetPINToken(std::move(pin),
{std::begin(options_.token_permissions),
std::end(options_.token_permissions)},
options_.rp_id,
base::BindOnce(&AuthTokenRequester::OnGetPINToken,
weak_factory_.GetWeakPtr(), pin));
}
bool AuthTokenRequester::NotifyAuthenticatorSelected() {
if (!authenticator_selected_result_.has_value()) {
authenticator_selected_result_ =
delegate_->AuthenticatorSelectedForPINUVAuthToken(authenticator_);
}
return *authenticator_selected_result_;
}
void AuthTokenRequester::NotifyAuthenticatorSelectedAndFailWithResult(
Result result) {
if (NotifyAuthenticatorSelected()) {
delegate_->HavePINUVAuthTokenResultForAuthenticator(authenticator_, result,
absl::nullopt);
}
}
} // namespace device