123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- // 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/get_assertion_task.h"
- #include <algorithm>
- #include <utility>
- #include "base/bind.h"
- #include "base/containers/contains.h"
- #include "device/base/features.h"
- #include "device/fido/authenticator_get_assertion_response.h"
- #include "device/fido/ctap2_device_operation.h"
- #include "device/fido/make_credential_task.h"
- #include "device/fido/pin.h"
- #include "device/fido/u2f_sign_operation.h"
- namespace device {
- namespace {
- bool MayFallbackToU2fWithAppIdExtension(
- const FidoDevice& device,
- const CtapGetAssertionRequest& request) {
- bool ctap2_device_supports_u2f =
- device.device_info() &&
- base::Contains(device.device_info()->versions, ProtocolVersion::kU2f);
- return request.alternative_application_parameter &&
- ctap2_device_supports_u2f && !request.allow_list.empty();
- }
- // SetResponseCredential sets the credential information in |response|. If the
- // allow list sent to the authenticator contained only a single entry then the
- // authenticator may omit the chosen credential in the response and this
- // function will fill it in. Otherwise, the credential chosen by the
- // authenticator must be one of the ones requested in the allow list, unless the
- // allow list was empty.
- bool SetResponseCredential(
- AuthenticatorGetAssertionResponse* response,
- const std::vector<PublicKeyCredentialDescriptor>& allow_list) {
- if (response->credential) {
- if (!allow_list.empty() &&
- std::none_of(allow_list.cbegin(), allow_list.cend(),
- [&response](const auto& credential) {
- return credential.id == response->credential->id;
- })) {
- return false;
- }
- return true;
- }
- if (allow_list.size() != 1) {
- return false;
- }
- response->credential = allow_list[0];
- return true;
- }
- // HasCredentialSpecificPRFInputs returns true if |options| specifies any PRF
- // inputs that are specific to a credential ID.
- bool HasCredentialSpecificPRFInputs(const CtapGetAssertionOptions& options) {
- const size_t num = options.prf_inputs.size();
- return num > 1 ||
- (num == 1 && options.prf_inputs[0].credential_id.has_value());
- }
- // GetDefaultPRFInput returns the default PRF input from |options|, if any.
- const CtapGetAssertionOptions::PRFInput* GetDefaultPRFInput(
- const CtapGetAssertionOptions& options) {
- if (options.prf_inputs.empty() ||
- options.prf_inputs[0].credential_id.has_value()) {
- return nullptr;
- }
- return &options.prf_inputs[0];
- }
- // GetPRFInputForCredential returns the PRF input specific to the given
- // credential ID from |options|, or the default PRF input if there's nothing
- // specific for |id|, or |nullptr| if there's not a default value.
- const CtapGetAssertionOptions::PRFInput* GetPRFInputForCredential(
- const CtapGetAssertionOptions& options,
- const std::vector<uint8_t>& id) {
- for (const auto& prf_input : options.prf_inputs) {
- if (prf_input.credential_id == id) {
- return &prf_input;
- }
- }
- return GetDefaultPRFInput(options);
- }
- } // namespace
- GetAssertionTask::GetAssertionTask(FidoDevice* device,
- CtapGetAssertionRequest request,
- CtapGetAssertionOptions options,
- GetAssertionTaskCallback callback)
- : FidoTask(device),
- request_(std::move(request)),
- options_(std::move(options)),
- callback_(std::move(callback)) {
- // This code assumes that user-presence is requested in order to implement
- // possible U2F-fallback.
- DCHECK(request_.user_presence_required);
- // The UV parameter should have been made binary by this point because CTAP2
- // only takes a binary value.
- DCHECK_NE(request_.user_verification,
- UserVerificationRequirement::kPreferred);
- }
- GetAssertionTask::~GetAssertionTask() = default;
- void GetAssertionTask::Cancel() {
- canceled_ = true;
- if (sign_operation_) {
- sign_operation_->Cancel();
- }
- if (dummy_register_operation_) {
- dummy_register_operation_->Cancel();
- }
- }
- // static
- bool GetAssertionTask::StringFixupPredicate(
- const std::vector<const cbor::Value*>& path) {
- // This filters out all elements that are not string-keyed, direct children
- // of key 0x04, which is the `user` element of a getAssertion response.
- if (path.size() != 2 || !path[0]->is_unsigned() ||
- path[0]->GetUnsigned() != 4 || !path[1]->is_string()) {
- return false;
- }
- // Of those string-keyed children, only `name` and `displayName` may have
- // truncated UTF-8 in their values.
- const std::string& user_key = path[1]->GetString();
- return user_key == "name" || user_key == "displayName";
- }
- void GetAssertionTask::StartTask() {
- if (device()->supported_protocol() == ProtocolVersion::kCtap2 &&
- !request_.is_u2f_only) {
- GetAssertion();
- } else {
- // |device_info| should be present iff the device is CTAP2.
- // |MaybeRevertU2fFallbackAndInvokeCallback| uses this to restore the
- // protocol of CTAP2 devices once this task is complete.
- DCHECK_EQ(device()->supported_protocol() == ProtocolVersion::kCtap2,
- device()->device_info().has_value());
- device()->set_supported_protocol(ProtocolVersion::kU2f);
- U2fSign();
- }
- }
- CtapGetAssertionRequest GetAssertionTask::NextSilentRequest() {
- DCHECK(current_allow_list_batch_ < allow_list_batches_.size());
- CtapGetAssertionRequest request = request_;
- request.allow_list = allow_list_batches_.at(current_allow_list_batch_++);
- request.user_presence_required = false;
- request.user_verification = UserVerificationRequirement::kDiscouraged;
- return request;
- }
- void GetAssertionTask::GetAssertion() {
- if (request_.allow_list.empty()) {
- MaybeSetPRFParameters(&request_, GetDefaultPRFInput(options_));
- sign_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
- device(), request_,
- base::BindOnce(&GetAssertionTask::HandleResponse,
- weak_factory_.GetWeakPtr(), request_.allow_list),
- base::BindOnce(&ReadCTAPGetAssertionResponse,
- device()->DeviceTransport()),
- StringFixupPredicate);
- sign_operation_->Start();
- return;
- }
- // Most authenticators can only process allowList parameters up to a certain
- // size. Batch the list into chunks according to what the device can handle
- // and filter out IDs that are too large to originate from this device.
- allow_list_batches_ =
- FilterAndBatchCredentialDescriptors(request_.allow_list, *device());
- DCHECK(!allow_list_batches_.empty());
- // If filtering eliminated all entries from the allowList, just collect a
- // dummy touch, then fail the request.
- if (allow_list_batches_.size() == 1 && allow_list_batches_[0].empty()) {
- dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
- device(), MakeCredentialTask::GetTouchRequest(device()),
- base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
- weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- dummy_register_operation_->Start();
- return;
- }
- // If the filtered allowList is small enough to be sent in a single request,
- // do so.
- if (allow_list_batches_.size() == 1 &&
- !MayFallbackToU2fWithAppIdExtension(*device(), request_) &&
- !HasCredentialSpecificPRFInputs(options_)) {
- CtapGetAssertionRequest request = request_;
- request.allow_list = allow_list_batches_.front();
- MaybeSetPRFParameters(&request, GetDefaultPRFInput(options_));
- sign_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
- device(), std::move(request),
- base::BindOnce(&GetAssertionTask::HandleResponse,
- weak_factory_.GetWeakPtr(), request.allow_list),
- base::BindOnce(&ReadCTAPGetAssertionResponse,
- device()->DeviceTransport()),
- StringFixupPredicate);
- sign_operation_->Start();
- return;
- }
- // If the filtered list is too large to be sent at once, or if an App ID might
- // need to be tested because the site used the appid extension, or if we might
- // need to send specific PRF inputs, probe the credential IDs silently.
- sign_operation_ =
- std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest,
- AuthenticatorGetAssertionResponse>>(
- device(), NextSilentRequest(),
- base::BindOnce(&GetAssertionTask::HandleResponseToSilentRequest,
- weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPGetAssertionResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- sign_operation_->Start();
- }
- void GetAssertionTask::U2fSign() {
- DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
- sign_operation_ = std::make_unique<U2fSignOperation>(
- device(), request_,
- base::BindOnce(&GetAssertionTask::MaybeRevertU2fFallbackAndInvokeCallback,
- weak_factory_.GetWeakPtr()));
- sign_operation_->Start();
- }
- void GetAssertionTask::HandleResponse(
- std::vector<PublicKeyCredentialDescriptor> allow_list,
- CtapDeviceResponseCode response_code,
- absl::optional<AuthenticatorGetAssertionResponse> response_data) {
- if (canceled_) {
- return;
- }
- if (response_code == CtapDeviceResponseCode::kCtap2ErrInvalidCredential) {
- // Some authenticators will return this error before waiting for a touch if
- // they don't recognise a credential. In other cases the result can be
- // returned immediately.
- // The request failed in a way that didn't request a touch. Simulate it.
- dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
- device(), MakeCredentialTask::GetTouchRequest(device()),
- base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
- weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- dummy_register_operation_->Start();
- return;
- }
- if (response_code == CtapDeviceResponseCode::kSuccess) {
- if (response_data->user_selected && !allow_list.empty()) {
- // The userSelected signal is only valid if the request had an empty
- // allowList.
- FIDO_LOG(DEBUG)
- << "Assertion response has userSelected for non-empty allowList";
- std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
- absl::nullopt);
- return;
- }
- if (!SetResponseCredential(&response_data.value(), allow_list)) {
- FIDO_LOG(DEBUG)
- << "Assertion response has invalid credential information";
- std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
- absl::nullopt);
- return;
- }
- // Decrypt any hmac-secret response.
- const absl::optional<cbor::Value>& extensions_cbor =
- response_data->authenticator_data.extensions();
- if (extensions_cbor) {
- // Parsing has already checked that |extensions_cbor| is a map.
- const cbor::Value::MapValue& extensions = extensions_cbor->GetMap();
- auto it = extensions.find(cbor::Value(kExtensionHmacSecret));
- if (it != extensions.end()) {
- if (!hmac_secret_request_ || !it->second.is_bytestring()) {
- FIDO_LOG(DEBUG) << "Unexpected or invalid hmac_secret extension";
- std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
- absl::nullopt);
- return;
- }
- absl::optional<std::vector<uint8_t>> plaintext =
- hmac_secret_request_->Decrypt(it->second.GetBytestring());
- if (!plaintext) {
- FIDO_LOG(DEBUG) << "Failed to decrypt hmac_secret extension";
- std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
- absl::nullopt);
- return;
- }
- response_data->hmac_secret = std::move(plaintext.value());
- }
- }
- }
- std::move(callback_).Run(response_code, std::move(response_data));
- }
- void GetAssertionTask::HandleResponseToSilentRequest(
- CtapDeviceResponseCode response_code,
- absl::optional<AuthenticatorGetAssertionResponse> response_data) {
- DCHECK(request_.allow_list.size() > 0);
- DCHECK(allow_list_batches_.size() > 0);
- DCHECK(0 < current_allow_list_batch_ &&
- current_allow_list_batch_ <= allow_list_batches_.size());
- if (canceled_) {
- return;
- }
- // One credential from the previous batch was recognized by the device. As
- // this authentication was a silent authentication (i.e. user touch was not
- // provided), try again with only that credential, user presence enforced and
- // with the original user verification configuration.
- if (response_code == CtapDeviceResponseCode::kSuccess &&
- SetResponseCredential(
- &response_data.value(),
- allow_list_batches_.at(current_allow_list_batch_ - 1))) {
- CtapGetAssertionRequest request = request_;
- const PublicKeyCredentialDescriptor& matching_credential =
- *response_data->credential;
- request.allow_list = {matching_credential};
- MaybeSetPRFParameters(
- &request, GetPRFInputForCredential(options_, matching_credential.id));
- sign_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
- device(), std::move(request),
- base::BindOnce(&GetAssertionTask::HandleResponse,
- weak_factory_.GetWeakPtr(), request.allow_list),
- base::BindOnce(&ReadCTAPGetAssertionResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- sign_operation_->Start();
- return;
- }
- // Credential was not recognized or an error occurred. Probe the next
- // credential.
- if (current_allow_list_batch_ < allow_list_batches_.size()) {
- sign_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>(
- device(), NextSilentRequest(),
- base::BindOnce(&GetAssertionTask::HandleResponseToSilentRequest,
- weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPGetAssertionResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- sign_operation_->Start();
- return;
- }
- // None of the credentials were recognized. Fall back to U2F or collect a
- // dummy touch.
- if (MayFallbackToU2fWithAppIdExtension(*device(), request_)) {
- device()->set_supported_protocol(ProtocolVersion::kU2f);
- U2fSign();
- return;
- }
- dummy_register_operation_ = std::make_unique<Ctap2DeviceOperation<
- CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
- device(), MakeCredentialTask::GetTouchRequest(device()),
- base::BindOnce(&GetAssertionTask::HandleDummyMakeCredentialComplete,
- weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
- /*string_fixup_predicate=*/nullptr);
- dummy_register_operation_->Start();
- }
- void GetAssertionTask::HandleDummyMakeCredentialComplete(
- CtapDeviceResponseCode response_code,
- absl::optional<AuthenticatorMakeCredentialResponse> response_data) {
- std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials,
- absl::nullopt);
- }
- void GetAssertionTask::MaybeSetPRFParameters(
- CtapGetAssertionRequest* request,
- const CtapGetAssertionOptions::PRFInput* maybe_inputs) {
- if (maybe_inputs == nullptr) {
- return;
- }
- hmac_secret_request_ = std::make_unique<pin::HMACSecretRequest>(
- *request->pin_protocol, *options_.pin_key_agreement, maybe_inputs->salt1,
- maybe_inputs->salt2);
- request->hmac_secret.emplace(hmac_secret_request_->public_key_x962,
- hmac_secret_request_->encrypted_salts,
- hmac_secret_request_->salts_auth);
- }
- void GetAssertionTask::MaybeRevertU2fFallbackAndInvokeCallback(
- CtapDeviceResponseCode status,
- absl::optional<AuthenticatorGetAssertionResponse> response) {
- DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol());
- if (device()->device_info()) {
- // This was actually a CTAP2 device, but the protocol version was set to U2F
- // in order to execute a sign command.
- device()->set_supported_protocol(ProtocolVersion::kCtap2);
- }
- std::move(callback_).Run(status, std::move(response));
- }
- } // namespace device
|