123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- // 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
|