blob: 60441e2c84163e6c572a1ca26085e8eec88107f2 [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "biod/cros_fp_biometrics_manager.h"
#include <utility>
#include <errno.h>
#include <poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <base/base64.h>
#include <base/bind.h>
#include <base/check.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_piece.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <crypto/random.h>
#include <metrics/metrics_library.h>
#include <base/timer/timer.h>
#include "biod/biod_crypto.h"
#include "biod/biod_metrics.h"
#include "biod/biod_storage.h"
#include "biod/power_button_filter.h"
#include "biod/utils.h"
namespace {
std::string MatchResultToString(int result) {
switch (result) {
case EC_MKBP_FP_ERR_MATCH_NO:
return "No match";
case EC_MKBP_FP_ERR_MATCH_NO_INTERNAL:
return "Internal error";
case EC_MKBP_FP_ERR_MATCH_NO_TEMPLATES:
return "No templates";
case EC_MKBP_FP_ERR_MATCH_NO_LOW_QUALITY:
return "Low quality";
case EC_MKBP_FP_ERR_MATCH_NO_LOW_COVERAGE:
return "Low coverage";
case EC_MKBP_FP_ERR_MATCH_YES:
return "Finger matched";
case EC_MKBP_FP_ERR_MATCH_YES_UPDATED:
return "Finger matched, template updated";
case EC_MKBP_FP_ERR_MATCH_YES_UPDATE_FAILED:
return "Finger matched, template updated failed";
default:
return "Unknown matcher result";
}
}
std::string EnrollResultToString(int result) {
switch (result) {
case EC_MKBP_FP_ERR_ENROLL_OK:
return "Success";
case EC_MKBP_FP_ERR_ENROLL_LOW_QUALITY:
return "Low quality";
case EC_MKBP_FP_ERR_ENROLL_IMMOBILE:
return "Same area";
case EC_MKBP_FP_ERR_ENROLL_LOW_COVERAGE:
return "Low coverage";
case EC_MKBP_FP_ERR_ENROLL_INTERNAL:
return "Internal error";
default:
return "Unknown enrollment result";
}
}
}; // namespace
namespace biod {
using Mode = ec::FpMode::Mode;
const std::string& CrosFpBiometricsManager::Record::GetId() const {
CHECK(biometrics_manager_);
CHECK(index_ < biometrics_manager_->records_.size());
return biometrics_manager_->records_[index_].record_id;
}
const std::string& CrosFpBiometricsManager::Record::GetUserId() const {
CHECK(biometrics_manager_);
CHECK(index_ <= biometrics_manager_->records_.size());
return biometrics_manager_->records_[index_].user_id;
}
const std::string& CrosFpBiometricsManager::Record::GetLabel() const {
CHECK(biometrics_manager_);
CHECK(index_ < biometrics_manager_->records_.size());
return biometrics_manager_->records_[index_].label;
}
const std::vector<uint8_t>& CrosFpBiometricsManager::Record::GetValidationVal()
const {
CHECK(biometrics_manager_);
CHECK(index_ <= biometrics_manager_->records_.size());
return biometrics_manager_->records_[index_].validation_val;
}
bool CrosFpBiometricsManager::Record::SetLabel(std::string label) {
CHECK(biometrics_manager_);
CHECK(index_ < biometrics_manager_->records_.size());
std::string old_label = biometrics_manager_->records_[index_].label;
std::unique_ptr<VendorTemplate> tmpl =
biometrics_manager_->cros_dev_->GetTemplate(index_);
// TODO(vpalatin): would be faster to read it from disk
if (!tmpl) {
return false;
}
biometrics_manager_->records_[index_].label = std::move(label);
if (!biometrics_manager_->WriteRecord(*this, tmpl->data(), tmpl->size())) {
biometrics_manager_->records_[index_].label = std::move(old_label);
return false;
}
return true;
}
bool CrosFpBiometricsManager::Record::SupportsPositiveMatchSecret() const {
return biometrics_manager_->use_positive_match_secret_;
}
bool CrosFpBiometricsManager::Record::Remove() {
if (!biometrics_manager_)
return false;
if (index_ >= biometrics_manager_->records_.size())
return false;
const auto& record = biometrics_manager_->records_[index_];
std::string user_id = record.user_id;
// TODO(mqg): only delete record if user_id is primary user.
if (!biometrics_manager_->biod_storage_->DeleteRecord(user_id,
record.record_id))
return false;
// We cannot remove only one record if we want to stay in sync with the MCU,
// Clear and reload everything.
return biometrics_manager_->ReloadAllRecords(user_id);
}
bool CrosFpBiometricsManager::ReloadAllRecords(std::string user_id) {
// Here we need a copy of user_id because the user_id could be part of
// records_ which is cleared in this method.
records_.clear();
suspicious_templates_.clear();
cros_dev_->SetContext(user_id);
auto result = biod_storage_->ReadRecordsForSingleUser(user_id);
for (const auto& record : result.valid_records) {
LoadRecord(std::move(record));
}
return result.invalid_records.empty();
}
BiometricType CrosFpBiometricsManager::GetType() {
return BIOMETRIC_TYPE_FINGERPRINT;
}
BiometricsManager::EnrollSession CrosFpBiometricsManager::StartEnrollSession(
std::string user_id, std::string label) {
LOG(INFO) << __func__;
// Another session is on-going, fail early ...
if (!next_session_action_.is_null()) {
LOG(ERROR) << "Another EnrollSession already exists";
return BiometricsManager::EnrollSession();
}
if (records_.size() >= cros_dev_->MaxTemplateCount()) {
LOG(ERROR) << "No space for an additional template.";
return BiometricsManager::EnrollSession();
}
std::vector<uint8_t> validation_val;
if (!RequestEnrollImage(BiodStorageInterface::RecordMetadata{
kRecordFormatVersion, BiodStorage::GenerateNewRecordId(),
std::move(user_id), std::move(label), std::move(validation_val)}))
return BiometricsManager::EnrollSession();
return BiometricsManager::EnrollSession(session_weak_factory_.GetWeakPtr());
}
BiometricsManager::AuthSession CrosFpBiometricsManager::StartAuthSession() {
LOG(INFO) << __func__;
// Another session is on-going, fail early ...
if (!next_session_action_.is_null()) {
LOG(ERROR) << "Another AuthSession already exists";
return BiometricsManager::AuthSession();
}
if (!RequestMatch())
return BiometricsManager::AuthSession();
return BiometricsManager::AuthSession(session_weak_factory_.GetWeakPtr());
}
std::vector<std::unique_ptr<BiometricsManagerRecord>>
CrosFpBiometricsManager::GetRecords() {
std::vector<std::unique_ptr<BiometricsManagerRecord>> records;
for (int i = 0; i < records_.size(); i++)
records.emplace_back(
std::make_unique<Record>(weak_factory_.GetWeakPtr(), i));
return records;
}
bool CrosFpBiometricsManager::DestroyAllRecords() {
// Enumerate through records_ and delete each record.
bool delete_all_records = true;
for (auto& record : records_) {
delete_all_records &=
biod_storage_->DeleteRecord(record.user_id, record.record_id);
}
RemoveRecordsFromMemory();
return delete_all_records;
}
void CrosFpBiometricsManager::RemoveRecordsFromMemory() {
records_.clear();
suspicious_templates_.clear();
cros_dev_->ResetContext();
}
bool CrosFpBiometricsManager::ReadRecordsForSingleUser(
const std::string& user_id) {
cros_dev_->SetContext(user_id);
auto result = biod_storage_->ReadRecordsForSingleUser(user_id);
for (const auto& record : result.valid_records) {
LoadRecord(record);
}
return result.invalid_records.empty();
}
void CrosFpBiometricsManager::SetEnrollScanDoneHandler(
const BiometricsManager::EnrollScanDoneCallback& on_enroll_scan_done) {
on_enroll_scan_done_ = on_enroll_scan_done;
}
void CrosFpBiometricsManager::SetAuthScanDoneHandler(
const BiometricsManager::AuthScanDoneCallback& on_auth_scan_done) {
on_auth_scan_done_ = on_auth_scan_done;
}
void CrosFpBiometricsManager::SetSessionFailedHandler(
const BiometricsManager::SessionFailedCallback& on_session_failed) {
on_session_failed_ = on_session_failed;
}
bool CrosFpBiometricsManager::SendStatsOnLogin() {
bool rc = true;
rc = biod_metrics_->SendEnrolledFingerCount(records_.size()) && rc;
// Even though it looks a bit redundant with the finger count, it's easier to
// discover and interpret.
rc = biod_metrics_->SendFpUnlockEnabled(!records_.empty()) && rc;
return rc;
}
void CrosFpBiometricsManager::SetDiskAccesses(bool allow) {
biod_storage_->set_allow_access(allow);
}
bool CrosFpBiometricsManager::ResetSensor() {
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kResetSensor))) {
LOG(ERROR) << "Failed to send reset_sensor command to FPMCU.";
return false;
}
int retries = 50;
bool reset_complete = false;
while (retries--) {
ec::FpMode cur_mode = cros_dev_->GetFpMode();
if (cur_mode == ec::FpMode(Mode::kModeInvalid)) {
LOG(ERROR) << "Failed to query sensor state during reset.";
return false;
}
if (cur_mode != ec::FpMode(Mode::kResetSensor)) {
reset_complete = true;
break;
}
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
}
if (!reset_complete) {
LOG(ERROR) << "Reset on FPMCU failed to complete.";
return false;
}
return true;
}
bool CrosFpBiometricsManager::ResetEntropy(bool factory_init) {
bool success = cros_dev_->InitEntropy(!factory_init);
if (!success) {
LOG(INFO) << "Entropy source reset failed.";
return false;
}
LOG(INFO) << "Entropy source has been successfully reset.";
return true;
}
void CrosFpBiometricsManager::EndEnrollSession() {
LOG(INFO) << __func__;
KillMcuSession();
}
void CrosFpBiometricsManager::EndAuthSession() {
LOG(INFO) << __func__;
KillMcuSession();
}
void CrosFpBiometricsManager::KillMcuSession() {
// TODO(vpalatin): test cros_dev_->FpMode(FP_MODE_DEEPSLEEP);
cros_dev_->SetFpMode(ec::FpMode(Mode::kNone));
session_weak_factory_.InvalidateWeakPtrs();
OnTaskComplete();
}
CrosFpBiometricsManager::CrosFpBiometricsManager(
std::unique_ptr<PowerButtonFilterInterface> power_button_filter,
std::unique_ptr<CrosFpDeviceInterface> cros_fp_device,
std::unique_ptr<BiodMetricsInterface> biod_metrics,
std::unique_ptr<BiodStorageInterface> biod_storage)
: biod_metrics_(std::move(biod_metrics)),
cros_dev_(std::move(cros_fp_device)),
session_weak_factory_(this),
weak_factory_(this),
power_button_filter_(std::move(power_button_filter)),
biod_storage_(std::move(biod_storage)),
use_positive_match_secret_(false),
maintenance_timer_(std::make_unique<base::RepeatingTimer>()) {
CHECK(power_button_filter_);
CHECK(cros_dev_);
CHECK(biod_metrics_);
CHECK(maintenance_timer_);
cros_dev_->SetMkbpEventCallback(base::BindRepeating(
&CrosFpBiometricsManager::OnMkbpEvent, base::Unretained(this)));
use_positive_match_secret_ = cros_dev_->SupportsPositiveMatchSecret();
// TODO(b/187951992): The following automatic maintenance routine needs to
// be re-enabled in such a way that it will not interfere with the
// auth/unlock or enroll session. This maintenance timer was disabled due
// to b/184783529.
#if 0
maintenance_timer_->Start(
FROM_HERE, base::TimeDelta::FromDays(1),
base::BindRepeating(&CrosFpBiometricsManager::OnMaintenanceTimerFired,
base::Unretained(this)));
#endif
}
CrosFpBiometricsManager::~CrosFpBiometricsManager() {}
void CrosFpBiometricsManager::OnEnrollScanDone(
ScanResult result, const BiometricsManager::EnrollStatus& enroll_status) {
if (!on_enroll_scan_done_.is_null())
on_enroll_scan_done_.Run(result, enroll_status);
}
void CrosFpBiometricsManager::OnAuthScanDone(
ScanResult result, const BiometricsManager::AttemptMatches& matches) {
if (!on_auth_scan_done_.is_null())
on_auth_scan_done_.Run(result, matches);
}
void CrosFpBiometricsManager::OnSessionFailed() {
LOG(INFO) << __func__;
if (!on_session_failed_.is_null())
on_session_failed_.Run();
}
void CrosFpBiometricsManager::OnMkbpEvent(uint32_t event) {
if (!next_session_action_.is_null())
next_session_action_.Run(event);
}
bool CrosFpBiometricsManager::RequestEnrollImage(
BiodStorageInterface::RecordMetadata record) {
next_session_action_ =
base::BindRepeating(&CrosFpBiometricsManager::DoEnrollImageEvent,
base::Unretained(this), std::move(record));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kEnrollSessionEnrollImage))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to start enrolling mode";
return false;
}
return true;
}
bool CrosFpBiometricsManager::RequestEnrollFingerUp(
BiodStorageInterface::RecordMetadata record) {
next_session_action_ =
base::BindRepeating(&CrosFpBiometricsManager::DoEnrollFingerUpEvent,
base::Unretained(this), std::move(record));
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kEnrollSessionFingerUp))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to wait for finger up";
return false;
}
return true;
}
bool CrosFpBiometricsManager::RequestMatch(int attempt) {
next_session_action_ = base::BindRepeating(
&CrosFpBiometricsManager::DoMatchEvent, base::Unretained(this), attempt);
if (!cros_dev_->SetFpMode(ec::FpMode(Mode::kMatch))) {
next_session_action_ = SessionAction();
LOG(ERROR) << "Failed to start matching mode";
return false;
}
return true;
}
bool CrosFpBiometricsManager::RequestMatchFingerUp() {
next_session_action_ = base::BindRepeating(
&CrosFpBiometricsManager::DoMatchFingerUpEvent, 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 CrosFpBiometricsManager::DoEnrollImageEvent(
BiodStorageInterface::RecordMetadata record, 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) {
BiometricsManager::EnrollStatus enroll_status = {false, percent};
OnEnrollScanDone(scan_result, enroll_status);
// The user needs to remove the finger before the next enrollment image.
if (!RequestEnrollFingerUp(std::move(record)))
OnSessionFailed();
return;
}
// we are done with captures, save the template.
OnTaskComplete();
std::unique_ptr<VendorTemplate> tmpl =
cros_dev_->GetTemplate(CrosFpDevice::kLastTemplate);
if (!tmpl) {
LOG(ERROR) << "Failed to retrieve enrolled finger";
OnSessionFailed();
return;
}
if (use_positive_match_secret_) {
base::Optional<brillo::SecureVector> secret =
cros_dev_->GetPositiveMatchSecret(CrosFpDevice::kLastTemplate);
if (!secret) {
LOG(ERROR) << "Failed to get positive match secret.";
OnSessionFailed();
return;
}
std::vector<uint8_t> validation_val;
if (!BiodCrypto::ComputeValidationValue(*secret, record.user_id,
&validation_val)) {
LOG(ERROR) << "Failed to compute validation value.";
OnSessionFailed();
return;
}
record.validation_val = std::move(validation_val);
LOG(INFO) << "Computed validation value for enrolled finger.";
}
records_.emplace_back(record);
Record current_record(weak_factory_.GetWeakPtr(), records_.size() - 1);
if (!WriteRecord(current_record, tmpl->data(), tmpl->size())) {
records_.pop_back();
OnSessionFailed();
return;
}
BiometricsManager::EnrollStatus enroll_status = {true, 100};
OnEnrollScanDone(ScanResult::SCAN_RESULT_SUCCESS, enroll_status);
}
void CrosFpBiometricsManager::DoEnrollFingerUpEvent(
BiodStorageInterface::RecordMetadata record, 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(std::move(record)))
OnSessionFailed();
}
void CrosFpBiometricsManager::DoMatchFingerUpEvent(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;
}
// The user has lifted their finger, try to match the next touch.
if (!RequestMatch())
OnSessionFailed();
}
bool CrosFpBiometricsManager::ValidationValueIsCorrect(uint32_t match_idx) {
base::Optional<brillo::SecureVector> secret =
cros_dev_->GetPositiveMatchSecret(match_idx);
biod_metrics_->SendReadPositiveMatchSecretSuccess(secret.has_value());
if (!secret) {
LOG(ERROR) << "Failed to read positive match secret on match for finger "
<< match_idx << ".";
return false;
}
std::vector<uint8_t> validation_value;
if (!BiodCrypto::ComputeValidationValue(*secret, records_[match_idx].user_id,
&validation_value)) {
LOG(ERROR) << "Got positive match secret but failed to compute validation "
"value for finger "
<< match_idx << ".";
return false;
}
if (validation_value != records_[match_idx].validation_val) {
LOG(ERROR) << "Validation value does not match for finger " << match_idx;
biod_metrics_->SendPositiveMatchSecretCorrect(false);
suspicious_templates_.emplace(match_idx);
return false;
}
LOG(INFO) << "Verified validation value for finger " << match_idx;
biod_metrics_->SendPositiveMatchSecretCorrect(true);
suspicious_templates_.erase(match_idx);
return true;
}
BiometricsManager::AttemptMatches CrosFpBiometricsManager::CalculateMatches(
int match_idx, bool matched) {
BiometricsManager::AttemptMatches matches;
if (!matched)
return matches;
if (match_idx >= records_.size()) {
LOG(ERROR) << "Invalid finger index " << match_idx;
return matches;
}
if (!use_positive_match_secret_ || ValidationValueIsCorrect(match_idx)) {
matches.emplace(records_[match_idx].user_id,
std::vector<std::string>({records_[match_idx].record_id}));
}
return matches;
}
void CrosFpBiometricsManager::DoMatchEvent(int attempt, uint32_t event) {
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;
}
// The user intention might be to press the power button. If so, ignore the
// current match.
if (power_button_filter_->ShouldFilterFingerprintMatch()) {
LOG(INFO)
<< "Power button event seen along with fp match. Ignoring fp match.";
// Try to match the next touch once the user lifts the finger as the client
// is still waiting for an auth. Wait for finger up event here is to prevent
// the following scenario.
// 1. Display is on. Now user presses power button with an enrolled finger.
// 3. Display goes off. biod starts auth session.
// 4. Fp match happens and is filtered by biod. biod immediately restarts
// a new auth session (if we do not wait for finger up).
// 5. fp sensor immediately sends a match event before user gets a chance to
// lift the finger.
// 6. biod sees a match again and this time notifies chrome without
// filtering it as it has filtered one already.
if (!RequestMatchFingerUp())
OnSessionFailed();
biod_metrics_->SendIgnoreMatchEventOnPowerButtonPress(true);
return;
}
biod_metrics_->SendIgnoreMatchEventOnPowerButtonPress(false);
ScanResult result;
int match_result = EC_MKBP_FP_ERRCODE(event);
// If the finger is positioned slightly off the sensor, retry a few times
// before failing. Typically the user has put their finger down and is now
// moving their finger to the correct position on the sensor. Instead of
// immediately failing, retry until we get a good image.
// Retry 20 times, which takes about 5 to 15s, before giving up and sending
// an error back to the user. Assume ~1s for user noticing that no matching
// happened, some time to move the finger on the sensor to allow a full
// capture and another 1s for the second matching attempt. 5s gives a bit of
// margin to avoid interrupting the user while they're moving the finger on
// the sensor.
const int kMaxPartialAttempts = 20;
if (match_result == EC_MKBP_FP_ERR_MATCH_NO_LOW_COVERAGE &&
attempt < kMaxPartialAttempts) {
/* retry a match */
if (!RequestMatch(attempt + 1))
OnSessionFailed();
return;
}
// 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 (!RequestMatchFingerUp()) {
OnSessionFailed();
return;
}
std::vector<int> dirty_list;
if (match_result == EC_MKBP_FP_ERR_MATCH_YES_UPDATED) {
dirty_list = GetDirtyList();
}
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;
result = ScanResult::SCAN_RESULT_SUCCESS;
break;
case EC_MKBP_FP_ERR_MATCH_NO_INTERNAL:
LOG(ERROR) << "Internal error when matching templates: " << std::hex
<< event;
result = ScanResult::SCAN_RESULT_SUCCESS;
break;
case EC_MKBP_FP_ERR_MATCH_NO:
// This is the API: empty matches but still SCAN_RESULT_SUCCESS.
result = ScanResult::SCAN_RESULT_SUCCESS;
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:
result = ScanResult::SCAN_RESULT_SUCCESS;
matched = true;
break;
case EC_MKBP_FP_ERR_MATCH_NO_LOW_QUALITY:
result = ScanResult::SCAN_RESULT_INSUFFICIENT;
break;
case EC_MKBP_FP_ERR_MATCH_NO_LOW_COVERAGE:
result = ScanResult::SCAN_RESULT_PARTIAL;
break;
default:
LOG(ERROR) << "Unexpected result from matching templates: " << std::hex
<< event;
OnSessionFailed();
return;
}
BiometricsManager::AttemptMatches matches =
CalculateMatches(match_idx, matched);
if (matches.empty())
matched = false;
// Send back the result directly (as we are running on the main thread).
OnAuthScanDone(result, std::move(matches));
base::Optional<CrosFpDeviceInterface::FpStats> stats =
cros_dev_->GetFpStats();
if (stats) {
biod_metrics_->SendFpLatencyStats(matched, *stats);
}
// Record updated templates
// TODO(vpalatin): this is slow, move to end of session ?
UpdateTemplatesOnDisk(dirty_list, suspicious_templates_);
}
void CrosFpBiometricsManager::OnTaskComplete() {
next_session_action_ = SessionAction();
}
bool CrosFpBiometricsManager::LoadRecord(
const BiodStorageInterface::Record record) {
std::string tmpl_data_str;
base::Base64Decode(record.data, &tmpl_data_str);
if (records_.size() >= cros_dev_->MaxTemplateCount()) {
LOG(ERROR) << "No space to upload template from "
<< LogSafeID(record.metadata.record_id) << ".";
return false;
}
biod_metrics_->SendRecordFormatVersion(record.metadata.record_format_version);
LOG(INFO) << "Upload record " << LogSafeID(record.metadata.record_id) << ".";
VendorTemplate tmpl(tmpl_data_str.begin(), tmpl_data_str.end());
auto* metadata =
reinterpret_cast<const ec_fp_template_encryption_metadata*>(tmpl.data());
if (metadata->struct_version != cros_dev_->TemplateVersion()) {
LOG(ERROR) << "Version mismatch between template ("
<< metadata->struct_version << ") and hardware ("
<< cros_dev_->TemplateVersion() << ")";
biod_storage_->DeleteRecord(record.metadata.user_id,
record.metadata.record_id);
return false;
}
if (!cros_dev_->UploadTemplate(tmpl)) {
LOG(ERROR) << "Cannot send template to the MCU from "
<< LogSafeID(record.metadata.record_id) << ".";
return false;
}
records_.emplace_back(std::move(record.metadata));
return true;
}
bool CrosFpBiometricsManager::WriteRecord(const BiometricsManagerRecord& record,
uint8_t* tmpl_data,
size_t tmpl_size) {
base::StringPiece tmpl_sp(reinterpret_cast<char*>(tmpl_data), tmpl_size);
std::string tmpl_base64;
base::Base64Encode(tmpl_sp, &tmpl_base64);
return biod_storage_->WriteRecord(record,
base::Value(std::move(tmpl_base64)));
}
void CrosFpBiometricsManager::OnMaintenanceTimerFired() {
LOG(INFO) << "Maintenance timer fired";
// Report the number of dead pixels
cros_dev_->UpdateFpInfo();
biod_metrics_->SendDeadPixelCount(cros_dev_->DeadPixelCount());
// The maintenance operation can take a couple hundred milliseconds, so it's
// an asynchronous mode (the state is cleared by the FPMCU after it is
// finished with the operation).
cros_dev_->SetFpMode(ec::FpMode(Mode::kSensorMaintenance));
}
std::vector<int> CrosFpBiometricsManager::GetDirtyList() {
std::vector<int> dirty_list;
// Retrieve which templates have been updated.
base::Optional<std::bitset<32>> dirty_bitmap = cros_dev_->GetDirtyMap();
if (!dirty_bitmap) {
LOG(ERROR) << "Failed to get updated templates map";
return dirty_list;
}
// Create a list of modified template indexes from the bitmap.
dirty_list.reserve(dirty_bitmap->count());
for (int i = 0; dirty_bitmap->any() && i < dirty_bitmap->size(); i++) {
if ((*dirty_bitmap)[i]) {
dirty_list.emplace_back(i);
dirty_bitmap->reset(i);
}
}
return dirty_list;
}
bool CrosFpBiometricsManager::UpdateTemplatesOnDisk(
const std::vector<int>& dirty_list,
const std::unordered_set<uint32_t>& suspicious_templates) {
bool ret = true;
for (int i : dirty_list) {
// If the template previously came with wrong validation value, do not
// accept it until it comes with correct validation value.
if (suspicious_templates.find(i) != suspicious_templates.end()) {
continue;
}
std::unique_ptr<VendorTemplate> templ = cros_dev_->GetTemplate(i);
LOG(INFO) << "Retrieve updated template " << i << " -> " << std::boolalpha
<< templ.get();
if (!templ) {
continue;
}
Record current_record(weak_factory_.GetWeakPtr(), i);
if (!WriteRecord(current_record, templ->data(), templ->size())) {
LOG(ERROR) << "Cannot update record " << LogSafeID(records_[i].record_id)
<< " in storage during AuthSession because writing failed.";
ret = false;
}
}
return ret;
}
} // namespace biod