diff options
author | Tom Hughes <tomhughes@chromium.org> | 2019-05-29 18:17:09 -0700 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2019-06-05 17:29:12 +0000 |
commit | b41026d4f3bcddc97cac280bbc2e2865f5a3ec10 (patch) | |
tree | e3136bd758a6879e12e375d5a956fc5a2b9c39fb /common/fpsensor | |
parent | b00dac4bf4d1727bb1e334269133d60c430a131a (diff) | |
download | chrome-ec-b41026d4f3bcddc97cac280bbc2e2865f5a3ec10.tar.gz |
Move fingerprint files to their own directory
We will be adding more files in the future, so this declutters the
common directory. It also lets us add a separate OWNERS file.
BRANCH=none
BUG=chromium:968518
TEST=make buildall -j
Change-Id: I22c08851fe2d5fbdb5beff8cc72a68618c85fb0e
Signed-off-by: Tom Hughes <tomhughes@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/1637440
Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
Diffstat (limited to 'common/fpsensor')
-rw-r--r-- | common/fpsensor/build.mk | 12 | ||||
-rw-r--r-- | common/fpsensor/fpsensor.c | 814 | ||||
-rw-r--r-- | common/fpsensor/fpsensor_state.c | 156 |
3 files changed, 982 insertions, 0 deletions
diff --git a/common/fpsensor/build.mk b/common/fpsensor/build.mk new file mode 100644 index 0000000000..c70772635b --- /dev/null +++ b/common/fpsensor/build.mk @@ -0,0 +1,12 @@ +# Copyright 2019 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. + +# Build for fingerprint sensor + +# Note that this variable includes the trailing "/" +_fpsensor_dir:=$(dir $(lastword $(MAKEFILE_LIST))) + +all-obj-$(HAS_TASK_FPSENSOR)+= \ + $(_fpsensor_dir)/fpsensor.o \ + $(_fpsensor_dir)/fpsensor_state.o diff --git a/common/fpsensor/fpsensor.c b/common/fpsensor/fpsensor.c new file mode 100644 index 0000000000..ca7f25cb19 --- /dev/null +++ b/common/fpsensor/fpsensor.c @@ -0,0 +1,814 @@ +/* Copyright 2017 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 "aes.h" +#include "aes-gcm.h" +#include "atomic.h" +#include "clock.h" +#include "common.h" +#include "console.h" +#include "ec_commands.h" +#include "fpsensor.h" +#include "fpsensor_state.h" +#include "gpio.h" +#include "host_command.h" +#include "link_defs.h" +#include "mkbp_event.h" +#include "rollback.h" +#include "sha256.h" +#include "spi.h" +#include "system.h" +#include "task.h" +#include "trng.h" +#include "timer.h" +#include "util.h" +#include "watchdog.h" + +#if !defined(CONFIG_AES) || !defined(CONFIG_AES_GCM) || \ + !defined(CONFIG_ROLLBACK_SECRET_SIZE) || !defined(CONFIG_RNG) +#error "fpsensor requires AES, AES_GCM, ROLLBACK_SECRET_SIZE and RNG" +#endif + +/* Ready to encrypt a template. */ +static timestamp_t encryption_deadline; + +#define CPRINTF(format, args...) cprintf(CC_FP, format, ## args) +#define CPRINTS(format, args...) cprints(CC_FP, format, ## args) + +/* raw image offset inside the acquired frame */ +#ifndef FP_SENSOR_IMAGE_OFFSET +#define FP_SENSOR_IMAGE_OFFSET 0 +#endif + +#define FP_MODE_ANY_CAPTURE (FP_MODE_CAPTURE | FP_MODE_ENROLL_IMAGE | \ + FP_MODE_MATCH) +#define FP_MODE_ANY_DETECT_FINGER (FP_MODE_FINGER_DOWN | FP_MODE_FINGER_UP | \ + FP_MODE_ANY_CAPTURE) +#define FP_MODE_ANY_WAIT_IRQ (FP_MODE_FINGER_DOWN | FP_MODE_ANY_CAPTURE) + +/* 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; +static int8_t template_matched; + +BUILD_ASSERT(sizeof(struct ec_fp_template_encryption_metadata) % 4 == 0); + +/* Interrupt line from the fingerprint sensor */ +void fps_event(enum gpio_signal signal) +{ + task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_SENSOR_IRQ, 0); +} + +static void send_mkbp_event(uint32_t event) +{ + atomic_or(&fp_events, event); + mkbp_send_event(EC_MKBP_EVENT_FINGERPRINT); +} + +static inline int is_raw_capture(uint32_t mode) +{ + int capture_type = FP_CAPTURE_TYPE(mode); + + return (capture_type == FP_CAPTURE_VENDOR_FORMAT + || capture_type == FP_CAPTURE_QUALITY_TEST); +} + +#ifdef HAVE_FP_PRIVATE_DRIVER +static inline int is_test_capture(uint32_t mode) +{ + int capture_type = FP_CAPTURE_TYPE(mode); + + return (mode & FP_MODE_CAPTURE) + && (capture_type == FP_CAPTURE_PATTERN0 + || capture_type == FP_CAPTURE_PATTERN1 + || capture_type == FP_CAPTURE_RESET_TEST); +} + +/* + * 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; + int res; + + /* begin/continue enrollment */ + CPRINTS("[%d]Enrolling ...", templ_valid); + res = fp_finger_enroll(fp_buffer, &percent); + CPRINTS("[%d]Enroll =>%d (%d%%)", templ_valid, res, percent); + if (res < 0) + return EC_MKBP_FP_ENROLL + | EC_MKBP_FP_ERRCODE(EC_MKBP_FP_ERR_ENROLL_INTERNAL); + templ_dirty |= BIT(templ_valid); + if (percent == 100) { + res = fp_enrollment_finish(fp_template[templ_valid]); + if (res) + res = EC_MKBP_FP_ERR_ENROLL_INTERNAL; + else + templ_valid++; + 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 = -1; + + /* match finger against current templates */ + template_matched = -1; + CPRINTS("Matching/%d ...", templ_valid); + if (templ_valid) { + res = fp_finger_match(fp_template[0], templ_valid, fp_buffer, + &fgr, &updated); + CPRINTS("Match =>%d (finger %d)", res, fgr); + if (res < 0) { + res = EC_MKBP_FP_ERR_MATCH_NO_INTERNAL; + timestamps_invalid |= FPSTATS_MATCHING_INV; + } else { + template_matched = (int8_t)fgr; + } + if (res == EC_MKBP_FP_ERR_MATCH_YES_UPDATED) + templ_dirty |= updated; + } else { + CPRINTS("No enrolled templates"); + res = EC_MKBP_FP_ERR_MATCH_NO_TEMPLATES; + 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(); + int res = fp_sensor_acquire_image_with_mode(fp_buffer, + FP_CAPTURE_TYPE(sensor_mode)); + capture_time_us = time_since32(t0); + if (!res) { + uint32_t evt = EC_MKBP_FP_IMAGE_READY; + + /* 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); + /* we need CPU power to do the computations */ + clock_enable_module(MODULE_FAST_CPU, 1); + + if (sensor_mode & FP_MODE_ENROLL_IMAGE) + evt = fp_process_enroll(); + else if (sensor_mode & FP_MODE_MATCH) + evt = fp_process_match(); + + sensor_mode &= ~FP_MODE_ANY_CAPTURE; + overall_time_us = time_since32(overall_t0); + send_mkbp_event(evt); + + /* go back to lower power mode */ + clock_enable_module(MODULE_FAST_CPU, 0); + } else { + timestamps_invalid |= FPSTATS_CAPTURE_INV; + } +} +#endif /* HAVE_FP_PRIVATE_DRIVER */ + +void fp_task(void) +{ + int timeout_us = -1; + + /* configure the SPI controller (also ensure that CS_N is high) */ + gpio_config_module(MODULE_SPI_MASTER, 1); + spi_enable(CONFIG_SPI_FP_PORT, 1); + +#ifdef HAVE_FP_PRIVATE_DRIVER + /* Reset and initialize the sensor IC */ + fp_sensor_init(); + + while (1) { + uint32_t evt; + enum finger_state st = FINGER_NONE; + + /* Wait for a sensor IRQ or a new mode configuration */ + evt = task_wait_event(timeout_us); + + if (evt & TASK_EVENT_UPDATE_CONFIG) { + uint32_t mode = sensor_mode; + + gpio_disable_interrupt(GPIO_FPS_INT); + if ((mode ^ enroll_session) & FP_MODE_ENROLL_SESSION) { + if (mode & FP_MODE_ENROLL_SESSION) { + if (fp_enrollment_begin()) + sensor_mode &= + ~FP_MODE_ENROLL_SESSION; + } else { + fp_enrollment_finish(NULL); + } + enroll_session = + sensor_mode & FP_MODE_ENROLL_SESSION; + } + if (is_test_capture(mode)) { + fp_sensor_acquire_image_with_mode(fp_buffer, + FP_CAPTURE_TYPE(mode)); + sensor_mode &= ~FP_MODE_CAPTURE; + send_mkbp_event(EC_MKBP_FP_IMAGE_READY); + continue; + } else if (sensor_mode & FP_MODE_ANY_DETECT_FINGER) { + /* wait for a finger on the sensor */ + fp_sensor_configure_detect(); + } + if (sensor_mode & FP_MODE_DEEPSLEEP) + /* Shutdown the sensor */ + fp_sensor_low_power(); + if (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) { + gpio_enable_interrupt(GPIO_FPS_INT); + } else if (mode & FP_MODE_RESET_SENSOR) { + fp_clear_context(); + fp_sensor_init(); + sensor_mode &= ~FP_MODE_RESET_SENSOR; + } else { + fp_sensor_low_power(); + } + } else if (evt & (TASK_EVENT_SENSOR_IRQ | TASK_EVENT_TIMER)) { + overall_t0 = get_time(); + timestamps_invalid = 0; + gpio_disable_interrupt(GPIO_FPS_INT); + if (sensor_mode & FP_MODE_ANY_DETECT_FINGER) { + st = fp_sensor_finger_status(); + if (st == FINGER_PRESENT && + sensor_mode & FP_MODE_FINGER_DOWN) { + CPRINTS("Finger!"); + sensor_mode &= ~FP_MODE_FINGER_DOWN; + send_mkbp_event(EC_MKBP_FP_FINGER_DOWN); + } + if (st == FINGER_NONE && + sensor_mode & FP_MODE_FINGER_UP) { + sensor_mode &= ~FP_MODE_FINGER_UP; + timeout_us = -1; + send_mkbp_event(EC_MKBP_FP_FINGER_UP); + } + } + + if (st == FINGER_PRESENT && + sensor_mode & FP_MODE_ANY_CAPTURE) + fp_process_finger(); + + if (sensor_mode & FP_MODE_ANY_WAIT_IRQ) { + fp_sensor_configure_detect(); + gpio_enable_interrupt(GPIO_FPS_INT); + } else { + 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 int derive_encryption_key(uint8_t *out_key, uint8_t *salt) +{ + int ret; + uint8_t key_buf[SHA256_DIGEST_SIZE]; + uint8_t prk[SHA256_DIGEST_SIZE]; + uint8_t message[sizeof(user_id) + 1]; + uint8_t ikm[CONFIG_ROLLBACK_SECRET_SIZE + sizeof(tpm_seed)]; + + BUILD_ASSERT(SBP_ENC_KEY_LEN <= SHA256_DIGEST_SIZE); + BUILD_ASSERT(SBP_ENC_KEY_LEN <= CONFIG_ROLLBACK_SECRET_SIZE); + BUILD_ASSERT(sizeof(user_id) == SHA256_DIGEST_SIZE); + + if (!fp_tpm_seed_is_set()) { + CPRINTS("Seed hasn't been set."); + return EC_RES_ERROR; + } + + /* + * The first CONFIG_ROLLBACK_SECRET_SIZE bytes of IKM are read from the + * anti-rollback blocks. + */ + ret = rollback_get_secret(ikm); + if (ret != EC_SUCCESS) { + CPRINTS("Failed to read rollback secret: %d", ret); + return EC_RES_ERROR; + } + /* + * IKM is the concatenation of the rollback secret and the seed from + * the TPM. + */ + memcpy(ikm + CONFIG_ROLLBACK_SECRET_SIZE, tpm_seed, sizeof(tpm_seed)); + + /* + * Derive a key with the "extract" step of HKDF + * https://tools.ietf.org/html/rfc5869#section-2.2 + */ + hmac_SHA256(prk, salt, FP_CONTEXT_SALT_BYTES, ikm, sizeof(ikm)); + memset(ikm, 0, sizeof(ikm)); + + /* + * Only 1 "expand" step of HKDF since the size of the "info" context + * (user_id in our case) is exactly SHA256_DIGEST_SIZE. + * https://tools.ietf.org/html/rfc5869#section-2.3 + */ + memcpy(message, user_id, sizeof(user_id)); + /* 1 step, set the counter byte to 1. */ + message[sizeof(message) - 1] = 0x01; + hmac_SHA256(key_buf, prk, sizeof(prk), message, sizeof(message)); + memset(prk, 0, sizeof(prk)); + + memcpy(out_key, key_buf, SBP_ENC_KEY_LEN); + memset(key_buf, 0, sizeof(key_buf)); + + return EC_RES_SUCCESS; +} + +static int fp_command_passthru(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_passthru *params = args->params; + void *out = args->response; + int rc; + int ret = EC_RES_SUCCESS; + + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; + + if (params->len > args->params_size + + offsetof(struct ec_params_fp_passthru, data) || + params->len > args->response_max) + return EC_RES_INVALID_PARAM; + + rc = spi_transaction_async(&spi_devices[0], params->data, + params->len, out, SPI_READBACK_ALL); + if (params->flags & EC_FP_FLAG_NOT_COMPLETE) + rc |= spi_transaction_wait(&spi_devices[0]); + else + rc |= spi_transaction_flush(&spi_devices[0]); + + if (rc == EC_ERROR_TIMEOUT) + ret = EC_RES_TIMEOUT; + else if (rc) + ret = EC_RES_ERROR; + + args->response_size = params->len; + return ret; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_PASSTHRU, fp_command_passthru, EC_VER_MASK(0)); + +static int fp_command_info(struct host_cmd_handler_args *args) +{ + struct ec_response_fp_info *r = args->response; + +#ifdef HAVE_FP_PRIVATE_DRIVER + if (fp_sensor_get_info(r) < 0) +#endif + return EC_RES_UNAVAILABLE; + + r->template_size = FP_ALGORITHM_ENCRYPTED_TEMPLATE_SIZE; + r->template_max = FP_MAX_FINGER_COUNT; + r->template_valid = templ_valid; + r->template_dirty = templ_dirty; + r->template_version = FP_TEMPLATE_FORMAT_VERSION; + + /* V1 is identical to V0 with more information appended */ + args->response_size = args->version ? sizeof(*r) : + sizeof(struct ec_response_fp_info_v0); + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_INFO, fp_command_info, + EC_VER_MASK(0) | EC_VER_MASK(1)); + +BUILD_ASSERT(FP_CONTEXT_NONCE_BYTES == 12); + +static int aes_gcm_encrypt(const uint8_t *key, int key_size, + const uint8_t *plaintext, + uint8_t *ciphertext, int plaintext_size, + const uint8_t *nonce, int nonce_size, + uint8_t *tag, int tag_size) +{ + int res; + AES_KEY aes_key; + GCM128_CONTEXT ctx; + + if (nonce_size != FP_CONTEXT_NONCE_BYTES) { + CPRINTS("Invalid nonce size %d bytes", nonce_size); + return EC_RES_INVALID_PARAM; + } + + res = AES_set_encrypt_key(key, 8 * key_size, &aes_key); + if (res) { + CPRINTS("Failed to set encryption key: %d", res); + return EC_RES_ERROR; + } + CRYPTO_gcm128_init(&ctx, &aes_key, (block128_f)AES_encrypt, 0); + CRYPTO_gcm128_setiv(&ctx, &aes_key, nonce, nonce_size); + /* CRYPTO functions return 1 on success, 0 on error. */ + res = CRYPTO_gcm128_encrypt(&ctx, &aes_key, plaintext, ciphertext, + plaintext_size); + if (!res) { + CPRINTS("Failed to encrypt: %d", res); + return EC_RES_ERROR; + } + CRYPTO_gcm128_tag(&ctx, tag, tag_size); + return EC_RES_SUCCESS; +} + +static int aes_gcm_decrypt(const uint8_t *key, int key_size, uint8_t *plaintext, + const uint8_t *ciphertext, int plaintext_size, + const uint8_t *nonce, int nonce_size, + const uint8_t *tag, int tag_size) +{ + int res; + AES_KEY aes_key; + GCM128_CONTEXT ctx; + + if (nonce_size != FP_CONTEXT_NONCE_BYTES) { + CPRINTS("Invalid nonce size %d bytes", nonce_size); + return EC_RES_INVALID_PARAM; + } + + res = AES_set_encrypt_key(key, 8 * key_size, &aes_key); + if (res) { + CPRINTS("Failed to set decryption key: %d", res); + return EC_RES_ERROR; + } + CRYPTO_gcm128_init(&ctx, &aes_key, (block128_f)AES_encrypt, 0); + CRYPTO_gcm128_setiv(&ctx, &aes_key, nonce, nonce_size); + /* CRYPTO functions return 1 on success, 0 on error. */ + res = CRYPTO_gcm128_decrypt(&ctx, &aes_key, ciphertext, plaintext, + plaintext_size); + if (!res) { + CPRINTS("Failed to decrypt: %d", res); + return EC_RES_ERROR; + } + res = CRYPTO_gcm128_finish(&ctx, tag, tag_size); + if (!res) { + CPRINTS("Found incorrect tag: %d", res); + return EC_RES_ERROR; + } + return EC_RES_SUCCESS; +} + +static int validate_fp_buffer_offset(const uint32_t buffer_size, + const uint32_t offset, const uint32_t size) +{ + if (size > buffer_size || offset > buffer_size || + size + offset > buffer_size) + return EC_ERROR_INVAL; + return EC_SUCCESS; +} + +static int fp_command_frame(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_frame *params = args->params; + void *out = args->response; + uint32_t idx = FP_FRAME_GET_BUFFER_INDEX(params->offset); + uint32_t offset = params->offset & FP_FRAME_OFFSET_MASK; + uint32_t size = params->size; + uint32_t fgr; + uint8_t key[SBP_ENC_KEY_LEN]; + struct ec_fp_template_encryption_metadata *enc_info; + int ret; + + if (size > args->response_max) + return EC_RES_INVALID_PARAM; + + if (idx == FP_FRAME_INDEX_RAW_IMAGE) { + /* The host requested a frame. */ + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; + if (!is_raw_capture(sensor_mode)) + offset += FP_SENSOR_IMAGE_OFFSET; + + ret = validate_fp_buffer_offset(sizeof(fp_buffer), offset, + size); + if (ret != EC_SUCCESS) + return EC_RES_INVALID_PARAM; + + memcpy(out, fp_buffer + offset, size); + args->response_size = size; + return EC_RES_SUCCESS; + } + + /* The host requested a template. */ + + /* Templates are numbered from 1 in this host request. */ + fgr = idx - FP_FRAME_INDEX_TEMPLATE; + + if (fgr >= FP_MAX_FINGER_COUNT) + return EC_RES_INVALID_PARAM; + if (fgr >= 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); + + memset(fp_enc_buffer, 0, sizeof(fp_enc_buffer)); + /* The beginning of the buffer contains nonce/salt/tag. */ + enc_info = (void *)fp_enc_buffer; + enc_info->struct_version = FP_TEMPLATE_FORMAT_VERSION; + init_trng(); + rand_bytes(enc_info->nonce, FP_CONTEXT_NONCE_BYTES); + rand_bytes(enc_info->salt, FP_CONTEXT_SALT_BYTES); + exit_trng(); + + ret = derive_encryption_key(key, enc_info->salt); + if (ret != EC_RES_SUCCESS) { + CPRINTS("fgr%d: Failed to derive key", fgr); + return EC_RES_UNAVAILABLE; + } + + ret = aes_gcm_encrypt(key, SBP_ENC_KEY_LEN, fp_template[fgr], + fp_enc_buffer + sizeof(*enc_info), + sizeof(fp_template[0]), + enc_info->nonce, FP_CONTEXT_NONCE_BYTES, + enc_info->tag, FP_CONTEXT_TAG_BYTES); + if (ret != EC_RES_SUCCESS) { + CPRINTS("fgr%d: Failed to encrypt template", fgr); + return EC_RES_UNAVAILABLE; + } + templ_dirty &= ~BIT(fgr); + } + memcpy(out, fp_enc_buffer + offset, size); + args->response_size = size; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_FRAME, fp_command_frame, EC_VER_MASK(0)); + +static int fp_command_stats(struct host_cmd_handler_args *args) +{ + struct ec_response_fp_stats *r = 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; + r->template_matched = 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 int 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 int fp_command_template(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_template *params = args->params; + uint32_t size = params->size & ~FP_TEMPLATE_COMMIT; + int xfer_complete = params->size & FP_TEMPLATE_COMMIT; + uint32_t offset = params->offset; + uint32_t idx = templ_valid; + uint8_t key[SBP_ENC_KEY_LEN]; + struct ec_fp_template_encryption_metadata *enc_info; + int ret; + + /* Can we store one more template ? */ + if (idx >= FP_MAX_FINGER_COUNT) + return EC_RES_OVERFLOW; + + if (args->params_size != + size + offsetof(struct ec_params_fp_template, data)) + return EC_RES_INVALID_PARAM; + ret = validate_fp_buffer_offset(sizeof(fp_enc_buffer), offset, size); + if (ret != EC_SUCCESS) + return EC_RES_INVALID_PARAM; + + memcpy(&fp_enc_buffer[offset], params->data, size); + + if (xfer_complete) { + /* + * The complete encrypted template has been received, start + * decryption. + */ + fp_clear_finger_context(idx); + /* The beginning of the buffer contains nonce/salt/tag. */ + enc_info = (void *)fp_enc_buffer; + ret = validate_template_format(enc_info); + if (ret != EC_RES_SUCCESS) { + CPRINTS("fgr%d: Template format not supported", idx); + return EC_RES_INVALID_PARAM; + } + ret = derive_encryption_key(key, enc_info->salt); + if (ret != EC_RES_SUCCESS) { + CPRINTS("fgr%d: Failed to derive key", idx); + return EC_RES_UNAVAILABLE; + } + + ret = aes_gcm_decrypt(key, SBP_ENC_KEY_LEN, fp_template[idx], + fp_enc_buffer + sizeof(*enc_info), + sizeof(fp_template[0]), + enc_info->nonce, FP_CONTEXT_NONCE_BYTES, + enc_info->tag, FP_CONTEXT_TAG_BYTES); + if (ret != EC_RES_SUCCESS) { + CPRINTS("fgr%d: Failed to decipher template", idx); + /* Don't leave bad data in the template buffer */ + fp_clear_finger_context(idx); + return EC_RES_UNAVAILABLE; + } + templ_valid++; + } + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_TEMPLATE, fp_command_template, EC_VER_MASK(0)); + +#ifdef CONFIG_CMD_FPSENSOR_DEBUG +/* --- Debug console commands --- */ + +/* + * Send the current Fingerprint buffer to the host + * it is formatted as an 8-bpp PGM ASCII file. + * + * In addition, it prepends a short Z-Modem download signature, + * which triggers automatically your preferred viewer if you configure it + * properly in "File transfer protocols" in the Minicom options menu. + * (as triggered by Ctrl-A O) + * +--------------------------------------------------------------------------+ + * | Name Program Name U/D FullScr IO-Red. Multi | + * | A zmodem /usr/bin/sz -vv -b Y U N Y Y | + * [...] + * | L pgm /usr/bin/display_pgm N D N Y N | + * | M Zmodem download string activates... L | + * + * My /usr/bin/display_pgm looks like this: + * #!/bin/sh + * TMPF=$(mktemp) + * ascii-xfr -rdv ${TMPF} + * display ${TMPF} + */ +static void upload_pgm_image(uint8_t *frame) +{ + int x, y; + uint8_t *ptr = frame; + + /* fake Z-modem ZRQINIT signature */ + ccprintf("#IGNORE for ZModem\r**\030B00"); + msleep(100); /* let the download program start */ + /* Print 8-bpp PGM ASCII header */ + ccprintf("P2\n%d %d\n255\n", FP_SENSOR_RES_X, FP_SENSOR_RES_Y); + + for (y = 0; y < FP_SENSOR_RES_Y; y++) { + watchdog_reload(); + for (x = 0; x < FP_SENSOR_RES_X; x++, ptr++) + ccprintf("%d ", *ptr); + ccputs("\n"); + cflush(); + } + + ccprintf("\x04"); /* End Of Transmission */ +} + +static int fp_console_action(uint32_t mode) +{ + int tries = 200; + ccprintf("Waiting for finger ...\n"); + sensor_mode = mode; + task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG, 0); + + while (tries--) { + if (!(sensor_mode & FP_MODE_ANY_CAPTURE)) { + ccprintf("done (events:%x)\n", fp_events); + return 0; + } + usleep(100 * MSEC); + } + return EC_ERROR_TIMEOUT; +} + +int command_fpcapture(int argc, char **argv) +{ + int capture_type = FP_CAPTURE_SIMPLE_IMAGE; + uint32_t mode; + int rc; + + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; + + if (argc >= 2) { + char *e; + + capture_type = strtoi(argv[1], &e, 0); + if (*e || capture_type < 0) + return EC_ERROR_PARAM1; + } + mode = FP_MODE_CAPTURE | ((capture_type << FP_MODE_CAPTURE_TYPE_SHIFT) + & FP_MODE_CAPTURE_TYPE_MASK); + + rc = fp_console_action(mode); + if (rc == EC_SUCCESS) + upload_pgm_image(fp_buffer + FP_SENSOR_IMAGE_OFFSET); + + return rc; +} +DECLARE_CONSOLE_COMMAND(fpcapture, command_fpcapture, "", ""); + +int command_fpenroll(int argc, char **argv) +{ + int rc; + int percent = 0; + uint32_t event; + static const char * const enroll_str[] = {"OK", "Low Quality", + "Immobile", "Low Coverage"}; + + if (system_is_locked()) + return EC_RES_ACCESS_DENIED; + + do { + int tries = 1000; + + rc = fp_console_action(FP_MODE_ENROLL_SESSION | + FP_MODE_ENROLL_IMAGE); + if (rc != EC_SUCCESS) + break; + event = atomic_read_clear(&fp_events); + percent = EC_MKBP_FP_ENROLL_PROGRESS(event); + ccprintf("Enroll capture: %s (%d%%)\n", + enroll_str[EC_MKBP_FP_ERRCODE(event) & 3], percent); + /* wait for finger release between captures */ + sensor_mode = FP_MODE_ENROLL_SESSION | FP_MODE_FINGER_UP; + task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG, 0); + while (tries-- && sensor_mode & FP_MODE_FINGER_UP) + usleep(20 * MSEC); + } while (percent < 100); + sensor_mode = 0; /* reset FP_MODE_ENROLL_SESSION */ + task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG, 0); + + return rc; +} +DECLARE_CONSOLE_COMMAND(fpenroll, command_fpenroll, "", ""); + + +int command_fpmatch(int argc, char **argv) +{ + int rc = fp_console_action(FP_MODE_MATCH); + uint32_t event = atomic_read_clear(&fp_events); + + if (rc == EC_SUCCESS && event & EC_MKBP_FP_MATCH) { + uint32_t errcode = EC_MKBP_FP_ERRCODE(event); + + ccprintf("Match: %s (%d)\n", + errcode & EC_MKBP_FP_ERR_MATCH_YES ? "YES" : "NO", + errcode); + } + + return rc; +} +DECLARE_CONSOLE_COMMAND(fpmatch, command_fpmatch, "", ""); + +int command_fpclear(int argc, char **argv) +{ + fp_clear_context(); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(fpclear, command_fpclear, "", ""); + +#endif /* CONFIG_CMD_FPSENSOR_DEBUG */ diff --git a/common/fpsensor/fpsensor_state.c b/common/fpsensor/fpsensor_state.c new file mode 100644 index 0000000000..c9beeb1f82 --- /dev/null +++ b/common/fpsensor/fpsensor_state.c @@ -0,0 +1,156 @@ +/* Copyright 2017 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 "common.h" +#include "ec_commands.h" +#include "fpsensor.h" +#include "fpsensor_state.h" +#include "host_command.h" +#include "system.h" +#include "task.h" +#include "util.h" + +#define CPRINTF(format, args...) cprintf(CC_FP, format, ## args) +#define CPRINTS(format, args...) cprints(CC_FP, format, ## args) + +/* Last acquired frame (aligned as it is used by arbitrary binary libraries) */ +uint8_t fp_buffer[FP_SENSOR_IMAGE_SIZE] FP_FRAME_SECTION __aligned(4); +/* Fingers templates for the current user */ +uint8_t fp_template[FP_MAX_FINGER_COUNT][FP_ALGORITHM_TEMPLATE_SIZE] + FP_TEMPLATE_SECTION; +/* Encryption/decryption buffer */ +/* TODO: On-the-fly encryption/decryption without a dedicated buffer */ +/* + * Store the encryption metadata at the beginning of the buffer containing the + * ciphered data. + */ +uint8_t fp_enc_buffer[FP_ALGORITHM_ENCRYPTED_TEMPLATE_SIZE] + FP_TEMPLATE_SECTION; +/* Number of used templates */ +uint32_t templ_valid; +/* Bitmap of the templates with local modifications */ +uint32_t templ_dirty; +/* Current user ID */ +uint32_t user_id[FP_CONTEXT_USERID_WORDS]; +/* Part of the IKM used to derive encryption keys received from the TPM. */ +uint8_t tpm_seed[FP_CONTEXT_TPM_BYTES]; +/* Flag indicating whether the seed has been initialised or not. */ +static int fp_tpm_seed_set; + +uint32_t fp_events; + +uint32_t sensor_mode; + +void fp_clear_finger_context(int idx) +{ + memset(fp_template[idx], 0, sizeof(fp_template[0])); +} + +void fp_clear_context(void) +{ + int idx; + + templ_valid = 0; + templ_dirty = 0; + memset(fp_buffer, 0, sizeof(fp_buffer)); + memset(fp_enc_buffer, 0, sizeof(fp_enc_buffer)); + memset(user_id, 0, sizeof(user_id)); + for (idx = 0; idx < FP_MAX_FINGER_COUNT; idx++) + fp_clear_finger_context(idx); + /* TODO maybe shutdown and re-init the private libraries ? */ +} + +int fp_get_next_event(uint8_t *out) +{ + uint32_t event_out = atomic_read_clear(&fp_events); + + memcpy(out, &event_out, sizeof(event_out)); + + return sizeof(event_out); +} +DECLARE_EVENT_SOURCE(EC_MKBP_EVENT_FINGERPRINT, fp_get_next_event); + +static int fp_command_tpm_seed(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_seed *params = args->params; + + if (params->struct_version != FP_TEMPLATE_FORMAT_VERSION) { + CPRINTS("Invalid seed format %d", params->struct_version); + return EC_RES_INVALID_PARAM; + } + + if (fp_tpm_seed_set) { + CPRINTS("Seed has already been set."); + return EC_RES_ACCESS_DENIED; + } + memcpy(tpm_seed, params->seed, sizeof(tpm_seed)); + fp_tpm_seed_set = 1; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_SEED, fp_command_tpm_seed, EC_VER_MASK(0)); + +int fp_tpm_seed_is_set(void) +{ + return fp_tpm_seed_set; +} + +static int validate_fp_mode(const uint32_t mode) +{ + uint32_t capture_type = FP_CAPTURE_TYPE(mode); + uint32_t algo_mode = mode & ~FP_MODE_CAPTURE_TYPE_MASK; + uint32_t cur_mode = sensor_mode; + + if (capture_type >= FP_CAPTURE_TYPE_MAX) + return EC_ERROR_INVAL; + + if (algo_mode & ~FP_VALID_MODES) + return EC_ERROR_INVAL; + + /* Don't allow sensor reset if any other mode is + * set (including FP_MODE_RESET_SENSOR itself). + */ + if (mode & FP_MODE_RESET_SENSOR) { + if (cur_mode & FP_VALID_MODES) + return EC_ERROR_INVAL; + } + + return EC_SUCCESS; +} + +static int fp_command_mode(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_mode *p = args->params; + struct ec_response_fp_mode *r = args->response; + int ret; + + ret = validate_fp_mode(p->mode); + if (ret != EC_SUCCESS) { + CPRINTS("Invalid FP mode 0x%x", p->mode); + return EC_RES_INVALID_PARAM; + } + + if (!(p->mode & FP_MODE_DONT_CHANGE)) { + sensor_mode = p->mode; + task_set_event(TASK_ID_FPSENSOR, TASK_EVENT_UPDATE_CONFIG, 0); + } + + r->mode = sensor_mode; + args->response_size = sizeof(*r); + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_MODE, fp_command_mode, EC_VER_MASK(0)); + +static int fp_command_context(struct host_cmd_handler_args *args) +{ + const struct ec_params_fp_context *params = args->params; + + fp_clear_context(); + + memcpy(user_id, params->userid, sizeof(user_id)); + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_FP_CONTEXT, fp_command_context, EC_VER_MASK(0)); |