blob: 0f8ce80cd83e1465b67cd3c59727094a82c7dd99 [file]
/* Copyright 2017 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "assert.h"
#include "atomic_bit.h"
#include "clock.h"
#include "common.h"
#include "compile_time_macros.h"
#include "console.h"
#include "crypto/cleanse_wrapper.h"
#include "ec_commands.h"
#include "fpsensor/fpsensor.h"
#include "fpsensor/fpsensor_auth_commands.h"
#include "fpsensor/fpsensor_console.h"
#include "fpsensor/fpsensor_crypto.h"
#include "fpsensor/fpsensor_detect.h"
#include "fpsensor/fpsensor_modes.h"
#include "fpsensor/fpsensor_state.h"
#include "fpsensor/fpsensor_utils.h"
#include "gpio.h"
#include "host_command.h"
#include "link_defs.h"
#include "mkbp_event.h"
#include "openssl/mem.h"
#include "scoped_fast_cpu.h"
#include "sha256.h"
#include "spi.h"
#include "system.h"
#include "task.h"
#include "trng.h"
#include "util.h"
#include "watchdog.h"
#include <array>
#include <variant>
#ifdef CONFIG_ZEPHYR
#include <zephyr/shell/shell.h>
#endif
#if !defined(CONFIG_RNG)
#error "fpsensor requires RNG"
#endif
#if defined(SECTION_IS_RO)
#error "fpsensor code should not be in RO image."
#endif
/* Ready to encrypt a template. */
static timestamp_t encryption_deadline;
/* Delay between 2 s of the sensor to detect finger removal */
#define FINGER_POLLING_DELAY (100 * MSEC)
/* Timing statistics. */
static uint32_t capture_time_us;
static uint32_t matching_time_us;
static uint32_t overall_time_us;
static timestamp_t overall_t0;
static uint8_t timestamps_invalid;
BUILD_ASSERT(sizeof(struct ec_fp_template_encryption_metadata) % 4 == 0);
/* Interrupt line from the fingerprint sensor */
extern "C" void fps_event(enum gpio_signal signal)
{
task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_SENSOR_IRQ);
}
static void send_mkbp_event(uint32_t event)
{
atomic_or(&global_context.fp_events, event);
mkbp_send_event(EC_MKBP_EVENT_FINGERPRINT);
}
#ifdef HAVE_FP_PRIVATE_DRIVER
/*
* contains the bit FP_MODE_ENROLL_SESSION if a finger enrollment is on-going.
* It is used to detect the ENROLL_SESSION transition when sensor_mode is
* updated by the host.
*/
static uint32_t enroll_session;
static uint32_t fp_process_enroll(void)
{
int percent = 0;
if (global_context.template_newly_enrolled != FP_NO_SUCH_TEMPLATE)
CPRINTS("Warning: previously enrolled template has not been "
"read yet.");
/* begin/continue enrollment */
CPRINTS("[%d]Enrolling ...", global_context.templ_valid);
int res = fp_finger_enroll(fp_buffer, &percent);
CPRINTS("[%d]Enroll =>%d (%d%%)", global_context.templ_valid, res,
percent);
if (res < 0)
return EC_MKBP_FP_ENROLL |
EC_MKBP_FP_ERRCODE(EC_MKBP_FP_ERR_ENROLL_INTERNAL);
global_context.templ_dirty |= BIT(global_context.templ_valid);
if (percent == 100) {
res = fp_enrollment_finish(
fp_template[global_context.templ_valid]);
if (res) {
res = EC_MKBP_FP_ERR_ENROLL_INTERNAL;
} else {
global_context.template_newly_enrolled =
global_context.templ_valid;
fp_enable_positive_match_secret(
global_context.templ_valid,
&global_context.positive_match_secret_state);
/*
* In the classic flow we immediately use this template
* for matching (templ_valid is incremented).
*
* In the Fingerprint Auth flow we must reliably inform
* Trusted Application that the template was enrolled.
* To achieve this, we will not use this template for
* matching, until Trusted Application sends us a signed
* confirmation.
*/
if (!fingerprint_auth_enabled()) {
global_context.templ_valid++;
}
}
global_context.sensor_mode &= ~FP_MODE_ENROLL_SESSION;
enroll_session &= ~FP_MODE_ENROLL_SESSION;
}
return EC_MKBP_FP_ENROLL | EC_MKBP_FP_ERRCODE(res) |
(percent << EC_MKBP_FP_ENROLL_PROGRESS_OFFSET);
}
static uint32_t fp_process_match(void)
{
timestamp_t t0 = get_time();
int res = -1;
uint32_t updated = 0;
int32_t fgr = FP_NO_SUCH_TEMPLATE;
/* match finger against current templates */
fp_disable_positive_match_secret(
&global_context.positive_match_secret_state);
CPRINTS("Matching/%d ...", global_context.templ_valid);
if (global_context.templ_valid) {
res = fp_finger_match(fp_template[0],
global_context.templ_valid, fp_buffer,
!(global_context.sensor_mode &
FP_MODE_MATCH_NO_TEMPLATE_UPDATE),
&fgr, &updated);
CPRINTS("Match =>%d (finger %d)", res, fgr);
if (fp_match_success(res)) {
/*
* Match succeded! Let's check if template number
* is valid. If it is not valid, overwrite result
* with EC_MKBP_FP_ERR_MATCH_NO_INTERNAL.
*/
if (fgr >= 0 && fgr < FP_MAX_FINGER_COUNT) {
fp_enable_positive_match_secret(
fgr,
&global_context
.positive_match_secret_state);
} else {
res = EC_MKBP_FP_ERR_MATCH_NO_INTERNAL;
}
} else if (res < 0) {
/*
* Negative result means that there is a problem with
* code responsible for matching. Overwrite it with
* MATCH_NO_INTERNAL to let upper layers know what
* happened.
*/
res = EC_MKBP_FP_ERR_MATCH_NO_INTERNAL;
}
if (res == EC_MKBP_FP_ERR_MATCH_YES_UPDATED)
global_context.templ_dirty |= updated;
} else {
CPRINTS("No enrolled templates");
res = EC_MKBP_FP_ERR_MATCH_NO_TEMPLATES;
}
if (!fp_match_success(res))
timestamps_invalid |= FPSTATS_MATCHING_INV;
matching_time_us = time_since32(t0);
return EC_MKBP_FP_MATCH | EC_MKBP_FP_ERRCODE(res) |
((fgr << EC_MKBP_FP_MATCH_IDX_OFFSET) &
EC_MKBP_FP_MATCH_IDX_MASK);
}
static void fp_process_finger(void)
{
timestamp_t t0 = get_time();
enum fp_capture_type capture_type =
FP_CAPTURE_TYPE(global_context.sensor_mode);
global_context.current_capture_type = FP_CAPTURE_TYPE_INVALID;
CPRINTS("Capturing ...");
int res = fp_acquire_image(fp_buffer, capture_type);
capture_time_us = time_since32(t0);
if (res) {
timestamps_invalid |= FPSTATS_CAPTURE_INV;
return;
}
global_context.current_capture_type = capture_type;
uint32_t evt = EC_MKBP_FP_IMAGE_READY;
#ifndef CONFIG_ZEPHYR
/* Clean up SPI before clocking up to avoid hang on the dsb
* in dma_go. Ignore the return value to let the WDT reboot
* the MCU (and avoid getting trapped in the loop).
* b/112781659 */
res = spi_transaction_flush(&spi_devices[0]);
if (res)
CPRINTS("Failed to flush SPI: 0x%x", res);
#endif
/* we need CPU power to do the computations */
ScopedFastCpu fast_cpu;
if (global_context.sensor_mode & FP_MODE_ENROLL_IMAGE)
evt = fp_process_enroll();
else if (global_context.sensor_mode & FP_MODE_MATCH)
evt = fp_process_match();
global_context.sensor_mode &=
~(FP_MODE_ANY_CAPTURE | FP_MODE_CAPTURE_TYPE_MASK |
FP_MODE_MATCH_NO_TEMPLATE_UPDATE);
overall_time_us = time_since32(overall_t0);
send_mkbp_event(evt);
}
#endif /* HAVE_FP_PRIVATE_DRIVER */
static enum ec_error_list encrypt_template(uint16_t fgr);
static enum ec_status fp_commit_template(std::span<const uint8_t> context);
extern "C" void fp_task(void)
{
int timeout_us = -1;
CPRINTS("FP_SENSOR_SEL: %s",
fp_sensor_type_to_str(fpsensor_detect_get_type()));
#ifdef HAVE_FP_PRIVATE_DRIVER
/* Reset and initialize the sensor IC */
fp_sensor_init();
global_context.fp_frame_size_cache.populate_cache(sizeof(fp_buffer));
while (1) {
enum finger_state st = FINGER_NONE;
/* Wait for a sensor IRQ or a new mode configuration */
uint32_t evt = task_wait_event(timeout_us);
if (evt & TASK_EVENT_UPDATE_CONFIG) {
uint32_t mode = global_context.sensor_mode;
/*
* TODO(b/316859625): Remove CONFIG_ZEPHYR block after
* migration to Zephyr is completed.
*/
#ifdef CONFIG_ZEPHYR
/*
* We are about to change sensor mode, so exit any
* previous states.
*/
fp_idle();
#else
gpio_disable_interrupt(GPIO_FPS_INT);
#endif
if ((mode ^ enroll_session) & FP_MODE_ENROLL_SESSION) {
int ret = 0;
if (mode & FP_MODE_ENROLL_SESSION) {
ret = fp_enrollment_begin();
if (ret)
global_context.sensor_mode &=
~FP_MODE_ENROLL_SESSION;
} else {
fp_enrollment_finish(nullptr);
}
enroll_session =
ret ? 0 :
(mode & FP_MODE_ENROLL_SESSION);
}
if (!is_finger_needed(mode)) {
enum fp_capture_type capture_type =
FP_CAPTURE_TYPE(mode);
global_context.current_capture_type =
FP_CAPTURE_TYPE_INVALID;
if (!fp_acquire_image(fp_buffer,
capture_type)) {
global_context.current_capture_type =
capture_type;
}
global_context.sensor_mode &=
~(FP_MODE_CAPTURE |
FP_MODE_CAPTURE_TYPE_MASK);
send_mkbp_event(EC_MKBP_FP_IMAGE_READY);
continue;
} else if (global_context.sensor_mode &
FP_MODE_ANY_DETECT_FINGER) {
/* wait for a finger on the sensor */
fp_configure_detect();
}
if (global_context.sensor_mode & FP_MODE_DEEPSLEEP)
/* Shutdown the sensor */
fp_sensor_low_power();
if (global_context.sensor_mode & FP_MODE_FINGER_UP)
/* Poll the sensor to detect finger removal */
timeout_us = FINGER_POLLING_DELAY;
else
timeout_us = -1;
if (mode & FP_MODE_ANY_WAIT_IRQ) {
/*
* FP_MODE_ANY_WAIT_IRQ is a subset of
* FP_MODE_ANY_DETECT_FINGER. In Zephyr FPMCU
* interrupts are enabled by the sensor driver
* when configuring finger detection.
*/
#ifndef CONFIG_ZEPHYR
gpio_clear_pending_interrupt(GPIO_FPS_INT);
gpio_enable_interrupt(GPIO_FPS_INT);
#endif
} else if (mode & FP_MODE_RESET_SENSOR) {
fp_reset_and_clear_context();
global_context.sensor_mode &=
~FP_MODE_RESET_SENSOR;
} else if (mode & FP_MODE_SENSOR_MAINTENANCE) {
fp_maintenance();
global_context.sensor_mode &=
~FP_MODE_SENSOR_MAINTENANCE;
} else if (mode & FP_MODE_ENCRYPT_TEMPLATE) {
global_context.fp_encryption_status &=
~FP_ENCRYPTED_TEMPLATE_READY;
if (global_context.template_encrypted_id !=
FP_NO_SUCH_TEMPLATE) {
ScopedFastCpu fast_cpu;
if (encrypt_template(
global_context
.template_encrypted_id) ==
EC_SUCCESS) {
global_context
.fp_encryption_status |=
FP_ENCRYPTED_TEMPLATE_READY;
}
}
global_context.sensor_mode &=
~FP_MODE_ENCRYPT_TEMPLATE;
} else if (mode & FP_MODE_DECRYPT_TEMPLATE) {
ScopedFastCpu fast_cpu;
enum ec_status res = fp_commit_template(
global_context.user_id);
atomic_set(&global_context
.template_decryption_result,
res);
global_context.sensor_mode &=
~FP_MODE_DECRYPT_TEMPLATE;
} else {
fp_sensor_low_power();
}
} else if (evt & (TASK_EVENT_SENSOR_IRQ | TASK_EVENT_TIMER)) {
overall_t0 = get_time();
timestamps_invalid = 0;
/*
* TODO(b/316859625): Remove CONFIG_ZEPHYR block after
* migration to Zephyr is completed.
*/
#ifdef CONFIG_ZEPHYR
/* On timeout, put sensor into idle state. */
if (evt & TASK_EVENT_TIMER)
fp_idle();
#else
gpio_disable_interrupt(GPIO_FPS_INT);
#endif
if (global_context.sensor_mode &
FP_MODE_ANY_DETECT_FINGER) {
st = fp_finger_status();
if (st == FINGER_PRESENT &&
global_context.sensor_mode &
FP_MODE_FINGER_DOWN) {
CPRINTS("Finger!");
global_context.sensor_mode &=
~FP_MODE_FINGER_DOWN;
send_mkbp_event(EC_MKBP_FP_FINGER_DOWN);
}
if (st == FINGER_NONE &&
global_context.sensor_mode &
FP_MODE_FINGER_UP) {
global_context.sensor_mode &=
~FP_MODE_FINGER_UP;
timeout_us = -1;
send_mkbp_event(EC_MKBP_FP_FINGER_UP);
}
}
if (st == FINGER_PRESENT &&
global_context.sensor_mode & FP_MODE_ANY_CAPTURE)
fp_process_finger();
if (global_context.sensor_mode & FP_MODE_ANY_WAIT_IRQ) {
fp_configure_detect();
/* In Zephyr FPMCU interrupts are enabled by the
* sensor driver when configuring finger
* detection.
*/
#ifndef CONFIG_ZEPHYR
gpio_clear_pending_interrupt(GPIO_FPS_INT);
gpio_enable_interrupt(GPIO_FPS_INT);
#endif
} else {
/*
* In Zephyr FPMCU interrupts are managed by
* the driver.
*/
#ifndef CONFIG_ZEPHYR
if (evt & (TASK_EVENT_SENSOR_IRQ))
gpio_clear_pending_interrupt(
GPIO_FPS_INT);
#endif
fp_sensor_low_power();
}
}
}
#else /* !HAVE_FP_PRIVATE_DRIVER */
while (1) {
uint32_t evt = task_wait_event(timeout_us);
send_mkbp_event(evt);
}
#endif /* !HAVE_FP_PRIVATE_DRIVER */
}
static enum ec_status fp_command_info(struct host_cmd_handler_args *args)
{
struct ec_response_fp_info_v3 *r =
static_cast<ec_response_fp_info_v3 *>(args->response);
size_t response_size =
sizeof(struct ec_response_fp_info_v3) +
FP_MAX_CAPTURE_TYPES * sizeof(struct fp_image_frame_params_v2);
if (response_size > args->response_max) {
return EC_RES_OVERFLOW;
}
/*
* The sensor may have fewer than FP_MAX_CAPTURE_TYPES number of
* captures, in which case the buffer will only be partially written. We
* want to make sure the rest of the buffer does not leak any data.
*/
memset(r, 0, response_size);
#ifdef HAVE_FP_PRIVATE_DRIVER
if (fp_sensor_get_info(r, response_size) < 0)
#endif
return EC_RES_UNAVAILABLE;
r->template_info.template_size = FP_ALGORITHM_ENCRYPTED_TEMPLATE_SIZE;
r->template_info.template_max = FP_MAX_FINGER_COUNT;
r->template_info.template_valid = global_context.templ_valid;
r->template_info.template_dirty = global_context.templ_dirty;
r->template_info.template_version = FP_TEMPLATE_FORMAT_VERSION;
if (args->version == 2) {
struct ec_response_fp_info_v2 *r_v2 =
static_cast<ec_response_fp_info_v2 *>(args->response);
/* Convert to v2 format. The formats differ only in the frame
* array, which is located at the end of the structures
*
* SAFETY: 'r->image_frame_params' and r_v2->image_frame_params
* overlap inexactly, but copying data is safe because we copy
* data forward (from the first field of the structure to the
* last).
*/
for (int i = 0; i < FP_MAX_CAPTURE_TYPES; i++) {
r_v2->image_frame_params[i].frame_size =
r->image_frame_params[i].frame_size;
r_v2->image_frame_params[i].pixel_format =
r->image_frame_params[i].pixel_format;
r_v2->image_frame_params[i].width =
r->image_frame_params[i].width;
r_v2->image_frame_params[i].height =
r->image_frame_params[i].height;
r_v2->image_frame_params[i].bpp =
r->image_frame_params[i].bpp;
r_v2->image_frame_params[i].fp_capture_type =
r->image_frame_params[i].fp_capture_type;
r_v2->image_frame_params[i].reserved =
r->image_frame_params[i].reserved;
}
response_size = sizeof(struct ec_response_fp_info_v2) +
FP_MAX_CAPTURE_TYPES *
sizeof(struct fp_image_frame_params);
}
args->response_size = response_size;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FP_INFO, fp_command_info,
EC_VER_MASK(2) | EC_VER_MASK(3));
BUILD_ASSERT(FP_CONTEXT_NONCE_BYTES == 12);
static enum ec_error_list encrypt_template(uint16_t fgr)
{
enum ec_error_list ret;
/* Encrypted template is after the metadata. */
std::span templ = fp_enc_buffer.fp_template;
/* Positive match salt is after the template. */
std::span positive_match_salt = fp_enc_buffer.positive_match_salt;
std::span encrypted_template_and_positive_match_salt(
templ.data(),
templ.size_bytes() + positive_match_salt.size_bytes());
fp_enc_buffer = {};
if (fgr >= FP_MAX_FINGER_COUNT)
return EC_ERROR_INVAL;
if (fgr >= global_context.templ_valid)
return EC_ERROR_UNAVAILABLE;
/*
* The beginning of the buffer contains nonce, encryption_salt
* and tag.
*/
struct ec_fp_template_encryption_metadata *enc_info =
&fp_enc_buffer.metadata;
enc_info->struct_version = FP_TEMPLATE_FORMAT_VERSION;
trng_init();
trng_rand_bytes(enc_info->nonce, FP_CONTEXT_NONCE_BYTES);
trng_rand_bytes(enc_info->encryption_salt,
FP_CONTEXT_ENCRYPTION_SALT_BYTES);
trng_exit();
if (fgr == global_context.template_newly_enrolled) {
/*
* Newly enrolled templates need new positive match
* salt, new positive match secret and new validation
* value.
*/
global_context.template_newly_enrolled = FP_NO_SUCH_TEMPLATE;
trng_init();
trng_rand_bytes(global_context.fp_positive_match_salt[fgr],
FP_POSITIVE_MATCH_SALT_BYTES);
trng_exit();
}
FpEncryptionKey key;
ret = derive_encryption_key(key, enc_info->encryption_salt,
global_context.user_id,
global_context.tpm_seed);
if (ret != EC_SUCCESS) {
CPRINTS("fgr%d: Failed to derive key", fgr);
return EC_ERROR_UNAVAILABLE;
}
/*
* Copy the payload to |fp_enc_buffer| where it will be
* encrypted in-place.
*/
std::ranges::copy(fp_template[fgr], templ.begin());
std::ranges::copy(global_context.fp_positive_match_salt[fgr],
positive_match_salt.begin());
/* Encrypt the secret blob in-place. */
ret = aes_128_gcm_encrypt(key,
encrypted_template_and_positive_match_salt,
encrypted_template_and_positive_match_salt,
enc_info->nonce, enc_info->tag);
if (ret != EC_SUCCESS) {
OPENSSL_cleanse(&fp_enc_buffer, sizeof(fp_enc_buffer));
CPRINTS("fgr%d: Failed to encrypt template", fgr);
return EC_ERROR_UNAVAILABLE;
}
global_context.templ_dirty &= ~BIT(fgr);
return EC_SUCCESS;
}
static enum ec_status get_frame(uint32_t offset, uint32_t size, uint8_t *output)
{
enum ec_error_list ret;
if (system_is_locked())
return EC_RES_ACCESS_DENIED;
if (global_context.current_capture_type == FP_CAPTURE_TYPE_INVALID) {
return EC_RES_INVALID_PARAM;
}
/*
* Checks if the capture type is one where we only care about
* the embedded/offset image bytes, like simple, pattern0,
* pattern1, and reset_test.
*/
if (skip_image_offset(global_context.current_capture_type))
offset += FP_SENSOR_IMAGE_OFFSET;
uint32_t current_frame_size =
global_context.fp_frame_size_cache.get_frame_size(
global_context.current_capture_type);
if (current_frame_size > sizeof(fp_buffer)) {
return EC_RES_INVALID_PARAM;
}
ret = validate_fp_buffer_offset(current_frame_size, offset, size);
if (ret != EC_SUCCESS)
return EC_RES_INVALID_PARAM;
memcpy(output, fp_buffer + offset, size);
return EC_RES_SUCCESS;
}
/* TODO(b/471160577): Remove FP_FRAME v0 after migration is completed. */
static enum ec_status fp_command_frame_v0(struct host_cmd_handler_args *args)
{
const auto *params =
static_cast<const struct ec_params_fp_frame *>(args->params);
void *out = args->response;
uint16_t idx = FP_FRAME_GET_BUFFER_INDEX(params->offset);
uint32_t offset = params->offset & FP_FRAME_OFFSET_MASK;
uint32_t size = params->size;
enum ec_error_list ret;
if (size > args->response_max)
return EC_RES_INVALID_PARAM;
if (idx == FP_FRAME_INDEX_RAW_IMAGE) {
/* The host requested a frame. */
enum ec_status ret = get_frame(offset, size, (uint8_t *)out);
if (ret != EC_RES_SUCCESS) {
return ret;
}
args->response_size = size;
return EC_RES_SUCCESS;
}
/* The host requested a template. */
/* Encryption or decryption is in progress. */
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
/* Templates are numbered from 1 in this host request. */
uint16_t fgr = idx - FP_FRAME_INDEX_TEMPLATE;
if (fgr >= FP_MAX_FINGER_COUNT)
return EC_RES_INVALID_PARAM;
if (fgr >= global_context.templ_valid)
return EC_RES_UNAVAILABLE;
ret = validate_fp_buffer_offset(sizeof(fp_enc_buffer), offset, size);
if (ret != EC_SUCCESS)
return EC_RES_INVALID_PARAM;
if (!offset) {
/* Host has requested the first chunk, do the encryption. */
timestamp_t now = get_time();
/* b/114160734: Not more than 1 encrypted message per second. */
if (!timestamp_expired(encryption_deadline, &now))
return EC_RES_BUSY;
encryption_deadline.val = now.val + (1 * SECOND);
ScopedFastCpu fast_cpu;
if (encrypt_template(fgr) != EC_SUCCESS) {
return EC_RES_UNAVAILABLE;
}
}
memcpy(out, reinterpret_cast<uint8_t *>(&fp_enc_buffer) + offset, size);
args->response_size = size;
return EC_RES_SUCCESS;
}
/*
* We are modifying variables in the global_context from this function running
* in the HOSTCMD task and encrypt_template() running in the FPSENSOR task,
* without protecting these data.
*
* This works because:
* - We use single-core MCUs
* - HOSTCMD task has higher priority than FPSENSOR task.
* - There could be only one host command running at the time.
* - Host commands cannot be interrupted by other tasks.
* - We are checking only one bit in global_context.sensor_mode,
* so we are not prone to partial (non-atomic) writes/reads.
*
* TODO(b/479824917): Unfortunately, lack of any synchronization is a general
* problem with FPSENSOR architecture. All fingerprint related work should be
* done by FPSENSOR task, HOSTCMD role should be limited to validating input
* (if possible), posting work to FPSENSOR task and waiting for result, if
* needed.
*/
static enum ec_status fp_command_frame_v1(struct host_cmd_handler_args *args)
{
const auto *params =
static_cast<const struct ec_params_fp_frame_v1 *>(args->params);
void *out = args->response;
uint32_t offset = params->offset;
uint32_t size = params->size;
enum ec_error_list ret;
enum ec_status status;
if (size > args->response_max)
return EC_RES_INVALID_PARAM;
switch (params->cmd) {
case FP_FRAME_GET_RAW_IMAGE:
/* The host requested a frame. */
status = get_frame(offset, size, (uint8_t *)out);
if (status != EC_RES_SUCCESS) {
return status;
}
args->response_size = size;
return EC_RES_SUCCESS;
case FP_FRAME_ENCRYPT_TEMPLATE: {
timestamp_t now;
uint32_t mode_output;
/*
* Do not change the content of fp_enc_buffer if the encryption
* or decryption is in progress.
*/
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
if (params->index >= FP_MAX_FINGER_COUNT)
return EC_RES_INVALID_PARAM;
if (params->index >= global_context.templ_valid)
return EC_RES_UNAVAILABLE;
now = get_time();
/* b/114160734: Not more than 1 encrypted message per second. */
if (!timestamp_expired(encryption_deadline, &now))
return EC_RES_BUSY;
encryption_deadline.val = now.val + (1 * SECOND);
global_context.fp_encryption_status &=
~FP_ENCRYPTED_TEMPLATE_READY;
global_context.template_encrypted_id = params->index;
status = fp_set_sensor_mode(FP_MODE_ENCRYPT_TEMPLATE,
&mode_output, std::nullopt);
if (status != EC_RES_SUCCESS) {
return EC_RES_ERROR;
}
break;
}
case FP_FRAME_GET_ENCRYPTED_TEMPLATE:
/* Encryption or decryption is still running */
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
/*
* Encrypted template not available (or encryption finished
* with error)
*/
if (!(global_context.fp_encryption_status &
FP_ENCRYPTED_TEMPLATE_READY)) {
return EC_RES_UNAVAILABLE;
}
/* Validate data request */
ret = validate_fp_buffer_offset(sizeof(fp_enc_buffer), offset,
size);
if (ret != EC_SUCCESS)
return EC_RES_INVALID_PARAM;
/* Encryption succeeded */
memcpy(out,
reinterpret_cast<uint8_t *>(&fp_enc_buffer) + offset,
size);
args->response_size = size;
break;
}
return EC_RES_SUCCESS;
}
static enum ec_status fp_command_frame(struct host_cmd_handler_args *args)
{
if (args->version == 1) {
return fp_command_frame_v1(args);
}
return fp_command_frame_v0(args);
}
DECLARE_HOST_COMMAND(EC_CMD_FP_FRAME, fp_command_frame,
EC_VER_MASK(0) | EC_VER_MASK(1));
static enum ec_status fp_command_stats(struct host_cmd_handler_args *args)
{
auto *r = static_cast<struct ec_response_fp_stats *>(args->response);
r->capture_time_us = capture_time_us;
r->matching_time_us = matching_time_us;
r->overall_time_us = overall_time_us;
r->overall_t0.lo = overall_t0.le.lo;
r->overall_t0.hi = overall_t0.le.hi;
r->timestamps_invalid = timestamps_invalid;
/*
* Note that this is set to FP_NO_SUCH_TEMPLATE when positive match
* secret is read/disabled, and we are not using this field in biod.
*/
r->template_matched =
global_context.positive_match_secret_state.template_matched;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FP_STATS, fp_command_stats, EC_VER_MASK(0));
static enum ec_status
validate_template_format(struct ec_fp_template_encryption_metadata *enc_info)
{
if (enc_info->struct_version != FP_TEMPLATE_FORMAT_VERSION) {
CPRINTS("Invalid template format %d", enc_info->struct_version);
return EC_RES_INVALID_PARAM;
}
return EC_RES_SUCCESS;
}
static enum ec_status fp_commit_template(std::span<const uint8_t> context)
{
ScopedFastCpu fast_cpu;
uint16_t idx = global_context.templ_valid;
if (idx >= FP_MAX_FINGER_COUNT)
return EC_RES_OVERFLOW;
/*
* The complete encrypted template has been received, start
* decryption.
*/
fp_clear_finger_context(idx);
/*
* The beginning of the buffer contains nonce, encryption_salt
* and tag.
*/
struct ec_fp_template_encryption_metadata *enc_info =
&fp_enc_buffer.metadata;
enum ec_status res = validate_template_format(enc_info);
if (res != EC_RES_SUCCESS) {
CPRINTS("fgr%d: Template format not supported", idx);
return EC_RES_INVALID_PARAM;
}
/* Encrypted template is after the metadata. */
std::span templ = fp_enc_buffer.fp_template;
/* Positive match salt is after the template. */
std::span positive_match_salt = fp_enc_buffer.positive_match_salt;
std::span encrypted_template_and_positive_match_salt(
templ.data(),
templ.size_bytes() + positive_match_salt.size_bytes());
FpEncryptionKey key;
enum ec_error_list ret =
derive_encryption_key(key, enc_info->encryption_salt, context,
global_context.tpm_seed);
if (ret != EC_SUCCESS) {
CPRINTS("fgr%d: Failed to derive key", idx);
return EC_RES_UNAVAILABLE;
}
/* Decrypt the secret blob in-place. */
ret = aes_128_gcm_decrypt(key,
encrypted_template_and_positive_match_salt,
encrypted_template_and_positive_match_salt,
enc_info->nonce, enc_info->tag);
if (ret != EC_SUCCESS) {
/* Don't leave partially decrypted data in the buffer! */
OPENSSL_cleanse(&fp_enc_buffer, sizeof(fp_enc_buffer));
CPRINTS("fgr%d: Failed to decipher template", idx);
return EC_RES_UNAVAILABLE;
}
if (bytes_are_trivial(positive_match_salt.data(),
positive_match_salt.size_bytes())) {
/* Don't leave decrypted data in the buffer! */
OPENSSL_cleanse(&fp_enc_buffer, sizeof(fp_enc_buffer));
CPRINTS("fgr%d: Trivial positive match salt.", idx);
return EC_RES_INVALID_PARAM;
}
std::ranges::copy(templ, fp_template[idx]);
std::ranges::copy(positive_match_salt,
global_context.fp_positive_match_salt[idx]);
/* Don't leave decrypted data in the buffer! */
OPENSSL_cleanse(&fp_enc_buffer, sizeof(fp_enc_buffer));
global_context.templ_valid++;
return EC_RES_SUCCESS;
}
static enum ec_status fp_command_template_v0(struct host_cmd_handler_args *args)
{
const auto *params =
static_cast<const struct ec_params_fp_template *>(args->params);
uint32_t size = params->size & ~FP_TEMPLATE_COMMIT;
bool xfer_complete = params->size & FP_TEMPLATE_COMMIT;
uint32_t offset = params->offset;
uint16_t idx = global_context.templ_valid;
/* Can we store one more template ? */
if (idx >= FP_MAX_FINGER_COUNT)
return EC_RES_OVERFLOW;
/* Encryption or decryption is in progress. */
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
if (args->params_size !=
size + offsetof(struct ec_params_fp_template, data))
return EC_RES_INVALID_PARAM;
enum ec_error_list ret =
validate_fp_buffer_offset(sizeof(fp_enc_buffer), offset, size);
if (ret != EC_SUCCESS)
return EC_RES_INVALID_PARAM;
memcpy(reinterpret_cast<uint8_t *>(&fp_enc_buffer) + offset,
params->data, size);
if (xfer_complete) {
return fp_commit_template(global_context.user_id);
}
return EC_RES_SUCCESS;
}
static enum ec_status fp_command_template_v1(struct host_cmd_handler_args *args)
{
const auto *params =
static_cast<const struct ec_params_fp_template_v1 *>(
args->params);
enum ec_status status;
switch (params->cmd) {
case FP_TEMPLATE_LOAD: {
uint32_t size = params->size;
uint32_t offset = params->offset;
/* Encryption or decryption is in progress. */
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
/* Can we store one more template ? */
if (global_context.templ_valid >= FP_MAX_FINGER_COUNT) {
return EC_RES_OVERFLOW;
}
if (args->params_size !=
size + offsetof(struct ec_params_fp_template_v1, data)) {
return EC_RES_INVALID_PARAM;
}
enum ec_error_list ret = validate_fp_buffer_offset(
sizeof(fp_enc_buffer), offset, size);
if (ret != EC_SUCCESS) {
return EC_RES_INVALID_PARAM;
}
/*
* We are going to modify 'fp_enc_buffer', so clear
* FP_ENCRYPTED_TEMPLATE_READY bit and 'template_encrypted_id'.
*/
global_context.fp_encryption_status &=
~FP_ENCRYPTED_TEMPLATE_READY;
global_context.template_encrypted_id = FP_NO_SUCH_TEMPLATE;
/* Copy part of the template to buffer. */
memcpy(reinterpret_cast<uint8_t *>(&fp_enc_buffer) + offset,
params->data, size);
return EC_RES_SUCCESS;
}
case FP_TEMPLATE_DECRYPT: {
uint32_t mode_output;
/* Encryption or decryption is in progress. */
if (global_context.sensor_mode & FP_MODES_CRYPTO_IN_PROGRESS) {
return EC_RES_BUSY;
}
/* Start template decryption. */
status = fp_set_sensor_mode(FP_MODE_DECRYPT_TEMPLATE,
&mode_output, std::nullopt);
if (status != EC_RES_SUCCESS) {
atomic_set(&global_context.template_decryption_result,
EC_RES_ERROR);
return EC_RES_ERROR;
}
atomic_set(&global_context.template_decryption_result,
EC_RES_BUSY);
return EC_RES_SUCCESS;
}
case FP_TEMPLATE_GET_RESULT:
/* Decryption is still running */
if (global_context.sensor_mode & FP_MODE_DECRYPT_TEMPLATE) {
return EC_RES_BUSY;
}
status = (enum ec_status)atomic_get(
&global_context.template_decryption_result);
return status;
}
return EC_RES_INVALID_PARAM;
}
static enum ec_status fp_command_template(struct host_cmd_handler_args *args)
{
if (args->version == 1) {
return fp_command_template_v1(args);
}
return fp_command_template_v0(args);
}
DECLARE_HOST_COMMAND(EC_CMD_FP_TEMPLATE, fp_command_template,
EC_VER_MASK(0) | EC_VER_MASK(1));
static enum ec_status
fp_command_confirm_template(struct host_cmd_handler_args *args)
{
const auto *params =
static_cast<const struct ec_params_fp_confirm_template *>(
args->params);
std::span<const uint8_t, FP_MAC_LENGTH> mac{ params->mac };
/*
* The context for signing/verifying messages is Android user id
* which is 4 byte integer.
*/
static_assert(global_context.user_id.size() >= sizeof(uint32_t));
std::span<const uint8_t> context{ global_context.user_id.data(),
sizeof(uint32_t) };
/* The operation is just an "enroll_finish" string */
static constexpr uint8_t operation_str[] = { 'e', 'n', 'r', 'o', 'l',
'l', '_', 'f', 'i', 'n',
'i', 's', 'h' };
std::span<const uint8_t> operation{ operation_str };
/*
* After successful enrollment, the 'template_newly_enrolled' (new
* template id) must be equal to 'templ_valid'.
*/
if (global_context.template_newly_enrolled !=
global_context.templ_valid) {
return EC_RES_ERROR;
}
if (validate_request(context, operation, mac) != EC_SUCCESS) {
return EC_RES_ACCESS_DENIED;
}
/* Add newly enrolled template to valid templates. */
global_context.templ_valid++;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FP_CONFIRM_TEMPLATE, fp_command_confirm_template,
EC_VER_MASK(0));