mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-14 09:58:07 +02:00
validity: enrollment, verification & DB operations\n\nImplement the complete enrollment and verification flow for\nSynaptics VCSFW (Validity) fingerprint sensors:\n\n- Enrollment state machine: LED control, capture loop with\n scan_complete polling, enrollment_update_start/end cycle,\n template building across ~8-9 stages until TID is received\n- DB write phase: StgWindsor storage auto-creation (0x04b3),\n user record creation, finger record creation with proper\n write_enable/call_cleanups wrapping\n- Pre-enrollment cleanup: delete existing user records from\n sensor DB before re-enrolling (prevents 0x0526 errors)\n- Stale session cleanup: send enrollment_update_end before\n starting new enrollment to close interrupted sessions\n- Verification: match_finger command with proper response\n parsing, delete and clear_storage operations\n- Print data: FPI_PRINT_RAW with fpi-data GVariant containing\n user identity string\n- Capture fixes: TST instruction search bug (save patched_tst\n before key_line replacement), ENROLL vs IDENTIFY mode\n differences in capture command structure\n- TLS improvements: proper session state tracking, reconnect\n handling, extended response buffer management\n- Pairing: device certificate chain validation, Windows Hello\n compatible key exchange\n\nTested on 06cb:009a (Synaptics Metallica MIS Touch):\n- Fresh enrollment: completes in 7-9 stages\n- Re-enrollment: pre-cleanup deletes stale records, then enrolls\n- Verification: verify-match confirmed (3x consecutive)\n\nReference: python-validity by uunicorn"
This commit is contained in:
parent
94bbb5fa2c
commit
a486b58c5a
13 changed files with 1612 additions and 196 deletions
|
|
@ -166,7 +166,9 @@ err_close:
|
|||
* 3) GET_FW_INFO (0x43 0x02) — check if fwext loaded
|
||||
* 4) Send init_hardcoded blob (per-device, via HAL)
|
||||
* 5) If no fwext: send init_hardcoded_clean_slate blob
|
||||
* 6) Upload firmware extension (if not loaded)
|
||||
* 6) Pairing check
|
||||
* 7) TLS handshake (works without fwext — uses partition 1 keys)
|
||||
* 8) Upload firmware extension via TLS (if not loaded)
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
|
|
@ -180,16 +182,21 @@ typedef enum {
|
|||
OPEN_RECV_INIT_HARDCODED,
|
||||
OPEN_SEND_INIT_CLEAN_SLATE,
|
||||
OPEN_RECV_INIT_CLEAN_SLATE,
|
||||
OPEN_UPLOAD_FWEXT,
|
||||
OPEN_PAIR,
|
||||
OPEN_TLS_READ_FLASH,
|
||||
OPEN_TLS_DERIVE_PSK,
|
||||
OPEN_TLS_HANDSHAKE,
|
||||
OPEN_UPLOAD_FWEXT,
|
||||
OPEN_SENSOR_IDENTIFY,
|
||||
OPEN_SENSOR_IDENTIFY_RECV,
|
||||
OPEN_SENSOR_FACTORY_BITS,
|
||||
OPEN_SENSOR_FACTORY_BITS_RECV,
|
||||
OPEN_CAPTURE_SETUP,
|
||||
OPEN_CALIBRATE_BUILD,
|
||||
OPEN_CALIBRATE_SEND,
|
||||
OPEN_CALIBRATE_SEND_RECV,
|
||||
OPEN_CALIBRATE_READ_DATA,
|
||||
OPEN_CALIBRATE_LOOP,
|
||||
OPEN_DONE,
|
||||
OPEN_NUM_STATES,
|
||||
} ValidityOpenSsmState;
|
||||
|
|
@ -296,21 +303,29 @@ pair_ssm_done (FpiSsm *ssm,
|
|||
|
||||
if (error)
|
||||
{
|
||||
/* Check if the pairing caused a reboot — same pattern as fwext upload */
|
||||
if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED))
|
||||
/* After reboot, USB transfers fail — this is expected */
|
||||
if (self->pair_state.reboot_pending)
|
||||
{
|
||||
fp_info ("Device rebooting after pairing (USB error expected)");
|
||||
g_clear_error (&error);
|
||||
}
|
||||
else if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_REMOVED))
|
||||
{
|
||||
fp_info ("Device rebooting after pairing");
|
||||
fpi_ssm_mark_failed (self->open_ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_warn ("Pairing failed: %s — continuing (device may not work)",
|
||||
error->message);
|
||||
g_clear_error (&error);
|
||||
else
|
||||
{
|
||||
fp_warn ("Pairing failed: %s — continuing (device may not work)",
|
||||
error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if pairing caused a reboot (PAIR_REBOOT_RECV was reached) */
|
||||
if (self->pair_state.priv_blob != NULL)
|
||||
/* Check if pairing caused a reboot */
|
||||
if (self->pair_state.priv_blob != NULL ||
|
||||
self->pair_state.reboot_pending)
|
||||
{
|
||||
/* Pairing was performed and device is rebooting.
|
||||
* Signal to fprintd to retry the open. */
|
||||
|
|
@ -345,6 +360,29 @@ tls_handshake_ssm_done (FpiSsm *ssm,
|
|||
fpi_ssm_next_state (self->open_ssm);
|
||||
}
|
||||
|
||||
/* Callback for calibration EP 0x82 bulk read — saves data for processing */
|
||||
static void
|
||||
calib_bulk_read_cb (FpiUsbTransfer *transfer,
|
||||
FpDevice *dev,
|
||||
gpointer user_data,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Save the raw calibration data for processing in the next state */
|
||||
g_clear_pointer (&self->bulk_data, g_free);
|
||||
self->bulk_data = g_memdup2 (transfer->buffer, transfer->actual_length);
|
||||
self->bulk_data_len = transfer->actual_length;
|
||||
|
||||
fpi_ssm_next_state (transfer->ssm);
|
||||
}
|
||||
|
||||
static void
|
||||
open_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
|
|
@ -534,7 +572,17 @@ open_run_state (FpiSsm *ssm,
|
|||
return;
|
||||
}
|
||||
|
||||
fp_info ("Firmware extension not loaded — starting upload");
|
||||
/* Fwext upload requires a TLS session (flash writes need TLS).
|
||||
* If TLS handshake failed/skipped, we can't upload. */
|
||||
if (!self->tls.secure_rx)
|
||||
{
|
||||
fp_warn ("No TLS session — cannot upload firmware extension "
|
||||
"(device may need pairing first)");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Firmware extension not loaded — starting upload via TLS");
|
||||
|
||||
self->open_ssm = ssm;
|
||||
FpiSsm *fwext_ssm = validity_fwext_upload_ssm_new (dev);
|
||||
|
|
@ -554,14 +602,6 @@ open_run_state (FpiSsm *ssm,
|
|||
return;
|
||||
}
|
||||
|
||||
/* Without fwext, no flash commands work */
|
||||
if (!self->fwext_loaded)
|
||||
{
|
||||
fp_info ("No firmware extension — skipping pairing check");
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Starting pairing check…");
|
||||
validity_pair_state_init (&self->pair_state);
|
||||
self->open_ssm = ssm;
|
||||
|
|
@ -582,17 +622,8 @@ open_run_state (FpiSsm *ssm,
|
|||
return;
|
||||
}
|
||||
|
||||
/* Without fwext, flash partition isn't accessible */
|
||||
if (!self->fwext_loaded)
|
||||
{
|
||||
fp_info ("No firmware extension — skipping TLS "
|
||||
"(device needs pairing or fwext upload)");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read flash partition 1 to get TLS keys.
|
||||
* Uses standalone SSM (not subsm) so failure is non-fatal. */
|
||||
* TLS works independently of fwext (partition 2). */
|
||||
self->open_ssm = ssm;
|
||||
FpiSsm *flash_ssm = fpi_ssm_new (dev,
|
||||
validity_tls_flash_read_run_state,
|
||||
|
|
@ -699,6 +730,14 @@ open_run_state (FpiSsm *ssm,
|
|||
return;
|
||||
}
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("identify_sensor raw: ");
|
||||
for (gsize i = 0; i < self->cmd_response_len; i++)
|
||||
g_string_append_printf (hex, "%02x ", self->cmd_response_data[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
if (!validity_sensor_parse_identify (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&self->sensor.ident))
|
||||
|
|
@ -835,6 +874,156 @@ open_run_state (FpiSsm *ssm,
|
|||
}
|
||||
break;
|
||||
|
||||
case OPEN_CALIBRATE_BUILD:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* Run calibration captures to establish sensor finger-detect baseline.
|
||||
* PY: sensor.calibrate() — 3 iterations of CALIBRATE capture.
|
||||
* Without this, chunk 0x26 (Finger Detect) always triggers. */
|
||||
if (!self->sensor.type_info ||
|
||||
self->capture.bytes_per_line == 0)
|
||||
{
|
||||
fp_info ("No capture state — skipping calibration");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
self->calib_iteration = 0;
|
||||
g_clear_pointer (&self->capture.calib_data, g_free);
|
||||
self->capture.calib_data_len = 0;
|
||||
|
||||
fp_info ("Starting sensor calibration (%u iterations)",
|
||||
self->capture.calibration_iterations);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_CALIBRATE_SEND:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
gsize cmd_len;
|
||||
guint8 *cmd;
|
||||
|
||||
cmd = validity_capture_build_cmd_02 (&self->capture,
|
||||
self->sensor.type_info,
|
||||
VALIDITY_CAPTURE_CALIBRATE,
|
||||
&cmd_len);
|
||||
if (!cmd)
|
||||
{
|
||||
fp_warn ("Failed to build calibration capture command");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_dbg ("Calibration iteration %u/%u",
|
||||
self->calib_iteration + 1,
|
||||
self->capture.calibration_iterations);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_CALIBRATE_SEND_RECV:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("Calibration capture failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read calibration data from EP 0x82.
|
||||
* PY: usb.read_82() — reads all bulk data from the sensor. */
|
||||
{
|
||||
gsize expected_size = (gsize)(self->capture.calibration_frames *
|
||||
self->capture.lines_per_frame + 1) *
|
||||
self->capture.bytes_per_line;
|
||||
FpiUsbTransfer *xfer = fpi_usb_transfer_new (dev);
|
||||
|
||||
fp_dbg ("Reading calibration data: %zu bytes from EP 0x82",
|
||||
expected_size);
|
||||
xfer->ssm = ssm;
|
||||
fpi_usb_transfer_fill_bulk (xfer, VALIDITY_EP_DATA_IN,
|
||||
expected_size);
|
||||
fpi_usb_transfer_submit (xfer, 5000, NULL,
|
||||
calib_bulk_read_cb, NULL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_CALIBRATE_READ_DATA:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (self->bulk_data && self->bulk_data_len > 0)
|
||||
{
|
||||
/* Average the raw calibration frames */
|
||||
gsize averaged_len = 0;
|
||||
guint8 *averaged = validity_capture_average_frames (
|
||||
self->bulk_data,
|
||||
self->bulk_data_len,
|
||||
self->capture.lines_per_frame,
|
||||
self->capture.bytes_per_line,
|
||||
self->sensor.type_info->lines_per_calibration_data,
|
||||
self->capture.calibration_frames,
|
||||
&averaged_len);
|
||||
|
||||
if (averaged && averaged_len > 0)
|
||||
{
|
||||
/* Process calibration: scale and accumulate into calib_data */
|
||||
validity_capture_process_calibration (
|
||||
&self->capture.calib_data,
|
||||
&self->capture.calib_data_len,
|
||||
averaged,
|
||||
averaged_len,
|
||||
self->capture.bytes_per_line);
|
||||
|
||||
fp_dbg ("Calibration iteration %u complete: "
|
||||
"averaged %zu bytes, calib_data %zu bytes",
|
||||
self->calib_iteration + 1,
|
||||
averaged_len,
|
||||
self->capture.calib_data_len);
|
||||
g_free (averaged);
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_dbg ("Calibration iteration %u: averaging failed",
|
||||
self->calib_iteration + 1);
|
||||
g_free (averaged);
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->bulk_data, g_free);
|
||||
self->bulk_data_len = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_dbg ("Calibration iteration %u: no bulk data",
|
||||
self->calib_iteration + 1);
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_CALIBRATE_LOOP:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
self->calib_iteration++;
|
||||
if (self->calib_iteration < self->capture.calibration_iterations)
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_CALIBRATE_SEND);
|
||||
else
|
||||
{
|
||||
fp_info ("Sensor calibration complete");
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_DONE:
|
||||
/* All init commands sent. Mark open complete. */
|
||||
fpi_ssm_mark_completed (ssm);
|
||||
|
|
|
|||
|
|
@ -105,37 +105,75 @@ typedef enum {
|
|||
CALIB_NUM_STATES,
|
||||
} ValidityCalibState;
|
||||
|
||||
/* Enrollment SSM states */
|
||||
/* Enrollment SSM states — matches python-validity sensor.py Sensor.enroll() */
|
||||
typedef enum {
|
||||
ENROLL_START = 0,
|
||||
ENROLL_CLEANUP_STALE = 0, /* Close any stale enrollment session */
|
||||
ENROLL_CLEANUP_STALE_RECV,
|
||||
/* Pre-enrollment: delete existing user records to avoid 0x0526 */
|
||||
ENROLL_PRE_GET_STORAGE,
|
||||
ENROLL_PRE_GET_STORAGE_RECV,
|
||||
ENROLL_PRE_DEL_USER,
|
||||
ENROLL_PRE_DEL_USER_RECV,
|
||||
ENROLL_START, /* create_enrollment (cmd 0x69 flag=1) */
|
||||
ENROLL_START_RECV,
|
||||
/* --- Per-iteration loop --- */
|
||||
ENROLL_LED_ON,
|
||||
ENROLL_LED_ON_RECV,
|
||||
ENROLL_WAIT_FINGER_DELAY,
|
||||
ENROLL_BUILD_CAPTURE,
|
||||
ENROLL_CAPTURE_SEND,
|
||||
ENROLL_CAPTURE_RECV,
|
||||
ENROLL_WAIT_FINGER,
|
||||
ENROLL_WAIT_SCAN_COMPLETE,
|
||||
ENROLL_GET_PRG_STATUS,
|
||||
ENROLL_GET_PRG_STATUS_RECV,
|
||||
ENROLL_CAPTURE_STOP,
|
||||
ENROLL_CAPTURE_STOP_RECV,
|
||||
ENROLL_UPDATE_START,
|
||||
ENROLL_UPDATE_START_RECV,
|
||||
ENROLL_DB_WRITE_ENABLE,
|
||||
ENROLL_WAIT_UPDATE_START_INT, /* PY: usb.wait_int() inside enrollment_update_start */
|
||||
ENROLL_DB_WRITE_ENABLE, /* PY: write_enable() before 1st enrollment_update */
|
||||
ENROLL_DB_WRITE_ENABLE_RECV,
|
||||
ENROLL_APPEND_IMAGE,
|
||||
ENROLL_APPEND_IMAGE, /* 1st enrollment_update (trigger) */
|
||||
ENROLL_APPEND_IMAGE_RECV,
|
||||
ENROLL_CLEANUPS,
|
||||
ENROLL_CLEANUPS, /* PY: call_cleanups() after 1st enrollment_update */
|
||||
ENROLL_CLEANUPS_RECV,
|
||||
ENROLL_UPDATE_END,
|
||||
ENROLL_WAIT_UPDATE_INT, /* PY: usb.wait_int() between the two calls */
|
||||
ENROLL_DB_WRITE_ENABLE_READ, /* PY: write_enable() before 2nd enrollment_update */
|
||||
ENROLL_DB_WRITE_ENABLE_READ_RECV,
|
||||
ENROLL_APPEND_IMAGE_READ, /* 2nd enrollment_update (read result) */
|
||||
ENROLL_APPEND_IMAGE_READ_RECV,
|
||||
ENROLL_CLEANUPS_READ, /* PY: call_cleanups() after 2nd enrollment_update */
|
||||
ENROLL_CLEANUPS_READ_RECV,
|
||||
ENROLL_UPDATE_END, /* PY: enrollment_update_end = cmd 0x69 flag=0 (finally) */
|
||||
ENROLL_UPDATE_END_RECV,
|
||||
ENROLL_LOOP_CHECK,
|
||||
/* --- Post-loop: DB commit --- */
|
||||
ENROLL_UPDATE_END2, /* PY: 2nd enrollment_update_end after loop */
|
||||
ENROLL_UPDATE_END2_RECV,
|
||||
ENROLL_GET_STORAGE,
|
||||
ENROLL_GET_STORAGE_RECV,
|
||||
/* If storage doesn't exist (0x04b3), create it: */
|
||||
ENROLL_INIT_STORAGE_WE, /* db_write_enable for storage creation */
|
||||
ENROLL_INIT_STORAGE_WE_RECV,
|
||||
ENROLL_INIT_STORAGE_CREATE, /* new_record(1, 4, 3, "StgWindsor\0") */
|
||||
ENROLL_INIT_STORAGE_CREATE_RECV,
|
||||
ENROLL_INIT_STORAGE_CLEAN, /* call_cleanups */
|
||||
ENROLL_INIT_STORAGE_CLEAN_RECV,
|
||||
ENROLL_DB_WRITE_ENABLE2,
|
||||
ENROLL_DB_WRITE_ENABLE2_RECV,
|
||||
ENROLL_CREATE_USER,
|
||||
ENROLL_CREATE_USER_RECV,
|
||||
ENROLL_CREATE_USER_CLEANUPS,
|
||||
ENROLL_CREATE_USER_CLEANUPS_RECV,
|
||||
ENROLL_DB_WRITE_ENABLE3,
|
||||
ENROLL_DB_WRITE_ENABLE3_RECV,
|
||||
ENROLL_CREATE_FINGER,
|
||||
ENROLL_CREATE_FINGER_RECV,
|
||||
ENROLL_FINAL_CLEANUPS,
|
||||
ENROLL_FINAL_CLEANUPS_RECV,
|
||||
ENROLL_LED_OFF,
|
||||
ENROLL_WAIT_FINGER_INT,
|
||||
ENROLL_LED_OFF, /* PY: glow_end_scan() — LAST step per PY */
|
||||
ENROLL_LED_OFF_RECV,
|
||||
ENROLL_DONE,
|
||||
ENROLL_NUM_STATES,
|
||||
|
|
@ -150,6 +188,10 @@ typedef enum {
|
|||
VERIFY_CAPTURE_RECV,
|
||||
VERIFY_WAIT_FINGER,
|
||||
VERIFY_WAIT_SCAN_COMPLETE,
|
||||
VERIFY_GET_PRG_STATUS,
|
||||
VERIFY_GET_PRG_STATUS_RECV,
|
||||
VERIFY_CAPTURE_STOP,
|
||||
VERIFY_CAPTURE_STOP_RECV,
|
||||
VERIFY_MATCH_START,
|
||||
VERIFY_MATCH_START_RECV,
|
||||
VERIFY_WAIT_MATCH_INT,
|
||||
|
|
@ -232,6 +274,8 @@ struct _FpiDeviceValidity
|
|||
gsize enroll_template_len;
|
||||
guint enroll_stage;
|
||||
guint16 enroll_user_dbid;
|
||||
guint16 enroll_storage_dbid;
|
||||
guint scan_incomplete_count;
|
||||
|
||||
/* Verify/identify mode flag: TRUE = identify, FALSE = verify */
|
||||
gboolean identify_mode;
|
||||
|
|
|
|||
|
|
@ -665,6 +665,12 @@ build_line_update_type1 (const ValidityCaptureState *capture,
|
|||
GArray *lines_arr;
|
||||
gsize cnt = 2; /* line counter starts at 2 per python-validity */
|
||||
|
||||
/* Save the patched TST (before key_line replacement) for instruction
|
||||
* searches later. PY searches the original patched tst variable, not
|
||||
* the chunk data that has the key_line prepended. */
|
||||
g_autofree guint8 *patched_tst = NULL;
|
||||
gsize patched_tst_len = 0;
|
||||
|
||||
/* Copy input chunks, patching timeslot table in-place */
|
||||
chunks_arr = g_array_new (FALSE, TRUE, sizeof (ValidityCaptureChunk));
|
||||
|
||||
|
|
@ -686,6 +692,11 @@ build_line_update_type1 (const ValidityCaptureState *capture,
|
|||
capture->factory_calibration_values_len,
|
||||
capture->key_calibration_line);
|
||||
|
||||
/* Save the patched TST before key_line replacement.
|
||||
* Instruction searches must use this, not the key_line-modified data. */
|
||||
patched_tst = g_memdup2 (c.data, c.size);
|
||||
patched_tst_len = c.size;
|
||||
|
||||
/* Prepend key line to the timeslot table.
|
||||
* In type 1: c[1] = get_key_line() + tst[line_width:] */
|
||||
{
|
||||
|
|
@ -782,6 +793,40 @@ build_line_update_type1 (const ValidityCaptureState *capture,
|
|||
};
|
||||
g_array_append_val (chunks_arr, ir);
|
||||
}
|
||||
else if (mode == VALIDITY_CAPTURE_ENROLL_IDENTIFY)
|
||||
{
|
||||
/* Hybrid: IDENTIFY chunk (0x4e) for reliable completion + ENROLL
|
||||
* image reconstruction for proper enrollment data processing.
|
||||
* Works around sensors where chunk 0x26 triggers false finger
|
||||
* detection from ambient capacitance. */
|
||||
static const guint8 wtf_data[] = {
|
||||
0xfb, 0xb2, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x00,
|
||||
0x30, 0x00, 0x00, 0x00, 0x87, 0x00, 0x02, 0x00,
|
||||
0x67, 0x00, 0x0a, 0x00, 0x01, 0x80, 0x00, 0x00,
|
||||
0x0a, 0x02, 0x00, 0x00, 0x0b, 0x19, 0x00, 0x00,
|
||||
0x88, 0x13, 0xb8, 0x0b, 0x01, 0x09, 0x10, 0x00,
|
||||
};
|
||||
ValidityCaptureChunk fd = {
|
||||
.type = CAPT_CHUNK_WTF,
|
||||
.size = sizeof (wtf_data),
|
||||
.data = g_memdup2 (wtf_data, sizeof (wtf_data)),
|
||||
};
|
||||
g_array_append_val (chunks_arr, fd);
|
||||
|
||||
/* Image Reconstruction — ENROLL mode (byte 4 = 0x23) */
|
||||
static const guint8 recon_enroll[] = {
|
||||
0x02, 0x00, 0x18, 0x00, 0x23, 0x00, 0x00, 0x00,
|
||||
0x70, 0x00, 0x70, 0x00, 0x4d, 0x01, 0x00, 0x00,
|
||||
0xa0, 0x00, 0x8c, 0x00, 0x3c, 0x32, 0x32, 0x1e,
|
||||
0x3c, 0x0a, 0x02, 0x02,
|
||||
};
|
||||
ValidityCaptureChunk ir = {
|
||||
.type = CAPT_CHUNK_IMAGE_RECON,
|
||||
.size = sizeof (recon_enroll),
|
||||
.data = g_memdup2 (recon_enroll, sizeof (recon_enroll)),
|
||||
};
|
||||
g_array_append_val (chunks_arr, ir);
|
||||
}
|
||||
/* CALIBRATE mode: no Finger Detect or Image Reconstruction */
|
||||
|
||||
/* --- Interleave --- */
|
||||
|
|
@ -800,25 +845,13 @@ build_line_update_type1 (const ValidityCaptureState *capture,
|
|||
* Build line entries from calibration data for the timeslot table. */
|
||||
lines_arr = g_array_new (FALSE, TRUE, sizeof (LineEntry));
|
||||
|
||||
/* We need the patched timeslot table for instruction searches */
|
||||
{
|
||||
const guint8 *tst_data = NULL;
|
||||
gsize tst_len = 0;
|
||||
|
||||
/* Find the Timeslot Table 2D chunk in our patched chunks */
|
||||
for (gsize i = 0; i < chunks_arr->len; i++)
|
||||
{
|
||||
ValidityCaptureChunk *ch = &g_array_index (chunks_arr, ValidityCaptureChunk, i);
|
||||
if (ch->type == CAPT_CHUNK_TIMESLOT_2D)
|
||||
{
|
||||
tst_data = ch->data;
|
||||
tst_len = ch->size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tst_data && tst_len > 0)
|
||||
{
|
||||
/* We need the patched timeslot table (before key_line replacement)
|
||||
* for instruction searches — see PY's line_update_type_1 which uses
|
||||
* the 'tst' variable, not 'c[1]' (which has key_line prepended). */
|
||||
if (patched_tst && patched_tst_len > 0)
|
||||
{
|
||||
const guint8 *tst_data = patched_tst;
|
||||
gsize tst_len = patched_tst_len;
|
||||
/* Line 0: calibration blob at Enable Rx position */
|
||||
{
|
||||
gssize pc = validity_capture_find_nth_insn (tst_data, tst_len,
|
||||
|
|
@ -895,8 +928,7 @@ build_line_update_type1 (const ValidityCaptureState *capture,
|
|||
g_array_append_val (lines_arr, le);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Align all line data to 4-byte boundary */
|
||||
for (gsize i = 0; i < lines_arr->len; i++)
|
||||
|
|
@ -1043,6 +1075,11 @@ validity_capture_build_cmd_02 (const ValidityCaptureState *capture,
|
|||
if (!patched)
|
||||
return NULL;
|
||||
|
||||
/* Debug: log chunk types and sizes */
|
||||
for (gsize i = 0; i < n_patched; i++)
|
||||
fp_dbg ("cmd_02 chunk[%zu]: type=0x%02x size=%zu",
|
||||
i, patched[i].type, patched[i].size);
|
||||
|
||||
/* Merge chunks back to binary */
|
||||
merged = validity_capture_merge_chunks (patched, n_patched, &merged_len);
|
||||
validity_capture_chunks_free (patched, n_patched);
|
||||
|
|
@ -1065,6 +1102,17 @@ validity_capture_build_cmd_02 (const ValidityCaptureState *capture,
|
|||
memcpy (cmd + 5, merged, merged_len);
|
||||
g_free (merged);
|
||||
|
||||
/* Debug: dump first 200 bytes of capture command for comparison with PY */
|
||||
{
|
||||
GString *hex = g_string_new ("");
|
||||
gsize dump_len = MIN (*out_len, 200);
|
||||
for (gsize i = 0; i < dump_len; i++)
|
||||
g_string_append_printf (hex, "%02x", cmd[i]);
|
||||
fp_dbg ("cmd_02 mode=%d len=%zu first %zu bytes: %s",
|
||||
mode, *out_len, dump_len, hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
|
@ -1476,11 +1524,11 @@ validity_subtype_to_finger (guint16 subtype)
|
|||
static const guint8 glow_start_data[] = {
|
||||
0x39, 0x20, 0xbf, 0x02, 0x00, 0xff, 0xff, 0x00,
|
||||
0x00, 0x01, 0x99, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x99, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0x00, 0x00, 0x99, 0x00, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
|
||||
0x00, 0x00, 0x99, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
|
@ -1489,17 +1537,17 @@ static const guint8 glow_start_data[] = {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const guint8 glow_end_data[] = {
|
||||
0x39, 0xf4, 0x01, 0x00, 0x00, 0xf4, 0x01, 0x00,
|
||||
0x00, 0x01, 0xff, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01,
|
||||
0x00, 0x00, 0x00, 0xff, 0x00, 0x20, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x01, 0x00,
|
||||
0x00, 0x00, 0xff, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
|
@ -1508,7 +1556,7 @@ static const guint8 glow_end_data[] = {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
const guint8 *
|
||||
|
|
@ -1582,6 +1630,96 @@ static const guint8 capture_prog_type1_b5[] = {
|
|||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* Device-specific capture program for sensor type 0x0199 (57K0 family).
|
||||
* From python-validity SensorCaptureProg entry: major=6, dev_type=0x199,
|
||||
* a0=0x18, a1=0x19, 2 blobs totalling 648 bytes. */
|
||||
static const guint8 capture_prog_type1_0199[] = {
|
||||
/* Blob 0: 228 bytes */
|
||||
0x23, 0x00, 0x00, 0x00, 0x20, 0x00, 0x08, 0x00,
|
||||
0x00, 0x20, 0x00, 0x80, 0x00, 0x00, 0x01, 0x00,
|
||||
0x32, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0x20, 0x20, 0x04, 0x00, 0x24, 0x20, 0x00, 0x00,
|
||||
0x50, 0x20, 0x77, 0x36, 0x28, 0x20, 0x01, 0x00,
|
||||
0x30, 0x20, 0x01, 0x00, 0x3c, 0x20, 0x80, 0x00,
|
||||
0x08, 0x21, 0x38, 0x00, 0x0c, 0x21, 0x00, 0x00,
|
||||
0x48, 0x21, 0x07, 0x00, 0x4c, 0x21, 0x00, 0x00,
|
||||
0x58, 0x20, 0x00, 0x00, 0x5c, 0x20, 0x00, 0x00,
|
||||
0x60, 0x20, 0x00, 0x00, 0x68, 0x20, 0x05, 0x00,
|
||||
0x6c, 0x20, 0x01, 0x49, 0x70, 0x20, 0x01, 0x41,
|
||||
0x74, 0x20, 0x01, 0x88, 0x78, 0x20, 0x01, 0x80,
|
||||
0x84, 0x20, 0x20, 0x00, 0x94, 0x20, 0x01, 0x80,
|
||||
0x9c, 0x20, 0x09, 0x02, 0xa0, 0x20, 0x0b, 0x19,
|
||||
0xb4, 0x20, 0x03, 0x00, 0xb8, 0x20, 0x3b, 0x04,
|
||||
0xbc, 0x20, 0x14, 0x00, 0xc0, 0x20, 0x02, 0x00,
|
||||
0xc4, 0x20, 0x01, 0x00, 0xc8, 0x20, 0x02, 0x00,
|
||||
0x33, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x80,
|
||||
0xcc, 0x20, 0x00, 0x00, 0xf5, 0x03, 0xd0, 0x20,
|
||||
0x00, 0x00, 0xa1, 0x01, 0x32, 0x00, 0x44, 0x00,
|
||||
0x00, 0x00, 0x00, 0x80, 0xdc, 0x20, 0xe8, 0x03,
|
||||
0xe0, 0x20, 0x64, 0x01, 0xe4, 0x20, 0xd0, 0x02,
|
||||
0xe8, 0x20, 0x00, 0x01, 0xf0, 0x20, 0x05, 0x00,
|
||||
0xf8, 0x20, 0x05, 0x00, 0xfc, 0x20, 0x00, 0x00,
|
||||
0xb8, 0x20, 0x3a, 0x00, 0x00, 0x08, 0x04, 0x00,
|
||||
0x14, 0x08, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00,
|
||||
0x08, 0x08, 0x00, 0x00, 0x14, 0x08, 0x30, 0x00,
|
||||
0x08, 0x08, 0x00, 0x00, 0x14, 0x08, 0x31, 0x00,
|
||||
0x1c, 0x08, 0x1a, 0x00,
|
||||
/* Blob 1: 420 bytes */
|
||||
0x32, 0x00, 0x0c, 0x00,
|
||||
0x00, 0x00, 0x00, 0x80, 0x50, 0x11, 0x01, 0x00,
|
||||
0x4c, 0x11, 0x1e, 0x00, 0x34, 0x00, 0x78, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x22, 0x17, 0x10, 0x22, 0x17, 0x10, 0x22,
|
||||
0x16, 0x10, 0x22, 0x16, 0x10, 0x22, 0x16, 0x01,
|
||||
0x06, 0x50, 0x10, 0x25, 0x01, 0x01, 0x00, 0x00,
|
||||
0x07, 0xc8, 0x07, 0x8c, 0x06, 0xff, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x4f, 0x80, 0x00, 0x6d,
|
||||
0x03, 0x00, 0x28, 0x03, 0x07, 0x03, 0x09, 0x90,
|
||||
0x09, 0x8d, 0xb0, 0x0b, 0x90, 0x88, 0x09, 0x91,
|
||||
0x85, 0x8e, 0x08, 0xc1, 0x81, 0x0b, 0x91, 0x90,
|
||||
0x91, 0x0a, 0xc1, 0xb8, 0x92, 0x8a, 0x09, 0x93,
|
||||
0x87, 0x8a, 0x89, 0x0b, 0x93, 0x88, 0x89, 0x89,
|
||||
0x08, 0xc8, 0x81, 0x91, 0x89, 0x0a, 0xc8, 0x88,
|
||||
0x92, 0x89, 0x09, 0x9a, 0x81, 0x8a, 0x89, 0x0b,
|
||||
0x9a, 0x88, 0x89, 0x89, 0x08, 0xd0, 0x81, 0x91,
|
||||
0x89, 0x0a, 0xd0, 0x88, 0x92, 0x89, 0x08, 0x02,
|
||||
0x81, 0x8a, 0x09, 0x5a, 0x81, 0x0a, 0x02, 0x88,
|
||||
0x89, 0x0b, 0x5a, 0x88, 0x08, 0xd9, 0x81, 0x89,
|
||||
0x89, 0x0a, 0xd9, 0x90, 0x89, 0x89, 0x09, 0x5e,
|
||||
0x82, 0x89, 0x89, 0x0b, 0x5e, 0x88, 0x89, 0x89,
|
||||
0x08, 0xe1, 0x81, 0x89, 0x89, 0x0a, 0xe1, 0x90,
|
||||
0x89, 0x89, 0x09, 0x64, 0x82, 0x89, 0x89, 0x0b,
|
||||
0x64, 0x88, 0x89, 0x09, 0x6e, 0x81, 0x08, 0xe9,
|
||||
0x81, 0x89, 0x0b, 0x6e, 0x88, 0x0a, 0xe9, 0x90,
|
||||
0x91, 0xb9, 0x09, 0x6f, 0x82, 0x8a, 0x8f, 0x0b,
|
||||
0x6f, 0x88, 0x91, 0x89, 0x08, 0xf0, 0x81, 0x8a,
|
||||
0x89, 0x0a, 0xf0, 0x90, 0x89, 0x89, 0x09, 0x76,
|
||||
0x82, 0x89, 0x91, 0x0b, 0x76, 0xb8, 0x91, 0x8a,
|
||||
0x08, 0xf8, 0x87, 0x92, 0x91, 0x0a, 0xf8, 0x88,
|
||||
0x8a, 0x92, 0x09, 0x7c, 0x81, 0x89, 0x8a, 0x0b,
|
||||
0x7c, 0x09, 0x01, 0x80, 0x89, 0x89, 0x0b, 0x01,
|
||||
0x88, 0x89, 0x91, 0x09, 0x7f, 0x81, 0x89, 0x92,
|
||||
0x0b, 0x7f, 0x09, 0x08, 0x80, 0x89, 0x92, 0x0b,
|
||||
0x08, 0x88, 0x89, 0x92, 0x0c, 0x07, 0x03, 0x03,
|
||||
0x07, 0x20, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2f, 0x00, 0x04, 0x00, 0x70, 0x00, 0x00, 0x00,
|
||||
0x29, 0x00, 0x04, 0x00, 0x70, 0x00, 0x00, 0x00,
|
||||
0x35, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* Device types that use line_update_type_1 */
|
||||
static const guint16 line_update_type1_devices[] = {
|
||||
0x00B5, 0x0885, 0x00B3, 0x143B, 0x1055,
|
||||
|
|
@ -1601,6 +1739,13 @@ validity_capture_prog_lookup (guint8 rom_major,
|
|||
* type-1 devices with 0x78 bytes/line geometry. */
|
||||
if (rom_major == 6)
|
||||
{
|
||||
/* Device-specific programs take priority */
|
||||
if (dev_type == 0x0199)
|
||||
{
|
||||
*out_len = sizeof (capture_prog_type1_0199);
|
||||
return capture_prog_type1_0199;
|
||||
}
|
||||
|
||||
for (gsize i = 0; i < G_N_ELEMENTS (line_update_type1_devices); i++)
|
||||
{
|
||||
if (line_update_type1_devices[i] == dev_type)
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@
|
|||
* Values match python-validity CaptureMode enum.
|
||||
* ================================================================ */
|
||||
typedef enum {
|
||||
VALIDITY_CAPTURE_CALIBRATE = 1,
|
||||
VALIDITY_CAPTURE_IDENTIFY = 2,
|
||||
VALIDITY_CAPTURE_ENROLL = 3,
|
||||
VALIDITY_CAPTURE_CALIBRATE = 1,
|
||||
VALIDITY_CAPTURE_IDENTIFY = 2,
|
||||
VALIDITY_CAPTURE_ENROLL = 3,
|
||||
VALIDITY_CAPTURE_ENROLL_IDENTIFY = 4, /* IDENTIFY chunk (0x4e) + ENROLL recon (0x23) */
|
||||
} ValidityCaptureMode;
|
||||
|
||||
/* ================================================================
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ validity_db_build_cmd_create_enrollment (gboolean start,
|
|||
}
|
||||
|
||||
/* cmd 0x68: Enrollment update start
|
||||
* Format: 0x68 | key(4LE) | 0(4LE) */
|
||||
* PY format: pack('<BLL', 0x68, key, 0) → 9 bytes: 0x68, key(4LE), 0(4LE) */
|
||||
guint8 *
|
||||
validity_db_build_cmd_enrollment_update_start (guint32 key,
|
||||
gsize *out_len)
|
||||
|
|
|
|||
|
|
@ -66,6 +66,30 @@ interrupt_cb (FpiUsbTransfer *transfer,
|
|||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
/* Scan-complete timeout — finger was likely stale (no fresh placement).
|
||||
* Stop capture, signal user to lift, then retry. */
|
||||
if (g_error_matches (error, G_USB_DEVICE_ERROR, G_USB_DEVICE_ERROR_TIMED_OUT) &&
|
||||
target_state == 3)
|
||||
{
|
||||
g_error_free (error);
|
||||
self->scan_incomplete_count++;
|
||||
fp_info ("Scan incomplete (attempt %u) — asking user to retry",
|
||||
self->scan_incomplete_count);
|
||||
if (self->scan_incomplete_count > 3)
|
||||
{
|
||||
fp_warn ("Too many scan retries, giving up");
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
|
||||
return;
|
||||
}
|
||||
fpi_device_enroll_progress (
|
||||
FP_DEVICE (self), self->enroll_stage, NULL,
|
||||
fpi_device_retry_new (FP_DEVICE_RETRY_REMOVE_FINGER));
|
||||
/* Skip get_prg_status (only valid after complete scan) — go
|
||||
* straight to capture_stop → LED off → delay → LED on → retry */
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_CAPTURE_STOP);
|
||||
return;
|
||||
}
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
return;
|
||||
}
|
||||
|
|
@ -79,12 +103,25 @@ interrupt_cb (FpiUsbTransfer *transfer,
|
|||
|
||||
int_type = transfer->buffer[0];
|
||||
|
||||
fp_dbg ("Interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->actual_length);
|
||||
if (transfer->actual_length >= 5)
|
||||
fp_dbg ("Interrupt: type=0x%02x bytes=[%02x %02x %02x %02x %02x] (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->buffer[0], transfer->buffer[1],
|
||||
transfer->buffer[2], transfer->buffer[3], transfer->buffer[4],
|
||||
transfer->actual_length);
|
||||
else
|
||||
fp_dbg ("Interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->actual_length);
|
||||
|
||||
/* Check if this is the interrupt we're waiting for */
|
||||
if (int_type == (guint8) target_state)
|
||||
{
|
||||
/* Finger-down detected */
|
||||
if (int_type == 2)
|
||||
{
|
||||
fp_info ("Finger detected on sensor");
|
||||
fpi_device_report_finger_status_changes (
|
||||
FP_DEVICE (self), FP_FINGER_STATUS_PRESENT, FP_FINGER_STATUS_NEEDED);
|
||||
}
|
||||
/* Check scan-complete bit if waiting for type 3 */
|
||||
if (int_type == 3 && transfer->actual_length >= 3)
|
||||
{
|
||||
|
|
@ -93,6 +130,8 @@ interrupt_cb (FpiUsbTransfer *transfer,
|
|||
/* Not scan complete yet, keep waiting */
|
||||
goto read_again;
|
||||
}
|
||||
/* Scan fully complete — reset retry counter */
|
||||
self->scan_incomplete_count = 0;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
|
|
@ -110,7 +149,9 @@ read_again:
|
|||
FpiUsbTransfer *new_transfer = fpi_usb_transfer_new (device);
|
||||
fpi_usb_transfer_fill_interrupt (new_transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (new_transfer, VALIDITY_USB_TIMEOUT,
|
||||
/* 30s timeout for scan_complete; unlimited for finger-down */
|
||||
fpi_usb_transfer_submit (new_transfer,
|
||||
(target_state == 3) ? 30000 : 0,
|
||||
self->interrupt_cancellable,
|
||||
interrupt_cb, ssm);
|
||||
}
|
||||
|
|
@ -130,11 +171,39 @@ start_interrupt_wait (FpiDeviceValidity *self,
|
|||
transfer = fpi_usb_transfer_new (FP_DEVICE (self));
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT,
|
||||
fpi_usb_transfer_submit (transfer, 0,
|
||||
self->interrupt_cancellable,
|
||||
interrupt_cb, ssm);
|
||||
}
|
||||
|
||||
/* Simple interrupt callback — accepts any interrupt and advances SSM.
|
||||
* Used between the two enrollment_update calls where PY does usb.wait_int(). */
|
||||
static void
|
||||
update_interrupt_cb (FpiUsbTransfer *transfer,
|
||||
FpDevice *device,
|
||||
gpointer user_data,
|
||||
GError *error)
|
||||
{
|
||||
FpiSsm *ssm = user_data;
|
||||
|
||||
if (error)
|
||||
{
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_REMOVED));
|
||||
else
|
||||
fpi_ssm_mark_failed (ssm, error);
|
||||
g_clear_error (&error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (transfer->actual_length >= 1)
|
||||
fp_dbg ("Update interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
||||
transfer->buffer[0], transfer->actual_length);
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Enrollment response parsing
|
||||
*
|
||||
|
|
@ -159,15 +228,30 @@ parse_enrollment_update_response (const guint8 *data,
|
|||
EnrollmentUpdateResult *result)
|
||||
{
|
||||
gsize pos = 0;
|
||||
guint16 declared_len;
|
||||
|
||||
memset (result, 0, sizeof (*result));
|
||||
|
||||
/* First 2 bytes are a length field (PY: l, = unpack('<H', res[:2])) */
|
||||
if (data_len < 2)
|
||||
return FALSE;
|
||||
|
||||
declared_len = FP_READ_UINT16_LE (data);
|
||||
pos = 2;
|
||||
|
||||
if (declared_len != data_len - 2)
|
||||
fp_warn ("enrollment_update: declared len %u != actual %zu",
|
||||
declared_len, data_len - 2);
|
||||
|
||||
while (pos + 4 <= data_len)
|
||||
{
|
||||
guint16 tag = FP_READ_UINT16_LE (&data[pos]);
|
||||
guint16 len = FP_READ_UINT16_LE (&data[pos + 2]);
|
||||
gsize block_size = ENROLLMENT_MAGIC_LEN + len;
|
||||
|
||||
fp_dbg ("enrollment_update: tag=%u len=%u block_size=%zu pos=%zu",
|
||||
tag, len, block_size, pos);
|
||||
|
||||
if (pos + block_size > data_len)
|
||||
break;
|
||||
|
||||
|
|
@ -214,16 +298,105 @@ enroll_run_state (FpiSsm *ssm,
|
|||
|
||||
switch (fpi_ssm_get_cur_state (ssm))
|
||||
{
|
||||
case ENROLL_CLEANUP_STALE:
|
||||
{
|
||||
/* Close any stale enrollment session before starting fresh.
|
||||
* Firmware may have leftover state from a previous session
|
||||
* (e.g. if enrollment was interrupted). Send cmd 0x69 flag=0
|
||||
* (enrollment_update_end) — errors are expected and ignored. */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_create_enrollment (FALSE, &cmd_len);
|
||||
self->enroll_key = 0;
|
||||
self->enroll_stage = 0;
|
||||
self->scan_incomplete_count = 0;
|
||||
g_clear_pointer (&self->enroll_template, g_free);
|
||||
self->enroll_template_len = 0;
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CLEANUP_STALE_RECV:
|
||||
/* Ignore status — no active session is fine (0x0405, etc.) */
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case ENROLL_PRE_GET_STORAGE:
|
||||
{
|
||||
/* Check for existing user records that would cause 0x0526 */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_get_user_storage (
|
||||
VALIDITY_STORAGE_NAME, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_PRE_GET_STORAGE_RECV:
|
||||
{
|
||||
validity_user_storage_clear (&self->list_storage);
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK ||
|
||||
!self->cmd_response_data ||
|
||||
!validity_db_parse_user_storage (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&self->list_storage))
|
||||
{
|
||||
/* No storage or parse error — skip cleanup, go to enrollment */
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_START);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->list_storage.user_count == 0)
|
||||
{
|
||||
fp_dbg ("No existing users — skipping pre-enrollment cleanup");
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_START);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Pre-enrollment cleanup: deleting %u existing user(s)",
|
||||
self->list_storage.user_count);
|
||||
self->list_user_idx = 0;
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_PRE_DEL_USER:
|
||||
{
|
||||
if (self->list_user_idx >= self->list_storage.user_count)
|
||||
{
|
||||
fp_info ("Pre-enrollment cleanup done");
|
||||
validity_user_storage_clear (&self->list_storage);
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_START);
|
||||
return;
|
||||
}
|
||||
|
||||
guint16 user_dbid = self->list_storage.user_dbids[self->list_user_idx];
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_del_record (user_dbid, &cmd_len);
|
||||
fp_info ("Deleting user record dbid=%u", user_dbid);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_PRE_DEL_USER_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
fp_warn ("Pre-enrollment del_record(dbid=%u) failed: status=0x%04x",
|
||||
self->list_storage.user_dbids[self->list_user_idx],
|
||||
self->cmd_response_status);
|
||||
|
||||
self->list_user_idx++;
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_PRE_DEL_USER);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_START:
|
||||
{
|
||||
/* cmd 0x69 flag=1: create enrollment session */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_create_enrollment (TRUE, &cmd_len);
|
||||
self->enroll_key = 0;
|
||||
self->enroll_stage = 0;
|
||||
g_clear_pointer (&self->enroll_template, g_free);
|
||||
self->enroll_template_len = 0;
|
||||
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
|
|
@ -252,7 +425,18 @@ enroll_run_state (FpiSsm *ssm,
|
|||
break;
|
||||
|
||||
case ENROLL_LED_ON_RECV:
|
||||
/* Glow start doesn't need status check (best effort) */
|
||||
/* LED is on — signal that we need a finger.
|
||||
* PY sends capture IMMEDIATELY after glow_start_scan(), no delay.
|
||||
* The ENROLL finger detect (chunk 0x26) needs to see the transition
|
||||
* from no-finger to finger-down to establish a proper baseline.
|
||||
* A delay would mean the finger is already on the sensor. */
|
||||
fpi_device_report_finger_status_changes (
|
||||
dev, FP_FINGER_STATUS_NEEDED, FP_FINGER_STATUS_NONE);
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case ENROLL_WAIT_FINGER_DELAY:
|
||||
/* Pass-through (no delay needed — capture waits for finger via interrupts) */
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
|
|
@ -303,8 +487,55 @@ enroll_run_state (FpiSsm *ssm,
|
|||
break;
|
||||
|
||||
case ENROLL_WAIT_SCAN_COMPLETE:
|
||||
/* Wait for interrupt type 3 with scan_complete bit */
|
||||
start_interrupt_wait (self, ssm, 3);
|
||||
{
|
||||
/* Wait for interrupt type 3 with scan_complete bit.
|
||||
* Use 30-second timeout: enroll mode scans need proper finger contact. */
|
||||
FpiUsbTransfer *transfer;
|
||||
fpi_ssm_set_data (ssm, GINT_TO_POINTER (3), NULL);
|
||||
transfer = fpi_usb_transfer_new (FP_DEVICE (self));
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, 30000,
|
||||
self->interrupt_cancellable,
|
||||
interrupt_cb, ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_GET_PRG_STATUS:
|
||||
{
|
||||
/* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */
|
||||
const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_GET_PRG_STATUS_RECV:
|
||||
/* Status doesn't matter, just advance */
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case ENROLL_CAPTURE_STOP:
|
||||
{
|
||||
/* cmd 0x04: capture stop/cleanup */
|
||||
const guint8 cmd[] = { 0x04 };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CAPTURE_STOP_RECV:
|
||||
/* PY: no glow_end after capture — LED stays on. */
|
||||
if (self->scan_incomplete_count > 0)
|
||||
{
|
||||
/* Incomplete scan: retry after a brief delay.
|
||||
* glow_start at the top of the loop will reinitialize.
|
||||
* PY: in the except block, just retries the whole loop. */
|
||||
fpi_ssm_jump_to_state_delayed (ssm, ENROLL_LED_ON, 3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Good scan — proceed to enrollment_update_start */
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_UPDATE_START:
|
||||
|
|
@ -337,9 +568,21 @@ enroll_run_state (FpiSsm *ssm,
|
|||
}
|
||||
break;
|
||||
|
||||
case ENROLL_WAIT_UPDATE_START_INT:
|
||||
{
|
||||
/* PY: usb.wait_int() inside enrollment_update_start() */
|
||||
FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev);
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, 0,
|
||||
self->interrupt_cancellable,
|
||||
update_interrupt_cb, ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE:
|
||||
{
|
||||
/* Send db_write_enable blob before enrollment_update */
|
||||
/* PY: write_enable() before 1st enrollment_update */
|
||||
gsize blob_len;
|
||||
const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL);
|
||||
|
|
@ -349,13 +592,8 @@ enroll_run_state (FpiSsm *ssm,
|
|||
case ENROLL_DB_WRITE_ENABLE_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("db_write_enable failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
return;
|
||||
}
|
||||
fp_warn ("db_write_enable (1st) failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
|
@ -373,54 +611,26 @@ enroll_run_state (FpiSsm *ssm,
|
|||
|
||||
case ENROLL_APPEND_IMAGE_RECV:
|
||||
{
|
||||
/* First enrollment_update call — just triggers firmware processing.
|
||||
* Response is status=OK with len=0; no data to parse here.
|
||||
* The actual result comes from the second call after the interrupt. */
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("enrollment_update failed: status=0x%04x",
|
||||
fp_warn ("enrollment_update (trigger) non-OK: status=0x%04x — skip to update_end",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
/* Don't fail — firmware may be rejecting this iteration.
|
||||
* Skip remaining enrollment_update states and go to UPDATE_END,
|
||||
* which will proceed to LOOP_CHECK and retry or exit. */
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the enrollment update response */
|
||||
if (self->cmd_response_data && self->cmd_response_len > 0)
|
||||
{
|
||||
EnrollmentUpdateResult result;
|
||||
|
||||
if (parse_enrollment_update_response (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&result))
|
||||
{
|
||||
/* Update template for next iteration */
|
||||
g_clear_pointer (&self->enroll_template, g_free);
|
||||
if (result.template_data)
|
||||
{
|
||||
self->enroll_template = g_steal_pointer (&result.template_data);
|
||||
self->enroll_template_len = result.template_len;
|
||||
}
|
||||
|
||||
/* If tid is present, enrollment is complete */
|
||||
if (result.tid)
|
||||
{
|
||||
/* Store tid for finger creation */
|
||||
/* tid stays in enroll_template context — we'll
|
||||
* build finger data in the commit phase */
|
||||
g_clear_pointer (&self->bulk_data, g_free);
|
||||
self->bulk_data = g_steal_pointer (&result.tid);
|
||||
self->bulk_data_len = result.tid_len;
|
||||
}
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CLEANUPS:
|
||||
{
|
||||
/* cmd 0x1a: call_cleanups after db_write_enable + enrollment_update */
|
||||
/* PY: call_cleanups() in finally block of enrollment_update (1st) */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
|
|
@ -433,17 +643,137 @@ enroll_run_state (FpiSsm *ssm,
|
|||
/* Status 0x0491 = nothing to commit, which is OK */
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK &&
|
||||
self->cmd_response_status != 0x0491)
|
||||
fp_warn ("call_cleanups (1st) failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_WAIT_UPDATE_INT:
|
||||
{
|
||||
/* PY: usb.wait_int() — wait for firmware to finish processing
|
||||
* the enrollment image before reading the result. */
|
||||
FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev);
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, 0,
|
||||
self->interrupt_cancellable,
|
||||
update_interrupt_cb, ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE_READ:
|
||||
{
|
||||
/* PY: write_enable() before 2nd enrollment_update */
|
||||
gsize blob_len;
|
||||
const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE_READ_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
fp_warn ("db_write_enable (2nd) failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_APPEND_IMAGE_READ:
|
||||
{
|
||||
/* Second cmd 0x6B: enrollment_update — reads the actual result
|
||||
* with template/header/tid data. Same payload as the first call. */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_enrollment_update (
|
||||
self->enroll_template, self->enroll_template_len, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_APPEND_IMAGE_READ_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("call_cleanups failed: status=0x%04x",
|
||||
fp_warn ("enrollment_update (read) failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the enrollment update response for template/header/tid */
|
||||
if (self->cmd_response_data && self->cmd_response_len > 0)
|
||||
{
|
||||
EnrollmentUpdateResult result;
|
||||
|
||||
fp_info ("enrollment_update read response: len=%zu",
|
||||
self->cmd_response_len);
|
||||
|
||||
if (parse_enrollment_update_response (self->cmd_response_data,
|
||||
self->cmd_response_len,
|
||||
&result))
|
||||
{
|
||||
/* Update template for next iteration */
|
||||
g_clear_pointer (&self->enroll_template, g_free);
|
||||
if (result.template_data)
|
||||
{
|
||||
self->enroll_template = g_steal_pointer (&result.template_data);
|
||||
self->enroll_template_len = result.template_len;
|
||||
fp_info (" template: %zu bytes", self->enroll_template_len);
|
||||
}
|
||||
|
||||
if (result.header)
|
||||
fp_info (" header: %zu bytes", result.header_len);
|
||||
|
||||
/* If tid is present, enrollment is complete */
|
||||
if (result.tid)
|
||||
{
|
||||
fp_info (" tid: %zu bytes — enrollment complete!",
|
||||
result.tid_len);
|
||||
g_clear_pointer (&self->bulk_data, g_free);
|
||||
self->bulk_data = g_steal_pointer (&result.tid);
|
||||
self->bulk_data_len = result.tid_len;
|
||||
}
|
||||
|
||||
enrollment_update_result_clear (&result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_info ("enrollment_update read response: EMPTY (len=0)");
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CLEANUPS_READ:
|
||||
{
|
||||
/* PY: call_cleanups() in finally block of enrollment_update (2nd) */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CLEANUPS_READ_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK &&
|
||||
self->cmd_response_status != 0x0491)
|
||||
fp_warn ("call_cleanups (2nd) failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_UPDATE_END:
|
||||
{
|
||||
/* cmd 0x69 flag=0: enrollment_update_end */
|
||||
/* PY: enrollment_update_end() → pack('<BL', 0x69, 0)
|
||||
* Same as create_enrollment(FALSE) — signals end of this iteration's
|
||||
* update cycle. Called in PY's finally block for each iteration. */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_create_enrollment (FALSE, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
|
|
@ -459,24 +789,28 @@ enroll_run_state (FpiSsm *ssm,
|
|||
{
|
||||
self->enroll_stage++;
|
||||
|
||||
/* Report progress */
|
||||
fpi_device_enroll_progress (dev, self->enroll_stage, NULL, NULL);
|
||||
/* Report progress (capped at nr_enroll_stages for the UI) */
|
||||
if (self->enroll_stage <= VALIDITY_ENROLL_STAGES)
|
||||
fpi_device_enroll_progress (dev, self->enroll_stage, NULL, NULL);
|
||||
|
||||
fp_info ("Enrollment stage %u/%u", self->enroll_stage,
|
||||
VALIDITY_ENROLL_STAGES);
|
||||
|
||||
/* If we have a TID, enrollment is complete — go to DB commit */
|
||||
/* If we have a TID, enrollment is complete.
|
||||
* PY calls enrollment_update_end twice: once in the finally
|
||||
* block (ENROLL_UPDATE_END) and once more after the loop. */
|
||||
if (self->bulk_data && self->bulk_data_len > 0)
|
||||
{
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_DB_WRITE_ENABLE2);
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_UPDATE_END2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we reached max stages without TID, that's an error */
|
||||
if (self->enroll_stage >= VALIDITY_ENROLL_STAGES)
|
||||
/* PY loops indefinitely until TID appears. Use a generous
|
||||
* upper bound to avoid an infinite loop on broken firmware. */
|
||||
if (self->enroll_stage >= 30)
|
||||
{
|
||||
fp_warn ("Enrollment did not complete within %u stages",
|
||||
VALIDITY_ENROLL_STAGES);
|
||||
self->enroll_stage);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_GENERAL));
|
||||
return;
|
||||
|
|
@ -487,6 +821,133 @@ enroll_run_state (FpiSsm *ssm,
|
|||
}
|
||||
break;
|
||||
|
||||
case ENROLL_UPDATE_END2:
|
||||
{
|
||||
/* PY: second enrollment_update_end() after the loop.
|
||||
* Same command: pack('<BL', 0x69, 0) */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_create_enrollment (FALSE, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_UPDATE_END2_RECV:
|
||||
{
|
||||
/* Ignore status — PY doesn't check it for the second call either */
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_GET_STORAGE:
|
||||
{
|
||||
/* Discover the StgWindsor storage dbid — PY uses
|
||||
* db.get_user_storage(name='StgWindsor').dbid for all record ops. */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_get_user_storage (
|
||||
VALIDITY_STORAGE_NAME, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_GET_STORAGE_RECV:
|
||||
{
|
||||
/* PY: parse_user_storage returns None on 0x04b3 (not found).
|
||||
* init_db then creates the storage. We handle both cases. */
|
||||
if (self->cmd_response_status == 0x04b3)
|
||||
{
|
||||
fp_info ("StgWindsor storage not found — creating it");
|
||||
fpi_ssm_next_state (ssm); /* → ENROLL_INIT_STORAGE_WE */
|
||||
return;
|
||||
}
|
||||
|
||||
ValidityUserStorage stg = { 0 };
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK ||
|
||||
!self->cmd_response_data ||
|
||||
!validity_db_parse_user_storage (self->cmd_response_data,
|
||||
self->cmd_response_len, &stg))
|
||||
{
|
||||
fp_warn ("get_user_storage failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
return;
|
||||
}
|
||||
|
||||
self->enroll_storage_dbid = stg.dbid;
|
||||
fp_info ("Storage dbid: %u", stg.dbid);
|
||||
validity_user_storage_clear (&stg);
|
||||
/* Skip storage creation states — jump to DB_WRITE_ENABLE2 */
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_DB_WRITE_ENABLE2);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_WE:
|
||||
{
|
||||
/* PY: db.new_user_storate() → new_record(1, 4, 3, 'StgWindsor\0')
|
||||
* First: db_write_enable */
|
||||
gsize blob_len;
|
||||
const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_WE_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
fp_warn ("db_write_enable (init_storage) failed: 0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_CREATE:
|
||||
{
|
||||
/* PY: db.new_record(1, 4, 3, b'StgWindsor\0')
|
||||
* parent=1 (root), type=4 (storage), storage=3, data=name */
|
||||
const gchar *name = VALIDITY_STORAGE_NAME;
|
||||
gsize name_len = strlen (name) + 1; /* include NUL */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_new_record (
|
||||
1, 4, 3, (const guint8 *) name, name_len, &cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_CREATE_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("create storage failed: status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
return;
|
||||
}
|
||||
fp_info ("StgWindsor storage created successfully");
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_CLEAN:
|
||||
{
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_INIT_STORAGE_CLEAN_RECV:
|
||||
{
|
||||
/* Now retry get_user_storage to get the dbid */
|
||||
fpi_ssm_jump_to_state (ssm, ENROLL_GET_STORAGE);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE2:
|
||||
{
|
||||
/* Enable DB writes for storing the finger record */
|
||||
|
|
@ -549,9 +1010,9 @@ enroll_run_state (FpiSsm *ssm,
|
|||
/* cmd 0x47: new_record(parent=storage_dbid, type=5=user, storage=storage_dbid, data=identity) */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_new_record (
|
||||
3, /* root storage dbid (standard for StgWindsor) */
|
||||
self->enroll_storage_dbid,
|
||||
VALIDITY_DB_RECORD_TYPE_USER,
|
||||
3, /* storage */
|
||||
self->enroll_storage_dbid,
|
||||
identity, identity_len,
|
||||
&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
|
|
@ -592,6 +1053,43 @@ enroll_run_state (FpiSsm *ssm,
|
|||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CREATE_USER_CLEANUPS:
|
||||
{
|
||||
/* PY: new_record always calls call_cleanups in finally block */
|
||||
gsize cmd_len;
|
||||
guint8 *cmd = validity_db_build_cmd_call_cleanups (&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
g_free (cmd);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CREATE_USER_CLEANUPS_RECV:
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE3:
|
||||
{
|
||||
/* PY: new_record calls db_write_enable before each cmd 0x47 */
|
||||
gsize blob_len;
|
||||
const guint8 *blob = validity_db_get_write_enable_blob (self->dev_type, &blob_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, blob, blob_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_DB_WRITE_ENABLE3_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("db_write_enable3 failed: 0x%04x",
|
||||
self->cmd_response_status);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
|
||||
return;
|
||||
}
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_CREATE_FINGER:
|
||||
{
|
||||
FpPrint *print = NULL;
|
||||
|
|
@ -617,7 +1115,7 @@ enroll_run_state (FpiSsm *ssm,
|
|||
guint8 *cmd = validity_db_build_cmd_new_record (
|
||||
user_dbid,
|
||||
0x0b, /* finger type: becomes 0x06 after db_write_enable */
|
||||
3, /* storage */
|
||||
self->enroll_storage_dbid,
|
||||
finger_data, finger_data_len,
|
||||
&cmd_len);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
|
|
@ -661,6 +1159,18 @@ enroll_run_state (FpiSsm *ssm,
|
|||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case ENROLL_WAIT_FINGER_INT:
|
||||
{
|
||||
/* PY: usb.wait_int() after new_finger/cleanups — accept any interrupt */
|
||||
FpiUsbTransfer *transfer = fpi_usb_transfer_new (dev);
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, 5000,
|
||||
self->interrupt_cancellable,
|
||||
update_interrupt_cb, ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case ENROLL_LED_OFF:
|
||||
{
|
||||
gsize cmd_len;
|
||||
|
|
@ -706,16 +1216,27 @@ enroll_ssm_done (FpiSsm *ssm,
|
|||
fpi_print_set_type (print, FPI_PRINT_RAW);
|
||||
fpi_print_set_device_stored (print, TRUE);
|
||||
|
||||
/* Store the user ID as driver data for later verify/identify */
|
||||
/* Store the user ID as driver data for later verify/identify.
|
||||
* The RAW data GVariant is required for serialization. */
|
||||
GVariant *user_id_var = g_object_get_data (G_OBJECT (print),
|
||||
"validity-user-id");
|
||||
if (user_id_var)
|
||||
{
|
||||
const gchar *uid = g_variant_get_string (user_id_var, NULL);
|
||||
GVariant *data = g_variant_new_string (uid);
|
||||
g_object_set (print, "fpi-data", data, NULL);
|
||||
|
||||
GDate *date = g_date_new ();
|
||||
g_date_set_time_t (date, time (NULL));
|
||||
fp_print_set_enroll_date (print, date);
|
||||
g_date_free (date);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Fallback: store an empty marker */
|
||||
GVariant *data = g_variant_new_string ("");
|
||||
g_object_set (print, "fpi-data", data, NULL);
|
||||
}
|
||||
|
||||
g_clear_pointer (&self->enroll_template, g_free);
|
||||
self->enroll_template_len = 0;
|
||||
|
|
|
|||
|
|
@ -349,10 +349,13 @@ validity_fwext_get_db_write_enable (guint16 vid,
|
|||
*
|
||||
* This SSM is started as a standalone child from the open sequence
|
||||
* when fwext_loaded == FALSE. It uses the subsm pattern:
|
||||
* SEND states call vcsfw_cmd_send(self, ssm, ..., NULL) with a
|
||||
* SEND states call vcsfw_tls_cmd_send(self, ssm, ..., NULL) with a
|
||||
* NULL callback. The child SSM auto-advances the parent to the
|
||||
* RECV state on completion. RECV states read
|
||||
* self->cmd_response_status and self->cmd_response_data.
|
||||
*
|
||||
* All commands are sent via TLS because the sensor requires
|
||||
* encrypted writes to flash partition 2.
|
||||
* ================================================================ */
|
||||
|
||||
/* SSM data for the upload state machine */
|
||||
|
|
@ -390,7 +393,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
validity_fwext_build_write_hw_reg32 (FWEXT_HW_REG_WRITE_ADDR,
|
||||
FWEXT_HW_REG_WRITE_VALUE,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -413,7 +416,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
|
||||
validity_fwext_build_read_hw_reg32 (FWEXT_HW_REG_READ_ADDR,
|
||||
cmd, &cmd_len);
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -501,7 +504,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
return;
|
||||
}
|
||||
|
||||
vcsfw_cmd_send (self, ssm, dbe, dbe_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, dbe, dbe_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -539,7 +542,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
|
||||
ud->write_offset += chunk_size;
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -560,7 +563,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_CLEANUP };
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -602,7 +605,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
fp_info ("FWEXT: Writing firmware signature (%d bytes)",
|
||||
FWEXT_SIGNATURE_SIZE);
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -623,7 +626,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
{
|
||||
guint8 cmd[] = { VCSFW_CMD_GET_FW_INFO, FWEXT_PARTITION };
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -658,7 +661,7 @@ validity_fwext_upload_run_state (FpiSsm *ssm,
|
|||
|
||||
fp_info ("FWEXT: Rebooting sensor to activate new firmware");
|
||||
|
||||
vcsfw_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, cmd_len, NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -663,14 +663,102 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
|
||||
if (ps->num_partitions > 0)
|
||||
{
|
||||
fp_info ("Flash has %u partitions — pairing not needed",
|
||||
fp_info ("Flash has %u partitions — verifying TLS keys",
|
||||
ps->num_partitions);
|
||||
fpi_ssm_jump_to_state (ssm, PAIR_DONE);
|
||||
/* Read flash partition 1 to check if TLS keys exist.
|
||||
* If they do, pairing is complete. If not, we re-pair. */
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("Flash has 0 partitions — device needs pairing");
|
||||
|
||||
/* Look up device descriptor */
|
||||
ps->dev_desc = validity_hal_device_lookup (self->dev_type);
|
||||
if (!ps->dev_desc)
|
||||
{
|
||||
fp_warn ("No HAL descriptor for dev_type=%u", self->dev_type);
|
||||
fpi_ssm_mark_failed (ssm,
|
||||
fpi_device_error_new (FP_DEVICE_ERROR_NOT_SUPPORTED));
|
||||
return;
|
||||
}
|
||||
|
||||
/* No partitions — skip TLS verify, go straight to pairing */
|
||||
fpi_ssm_jump_to_state (ssm, PAIR_SEND_RESET_BLOB);
|
||||
}
|
||||
break;
|
||||
|
||||
case PAIR_VERIFY_TLS_SEND:
|
||||
{
|
||||
/* Read flash partition 1 (TLS cert store) to verify keys exist */
|
||||
guint8 cmd[13];
|
||||
cmd[0] = VCSFW_CMD_READ_FLASH;
|
||||
cmd[1] = 0x01; /* partition */
|
||||
cmd[2] = 0x01; /* access flag */
|
||||
FP_WRITE_UINT16_LE (&cmd[3], 0x0000);
|
||||
FP_WRITE_UINT32_LE (&cmd[5], 0x0000);
|
||||
FP_WRITE_UINT32_LE (&cmd[9], 0x1000);
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case PAIR_VERIFY_TLS_RECV:
|
||||
{
|
||||
/* Check if TLS flash has valid key data */
|
||||
gboolean have_keys = FALSE;
|
||||
|
||||
if (self->cmd_response_status == VCSFW_STATUS_OK &&
|
||||
self->cmd_response_data && self->cmd_response_len > 6)
|
||||
{
|
||||
guint32 flash_sz = FP_READ_UINT32_LE (self->cmd_response_data);
|
||||
const guint8 *flash_data = self->cmd_response_data + 6;
|
||||
gsize flash_avail = self->cmd_response_len - 6;
|
||||
|
||||
if (flash_sz > flash_avail)
|
||||
flash_sz = flash_avail;
|
||||
|
||||
/* Quick check: scan for block IDs 3 (cert), 4 (privkey), 6 (ecdh) */
|
||||
const guint8 *pos = flash_data;
|
||||
gsize remaining = flash_sz;
|
||||
gboolean found_priv = FALSE, found_ecdh = FALSE, found_cert = FALSE;
|
||||
|
||||
while (remaining >= 36) /* header(4) + hash(32) */
|
||||
{
|
||||
guint16 block_id = FP_READ_UINT16_LE (pos);
|
||||
guint16 block_size = FP_READ_UINT16_LE (pos + 2);
|
||||
|
||||
if (block_id == 0xFFFF)
|
||||
break;
|
||||
|
||||
pos += 36; /* skip header + hash */
|
||||
remaining -= 36;
|
||||
|
||||
if (block_size > remaining)
|
||||
break;
|
||||
|
||||
if (block_id == 4)
|
||||
found_priv = TRUE;
|
||||
if (block_id == 6)
|
||||
found_ecdh = TRUE;
|
||||
if (block_id == 3)
|
||||
found_cert = TRUE;
|
||||
|
||||
pos += block_size;
|
||||
remaining -= block_size;
|
||||
}
|
||||
|
||||
have_keys = found_priv && found_ecdh && found_cert;
|
||||
}
|
||||
|
||||
if (have_keys)
|
||||
{
|
||||
fp_info ("TLS keys verified on flash — pairing not needed");
|
||||
fpi_ssm_jump_to_state (ssm, PAIR_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fp_info ("TLS keys missing from flash — starting pairing");
|
||||
|
||||
/* Look up device descriptor */
|
||||
ps->dev_desc = validity_hal_device_lookup (self->dev_type);
|
||||
if (!ps->dev_desc)
|
||||
|
|
@ -788,6 +876,16 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
|
||||
case PAIR_PARTITION_FLASH_RECV:
|
||||
{
|
||||
if (self->cmd_response_status == 0x0404)
|
||||
{
|
||||
/* 0x0404 = partitions already exist (half-initialized device).
|
||||
* Factory reset will wipe flash, then reboot. Next device open
|
||||
* will start with a clean slate and full pairing will succeed. */
|
||||
fp_info ("Flash already partitioned (0x0404) — factory reset needed");
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
{
|
||||
fp_warn ("partition_flash failed: status=0x%04x",
|
||||
|
|
@ -810,7 +908,32 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
}
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
/* Skip factory reset states — go straight to CMD50 */
|
||||
fpi_ssm_jump_to_state (ssm, PAIR_CMD50_SEND);
|
||||
}
|
||||
break;
|
||||
|
||||
case PAIR_FACTORY_RESET_SEND:
|
||||
{
|
||||
/* CMD 0x10 + 0x61 zero bytes: wipes flash partition table.
|
||||
* python-validity: usb.cmd(b'\x10' + b'\0' * 0x61) */
|
||||
guint8 cmd[98];
|
||||
memset (cmd, 0, sizeof (cmd));
|
||||
cmd[0] = 0x10;
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case PAIR_FACTORY_RESET_RECV:
|
||||
{
|
||||
if (self->cmd_response_status != VCSFW_STATUS_OK)
|
||||
fp_warn ("Factory reset cmd 0x10 status=0x%04x",
|
||||
self->cmd_response_status);
|
||||
else
|
||||
fp_info ("Factory reset complete — rebooting sensor");
|
||||
|
||||
/* Reboot; next device open will pair from clean state */
|
||||
fpi_ssm_jump_to_state (ssm, PAIR_REBOOT_SEND);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -963,6 +1086,12 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
self->tls.tls_cert_len = ps->server_cert_len;
|
||||
}
|
||||
|
||||
/* Set priv_key — the TLS handshake needs the actual EC private key
|
||||
* (EVP_PKEY*) to sign cert_verify. We have it as ps->client_key. */
|
||||
if (self->tls.priv_key)
|
||||
EVP_PKEY_free (self->tls.priv_key);
|
||||
self->tls.priv_key = EVP_PKEY_dup (ps->client_key);
|
||||
|
||||
OPENSSL_cleanse (priv_le, sizeof (priv_le));
|
||||
OPENSSL_cleanse (priv_be, sizeof (priv_be));
|
||||
|
||||
|
|
@ -997,8 +1126,10 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
|
||||
case PAIR_TLS_HANDSHAKE:
|
||||
{
|
||||
/* Establish TLS session — python-validity: tls.open() */
|
||||
self->open_ssm = ssm; /* for handshake callback */
|
||||
/* Establish TLS session — python-validity: tls.open()
|
||||
* Uses subsm: tls_ssm completion/failure propagates to pair SSM.
|
||||
* NOTE: do NOT overwrite self->open_ssm here — it must remain
|
||||
* pointing to the open SSM for pair_ssm_done to work. */
|
||||
FpiSsm *tls_ssm = fpi_ssm_new (dev,
|
||||
validity_tls_handshake_run_state,
|
||||
TLS_HS_NUM_STATES);
|
||||
|
|
@ -1156,9 +1287,11 @@ validity_pair_run_state (FpiSsm *ssm,
|
|||
|
||||
case PAIR_REBOOT_SEND:
|
||||
{
|
||||
/* Reboot: 0x05 0x02 0x00 (python-validity: tls.cmd(unhex('050200'))) */
|
||||
/* Reboot: 0x05 0x02 0x00 (python-validity: tls.cmd(unhex('050200')))
|
||||
* Use raw USB — TLS may not be established (factory reset path). */
|
||||
guint8 cmd[] = { VCSFW_CMD_REBOOT, 0x02, 0x00 };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
ps->reboot_pending = TRUE;
|
||||
vcsfw_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ typedef struct
|
|||
|
||||
/* Flash erase progress counter */
|
||||
guint erase_step;
|
||||
|
||||
/* Set TRUE when reboot command has been sent (normal pairing or factory reset) */
|
||||
gboolean reboot_pending;
|
||||
} ValidityPairState;
|
||||
|
||||
/* Partition entry serialized format: 12 bytes data + 4 zero + 32 SHA-256 = 48 */
|
||||
|
|
@ -239,11 +242,15 @@ typedef enum {
|
|||
PAIR_GET_FLASH_INFO = 0,
|
||||
PAIR_GET_FLASH_INFO_RECV,
|
||||
PAIR_CHECK_NEEDED,
|
||||
PAIR_VERIFY_TLS_SEND,
|
||||
PAIR_VERIFY_TLS_RECV,
|
||||
PAIR_SEND_RESET_BLOB,
|
||||
PAIR_SEND_RESET_BLOB_RECV,
|
||||
PAIR_GENERATE_KEYS,
|
||||
PAIR_PARTITION_FLASH_SEND,
|
||||
PAIR_PARTITION_FLASH_RECV,
|
||||
PAIR_FACTORY_RESET_SEND,
|
||||
PAIR_FACTORY_RESET_RECV,
|
||||
PAIR_CMD50_SEND,
|
||||
PAIR_CMD50_RECV,
|
||||
PAIR_CMD50_PROCESS,
|
||||
|
|
|
|||
|
|
@ -162,6 +162,157 @@ static const ValidityDeviceInfo device_info_table[] = {
|
|||
{ 0x004a, 0x00b5, 0x13, 0xff, "SYN 57K0 FM3297-02" },
|
||||
{ 0x004a, 0x00b5, 0x14, 0xff, "SYN 57K0 FM3297-03" },
|
||||
|
||||
/* major=0x0190: post-firmware-update major (06cb:009a etc.) */
|
||||
{ 0x0190, 0x2449, 0x01, 0xff, "86C FM-3290-002" },
|
||||
{ 0x0190, 0x2449, 0x02, 0xff, "86C FM-3324-001" },
|
||||
{ 0x0190, 0x057b, 0x03, 0xff, "88B0 FM-3316-001" },
|
||||
{ 0x0190, 0x1ff5, 0x04, 0xff, "57K0 FM-3328-001" },
|
||||
{ 0x0190, 0x00b5, 0x05, 0xff, "57K0 FM-3297-004" },
|
||||
{ 0x0190, 0x0c6d, 0x06, 0xff, "57K0 FM-3297-005" },
|
||||
{ 0x0190, 0x00b5, 0x07, 0xff, "57K0 FM-3297-006" },
|
||||
{ 0x0190, 0x00b5, 0x08, 0xff, "57K0 FM-3297-007" },
|
||||
{ 0x0190, 0x00b5, 0x09, 0xff, "57K0 FM-3297-008" },
|
||||
{ 0x0190, 0x00b5, 0x0a, 0xff, "57K0 FM-3297-009" },
|
||||
{ 0x0190, 0x00b5, 0x0b, 0xff, "57K0 FM-3297-010" },
|
||||
{ 0x0190, 0x00b5, 0x0c, 0xff, "57K0 FM-3297-011" },
|
||||
{ 0x0190, 0x057b, 0x0d, 0xff, "88B0 FM-3300-001" },
|
||||
{ 0x0190, 0x04c3, 0x0e, 0xff, "55E FM3327-FM3342" },
|
||||
{ 0x0190, 0x0191, 0x0f, 0xff, "57L0 FM-3331-001" },
|
||||
{ 0x0190, 0x0191, 0x10, 0xff, "57L0 FM-3331-002" },
|
||||
{ 0x0190, 0x0191, 0x11, 0xff, "57L0 FM-3331-003" },
|
||||
{ 0x0190, 0x0580, 0x12, 0xff, "88B0 FM-3310-001" },
|
||||
{ 0x0190, 0x0580, 0x13, 0xff, "88B0 FM-3310-002" },
|
||||
{ 0x0190, 0x0191, 0x14, 0xff, "57L0 FM-151-003" },
|
||||
{ 0x0190, 0x0191, 0x15, 0xff, "57L0 FM-211-002" },
|
||||
{ 0x0190, 0x0191, 0x16, 0xff, "57L0 FM-3299-002" },
|
||||
{ 0x0190, 0x0d49, 0x17, 0xff, "57L0 FM-3331-004" },
|
||||
{ 0x0190, 0x1131, 0x18, 0xff, "57L0 FM-3331-005" },
|
||||
{ 0x0190, 0x0197, 0x19, 0xff, "73A0 FM-3332-001" },
|
||||
{ 0x0190, 0x0195, 0x1a, 0xff, "86D TM3329-001-003" },
|
||||
{ 0x0190, 0x0195, 0x1b, 0xff, "86D TM3329-002-006" },
|
||||
{ 0x0190, 0x0196, 0x1c, 0xff, "57K0 FM-155-003" },
|
||||
{ 0x0190, 0x2449, 0x1d, 0xff, "86C TM-3315-001" },
|
||||
{ 0x0190, 0x2449, 0x1e, 0xff, "86C TM-3315-002" },
|
||||
{ 0x0190, 0x2449, 0x1f, 0xff, "86C TM-3322-001" },
|
||||
{ 0x0190, 0x2449, 0x20, 0xff, "86C FM-3326-001" },
|
||||
{ 0x0190, 0x2449, 0x21, 0xff, "86C FM-3208-002" },
|
||||
{ 0x0190, 0x2449, 0x22, 0xff, "86C FM-3340-001" },
|
||||
{ 0x0190, 0x0196, 0x23, 0xff, "57K0 FM-155-004" },
|
||||
{ 0x0190, 0x00b5, 0x24, 0xff, "57K0 FM-3297-012" },
|
||||
{ 0x0190, 0x00b5, 0x25, 0xff, "57K0 FM-3297-013" },
|
||||
{ 0x0190, 0x0197, 0x26, 0xff, "73A0 FM-3341-001" },
|
||||
{ 0x0190, 0x2449, 0x28, 0xff, "86C TM-3315-003" },
|
||||
{ 0x0190, 0x00b5, 0x29, 0xff, "57K0 FM-3297-020" },
|
||||
{ 0x0190, 0x00b5, 0x2a, 0xff, "57K0 FM-3297-021" },
|
||||
{ 0x0190, 0x00b5, 0x2b, 0xff, "57K0 FM-3297-022" },
|
||||
{ 0x0190, 0x00b5, 0x2c, 0xff, "57K0 FM-3297-023" },
|
||||
{ 0x0190, 0x00b5, 0x2d, 0xff, "57K0 FM-3297-024" },
|
||||
{ 0x0190, 0x00b5, 0x2e, 0xff, "57K0 FM-3297-025" },
|
||||
{ 0x0190, 0x00b5, 0x2f, 0xff, "57K0 FM-3297-026" },
|
||||
{ 0x0190, 0x00b5, 0x30, 0xff, "57K0 FM-3297-027" },
|
||||
{ 0x0190, 0x00b5, 0x31, 0xff, "57K0 FM-3297-028" },
|
||||
{ 0x0190, 0x00b5, 0x32, 0xff, "57K0 FM-3297-029" },
|
||||
{ 0x0190, 0x00b5, 0x33, 0xff, "57K0 FM-3297-030" },
|
||||
{ 0x0190, 0x00b5, 0x34, 0xff, "57K0 FM-3297-031" },
|
||||
{ 0x0190, 0x00b5, 0x35, 0xff, "57K0 FM-3297-014" },
|
||||
{ 0x0190, 0x00b5, 0x36, 0xff, "57K0 FM-3297-015" },
|
||||
{ 0x0190, 0x00b5, 0x37, 0xff, "57K0 FM-3297-032" },
|
||||
{ 0x0190, 0x00b5, 0x38, 0xff, "57K0 FM-3297-033" },
|
||||
{ 0x0190, 0x057b, 0x39, 0xff, "88B0 FM-3300-002" },
|
||||
{ 0x0190, 0x00de, 0x3a, 0xff, "109A FM-3302-001" },
|
||||
{ 0x0190, 0x057e, 0x3b, 0xff, "57K0 FM-154-020" },
|
||||
{ 0x0190, 0x0581, 0x3c, 0xff, "57K0 FM-154-021" },
|
||||
{ 0x0190, 0x2449, 0x3d, 0xff, "86C TM-3226-001" },
|
||||
{ 0x0190, 0x0195, 0x3e, 0xff, "86D TM3329-004-007" },
|
||||
{ 0x0190, 0x0196, 0x3f, 0xff, "57K0 FM-155-002" },
|
||||
{ 0x0190, 0x1825, 0x41, 0xff, "57K0 FM-154-001" },
|
||||
{ 0x0190, 0x0581, 0x42, 0xff, "57K0 FM-154-022" },
|
||||
{ 0x0190, 0x057b, 0x43, 0xff, "88B0 FM3358-3359" },
|
||||
{ 0x0190, 0x057b, 0x44, 0xff, "88B0 FM-3358-002" },
|
||||
{ 0x0190, 0x057b, 0x45, 0xff, "88B0 FM-3359-001" },
|
||||
{ 0x0190, 0x00b5, 0x46, 0xff, "57K0 FM-3297-100" },
|
||||
{ 0x0190, 0x057e, 0x47, 0xff, "57K0 FM-154-200" },
|
||||
{ 0x0190, 0x0198, 0x49, 0xff, "88B0 FM-3366-001" },
|
||||
{ 0x0190, 0x0199, 0x4a, 0xff, "57K0 FM-3367-001" },
|
||||
{ 0x0190, 0x2449, 0x4b, 0xff, "86C TM-3368-001" },
|
||||
{ 0x0190, 0x00db, 0x4c, 0xff, "55E FM-209-005" },
|
||||
{ 0x0190, 0x0969, 0x4f, 0xff, "57K0 FM-154-023" },
|
||||
{ 0x0190, 0x0580, 0x50, 0xff, "88B0 FM-3373-001" },
|
||||
{ 0x0190, 0x00db, 0x51, 0xff, "55E FM-209-006" },
|
||||
{ 0x0190, 0x0581, 0x52, 0xff, "57K0 FM-154-001" },
|
||||
{ 0x0190, 0x0581, 0x53, 0xff, "57K0 FM-154-002" },
|
||||
{ 0x0190, 0x0581, 0x54, 0xff, "57K0 FM-154-003" },
|
||||
{ 0x0190, 0x0d51, 0x55, 0xff, "57K0 FM-154-020" },
|
||||
{ 0x0190, 0x0581, 0x56, 0xff, "57K0 FM-155-001" },
|
||||
{ 0x0190, 0x0199, 0x57, 0xff, "57K0 FM-155-002" },
|
||||
{ 0x0190, 0x0195, 0x58, 0xff, "86D TM-3329-005" },
|
||||
{ 0x0190, 0x0199, 0x59, 0xff, "57K0 FM-3367-002" },
|
||||
{ 0x0190, 0x0199, 0x5a, 0xff, "57K0 FM-3367-003" },
|
||||
{ 0x0190, 0x0199, 0x5b, 0xff, "57K0 FM-3367-004" },
|
||||
{ 0x0190, 0x00db, 0x5c, 0xff, "55E FM-160-004" },
|
||||
{ 0x0190, 0x0968, 0x5d, 0xff, "88B0 FM-3366-002" },
|
||||
{ 0x0190, 0x2449, 0x5e, 0xff, "86C TM-P3376-P3404" },
|
||||
{ 0x0190, 0x00b5, 0x5f, 0xff, "57K0 FM-3380-001" },
|
||||
{ 0x0190, 0x0199, 0x60, 0xff, "57K0 FM-3380-002" },
|
||||
{ 0x0190, 0x0199, 0x61, 0xff, "57K0 FM-3380-003" },
|
||||
{ 0x0190, 0x0199, 0x62, 0xff, "57K0 FM-3380-004" },
|
||||
{ 0x0190, 0x2449, 0x63, 0xff, "86C FM-3290-003" },
|
||||
{ 0x0190, 0x057b, 0x64, 0xff, "88B0 FM-3358-003" },
|
||||
{ 0x0190, 0x2449, 0x65, 0xff, "86C FM-3389-001" },
|
||||
{ 0x0190, 0x0199, 0x68, 0xff, "57K0 FM-3367-005" },
|
||||
{ 0x0190, 0x0199, 0x69, 0xff, "57K0 FM-3367-006" },
|
||||
{ 0x0190, 0x0199, 0x6a, 0xff, "57K0 FM-3380-001b" },
|
||||
{ 0x0190, 0x0191, 0x6b, 0xff, "57L0 FM-3396-001" },
|
||||
{ 0x0190, 0x0191, 0x6c, 0xff, "57L0 FM-3397-001" },
|
||||
{ 0x0190, 0x2449, 0x6e, 0xff, "86C TM3261-003-004" },
|
||||
{ 0x0190, 0x0581, 0x6f, 0xff, "57K0 FM-3395-001" },
|
||||
{ 0x0190, 0x0d51, 0x70, 0xff, "57K0 FM-154-120" },
|
||||
{ 0x0190, 0x0969, 0x71, 0xff, "57K0 FM-154-123" },
|
||||
{ 0x0190, 0x00b5, 0x72, 0xff, "57K0 FM-3401-001" },
|
||||
{ 0x0190, 0x00b5, 0x73, 0xff, "57K0 FM-3401-004" },
|
||||
{ 0x0190, 0x00b5, 0x74, 0xff, "57K0 FM-3401-005" },
|
||||
{ 0x0190, 0x00b5, 0x75, 0xff, "57K0 FM-3401-006" },
|
||||
{ 0x0190, 0x0199, 0x76, 0xff, "57K0 FM-155-005" },
|
||||
{ 0x0190, 0x0c6d, 0x79, 0xff, "57K0 FM-3297-034" },
|
||||
{ 0x0190, 0x00b5, 0x7a, 0xff, "57K0 FM-3297-035" },
|
||||
{ 0x0190, 0x057b, 0x7b, 0xff, "88B0 FM-3358-004" },
|
||||
{ 0x0190, 0x057b, 0x7c, 0xff, "88B0 FM-3358-005" },
|
||||
{ 0x0190, 0x0199, 0x7e, 0xff, "57K0 FM-155-007" },
|
||||
{ 0x0190, 0x0199, 0x82, 0xff, "57K0 FM-155-102" },
|
||||
{ 0x0190, 0x0d51, 0x83, 0xff, "57K0 FM-3439-001" },
|
||||
{ 0x0190, 0x2449, 0x84, 0xff, "86C FM-3324-002" },
|
||||
{ 0x0190, 0x0969, 0x85, 0xff, "57K0 FM-3439-002" },
|
||||
{ 0x0190, 0x2449, 0x86, 0xff, "86C FM-3324-003" },
|
||||
{ 0x0190, 0x0969, 0x87, 0xff, "57K0 FM-3439-003" },
|
||||
{ 0x0190, 0x0969, 0x88, 0xff, "57K0 FM-3439-004" },
|
||||
{ 0x0190, 0x0199, 0x89, 0xff, "57K0 FM-155-008" },
|
||||
{ 0x0190, 0x0581, 0x8a, 0xff, "57K0 FM-154-124" },
|
||||
{ 0x0190, 0x057b, 0x8b, 0xff, "88B0 FM-3358-007" },
|
||||
{ 0x0190, 0x0581, 0x8c, 0xff, "57K0 FM-3439-005" },
|
||||
{ 0x0190, 0x0969, 0x8d, 0xff, "57K0 FM-3439-006" },
|
||||
{ 0x0190, 0x0199, 0x8e, 0xff, "57K0 FM-155-103" },
|
||||
{ 0x0190, 0x0581, 0x8f, 0xff, "57K0 FM-154-125" },
|
||||
{ 0x0190, 0x0581, 0x90, 0xff, "57K0 FM-3439-007" },
|
||||
{ 0x0190, 0x0969, 0x91, 0xff, "57K0 FM-3439-008" },
|
||||
{ 0x0190, 0x0969, 0x92, 0xff, "57K0 FM-3439-009" },
|
||||
{ 0x0190, 0x0969, 0x93, 0xff, "57K0 FM-3439-010" },
|
||||
{ 0x0190, 0x0969, 0x94, 0xff, "57K0 FM-3439-011" },
|
||||
{ 0x0190, 0x0969, 0x95, 0xff, "57K0 FM-3439-108" },
|
||||
{ 0x0190, 0x0969, 0x96, 0xff, "57K0 FM-3439-109" },
|
||||
{ 0x0190, 0x0969, 0x97, 0xff, "57K0 FM-3439-110" },
|
||||
{ 0x0190, 0x057b, 0x98, 0xff, "88B0 FM-3358-008" },
|
||||
{ 0x0190, 0x057b, 0x99, 0xff, "88B0 FM-3358-009" },
|
||||
{ 0x0190, 0x0d51, 0x9a, 0xff, "57K0 FM-3439-101" },
|
||||
{ 0x0190, 0x0969, 0x9b, 0xff, "57K0 FM-3439-102" },
|
||||
{ 0x0190, 0x0969, 0x9c, 0xff, "57K0 FM-3439-012" },
|
||||
{ 0x0190, 0x1139, 0x9d, 0xff, "57K0 FM-3439-013" },
|
||||
{ 0x0190, 0x0969, 0x9e, 0xff, "57K0 FM-3439-014" },
|
||||
{ 0x0190, 0x0c6d, 0x9f, 0xff, "57K0 FM-3297-036" },
|
||||
{ 0x0190, 0x057b, 0xa0, 0xff, "88B0 FM-3358-010" },
|
||||
{ 0x0190, 0x057b, 0xa1, 0xff, "88B0 FM-3316-002" },
|
||||
{ 0x0190, 0x2449, 0xa2, 0xff, "86C TM-P3568-001" },
|
||||
{ 0x0190, 0x2449, 0xa3, 0xff, "86C TM-P3569-001" },
|
||||
|
||||
/* major=0x0071: VSI 55E (type 0xdb) */
|
||||
{ 0x0071, 0x00db, 0x01, 0xff, "VSI 55E FM72-001" },
|
||||
{ 0x0071, 0x00db, 0x02, 0xff, "VSI 55E FM72-002" },
|
||||
|
|
|
|||
|
|
@ -62,9 +62,9 @@ static const guint8 fw_pubkey_x[32] = {
|
|||
|
||||
static const guint8 fw_pubkey_y[32] = {
|
||||
0x94, 0xca, 0xa6, 0x21, 0x47, 0xa8, 0x61, 0xf7,
|
||||
0x8d, 0x94, 0x93, 0x23, 0x8b, 0x58, 0x3c, 0x24,
|
||||
0x86, 0xa8, 0x07, 0x4d, 0xf4, 0xd5, 0x8b, 0xef,
|
||||
0x6e, 0x0d, 0xc5, 0xbe, 0xb6, 0xf8, 0x38, 0xa8
|
||||
0x8d, 0x94, 0x93, 0x23, 0x8b, 0xc5, 0x43, 0x62,
|
||||
0x88, 0x7a, 0xd0, 0xf4, 0xd5, 0x8b, 0xef, 0x6e,
|
||||
0x0d, 0xc5, 0xbe, 0xb6, 0xf8, 0x38, 0x55, 0xa8
|
||||
};
|
||||
|
||||
/* ================================================================
|
||||
|
|
@ -701,16 +701,37 @@ handle_priv_block (ValidityTlsState *tls,
|
|||
for (gsize i = 0; i < TLS_EC_COORD_SIZE; i++)
|
||||
d_be[i] = d_le[TLS_EC_COORD_SIZE - 1 - i];
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("TLS priv d(BE): ");
|
||||
for (gsize i = 0; i < TLS_EC_COORD_SIZE; i++)
|
||||
g_string_append_printf (hex, "%02x", d_be[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
BIGNUM *d_bn = BN_bin2bn (d_be, TLS_EC_COORD_SIZE, NULL);
|
||||
|
||||
/* Derive public key Q = d * G on P-256 */
|
||||
EC_GROUP *group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
|
||||
EC_POINT *pub_pt = EC_POINT_new (group);
|
||||
EC_POINT_mul (group, pub_pt, d_bn, NULL, NULL, NULL);
|
||||
|
||||
guint8 pub_uncompressed[1 + 2 * TLS_EC_COORD_SIZE]; /* 0x04 || x || y */
|
||||
size_t pt_len = EC_POINT_point2oct (group, pub_pt,
|
||||
POINT_CONVERSION_UNCOMPRESSED,
|
||||
pub_uncompressed,
|
||||
sizeof (pub_uncompressed), NULL);
|
||||
EC_POINT_free (pub_pt);
|
||||
EC_GROUP_free (group);
|
||||
|
||||
EVP_PKEY *pkey = NULL;
|
||||
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new ();
|
||||
OSSL_PARAM_BLD_push_utf8_string (bld, OSSL_PKEY_PARAM_GROUP_NAME,
|
||||
"prime256v1", 0);
|
||||
OSSL_PARAM_BLD_push_BN (bld, OSSL_PKEY_PARAM_PRIV_KEY, d_bn);
|
||||
OSSL_PARAM_BLD_push_octet_string (bld, OSSL_PKEY_PARAM_PUB_KEY,
|
||||
pub_uncompressed, pt_len);
|
||||
|
||||
/* We need to derive the public key from d. Use EVP_PKEY_fromdata with
|
||||
* just the private key — OpenSSL 3.x can derive the public key. */
|
||||
OSSL_PARAM *params = OSSL_PARAM_BLD_to_param (bld);
|
||||
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL);
|
||||
EVP_PKEY_fromdata_init (pctx);
|
||||
|
|
@ -805,7 +826,7 @@ handle_ecdh_block (ValidityTlsState *tls,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
/* Note: fw_pubkey_x/y are already big-endian in our constants */
|
||||
/* Note: fw_pubkey_x/y are stored as little-endian, like ECDH blob coords */
|
||||
|
||||
/* Verify: fwpub.verify(signature, key_blob, ECDSA(SHA256)) */
|
||||
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new ();
|
||||
|
|
@ -964,6 +985,17 @@ hs_append_msg (GByteArray *buf, GChecksum *hash,
|
|||
/* Update handshake hash */
|
||||
g_checksum_update (hash, hdr, 4);
|
||||
g_checksum_update (hash, body, body_len);
|
||||
|
||||
{
|
||||
static const char *names[] = {
|
||||
[0x01] = "ClientHello", [0x02] = "ServerHello",
|
||||
[0x0b] = "Certificate", [0x0d] = "CertRequest",
|
||||
[0x0e] = "ServerHelloDone", [0x10] = "ClientKEX",
|
||||
[0x0f] = "CertVerify", [0x14] = "Finished"
|
||||
};
|
||||
const char *n = (type < 0x15 && names[type]) ? names[type] : "unknown";
|
||||
fp_dbg ("hs_hash UPDATE %s (type=0x%02x, %zu bytes fed)", n, type, 4 + body_len);
|
||||
}
|
||||
}
|
||||
|
||||
/* Build ClientHello */
|
||||
|
|
@ -1002,9 +1034,9 @@ validity_tls_build_client_hello (ValidityTlsState *tls, gsize *out_len)
|
|||
};
|
||||
g_byte_array_append (hello, suites, sizeof (suites));
|
||||
|
||||
/* compression (none) */
|
||||
guint8 comp[] = { 0x01, 0x00 };
|
||||
g_byte_array_append (hello, comp, 2);
|
||||
/* compression (none — python-validity sends length=0, no methods) */
|
||||
guint8 comp[] = { 0x00 };
|
||||
g_byte_array_append (hello, comp, 1);
|
||||
|
||||
/* extensions */
|
||||
guint8 ext_truncated_hmac[] = {
|
||||
|
|
@ -1100,6 +1132,19 @@ validity_tls_parse_server_hello (ValidityTlsState *tls,
|
|||
/* Update handshake hash */
|
||||
g_checksum_update (tls->handshake_hash, hs_pos, 4 + hs_len);
|
||||
|
||||
{
|
||||
static const char *names[] = {
|
||||
[0x01] = "ClientHello", [0x02] = "ServerHello",
|
||||
[0x0b] = "Certificate", [0x0d] = "CertRequest",
|
||||
[0x0e] = "ServerHelloDone", [0x10] = "ClientKEX",
|
||||
[0x0f] = "CertVerify", [0x14] = "Finished"
|
||||
};
|
||||
const char *n = (hs_type < 0x15 && names[hs_type]) ? names[hs_type] : "unknown";
|
||||
fp_dbg ("hs_hash UPDATE(srv) %s (type=0x%02x, %u bytes fed, first4: %02x%02x%02x%02x)",
|
||||
n, hs_type, (unsigned) (4 + hs_len),
|
||||
hs_pos[0], hs_pos[1], hs_pos[2], hs_pos[3]);
|
||||
}
|
||||
|
||||
switch (hs_type)
|
||||
{
|
||||
case TLS_HS_SERVER_HELLO:
|
||||
|
|
@ -1216,8 +1261,35 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
EVP_PKEY_derive (derive_ctx, NULL, &pms_len);
|
||||
EVP_PKEY_derive (derive_ctx, pre_master_secret, &pms_len);
|
||||
EVP_PKEY_CTX_free (derive_ctx);
|
||||
{
|
||||
GString *hex = g_string_new ("TLS pms: ");
|
||||
for (gsize i = 0; i < pms_len; i++)
|
||||
g_string_append_printf (hex, "%02x", pre_master_secret[i]);
|
||||
g_string_append_printf (hex, " (len=%zu)", pms_len);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
/* ---- Derive master_secret and key_block ---- */
|
||||
{
|
||||
GString *hex = g_string_new ("TLS server_random: ");
|
||||
for (gsize i = 0; i < TLS_RANDOM_SIZE; i++)
|
||||
g_string_append_printf (hex, "%02x", tls->server_random[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
{
|
||||
GChecksum *hc = g_checksum_copy (tls->handshake_hash);
|
||||
guint8 hd[32]; gsize hl = 32;
|
||||
g_checksum_get_digest (hc, hd, &hl);
|
||||
g_checksum_free (hc);
|
||||
GString *hex = g_string_new ("TLS hash after srv: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hd[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
guint8 seed[2 * TLS_RANDOM_SIZE];
|
||||
memcpy (seed, tls->client_random, TLS_RANDOM_SIZE);
|
||||
memcpy (seed + TLS_RANDOM_SIZE, tls->server_random, TLS_RANDOM_SIZE);
|
||||
|
|
@ -1243,6 +1315,17 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
memcpy (tls->encryption_key, key_block + 0x40, TLS_AES_KEY_SIZE);
|
||||
memcpy (tls->decryption_key, key_block + 0x60, TLS_AES_KEY_SIZE);
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("TLS sign_key: ");
|
||||
for (gsize i = 0; i < 8; i++)
|
||||
g_string_append_printf (hex, "%02x", tls->sign_key[i]);
|
||||
g_string_append_printf (hex, "... enc_key: ");
|
||||
for (gsize i = 0; i < 8; i++)
|
||||
g_string_append_printf (hex, "%02x", tls->encryption_key[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
OPENSSL_cleanse (pre_master_secret, sizeof (pre_master_secret));
|
||||
OPENSSL_cleanse (key_block, sizeof (key_block));
|
||||
|
||||
|
|
@ -1251,6 +1334,16 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
|
||||
/* 1. Certificate (type 0x0B) */
|
||||
{
|
||||
fp_dbg ("TLS cert_len=%zu, cert first 20 bytes:", tls->tls_cert_len);
|
||||
{
|
||||
GString *hex = g_string_new (" cert: ");
|
||||
gsize dump_len = MIN (tls->tls_cert_len, 40);
|
||||
for (gsize i = 0; i < dump_len; i++)
|
||||
g_string_append_printf (hex, "%02x", tls->tls_cert[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
GByteArray *cert_body = g_byte_array_new ();
|
||||
guint8 cert_prefix[] = { 0xac, 0x16 };
|
||||
g_byte_array_append (cert_body, cert_prefix, 2);
|
||||
|
|
@ -1274,6 +1367,17 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
hs_append_msg (hs_msgs, tls->handshake_hash,
|
||||
TLS_HS_CERTIFICATE, wrapped2->data, wrapped2->len);
|
||||
g_byte_array_free (wrapped2, TRUE);
|
||||
{
|
||||
GChecksum *hc = g_checksum_copy (tls->handshake_hash);
|
||||
guint8 hd[32]; gsize hl = 32;
|
||||
g_checksum_get_digest (hc, hd, &hl);
|
||||
g_checksum_free (hc);
|
||||
GString *hex = g_string_new ("TLS hash after cert: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hd[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/* 2. ClientKeyExchange (type 0x10) */
|
||||
|
|
@ -1281,19 +1385,34 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
guint8 pubpoint[65]; /* 0x04 + 32 + 32 */
|
||||
get_ec_pubpoint_bytes (tls->session_key, pubpoint, sizeof (pubpoint));
|
||||
|
||||
/* python-validity sends: 0x04 || x_le || y_le
|
||||
* OpenSSL gives us: 0x04 || x_be || y_be
|
||||
* We need to reverse each coordinate to little-endian */
|
||||
guint8 kex_body[65];
|
||||
kex_body[0] = 0x04;
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
{
|
||||
kex_body[1 + i] = pubpoint[32 - i]; /* x: reverse BE to LE */
|
||||
kex_body[33 + i] = pubpoint[64 - i]; /* y: reverse BE to LE */
|
||||
}
|
||||
/* python-validity sends: 0x04 || to_bytes(x)[::-1] || to_bytes(y)[::-1]
|
||||
* to_bytes() returns LE, [::-1] converts back to BE.
|
||||
* So python-validity sends BE coordinates.
|
||||
* OpenSSL gives us: 0x04 || x_be || y_be — already correct! */
|
||||
guint8 *kex_body = pubpoint; /* Use as-is, it's already BE */
|
||||
gsize kex_body_len = 65;
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("TLS kex_body: ");
|
||||
for (gsize i = 0; i < 65; i++)
|
||||
g_string_append_printf (hex, "%02x", kex_body[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
hs_append_msg (hs_msgs, tls->handshake_hash,
|
||||
TLS_HS_CLIENT_KEY_EXCHANGE, kex_body, sizeof (kex_body));
|
||||
TLS_HS_CLIENT_KEY_EXCHANGE, kex_body, kex_body_len);
|
||||
{
|
||||
GChecksum *hc = g_checksum_copy (tls->handshake_hash);
|
||||
guint8 hd[32]; gsize hl = 32;
|
||||
g_checksum_get_digest (hc, hd, &hl);
|
||||
g_checksum_free (hc);
|
||||
GString *hex = g_string_new ("TLS hash after kex: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hd[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. CertificateVerify (type 0x0F) */
|
||||
|
|
@ -1305,12 +1424,15 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
g_checksum_get_digest (hash_copy, hs_hash, &hash_len);
|
||||
g_checksum_free (hash_copy);
|
||||
|
||||
/* ECDSA sign with preshared hash (Prehashed) */
|
||||
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new ();
|
||||
EVP_PKEY_CTX *sign_pctx = NULL;
|
||||
EVP_DigestSignInit (md_ctx, &sign_pctx, NULL, NULL, tls->priv_key);
|
||||
{
|
||||
GString *hex = g_string_new ("TLS hs_hash for CertVerify: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hs_hash[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
/* We're signing a pre-hashed value, so use raw ECDSA */
|
||||
/* ECDSA sign pre-hashed value */
|
||||
size_t sig_len = 0;
|
||||
EVP_PKEY_CTX *raw_ctx = EVP_PKEY_CTX_new (tls->priv_key, NULL);
|
||||
EVP_PKEY_sign_init (raw_ctx);
|
||||
|
|
@ -1319,7 +1441,15 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
EVP_PKEY_sign (raw_ctx, signature, &sig_len, hs_hash, 32);
|
||||
EVP_PKEY_CTX_free (raw_ctx);
|
||||
|
||||
EVP_MD_CTX_free (md_ctx);
|
||||
/* Self-verify the CertVerify signature */
|
||||
{
|
||||
EVP_PKEY_CTX *vfy_ctx = EVP_PKEY_CTX_new (tls->priv_key, NULL);
|
||||
EVP_PKEY_verify_init (vfy_ctx);
|
||||
int vrc = EVP_PKEY_verify (vfy_ctx, signature, sig_len, hs_hash, 32);
|
||||
fp_dbg ("TLS CertVerify self-verify: %s (rc=%d, sig_len=%zu)",
|
||||
vrc == 1 ? "OK" : "FAILED", vrc, sig_len);
|
||||
EVP_PKEY_CTX_free (vfy_ctx);
|
||||
}
|
||||
|
||||
hs_append_msg (hs_msgs, tls->handshake_hash,
|
||||
TLS_HS_CERT_VERIFY, signature, sig_len);
|
||||
|
|
@ -1362,6 +1492,17 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
vd_seed, vd_seed_len,
|
||||
verify_data, TLS_VERIFY_DATA_SIZE);
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("TLS Finished hs_hash: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hs_hash[i]);
|
||||
g_string_append_printf (hex, " verify_data: ");
|
||||
for (gsize i = 0; i < TLS_VERIFY_DATA_SIZE; i++)
|
||||
g_string_append_printf (hex, "%02x", verify_data[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
/* Build Finished handshake message: type(1) || 3-byte-len || verify_data */
|
||||
guint8 fin_msg[4 + TLS_VERIFY_DATA_SIZE];
|
||||
fin_msg[0] = TLS_HS_FINISHED;
|
||||
|
|
@ -1370,8 +1511,10 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
fin_msg[3] = TLS_VERIFY_DATA_SIZE;
|
||||
memcpy (fin_msg + 4, verify_data, TLS_VERIFY_DATA_SIZE);
|
||||
|
||||
/* Update handshake hash with the Finished message we're sending */
|
||||
g_checksum_update (tls->handshake_hash, fin_msg, sizeof (fin_msg));
|
||||
/* NOTE: Do NOT update handshake hash with client Finished.
|
||||
* python-validity's make_finish() doesn't call update_neg(), and the
|
||||
* device's server Finished verify_data is computed WITHOUT including
|
||||
* the client Finished in the hash (non-standard TLS behavior). */
|
||||
|
||||
/* Encrypt Finished as handshake record */
|
||||
gsize signed_len = sizeof (fin_msg) + TLS_HMAC_SIZE;
|
||||
|
|
@ -1397,6 +1540,16 @@ validity_tls_build_client_finish (ValidityTlsState *tls, gsize *out_len)
|
|||
g_free (encrypted);
|
||||
|
||||
*out_len = output->len;
|
||||
|
||||
/* Debug: hex dump the full client finish */
|
||||
{
|
||||
GString *hex = g_string_new ("TLS_CF:");
|
||||
for (gsize i = 0; i < output->len; i++)
|
||||
g_string_append_printf (hex, "%02x", output->data[i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
return g_byte_array_free (output, FALSE);
|
||||
}
|
||||
|
||||
|
|
@ -1515,6 +1668,20 @@ validity_tls_parse_server_finish (ValidityTlsState *tls,
|
|||
sf_seed, sf_seed_len,
|
||||
expected_vd, TLS_VERIFY_DATA_SIZE);
|
||||
|
||||
{
|
||||
GString *hex = g_string_new ("TLS ServerFinished hs_hash: ");
|
||||
for (gsize i = 0; i < 32; i++)
|
||||
g_string_append_printf (hex, "%02x", hs_hash[i]);
|
||||
g_string_append_printf (hex, " expected_vd: ");
|
||||
for (gsize i = 0; i < TLS_VERIFY_DATA_SIZE; i++)
|
||||
g_string_append_printf (hex, "%02x", expected_vd[i]);
|
||||
g_string_append_printf (hex, " received_vd: ");
|
||||
for (gsize i = 0; i < TLS_VERIFY_DATA_SIZE; i++)
|
||||
g_string_append_printf (hex, "%02x", decrypted[4 + i]);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
|
||||
if (memcmp (decrypted + 4, expected_vd, TLS_VERIFY_DATA_SIZE) != 0)
|
||||
{
|
||||
g_free (decrypted);
|
||||
|
|
@ -1626,6 +1793,19 @@ tls_raw_recv_cb (FpiUsbTransfer *transfer,
|
|||
self->cmd_response_data = NULL;
|
||||
|
||||
fp_dbg ("TLS recv: %zu bytes", self->cmd_response_len);
|
||||
|
||||
/* Hex dump first bytes for debugging */
|
||||
if (self->cmd_response_data && self->cmd_response_len > 0)
|
||||
{
|
||||
gsize dump_len = self->cmd_response_len;
|
||||
g_autofree gchar *hex = g_malloc (dump_len * 3 + 1);
|
||||
for (gsize i = 0; i < dump_len; i++)
|
||||
g_snprintf (hex + i * 3, 4, "%02x ",
|
||||
self->cmd_response_data[i]);
|
||||
hex[dump_len * 3] = '\0';
|
||||
fp_dbg ("TLS recv hex: %s", hex);
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (transfer->ssm);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,13 +85,19 @@ verify_interrupt_cb (FpiUsbTransfer *transfer,
|
|||
|
||||
int_type = transfer->buffer[0];
|
||||
|
||||
fp_dbg ("Verify interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->actual_length);
|
||||
if (transfer->actual_length >= 5)
|
||||
fp_dbg ("Verify interrupt: type=0x%02x bytes=[%02x %02x %02x %02x %02x] (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->buffer[0], transfer->buffer[1],
|
||||
transfer->buffer[2], transfer->buffer[3], transfer->buffer[4],
|
||||
transfer->actual_length);
|
||||
else
|
||||
fp_dbg ("Verify interrupt: type=0x%02x (len=%" G_GSSIZE_FORMAT ")",
|
||||
int_type, transfer->actual_length);
|
||||
|
||||
/* During match wait, type 3 = result available */
|
||||
/* During match wait, type 3 = match found, type 5 = no match */
|
||||
if (fpi_ssm_get_cur_state (ssm) == VERIFY_WAIT_MATCH_INT)
|
||||
{
|
||||
if (int_type == 3)
|
||||
if (int_type == 3 || int_type == 5)
|
||||
{
|
||||
fpi_ssm_next_state (ssm);
|
||||
return;
|
||||
|
|
@ -135,7 +141,7 @@ read_again:
|
|||
FpiUsbTransfer *new_transfer = fpi_usb_transfer_new (device);
|
||||
fpi_usb_transfer_fill_interrupt (new_transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (new_transfer, VALIDITY_USB_TIMEOUT,
|
||||
fpi_usb_transfer_submit (new_transfer, 0,
|
||||
self->interrupt_cancellable,
|
||||
verify_interrupt_cb, ssm);
|
||||
}
|
||||
|
|
@ -150,7 +156,10 @@ verify_start_interrupt_wait (FpiDeviceValidity *self,
|
|||
transfer = fpi_usb_transfer_new (FP_DEVICE (self));
|
||||
fpi_usb_transfer_fill_interrupt (transfer, VALIDITY_EP_INT_IN,
|
||||
VALIDITY_USB_INT_DATA_SIZE);
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT,
|
||||
/* Use no timeout (0) for finger-wait and scan-complete states,
|
||||
* since these wait for physical user interaction.
|
||||
* The interrupt_cancellable handles cancellation. */
|
||||
fpi_usb_transfer_submit (transfer, 0,
|
||||
self->interrupt_cancellable,
|
||||
verify_interrupt_cb, ssm);
|
||||
}
|
||||
|
|
@ -370,6 +379,32 @@ verify_run_state (FpiSsm *ssm,
|
|||
verify_start_interrupt_wait (self, ssm);
|
||||
break;
|
||||
|
||||
case VERIFY_GET_PRG_STATUS:
|
||||
{
|
||||
/* cmd 0x51: get_prg_status2 (after scan complete, before capture stop) */
|
||||
const guint8 cmd[] = { 0x51, 0x00, 0x20, 0x00, 0x00 };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case VERIFY_GET_PRG_STATUS_RECV:
|
||||
/* Status doesn't matter, just advance */
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case VERIFY_CAPTURE_STOP:
|
||||
{
|
||||
/* cmd 0x04: capture stop/cleanup */
|
||||
const guint8 cmd[] = { 0x04 };
|
||||
vcsfw_tls_cmd_send (self, ssm, cmd, sizeof (cmd), NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case VERIFY_CAPTURE_STOP_RECV:
|
||||
/* Cleanup status doesn't matter */
|
||||
fpi_ssm_next_state (ssm);
|
||||
break;
|
||||
|
||||
case VERIFY_MATCH_START:
|
||||
{
|
||||
/* cmd 0x5E: match_finger */
|
||||
|
|
|
|||
|
|
@ -226,6 +226,16 @@ tls_cmd_receive_cb (FpiUsbTransfer *transfer,
|
|||
}
|
||||
|
||||
/* Decrypt TLS app data response */
|
||||
{
|
||||
GString *hex = g_string_new ("VCSFW TLS raw response: ");
|
||||
for (gsize i = 0; i < MIN ((gsize) transfer->actual_length, (gsize) 40); i++)
|
||||
g_string_append_printf (hex, "%02x ", transfer->buffer[i]);
|
||||
if (transfer->actual_length > 40)
|
||||
g_string_append_printf (hex, "... (%" G_GSSIZE_FORMAT " total)",
|
||||
transfer->actual_length);
|
||||
fp_dbg ("%s", hex->str);
|
||||
g_string_free (hex, TRUE);
|
||||
}
|
||||
gsize decrypted_len;
|
||||
guint8 *decrypted = validity_tls_unwrap_response (
|
||||
&self->tls,
|
||||
|
|
@ -304,8 +314,9 @@ vcsfw_tls_cmd_run_state (FpiSsm *ssm,
|
|||
cmd_data->cmd_len,
|
||||
&wrapped_len);
|
||||
|
||||
/* Build USB payload: 0x44000000 prefix + TLS record */
|
||||
gsize usb_len = 4 + wrapped_len;
|
||||
/* Build USB payload: TLS record directly (no 0x44000000 prefix
|
||||
* for post-handshake app data, per python-validity) */
|
||||
gsize usb_len = wrapped_len;
|
||||
|
||||
fp_dbg ("VCSFW TLS send cmd 0x%02x, plaintext=%" G_GSIZE_FORMAT
|
||||
", wire=%" G_GSIZE_FORMAT,
|
||||
|
|
@ -314,11 +325,7 @@ vcsfw_tls_cmd_run_state (FpiSsm *ssm,
|
|||
transfer = fpi_usb_transfer_new (dev);
|
||||
transfer->short_is_error = TRUE;
|
||||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_OUT, usb_len);
|
||||
transfer->buffer[0] = 0x44;
|
||||
transfer->buffer[1] = 0x00;
|
||||
transfer->buffer[2] = 0x00;
|
||||
transfer->buffer[3] = 0x00;
|
||||
memcpy (transfer->buffer + 4, wrapped, wrapped_len);
|
||||
memcpy (transfer->buffer, wrapped, wrapped_len);
|
||||
g_free (wrapped);
|
||||
|
||||
transfer->ssm = ssm;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue