blob: 4e555e313682f05d1806ce54a7c058e632ba585a [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "biod/cros_fp_auth_stack_manager.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/notreached.h>
#include <libhwsec/frontend/pinweaver/frontend.h>
#include <libhwsec/status.h>
#include "biod/cros_fp_device.h"
#include "biod/cros_fp_record_manager.h"
#include "biod/maintenance_scheduler.h"
#include "biod/pairing_key_storage.h"
#include "biod/power_button_filter_interface.h"
#include "biod/proto_bindings/constants.pb.h"
#include "biod/proto_bindings/messages.pb.h"
#include "biod/utils.h"
namespace biod {
// There is already a Session class in the biod namespace.
using BioSession = CrosFpAuthStackManager::Session;
using State = CrosFpAuthStackManager::State;
using Mode = ec::FpMode::Mode;
using PinWeaverEccPoint = hwsec::PinWeaverFrontend::PinWeaverEccPoint;
using hwsec::PinWeaverEccPointSize;
namespace {
constexpr uint8_t kCrosFpAuthChannel = 0;
}
CrosFpAuthStackManager::CrosFpAuthStackManager(
std::unique_ptr<PowerButtonFilterInterface> power_button_filter,
std::unique_ptr<ec::CrosFpDeviceInterface> cros_fp_device,
BiodMetricsInterface* biod_metrics,
std::unique_ptr<CrosFpSessionManager> session_manager,
std::unique_ptr<PairingKeyStorage> pk_storage,
std::unique_ptr<const hwsec::PinWeaverFrontend> pinweaver,
State state)
: biod_metrics_(biod_metrics),
cros_dev_(std::move(cros_fp_device)),
power_button_filter_(std::move(power_button_filter)),
session_manager_(std::move(session_manager)),
pk_storage_(std::move(pk_storage)),
pinweaver_(std::move(pinweaver)),
state_(state),
maintenance_scheduler_(std::make_unique<MaintenanceScheduler>(
cros_dev_.get(), biod_metrics_)),
session_weak_factory_(this) {
CHECK(power_button_filter_);
CHECK(cros_dev_);
CHECK(biod_metrics_);
CHECK(session_manager_);
CHECK(pk_storage_);
CHECK(pinweaver_);
cros_dev_->SetMkbpEventCallback(base::BindRepeating(
&CrosFpAuthStackManager::OnMkbpEvent, base::Unretained(this)));
maintenance_scheduler_->Start();
}
bool CrosFpAuthStackManager::Initialize() {
if (!pk_storage_->PairingKeyExists() && !EstablishPairingKey()) {
return false;
}
return LoadPairingKey();
}
bool CrosFpAuthStackManager::EstablishPairingKey() {
if (!pinweaver_->IsEnabled().value_or(false)) {
LOG(ERROR) << __func__ << "PinWeaver is not enabled.";
return false;
}
// Pk related mechanisms are only added in PW version 2.
if (pinweaver_->GetVersion().value_or(0) <= 1) {
LOG(ERROR) << __func__ << "PinWeaver version isn't new enough.";
return false;
}
// Step 1: Keygen in FPMCU.
std::optional<ec::CrosFpDeviceInterface::PairingKeyKeygenReply> reply =
cros_dev_->PairingKeyKeygen();
if (!reply.has_value()) {
return false;
}
if (reply->pub_x.size() != PinWeaverEccPointSize ||
reply->pub_y.size() != PinWeaverEccPointSize) {
LOG(ERROR) << __func__
<< "Point size in PairingKeyKeygenReply is incorrect.";
return false;
}
// Step 2: Keygen in GSC.
PinWeaverEccPoint pub_in;
std::copy(reply->pub_x.begin(), reply->pub_x.end(), pub_in.x);
std::copy(reply->pub_y.begin(), reply->pub_y.end(), pub_in.y);
hwsec::StatusOr<PinWeaverEccPoint> pub_out =
pinweaver_->GeneratePk(kCrosFpAuthChannel, pub_in);
if (!pub_out.ok()) {
LOG(ERROR) << __func__ << "GeneratePk for GSC failed.";
return false;
}
// Step 3: Finish Pk establishment and retrieve it from FPMCU.
std::optional<brillo::Blob> encrypted_pairing_key = cros_dev_->PairingKeyWrap(
brillo::Blob(pub_out->x, pub_out->x + PinWeaverEccPointSize),
brillo::Blob(pub_out->y, pub_out->y + PinWeaverEccPointSize),
reply->encrypted_private_key);
if (!encrypted_pairing_key.has_value()) {
return false;
}
if (!pk_storage_->WriteWrappedPairingKey(*encrypted_pairing_key)) {
LOG(ERROR) << "Failed to persist Pk.";
return false;
}
return true;
}
BiometricType CrosFpAuthStackManager::GetType() {
return BIOMETRIC_TYPE_FINGERPRINT;
}
BioSession CrosFpAuthStackManager::StartEnrollSession() {
if (!CanStartEnroll()) {
LOG(ERROR) << "Can't start an enroll session now, current state is: "
<< CurrentStateToString();
return BioSession(base::NullCallback());
}
if (!session_manager_->GetUser().has_value()) {
LOG(ERROR) << "Can only start enroll session when there is a user session.";
return Session(base::NullCallback());
}
if (session_manager_->GetNumOfTemplates() >= cros_dev_->MaxTemplateCount()) {
LOG(ERROR) << "No space for an additional template.";
return BioSession(base::NullCallback());
}
// Make sure context is cleared before starting enroll session.
cros_dev_->ResetContext();
if (!RequestEnrollImage())
return BioSession(base::NullCallback());
state_ = State::kEnroll;
return BioSession(base::BindOnce(&CrosFpAuthStackManager::EndEnrollSession,
session_weak_factory_.GetWeakPtr()));
}
CreateCredentialReply CrosFpAuthStackManager::CreateCredential(
const CreateCredentialRequest& request) {
CreateCredentialReply reply;
if (!CanCreateCredential()) {
LOG(ERROR) << "Can't create credential now, current state is: "
<< CurrentStateToString();
reply.set_status(CreateCredentialReply::INCORRECT_STATE);
return reply;
}
std::optional<std::string> current_user_id = session_manager_->GetUser();
if (!current_user_id.has_value() || request.user_id() != *current_user_id) {
LOG(ERROR) << "Credential can only be created for the current user.";
reply.set_status(CreateCredentialReply::INCORRECT_STATE);
return reply;
}
if (!cros_dev_->SetNonceContext(
brillo::BlobFromString(request.gsc_nonce()),
brillo::BlobFromString(request.encrypted_label_seed()),
brillo::BlobFromString(request.iv()))) {
LOG(ERROR) << "Failed to set nonce context";
reply.set_status(CreateCredentialReply::SET_NONCE_FAILED);
return reply;
}
std::unique_ptr<VendorTemplate> tmpl =
cros_dev_->GetTemplate(CrosFpDevice::kLastTemplate);
if (!tmpl) {
LOG(ERROR) << "Failed to retrieve enrolled finger";
reply.set_status(CreateCredentialReply::NO_TEMPLATE);
return reply;
}
std::optional<ec::CrosFpDeviceInterface::GetSecretReply> secret_reply =
cros_dev_->GetPositiveMatchSecretWithPubkey(
CrosFpDevice::kLastTemplate,
brillo::BlobFromString(request.pub().x()),
brillo::BlobFromString(request.pub().y()));
if (!secret_reply.has_value()) {
LOG(ERROR) << "Failed to get positive match secret.";
reply.set_status(CreateCredentialReply::NO_SECRET);
return reply;
}
std::string record_id = BiodStorage::GenerateNewRecordId();
// Label and validation value are not used in the new AuthStack flow.
BiodStorageInterface::RecordMetadata record{
.record_format_version = kRecordFormatVersion,
.record_id = record_id,
.user_id = std::move(request.user_id()),
.label = "",
.validation_val = {},
};
VendorTemplate actual_tmpl = *tmpl;
if (!session_manager_->CreateRecord(record, std::move(tmpl))) {
LOG(ERROR) << "Failed to create record for template.";
reply.set_status(CreateCredentialReply::CREATE_RECORD_FAILED);
return reply;
}
// We need to upload the newly-enrolled template to the preloaded buffer, so
// that we can load it properly with other preloaded templates the next time
// we want to AuthenticateCredential.
LOG(INFO) << "Upload record " << LogSafeID(record_id) << ".";
if (!cros_dev_->PreloadTemplate(session_manager_->GetNumOfTemplates() - 1,
std::move(actual_tmpl))) {
LOG(ERROR) << "Preload template failed.";
state_ = State::kLocked;
} else {
state_ = State::kNone;
}
reply.set_status(CreateCredentialReply::SUCCESS);
reply.set_encrypted_secret(
brillo::BlobToString(secret_reply->encrypted_secret));
reply.set_iv(brillo::BlobToString(secret_reply->iv));
reply.mutable_pub()->set_x(brillo::BlobToString(secret_reply->pk_out_x));
reply.mutable_pub()->set_y(brillo::BlobToString(secret_reply->pk_out_y));
reply.set_record_id(std::move(record_id));
return reply;
}
BioSession CrosFpAuthStackManager::StartAuthSession(std::string user_id) {
if (!CanStartAuth()) {
LOG(ERROR) << "Can't start an auth session now, current state is: "
<< CurrentStateToString();
return BioSession(base::NullCallback());
}
if (!LoadUser(user_id, false)) {
LOG(ERROR) << "Failed to load user for authentication.";
return BioSession(base::NullCallback());
}
if (state_ == State::kWaitForFingerUp) {
state_ = State::kAuthWaitForFingerUp;
} else {
// Make sure context is cleared before starting auth session.
cros_dev_->ResetContext();
if (!RequestMatchFingerDown())
return BioSession(base::NullCallback());
state_ = State::kAuth;
}
return BioSession(base::BindOnce(&CrosFpAuthStackManager::EndAuthSession,
session_weak_factory_.GetWeakPtr()));
}
void CrosFpAuthStackManager::AuthenticateCredential(
const AuthenticateCredentialRequest& request,
AuthStackManager::AuthenticateCredentialCallback callback) {
AuthenticateCredentialReply reply;
if (!CanAuthenticateCredential()) {
LOG(ERROR) << "Can't authenticate credential now, current state is: "
<< CurrentStateToString();
reply.set_status(AuthenticateCredentialReply::INCORRECT_STATE);
std::move(callback).Run(std::move(reply));
return;
}
if (!session_manager_->GetUser().has_value()) {
LOG(ERROR)
<< "Can only authenticate credential when there is a user session.";
reply.set_status(AuthenticateCredentialReply::INCORRECT_STATE);
std::move(callback).Run(std::move(reply));
return;
}
if (!cros_dev_->SetNonceContext(
brillo::BlobFromString(request.gsc_nonce()),
brillo::BlobFromString(request.encrypted_label_seed()),
brillo::BlobFromString(request.iv()))) {
LOG(ERROR) << "Failed to set nonce context";
reply.set_status(AuthenticateCredentialReply::SET_NONCE_FAILED);
std::move(callback).Run(std::move(reply));
return;
}
if (!cros_dev_->ReloadTemplates(session_manager_->GetNumOfTemplates())) {
LOG(ERROR) << "Failed to reload templates.";
reply.set_status(AuthenticateCredentialReply::UPLOAD_TEMPLATES_FAILED);
std::move(callback).Run(std::move(reply));
return;
}
DoMatch(request, std::move(callback));
return;
}
DeleteCredentialReply CrosFpAuthStackManager::DeleteCredential(
const DeleteCredentialRequest& request) {
DeleteCredentialReply reply;
const std::optional<std::string>& current_user = session_manager_->GetUser();
if (!current_user.has_value() || current_user.value() != request.user_id()) {
if (!session_manager_->DeleteNotLoadedRecord(request.user_id(),
request.record_id())) {
LOG(ERROR) << "Failed to delete credential.";
reply.set_status(DeleteCredentialReply::DELETION_FAILED);
} else {
reply.set_status(DeleteCredentialReply::SUCCESS);
}
return reply;
}
const std::string& record_id = request.record_id();
if (!session_manager_->HasRecordId(record_id)) {
LOG(WARNING) << "Trying to delete a non-existing credential.";
reply.set_status(DeleteCredentialReply::NOT_EXIST);
return reply;
}
if (!session_manager_->DeleteRecord(record_id)) {
LOG(ERROR) << "Failed to delete credential.";
reply.set_status(DeleteCredentialReply::DELETION_FAILED);
return reply;
}
if (!PreloadCurrentUserTemplates()) {
LOG(ERROR) << "Failed to reload the current user's templates. Biod locked "
"for further actions.";
// The credential is still deleted successfully, so don't need to return
// error here.
}
reply.set_status(DeleteCredentialReply::SUCCESS);
return reply;
}
void CrosFpAuthStackManager::OnUserLoggedOut() {
// Note that CrOS currently always logouts all users together.
session_manager_->UnloadUser();
locked_to_current_user_ = false;
}
void CrosFpAuthStackManager::OnUserLoggedIn(const std::string& user_id) {
LoadUser(user_id, true);
}
void CrosFpAuthStackManager::SetEnrollScanDoneHandler(
const AuthStackManager::EnrollScanDoneCallback& on_enroll_scan_done) {
on_enroll_scan_done_ = on_enroll_scan_done;
}
void CrosFpAuthStackManager::SetAuthScanDoneHandler(
const AuthStackManager::AuthScanDoneCallback& on_auth_scan_done) {
on_auth_scan_done_ = on_auth_scan_done;
}
void CrosFpAuthStackManager::SetSessionFailedHandler(
const AuthStackManager::SessionFailedCallback& on_session_failed) {
on_session_failed_ = on_session_failed;
}
void CrosFpAuthStackManager::EndEnrollSession() {
KillMcuSession();
}
void CrosFpAuthStackManager::EndAuthSession() {
KillMcuSession();
}
void CrosFpAuthStackManager::KillMcuSession() {
if (IsActiveState()) {
state_ = State::kNone;
}
// TODO(b/274509408): test cros_dev_->FpMode(FP_MODE_DEEPSLEEP);
cros_dev_->SetFpMode(ec::FpMode(Mode::kNone));
session_weak_factory_.InvalidateWeakPtrs();
OnTaskComplete();
}
void CrosFpAuthStackManager::OnMkbpEvent(uint32_t event) {
if (!next_session_action_.is_null())
next_session_action_.Run(event);
}
void CrosFpAuthStackManager::OnTaskComplete() {
next_session_action_ = SessionAction();
}
bool CrosFpAuthStackManager::LoadPairingKey() {
std::optional<brillo::Blob> wrapped_pairing_key =
pk_storage_->ReadWrappedPairingKey();
if (!wrapped_pairing_key.has_value()) {
LOG(ERROR) << "Failed to read Pk from storage.";
return false;
}
if (!cros_dev_->LoadPairingKey(*wrapped_pairing_key)) {
LOG(ERROR) << "Failed to load Pk.";
return false;
}
return true;
}
void CrosFpAuthStackManager::OnEnrollScanDone(
ScanResult result,
const AuthStackManager::EnrollStatus& enroll_status,
brillo::Blob auth_nonce) {
on_enroll_scan_done_.Run(result, enroll_status, std::move(auth_nonce));
}
void CrosFpAuthStackManager::OnAuthScanDone(brillo::Blob auth_nonce) {
on_auth_scan_done_.Run(std::move(auth_nonce));
}
void CrosFpAuthStackManager::OnSessionFailed() {
on_session_failed_.Run();
}
bool CrosFpAuthStackManager::LoadUser(std::string user_id, bool lock_to_user) {
const std::optional<std::string>& current_user = session_manager_->GetUser();
if (current_user.has_value() && current_user.value() == user_id) {
// No action required, the user is already loaded.
return true;
} else if (current_user.has_value()) {
if (locked_to_current_user_) {
LOG(ERROR) << "Can't load another user as a user is logged-in.";
return false;
}
session_manager_->UnloadUser();
}
// Any failure beyond this will lock the whole biod state machine.
if (lock_to_user) {
locked_to_current_user_ = true;
}
if (!session_manager_->LoadUser(std::move(user_id))) {
LOG(ERROR) << "Failed to start user session.";
state_ = State::kLocked;
return false;
}
return PreloadCurrentUserTemplates();
}
bool CrosFpAuthStackManager::PreloadCurrentUserTemplates() {
std::vector<CrosFpSessionManager::SessionRecord> records =
session_manager_->GetRecords();
for (size_t i = 0; i < records.size(); i++) {
const auto& record = records[i];
// TODO(b/253993586): Send record format version metrics here.
LOG(INFO) << "Upload record " << LogSafeID(record.record_metadata.record_id)
<< ".";
if (!cros_dev_->PreloadTemplate(i, record.tmpl)) {
LOG(ERROR) << "Preload template failed.";
state_ = State::kLocked;
return false;
}
}
return true;
}
bool CrosFpAuthStackManager::RequestEnrollImage() {
next_session_action_ = base::BindRepeating(
&CrosFpAuthStackManager::DoEnrollImageEvent, base::Unretained(this));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kEnrollSessionEnrollImage))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to start enrolling mode";
return false;
}
return true;
}
bool CrosFpAuthStackManager::RequestEnrollFingerUp() {
next_session_action_ = base::BindRepeating(
&CrosFpAuthStackManager::DoEnrollFingerUpEvent, base::Unretained(this));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kEnrollSessionFingerUp))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to wait for finger up";
return false;
}
return true;
}
void CrosFpAuthStackManager::DoEnrollImageEvent(uint32_t event) {
if (!(event & EC_MKBP_FP_ENROLL)) {
LOG(WARNING) << "Unexpected MKBP event: 0x" << std::hex << event;
// Continue waiting for the proper event, do not abort session.
return;
}
int image_result = EC_MKBP_FP_ERRCODE(event);
LOG(INFO) << __func__ << " result: '" << EnrollResultToString(image_result)
<< "'";
ScanResult scan_result;
switch (image_result) {
case EC_MKBP_FP_ERR_ENROLL_OK:
scan_result = ScanResult::SCAN_RESULT_SUCCESS;
break;
case EC_MKBP_FP_ERR_ENROLL_IMMOBILE:
scan_result = ScanResult::SCAN_RESULT_IMMOBILE;
break;
case EC_MKBP_FP_ERR_ENROLL_LOW_COVERAGE:
scan_result = ScanResult::SCAN_RESULT_PARTIAL;
break;
case EC_MKBP_FP_ERR_ENROLL_LOW_QUALITY:
scan_result = ScanResult::SCAN_RESULT_INSUFFICIENT;
break;
case EC_MKBP_FP_ERR_ENROLL_INTERNAL:
default:
LOG(ERROR) << "Unexpected result from capture: " << std::hex << event;
OnSessionFailed();
return;
}
int percent = EC_MKBP_FP_ENROLL_PROGRESS(event);
if (percent < 100) {
AuthStackManager::EnrollStatus enroll_status = {
.done = false,
.percent_complete = percent,
};
OnEnrollScanDone(scan_result, enroll_status, brillo::Blob());
// The user needs to remove the finger before the next enrollment image.
if (!RequestEnrollFingerUp())
OnSessionFailed();
return;
}
std::optional<brillo::Blob> auth_nonce = cros_dev_->GetNonce();
if (!auth_nonce.has_value()) {
LOG(ERROR) << "Failed to get auth nonce.";
OnSessionFailed();
return;
}
OnTaskComplete();
state_ = State::kEnrollDone;
AuthStackManager::EnrollStatus enroll_status = {
.done = true,
.percent_complete = 100,
};
OnEnrollScanDone(ScanResult::SCAN_RESULT_SUCCESS, enroll_status,
std::move(*auth_nonce));
}
void CrosFpAuthStackManager::DoEnrollFingerUpEvent(uint32_t event) {
if (!(event & EC_MKBP_FP_FINGER_UP)) {
LOG(WARNING) << "Unexpected MKBP event: 0x" << std::hex << event;
// Continue waiting for the proper event, do not abort session.
return;
}
if (!RequestEnrollImage())
OnSessionFailed();
}
bool CrosFpAuthStackManager::RequestMatchFingerDown() {
next_session_action_ = base::BindRepeating(
&CrosFpAuthStackManager::OnMatchFingerDown, base::Unretained(this));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kFingerDown))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to start finger down mode";
return false;
}
return true;
}
void CrosFpAuthStackManager::OnMatchFingerDown(uint32_t event) {
if (!(event & EC_MKBP_FP_FINGER_DOWN)) {
LOG(WARNING) << "Unexpected MKBP event: 0x" << std::hex << event;
// Continue waiting for the proper event, do not abort session.
return;
}
std::optional<brillo::Blob> auth_nonce = cros_dev_->GetNonce();
if (!auth_nonce) {
LOG(ERROR) << "Failed to get auth nonce.";
OnSessionFailed();
return;
}
OnTaskComplete();
state_ = State::kAuthDone;
OnAuthScanDone(std::move(*auth_nonce));
}
void CrosFpAuthStackManager::DoMatch(
const AuthenticateCredentialRequest& request,
AuthStackManager::AuthenticateCredentialCallback callback) {
AuthenticateCredentialReply reply;
// Either SetFpMode failed and only cb_failed will be called, or SetFpMode
// succeeded and one of cb_success, cb_timeout will be called depending on
// whether match even is emitted within a specified timer.
auto [cb, cb_failed] = base::SplitOnceCallback(std::move(callback));
auto [cb_success, cb_timeout] = base::SplitOnceCallback(std::move(cb));
next_session_action_ = base::BindRepeating(
&CrosFpAuthStackManager::DoMatchEvent, base::Unretained(this), request,
// We guarantee that the callback will only at most be called once: we
// never run the successful callback in DoMatchEvent before
// RequestFingerUp, and RequestFingerUp always overwrite
// next_session_action_ to another callback.
std::make_shared<AuthStackManager::AuthenticateCredentialCallback>(
std::move(cb_success)));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kMatch))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to set FPMCU to match mode.";
reply.set_status(AuthenticateCredentialReply::MATCH_FAILED);
std::move(cb_failed).Run(std::move(reply));
return;
}
state_ = State::kMatch;
do_match_timer_.Start(
FROM_HERE, base::Seconds(2),
base::BindOnce(&CrosFpAuthStackManager::AbortDoMatch,
base::Unretained(this), std::move(cb_timeout)));
return;
}
void CrosFpAuthStackManager::DoMatchEvent(
AuthenticateCredentialRequest request,
std::shared_ptr<AuthStackManager::AuthenticateCredentialCallback> callback,
uint32_t event) {
AuthenticateCredentialReply reply;
if (!(event & EC_MKBP_FP_MATCH)) {
LOG(WARNING) << "Unexpected MKBP event: 0x" << std::hex << event;
// Continue waiting for the proper event, do not abort session.
return;
}
do_match_timer_.Stop();
int match_result = EC_MKBP_FP_ERRCODE(event);
// Don't try to match again until the user has lifted their finger from the
// sensor. Request the FingerUp event as soon as the HW signaled a match so it
// doesn't attempt a new match while the host is processing the first
// match event.
if (!RequestFingerUp()) {
state_ = State::kNone;
LOG(WARNING) << "Failed to request finger up.";
} else {
state_ = State::kWaitForFingerUp;
}
bool matched = false;
uint32_t match_idx = EC_MKBP_FP_MATCH_IDX(event);
LOG(INFO) << __func__ << " result: '" << MatchResultToString(match_result)
<< "' (finger: " << match_idx << ")";
switch (match_result) {
case EC_MKBP_FP_ERR_MATCH_NO_TEMPLATES:
LOG(ERROR) << "No templates to match: " << std::hex << event;
reply.set_status(AuthenticateCredentialReply::NO_TEMPLATES);
break;
case EC_MKBP_FP_ERR_MATCH_NO_INTERNAL:
LOG(ERROR) << "Internal error when matching templates: " << std::hex
<< event;
reply.set_status(AuthenticateCredentialReply::INTERNAL_ERROR);
break;
case EC_MKBP_FP_ERR_MATCH_NO:
reply.set_status(AuthenticateCredentialReply::SUCCESS);
reply.set_scan_result(ScanResult::SCAN_RESULT_NO_MATCH);
break;
case EC_MKBP_FP_ERR_MATCH_YES:
case EC_MKBP_FP_ERR_MATCH_YES_UPDATED:
case EC_MKBP_FP_ERR_MATCH_YES_UPDATE_FAILED:
// We are on a good path to successfully authenticate user, but
// we still need to fetch the positive match secret.
matched = true;
break;
case EC_MKBP_FP_ERR_MATCH_NO_LOW_QUALITY:
reply.set_status(AuthenticateCredentialReply::SUCCESS);
reply.set_scan_result(ScanResult::SCAN_RESULT_INSUFFICIENT);
break;
case EC_MKBP_FP_ERR_MATCH_NO_LOW_COVERAGE:
reply.set_status(AuthenticateCredentialReply::SUCCESS);
reply.set_scan_result(ScanResult::SCAN_RESULT_PARTIAL);
break;
default:
LOG(ERROR) << "Unexpected result from matching templates: " << std::hex
<< event;
reply.set_status(AuthenticateCredentialReply::INTERNAL_ERROR);
}
if (!matched) {
std::move(*callback).Run(std::move(reply));
return;
}
std::optional<RecordMetadata> metadata =
session_manager_->GetRecordMetadata(match_idx);
if (!metadata.has_value()) {
LOG(ERROR) << "Matched template idx not found in in-memory records.";
reply.set_status(AuthenticateCredentialReply::NO_TEMPLATES);
std::move(*callback).Run(std::move(reply));
return;
}
std::optional<ec::CrosFpDeviceInterface::GetSecretReply> secret_reply =
cros_dev_->GetPositiveMatchSecretWithPubkey(
match_idx, brillo::BlobFromString(request.pub().x()),
brillo::BlobFromString(request.pub().y()));
if (!secret_reply.has_value()) {
LOG(ERROR) << "Failed to get positive match secret.";
reply.set_status(AuthenticateCredentialReply::NO_SECRET);
std::move(*callback).Run(std::move(reply));
return;
}
reply.set_status(AuthenticateCredentialReply::SUCCESS);
reply.set_scan_result(SCAN_RESULT_SUCCESS);
reply.set_encrypted_secret(
brillo::BlobToString(secret_reply->encrypted_secret));
reply.set_iv(brillo::BlobToString(secret_reply->iv));
reply.mutable_pub()->set_x(brillo::BlobToString(secret_reply->pk_out_x));
reply.mutable_pub()->set_y(brillo::BlobToString(secret_reply->pk_out_y));
reply.set_record_id(metadata->record_id);
std::move(*callback).Run(std::move(reply));
// TODO(b/253993586): Get latency stats and send UMA.
// TODO(b/254164023): Update dirty templates.
return;
}
void CrosFpAuthStackManager::AbortDoMatch(
AuthStackManager::AuthenticateCredentialCallback callback) {
AuthenticateCredentialReply reply;
next_session_action_ = SessionAction();
LOG(ERROR) << "Match timed out.";
reply.set_status(AuthenticateCredentialReply::MATCH_FAILED);
// If match timed out, finger should already be up at this moment, so we don't
// have to wait for finger up event again.
state_ = State::kNone;
std::move(callback).Run(std::move(reply));
}
bool CrosFpAuthStackManager::RequestFingerUp() {
next_session_action_ = base::BindRepeating(
&CrosFpAuthStackManager::OnFingerUpEvent, base::Unretained(this));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kFingerUp))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to request finger up event";
return false;
}
return true;
}
void CrosFpAuthStackManager::OnFingerUpEvent(uint32_t event) {
if (!(event & EC_MKBP_FP_FINGER_UP)) {
LOG(WARNING) << "Unexpected MKBP event: 0x" << std::hex << event;
// Continue waiting for the proper event, do not abort session.
return;
}
if (state_ == State::kWaitForFingerUp) {
state_ = State::kNone;
} else if (state_ == State::kAuthWaitForFingerUp) {
// Make sure context is cleared before starting auth session.
cros_dev_->ResetContext();
if (!RequestMatchFingerDown()) {
OnSessionFailed();
state_ = State::kNone;
} else {
state_ = State::kAuth;
}
} else {
LOG(ERROR) << "Finger up event receiving in unexpected state: "
<< CurrentStateToString();
}
}
std::string CrosFpAuthStackManager::CurrentStateToString() {
switch (state_) {
case State::kNone:
return "None";
case State::kEnroll:
return "Enroll";
case State::kEnrollDone:
return "EnrollDone";
case State::kAuth:
return "Auth";
case State::kAuthDone:
return "AuthDone";
case State::kMatch:
return "Match";
case State::kWaitForFingerUp:
return "WaitForFingerUp";
case State::kAuthWaitForFingerUp:
return "AuthWaitForFingerUp";
case State::kLocked:
return "Locked";
}
}
bool CrosFpAuthStackManager::IsActiveState() {
switch (state_) {
case State::kEnroll:
case State::kAuth:
case State::kWaitForFingerUp:
case State::kAuthWaitForFingerUp:
return true;
case State::kNone:
case State::kEnrollDone:
case State::kAuthDone:
case State::kMatch:
case State::kLocked:
return false;
}
}
bool CrosFpAuthStackManager::CanStartEnroll() {
switch (state_) {
case State::kNone:
case State::kEnrollDone:
case State::kAuthDone:
case State::kWaitForFingerUp:
return true;
case State::kEnroll:
case State::kAuth:
case State::kMatch:
case State::kAuthWaitForFingerUp:
case State::kLocked:
return false;
}
}
bool CrosFpAuthStackManager::CanCreateCredential() {
return state_ == State::kEnrollDone;
}
bool CrosFpAuthStackManager::CanStartAuth() {
switch (state_) {
case State::kNone:
case State::kEnrollDone:
case State::kAuthDone:
case State::kWaitForFingerUp:
return true;
case State::kEnroll:
case State::kAuth:
case State::kMatch:
case State::kLocked:
case State::kAuthWaitForFingerUp:
return false;
}
}
bool CrosFpAuthStackManager::CanAuthenticateCredential() {
return state_ == State::kAuthDone;
}
} // namespace biod