blob: c6ae9b880c6e27589a2b12497bfd5fd2ee2e1533 [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/get_assertion_task.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/ctap_empty_authenticator_request.h"
#include "device/fido/device_response_converter.h"
namespace device {
namespace {
bool ResponseContainsUserIdentifiableInfo(
const AuthenticatorGetAssertionResponse& response) {
const auto& user_entity = response.user_entity();
if (!user_entity)
return false;
return user_entity->user_display_name() || user_entity->user_name() ||
user_entity->user_icon_url();
}
} // namespace
GetAssertionTask::GetAssertionTask(FidoDevice* device,
CtapGetAssertionRequest request,
GetAssertionTaskCallback callback)
: FidoTask(device),
request_(std::move(request)),
callback_(std::move(callback)),
weak_factory_(this) {}
GetAssertionTask::~GetAssertionTask() = default;
void GetAssertionTask::StartTask() {
GetAuthenticatorInfo(
base::BindOnce(&GetAssertionTask::GetAssertion,
weak_factory_.GetWeakPtr()),
base::BindOnce(&GetAssertionTask::U2fSign, weak_factory_.GetWeakPtr()));
}
void GetAssertionTask::GetAssertion() {
if (!CheckUserVerificationCompatible()) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
device()->DeviceTransact(
request_.EncodeAsCBOR(),
base::BindOnce(&GetAssertionTask::OnCtapGetAssertionResponseReceived,
weak_factory_.GetWeakPtr()));
}
void GetAssertionTask::U2fSign() {
// TODO(hongjunchoi): Implement U2F sign request logic to support
// interoperability with U2F protocol. Currently all requests for U2F devices
// are silently dropped.
// See: https://www.crbug.com/798573
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
}
bool GetAssertionTask::CheckRequirementsOnReturnedUserEntities(
const AuthenticatorGetAssertionResponse& response) {
// If assertion has been made without user verification, user identifiable
// information must not be included.
if (!response.auth_data().obtained_user_verification() &&
ResponseContainsUserIdentifiableInfo(response)) {
return false;
}
// For resident key credentials, user id of the user entity is mandatory.
if ((!request_.allow_list() || request_.allow_list()->empty()) &&
!response.user_entity()) {
return false;
}
// When multiple accounts exist for specified RP ID, user entity is mandatory.
if (response.num_credentials().value_or(0u) > 1 && !response.user_entity()) {
return false;
}
return true;
}
bool GetAssertionTask::CheckRequirementsOnReturnedCredentialId(
const AuthenticatorGetAssertionResponse& response) {
if (device()->device_info() &&
device()->device_info()->options().supports_resident_key()) {
return true;
}
const auto& allow_list = request_.allow_list();
return allow_list &&
(allow_list->size() == 1 ||
std::any_of(allow_list->cbegin(), allow_list->cend(),
[&response](const auto& credential) {
return credential.id() == response.raw_credential_id();
}));
}
void GetAssertionTask::OnCtapGetAssertionResponseReceived(
base::Optional<std::vector<uint8_t>> device_response) {
if (!device_response) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
auto response_code = GetResponseCode(*device_response);
if (response_code != CtapDeviceResponseCode::kSuccess) {
std::move(callback_).Run(response_code, base::nullopt);
return;
}
auto parsed_response = ReadCTAPGetAssertionResponse(*device_response);
if (!parsed_response || !parsed_response->CheckRpIdHash(request_.rp_id()) ||
!CheckRequirementsOnReturnedCredentialId(*parsed_response) ||
!CheckRequirementsOnReturnedUserEntities(*parsed_response)) {
std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther,
base::nullopt);
return;
}
std::move(callback_).Run(response_code, std::move(parsed_response));
}
bool GetAssertionTask::CheckUserVerificationCompatible() {
DCHECK(device()->device_info());
const auto uv_availability =
device()->device_info()->options().user_verification_availability();
switch (request_.user_verification()) {
case UserVerificationRequirement::kRequired:
return uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured;
case UserVerificationRequirement::kDiscouraged:
return true;
case UserVerificationRequirement::kPreferred:
if (uv_availability ==
AuthenticatorSupportedOptions::UserVerificationAvailability::
kSupportedAndConfigured) {
request_.SetUserVerification(UserVerificationRequirement::kRequired);
} else {
request_.SetUserVerification(UserVerificationRequirement::kDiscouraged);
}
return true;
}
NOTREACHED();
return false;
}
} // namespace device