mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-11 09:38:09 +02:00
validity: Add TLS session management (Iteration 2)
Implement the TLS handshake and encrypted channel for VCSFW sensors:
- validity_tls.c/h: TLS PRF (P_SHA256), AES-256-CBC encrypt/decrypt,
PSK derivation from DMI (machine binding), flash partition parsing
(cert/privkey/ECDH blocks with SHA-256 integrity), ClientHello/
ServerHello builders, full TLS handshake state machine
- validity.c: Integrate TLS into open sequence — check fwext status,
read flash partition 1, perform TLS handshake when keys available,
graceful skip when fwext not loaded
- validity.h: Add ValidityTlsState, fwext_loaded flag, TLS fields
- OpenSSL dependency for ECDH, AES-256-CBC, HMAC-SHA256
Tests (18 total in test-validity-tls):
- 13 unit tests: init/free, ClientHello format, PRF determinism/
length/short, encrypt roundtrip/alignment, decrypt invalid,
PSK derivation/determinism, flash parse empty/truncated,
unwrap invalid
- 5 regression tests for bugs found during hardware testing:
- flash parse ordering (PSK must precede parse)
- READ_FLASH command format (13-byte layout)
- flash response 6-byte header unwrap
- ServerHello expects raw TLS (no VCSFW prefix)
- ClientHello TLS record prefix (0x44000000)
- Hardware integration test script (test_tls_hardware.py)
All 33 project tests pass (0 fail, 2 skipped).
This commit is contained in:
parent
29d6fea0de
commit
67b9c18696
9 changed files with 3254 additions and 5 deletions
|
|
@ -23,6 +23,7 @@
|
|||
#include "drivers_api.h"
|
||||
#include "fpi-byte-reader.h"
|
||||
#include "validity.h"
|
||||
#include "validity_tls.h"
|
||||
#include "vcsfw_protocol.h"
|
||||
|
||||
G_DEFINE_TYPE (FpiDeviceValidity, fpi_device_validity, FP_TYPE_DEVICE)
|
||||
|
|
@ -172,6 +173,9 @@ typedef enum {
|
|||
OPEN_RECV_CMD19,
|
||||
OPEN_SEND_GET_FW_INFO,
|
||||
OPEN_RECV_GET_FW_INFO,
|
||||
OPEN_TLS_READ_FLASH,
|
||||
OPEN_TLS_DERIVE_PSK,
|
||||
OPEN_TLS_HANDSHAKE,
|
||||
OPEN_DONE,
|
||||
OPEN_NUM_STATES,
|
||||
} ValidityOpenSsmState;
|
||||
|
|
@ -182,6 +186,83 @@ typedef struct
|
|||
gboolean fwext_loaded;
|
||||
} OpenSsmData;
|
||||
|
||||
/* Callback for GET_FW_INFO response — check if fwext is loaded */
|
||||
static void
|
||||
fw_info_recv_cb (FpiUsbTransfer *transfer,
|
||||
FpDevice *device,
|
||||
gpointer user_data,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (device);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fpi_ssm_mark_failed (transfer->ssm, error);
|
||||
return;
|
||||
}
|
||||
|
||||
/* GET_FW_INFO response: first 2 bytes are status.
|
||||
* 0x0000 = fwext loaded, 0xb004 = no fwext, others = error */
|
||||
if (transfer->actual_length >= 2)
|
||||
{
|
||||
guint16 status = FP_READ_UINT16_LE (transfer->buffer);
|
||||
if (status == VCSFW_STATUS_OK)
|
||||
{
|
||||
self->fwext_loaded = TRUE;
|
||||
fp_info ("Firmware extension is loaded");
|
||||
}
|
||||
else
|
||||
{
|
||||
self->fwext_loaded = FALSE;
|
||||
fp_info ("Firmware extension not loaded (status=0x%04x)", status);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->fwext_loaded = FALSE;
|
||||
fp_warn ("GET_FW_INFO response too short");
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (transfer->ssm);
|
||||
}
|
||||
|
||||
/* Callback for optional flash-read child SSM */
|
||||
static void
|
||||
flash_read_ssm_done (FpiSsm *ssm,
|
||||
FpDevice *dev,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fp_warn ("TLS flash read failed: %s — skipping TLS", error->message);
|
||||
g_clear_error (&error);
|
||||
fpi_ssm_jump_to_state (self->open_ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (self->open_ssm);
|
||||
}
|
||||
|
||||
/* Callback for optional TLS handshake child SSM */
|
||||
static void
|
||||
tls_handshake_ssm_done (FpiSsm *ssm,
|
||||
FpDevice *dev,
|
||||
GError *error)
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (error)
|
||||
{
|
||||
fp_warn ("TLS handshake failed: %s — continuing without TLS",
|
||||
error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (self->open_ssm);
|
||||
}
|
||||
|
||||
static void
|
||||
open_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev)
|
||||
|
|
@ -250,11 +331,104 @@ open_run_state (FpiSsm *ssm,
|
|||
fpi_usb_transfer_fill_bulk (transfer, VALIDITY_EP_CMD_IN,
|
||||
VALIDITY_MAX_TRANSFER_LEN);
|
||||
fpi_usb_transfer_submit (transfer, VALIDITY_USB_TIMEOUT, NULL,
|
||||
fpi_ssm_usb_transfer_cb, NULL);
|
||||
fw_info_recv_cb, NULL);
|
||||
break;
|
||||
|
||||
/* Parse result: check if fwext is loaded.
|
||||
* We only need the 2-byte status: 0x0000 = loaded, anything else = not loaded.
|
||||
* This is checked later when we get the actual response. For now, just read. */
|
||||
case OPEN_TLS_READ_FLASH:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* In emulation mode (tests), skip TLS — no real device to talk to */
|
||||
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
|
||||
{
|
||||
fp_dbg ("Emulation mode — skipping TLS flash read");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
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. */
|
||||
self->open_ssm = ssm;
|
||||
FpiSsm *flash_ssm = fpi_ssm_new (dev,
|
||||
validity_tls_flash_read_run_state,
|
||||
TLS_FLASH_READ_NUM_STATES);
|
||||
fpi_ssm_start (flash_ssm, flash_read_ssm_done);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_TLS_DERIVE_PSK:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
/* Derive PSK from hardware identity (DMI) */
|
||||
validity_tls_derive_psk (&self->tls);
|
||||
|
||||
/* Flash response format (after 2-byte status already stripped):
|
||||
* [size:4 LE][unknown:2][flash_data:size]
|
||||
* See python-validity: sz, = unpack('<xxLxx', rsp[:8]) */
|
||||
GError *error = NULL;
|
||||
if (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;
|
||||
|
||||
fp_dbg ("TLS flash: %u bytes of data (response had %zu)",
|
||||
flash_sz, self->cmd_response_len);
|
||||
|
||||
if (!validity_tls_parse_flash (&self->tls,
|
||||
flash_data,
|
||||
flash_sz,
|
||||
&error))
|
||||
{
|
||||
fp_warn ("TLS flash parse failed: %s — "
|
||||
"device may need pairing", error->message);
|
||||
/* Non-fatal for now: skip TLS handshake */
|
||||
g_clear_error (&error);
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_warn ("No flash data available — skipping TLS");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
fpi_ssm_next_state (ssm);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_TLS_HANDSHAKE:
|
||||
{
|
||||
FpiDeviceValidity *self = FPI_DEVICE_VALIDITY (dev);
|
||||
|
||||
if (!self->tls.keys_loaded)
|
||||
{
|
||||
fp_info ("TLS keys not loaded — skipping handshake");
|
||||
fpi_ssm_jump_to_state (ssm, OPEN_DONE);
|
||||
return;
|
||||
}
|
||||
|
||||
self->open_ssm = ssm;
|
||||
FpiSsm *tls_ssm = fpi_ssm_new (dev,
|
||||
validity_tls_handshake_run_state,
|
||||
TLS_HS_NUM_STATES);
|
||||
fpi_ssm_start (tls_ssm, tls_handshake_ssm_done);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPEN_DONE:
|
||||
|
|
@ -294,6 +468,7 @@ dev_open (FpDevice *device)
|
|||
G_DEBUG_HERE ();
|
||||
|
||||
self->interrupt_cancellable = g_cancellable_new ();
|
||||
validity_tls_init (&self->tls);
|
||||
|
||||
if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device), 0, 0, &error))
|
||||
{
|
||||
|
|
@ -322,6 +497,8 @@ dev_close (FpDevice *device)
|
|||
g_clear_pointer (&self->cmd_response_data, g_free);
|
||||
self->cmd_response_len = 0;
|
||||
|
||||
validity_tls_free (&self->tls);
|
||||
|
||||
g_clear_object (&self->interrupt_cancellable);
|
||||
|
||||
g_usb_device_release_interface (fpi_device_get_usb_device (device), 0, 0, &error);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "validity_tls.h"
|
||||
|
||||
/* USB Endpoint addresses */
|
||||
#define VALIDITY_EP_CMD_OUT 0x01
|
||||
|
|
@ -94,9 +95,18 @@ struct _FpiDeviceValidity
|
|||
ValidityVersionInfo version_info;
|
||||
GCancellable *interrupt_cancellable;
|
||||
|
||||
/* TLS session state */
|
||||
ValidityTlsState tls;
|
||||
|
||||
/* Firmware extension status */
|
||||
gboolean fwext_loaded;
|
||||
|
||||
/* Command SSM: manages the send-cmd/recv-response cycle */
|
||||
FpiSsm *cmd_ssm;
|
||||
|
||||
/* Open SSM: back-pointer for non-subsm child SSMs */
|
||||
FpiSsm *open_ssm;
|
||||
|
||||
/* Pending response data stashed for higher-level SSM consumption */
|
||||
guint8 *cmd_response_data;
|
||||
gsize cmd_response_len;
|
||||
|
|
|
|||
1789
libfprint/drivers/validity/validity_tls.c
Normal file
1789
libfprint/drivers/validity/validity_tls.c
Normal file
File diff suppressed because it is too large
Load diff
217
libfprint/drivers/validity/validity_tls.h
Normal file
217
libfprint/drivers/validity/validity_tls.h
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* TLS session management for Validity/Synaptics VCSFW fingerprint sensors
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/ec.h>
|
||||
|
||||
/* TLS record content types */
|
||||
#define TLS_CONTENT_CHANGE_CIPHER 0x14
|
||||
#define TLS_CONTENT_HANDSHAKE 0x16
|
||||
#define TLS_CONTENT_APP_DATA 0x17
|
||||
|
||||
/* TLS version 1.2 */
|
||||
#define TLS_VERSION_MAJOR 0x03
|
||||
#define TLS_VERSION_MINOR 0x03
|
||||
|
||||
/* TLS handshake message types */
|
||||
#define TLS_HS_CLIENT_HELLO 0x01
|
||||
#define TLS_HS_SERVER_HELLO 0x02
|
||||
#define TLS_HS_CERTIFICATE 0x0B
|
||||
#define TLS_HS_CERT_REQUEST 0x0D
|
||||
#define TLS_HS_SERVER_HELLO_DONE 0x0E
|
||||
#define TLS_HS_CERT_VERIFY 0x0F
|
||||
#define TLS_HS_CLIENT_KEY_EXCHANGE 0x10
|
||||
#define TLS_HS_FINISHED 0x14
|
||||
|
||||
/* Cipher suite */
|
||||
#define TLS_CS_ECDH_ECDSA_AES256_CBC_SHA 0xC005
|
||||
|
||||
/* Key/block sizes */
|
||||
#define TLS_AES_KEY_SIZE 32
|
||||
#define TLS_IV_SIZE 16
|
||||
#define TLS_HMAC_SIZE 32
|
||||
#define TLS_AES_BLOCK_SIZE 16
|
||||
#define TLS_MASTER_SECRET_SIZE 48
|
||||
#define TLS_KEY_BLOCK_SIZE 0x120
|
||||
#define TLS_RANDOM_SIZE 32
|
||||
#define TLS_VERIFY_DATA_SIZE 12
|
||||
|
||||
/* VCSFW TLS command prefix */
|
||||
#define TLS_CMD_PREFIX_SIZE 4
|
||||
|
||||
/* Flash block IDs */
|
||||
#define TLS_FLASH_BLOCK_EMPTY0 0x0000
|
||||
#define TLS_FLASH_BLOCK_EMPTY1 0x0001
|
||||
#define TLS_FLASH_BLOCK_EMPTY2 0x0002
|
||||
#define TLS_FLASH_BLOCK_CERT 0x0003
|
||||
#define TLS_FLASH_BLOCK_PRIVKEY 0x0004
|
||||
#define TLS_FLASH_BLOCK_CA_CERT 0x0005
|
||||
#define TLS_FLASH_BLOCK_ECDH 0x0006
|
||||
#define TLS_FLASH_BLOCK_END 0xFFFF
|
||||
|
||||
/* Flash block header: [id:2 LE][size:2 LE][sha256:32] */
|
||||
#define TLS_FLASH_BLOCK_HEADER_SIZE (2 + 2 + 32)
|
||||
|
||||
/* ECDH key blob offsets */
|
||||
#define TLS_ECDH_BLOB_SIZE 0x90
|
||||
#define TLS_ECDH_X_OFFSET 0x08
|
||||
#define TLS_ECDH_Y_OFFSET 0x4C
|
||||
#define TLS_EC_COORD_SIZE 0x20
|
||||
|
||||
/* Forward declaration */
|
||||
typedef struct _FpiDeviceValidity FpiDeviceValidity;
|
||||
|
||||
/* TLS session state */
|
||||
typedef struct
|
||||
{
|
||||
/* Session keys (derived during handshake) */
|
||||
guint8 sign_key[TLS_AES_KEY_SIZE];
|
||||
guint8 validation_key[TLS_AES_KEY_SIZE];
|
||||
guint8 encryption_key[TLS_AES_KEY_SIZE];
|
||||
guint8 decryption_key[TLS_AES_KEY_SIZE];
|
||||
|
||||
/* Pre-shared keys (derived from hardware identity) */
|
||||
guint8 psk_encryption_key[TLS_AES_KEY_SIZE];
|
||||
guint8 psk_validation_key[TLS_AES_KEY_SIZE];
|
||||
|
||||
/* Handshake state */
|
||||
GChecksum *handshake_hash; /* running SHA-256 of handshake messages */
|
||||
guint8 client_random[TLS_RANDOM_SIZE];
|
||||
guint8 server_random[TLS_RANDOM_SIZE];
|
||||
guint8 master_secret[TLS_MASTER_SECRET_SIZE];
|
||||
|
||||
/* ECDH session ephemeral key pair (generated per handshake) */
|
||||
EVP_PKEY *session_key;
|
||||
|
||||
/* TLS client certificate from flash (block ID 3) */
|
||||
guint8 *tls_cert;
|
||||
gsize tls_cert_len;
|
||||
|
||||
/* Client private key from flash (block ID 4, decrypted with PSK) */
|
||||
EVP_PKEY *priv_key;
|
||||
|
||||
/* ECDH server public key from flash (block ID 6) */
|
||||
EVP_PKEY *ecdh_q;
|
||||
|
||||
/* Raw flash blobs (needed for pairing later) */
|
||||
guint8 *priv_blob;
|
||||
gsize priv_blob_len;
|
||||
guint8 *ecdh_blob;
|
||||
gsize ecdh_blob_len;
|
||||
|
||||
/* TLS channel state */
|
||||
gboolean secure_rx;
|
||||
gboolean secure_tx;
|
||||
gboolean keys_loaded;
|
||||
} ValidityTlsState;
|
||||
|
||||
/* TLS handshake SSM states */
|
||||
typedef enum {
|
||||
TLS_HS_SEND_CLIENT_HELLO = 0,
|
||||
TLS_HS_RECV_SERVER_HELLO,
|
||||
TLS_HS_SEND_CLIENT_FINISH,
|
||||
TLS_HS_RECV_SERVER_FINISH,
|
||||
TLS_HS_PARSE_SERVER_FINISH,
|
||||
TLS_HS_NUM_STATES,
|
||||
} ValidityTlsHandshakeState;
|
||||
|
||||
/* TLS flash read SSM states */
|
||||
typedef enum {
|
||||
TLS_FLASH_READ_CMD = 0,
|
||||
TLS_FLASH_READ_RECV,
|
||||
TLS_FLASH_READ_NUM_STATES,
|
||||
} ValidityTlsFlashReadState;
|
||||
|
||||
/* ---- Public API ---- */
|
||||
|
||||
void validity_tls_init (ValidityTlsState *tls);
|
||||
void validity_tls_free (ValidityTlsState *tls);
|
||||
|
||||
void validity_tls_derive_psk (ValidityTlsState *tls);
|
||||
|
||||
gboolean validity_tls_parse_flash (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* PRF — exported for testing */
|
||||
void validity_tls_prf (const guint8 *secret,
|
||||
gsize secret_len,
|
||||
const guint8 *seed,
|
||||
gsize seed_len,
|
||||
guint8 *output,
|
||||
gsize output_len);
|
||||
|
||||
/* Encrypt/decrypt for TLS app data */
|
||||
guint8 *validity_tls_encrypt (ValidityTlsState *tls,
|
||||
const guint8 *plaintext,
|
||||
gsize plaintext_len,
|
||||
gsize *out_len);
|
||||
|
||||
guint8 *validity_tls_decrypt (ValidityTlsState *tls,
|
||||
const guint8 *ciphertext,
|
||||
gsize ciphertext_len,
|
||||
gsize *out_len,
|
||||
GError **error);
|
||||
|
||||
/* Build TLS app_data record wrapping a VCSFW command */
|
||||
guint8 *validity_tls_wrap_app_data (ValidityTlsState *tls,
|
||||
const guint8 *cmd,
|
||||
gsize cmd_len,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse TLS response, returning decrypted app_data */
|
||||
guint8 *validity_tls_unwrap_response (ValidityTlsState *tls,
|
||||
const guint8 *response,
|
||||
gsize response_len,
|
||||
gsize *out_len,
|
||||
GError **error);
|
||||
|
||||
/* Build the full handshake USB commands.
|
||||
* Returns the first USB command (ClientHello with 0x44000000 prefix). */
|
||||
guint8 *validity_tls_build_client_hello (ValidityTlsState *tls,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse ServerHello + CertReq + ServerHelloDone from USB response */
|
||||
gboolean validity_tls_parse_server_hello (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* Build Certificate + ClientKeyExchange + CertVerify + ChangeCipherSpec + Finished */
|
||||
guint8 *validity_tls_build_client_finish (ValidityTlsState *tls,
|
||||
gsize *out_len);
|
||||
|
||||
/* Parse server ChangeCipherSpec + Finished */
|
||||
gboolean validity_tls_parse_server_finish (ValidityTlsState *tls,
|
||||
const guint8 *data,
|
||||
gsize data_len,
|
||||
GError **error);
|
||||
|
||||
/* SSM runner for TLS handshake */
|
||||
void validity_tls_handshake_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
||||
/* SSM runner for reading flash TLS data */
|
||||
void validity_tls_flash_read_run_state (FpiSsm *ssm,
|
||||
FpDevice *dev);
|
||||
|
|
@ -155,7 +155,8 @@ driver_sources = {
|
|||
[ 'drivers/focaltech_moc/focaltech_moc.c' ],
|
||||
'validity' :
|
||||
[ 'drivers/validity/validity.c',
|
||||
'drivers/validity/vcsfw_protocol.c' ],
|
||||
'drivers/validity/vcsfw_protocol.c',
|
||||
'drivers/validity/validity_tls.c' ],
|
||||
}
|
||||
|
||||
helper_sources = {
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ driver_helper_mapping = {
|
|||
'aes3500' : [ 'aeslib', 'aes3k' ],
|
||||
'aes4000' : [ 'aeslib', 'aes3k' ],
|
||||
'uru4000' : [ 'openssl' ],
|
||||
'validity' : [ 'openssl' ],
|
||||
'elanspi' : [ 'udev' ],
|
||||
'virtual_image' : [ 'virtual' ],
|
||||
'virtual_device' : [ 'virtual' ],
|
||||
|
|
|
|||
|
|
@ -322,6 +322,25 @@ foreach test_name: unit_tests
|
|||
)
|
||||
endforeach
|
||||
|
||||
# Validity TLS unit tests (needs driver library + OpenSSL)
|
||||
if 'validity' in supported_drivers
|
||||
openssl_dep = dependency('openssl', version: '>= 3.0', required: false)
|
||||
if openssl_dep.found()
|
||||
validity_tls_test = executable('test-validity-tls',
|
||||
sources: 'test-validity-tls.c',
|
||||
dependencies: [ libfprint_private_dep, openssl_dep ],
|
||||
c_args: common_cflags,
|
||||
link_with: libfprint_drivers,
|
||||
install: false,
|
||||
)
|
||||
test('validity-tls',
|
||||
validity_tls_test,
|
||||
suite: ['unit-tests'],
|
||||
env: envs,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
|
||||
# Run udev rule generator with fatal warnings
|
||||
envs.set('UDEV_HWDB', udev_hwdb.full_path())
|
||||
envs.set('UDEV_HWDB_CHECK_CONTENTS', default_drivers_are_enabled ? '1' : '0')
|
||||
|
|
|
|||
776
tests/test-validity-tls.c
Normal file
776
tests/test-validity-tls.c
Normal file
|
|
@ -0,0 +1,776 @@
|
|||
/*
|
||||
* Unit tests for validity TLS session management functions
|
||||
*
|
||||
* Copyright (C) 2024 libfprint contributors
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-ssm.h"
|
||||
#include "fpi-byte-reader.h"
|
||||
|
||||
/* We include the TLS header and use function declarations directly.
|
||||
* The test links against the driver static lib. */
|
||||
#include "drivers/validity/validity_tls.h"
|
||||
#include "drivers/validity/vcsfw_protocol.h"
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF produces deterministic output
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_prf_deterministic (void)
|
||||
{
|
||||
guint8 secret[] = { 0x01, 0x02, 0x03, 0x04 };
|
||||
guint8 seed[] = { 0x05, 0x06, 0x07, 0x08 };
|
||||
guint8 output1[48];
|
||||
guint8 output2[48];
|
||||
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output1, sizeof (output1));
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output2, sizeof (output2));
|
||||
|
||||
g_assert_cmpmem (output1, sizeof (output1), output2, sizeof (output2));
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF with known TLS 1.2 test vector
|
||||
* ================================================================
|
||||
* RFC 5246 does not define test vectors for SHA-256 PRF directly,
|
||||
* but we verify our implementation against python-validity's output.
|
||||
*/
|
||||
static void
|
||||
test_prf_output_length (void)
|
||||
{
|
||||
guint8 secret[32];
|
||||
guint8 seed[64];
|
||||
guint8 output[0x120]; /* Same as key_block size */
|
||||
|
||||
memset (secret, 0xAB, sizeof (secret));
|
||||
memset (seed, 0xCD, sizeof (seed));
|
||||
|
||||
validity_tls_prf (secret, sizeof (secret), seed, sizeof (seed),
|
||||
output, sizeof (output));
|
||||
|
||||
/* PRF output should not be all zeros */
|
||||
gboolean all_zero = TRUE;
|
||||
for (gsize i = 0; i < sizeof (output); i++)
|
||||
{
|
||||
if (output[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PRF with different lengths uses correct number of HMAC iters
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_prf_short_output (void)
|
||||
{
|
||||
guint8 secret[] = { 0x01 };
|
||||
guint8 seed[] = { 0x02 };
|
||||
guint8 output_short[16];
|
||||
guint8 output_long[48];
|
||||
|
||||
validity_tls_prf (secret, 1, seed, 1, output_short, sizeof (output_short));
|
||||
validity_tls_prf (secret, 1, seed, 1, output_long, sizeof (output_long));
|
||||
|
||||
/* First 16 bytes should match */
|
||||
g_assert_cmpmem (output_short, 16, output_long, 16);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Encrypt then decrypt roundtrip
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_decrypt_roundtrip (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
/* Set up encryption/decryption keys (same for roundtrip test) */
|
||||
memset (tls.encryption_key, 0x42, TLS_AES_KEY_SIZE);
|
||||
memset (tls.decryption_key, 0x42, TLS_AES_KEY_SIZE);
|
||||
|
||||
guint8 plaintext[] = "Hello, TLS! This is a test message for encryption.";
|
||||
gsize pt_len = sizeof (plaintext);
|
||||
|
||||
gsize enc_len;
|
||||
guint8 *encrypted = validity_tls_encrypt (&tls, plaintext, pt_len, &enc_len);
|
||||
g_assert_nonnull (encrypted);
|
||||
g_assert_cmpuint (enc_len, >, pt_len); /* IV + padded ciphertext */
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, encrypted, enc_len,
|
||||
&dec_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (decrypted);
|
||||
g_assert_cmpmem (plaintext, pt_len, decrypted, dec_len);
|
||||
|
||||
g_free (encrypted);
|
||||
g_free (decrypted);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Encrypt with block-aligned data
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_encrypt_block_aligned (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
memset (tls.encryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
memset (tls.decryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
|
||||
/* 16 bytes = exactly one AES block */
|
||||
guint8 plaintext[16];
|
||||
memset (plaintext, 0xAA, 16);
|
||||
|
||||
gsize enc_len;
|
||||
guint8 *encrypted = validity_tls_encrypt (&tls, plaintext, 16, &enc_len);
|
||||
g_assert_nonnull (encrypted);
|
||||
/* Should be IV(16) + 32 bytes (16 data + 16 padding since pad=0x0f*16) */
|
||||
g_assert_cmpuint (enc_len, ==, 16 + 32);
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, encrypted, enc_len,
|
||||
&dec_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (decrypted);
|
||||
g_assert_cmpuint (dec_len, ==, 16);
|
||||
g_assert_cmpmem (plaintext, 16, decrypted, 16);
|
||||
|
||||
g_free (encrypted);
|
||||
g_free (decrypted);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Decrypt with invalid data fails
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_decrypt_invalid (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
memset (tls.decryption_key, 0x55, TLS_AES_KEY_SIZE);
|
||||
|
||||
/* Too short for IV + block */
|
||||
guint8 short_data[10];
|
||||
memset (short_data, 0, sizeof (short_data));
|
||||
|
||||
GError *error = NULL;
|
||||
gsize dec_len;
|
||||
guint8 *decrypted = validity_tls_decrypt (&tls, short_data,
|
||||
sizeof (short_data),
|
||||
&dec_len, &error);
|
||||
g_assert_null (decrypted);
|
||||
g_assert_error (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_PROTO);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PSK derivation runs without crashing
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_psk_derivation (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
validity_tls_derive_psk (&tls);
|
||||
|
||||
/* PSK keys should not be all zeros */
|
||||
gboolean all_zero = TRUE;
|
||||
for (gsize i = 0; i < TLS_AES_KEY_SIZE; i++)
|
||||
{
|
||||
if (tls.psk_encryption_key[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
|
||||
all_zero = TRUE;
|
||||
for (gsize i = 0; i < TLS_AES_KEY_SIZE; i++)
|
||||
{
|
||||
if (tls.psk_validation_key[i] != 0)
|
||||
{
|
||||
all_zero = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_false (all_zero);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: PSK derivation is deterministic
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_psk_deterministic (void)
|
||||
{
|
||||
ValidityTlsState tls1, tls2;
|
||||
validity_tls_init (&tls1);
|
||||
validity_tls_init (&tls2);
|
||||
|
||||
validity_tls_derive_psk (&tls1);
|
||||
validity_tls_derive_psk (&tls2);
|
||||
|
||||
g_assert_cmpmem (tls1.psk_encryption_key, TLS_AES_KEY_SIZE,
|
||||
tls2.psk_encryption_key, TLS_AES_KEY_SIZE);
|
||||
g_assert_cmpmem (tls1.psk_validation_key, TLS_AES_KEY_SIZE,
|
||||
tls2.psk_validation_key, TLS_AES_KEY_SIZE);
|
||||
|
||||
validity_tls_free (&tls1);
|
||||
validity_tls_free (&tls2);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Flash parse with empty data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_empty (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
guint8 empty_flash[] = { 0xFF, 0xFF, 0x00, 0x00 }; /* end block */
|
||||
|
||||
/* Flash with only end marker → missing keys */
|
||||
gboolean result = validity_tls_parse_flash (&tls, empty_flash,
|
||||
sizeof (empty_flash),
|
||||
&error);
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_false (tls.keys_loaded);
|
||||
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Flash parse with truncated data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_truncated (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
guint8 truncated[] = { 0x03, 0x00, 0xFF, 0x00 }; /* cert block w/ impossibly large size */
|
||||
|
||||
gboolean result = validity_tls_parse_flash (&tls, truncated,
|
||||
sizeof (truncated),
|
||||
&error);
|
||||
/* Should fail due to block size exceeding remaining data */
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Init/free cycle doesn't leak
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_init_free (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
validity_tls_init (&tls);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Build ClientHello produces valid TLS record
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_build_client_hello (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
gsize out_len;
|
||||
guint8 *hello = validity_tls_build_client_hello (&tls, &out_len);
|
||||
|
||||
g_assert_nonnull (hello);
|
||||
g_assert_cmpuint (out_len, >, 4 + 5); /* prefix(4) + record header(5) minimum */
|
||||
|
||||
/* Check prefix: 0x44 0x00 0x00 0x00 */
|
||||
g_assert_cmpint (hello[0], ==, 0x44);
|
||||
g_assert_cmpint (hello[1], ==, 0x00);
|
||||
g_assert_cmpint (hello[2], ==, 0x00);
|
||||
g_assert_cmpint (hello[3], ==, 0x00);
|
||||
|
||||
/* Check TLS record header */
|
||||
g_assert_cmpint (hello[4], ==, 0x16); /* handshake */
|
||||
g_assert_cmpint (hello[5], ==, 0x03); /* version major */
|
||||
g_assert_cmpint (hello[6], ==, 0x03); /* version minor */
|
||||
|
||||
/* client_random should have been set */
|
||||
gboolean has_random = FALSE;
|
||||
for (gsize i = 0; i < TLS_RANDOM_SIZE; i++)
|
||||
{
|
||||
if (tls.client_random[i] != 0)
|
||||
{
|
||||
has_random = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
g_assert_true (has_random);
|
||||
|
||||
g_free (hello);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Test: Wrap/unwrap with invalid data fails gracefully
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_unwrap_invalid (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
GError *error = NULL;
|
||||
gsize out_len;
|
||||
|
||||
/* Short data → truncated record header */
|
||||
guint8 short_data[] = { 0x17, 0x03 };
|
||||
guint8 *result = validity_tls_unwrap_response (&tls, short_data,
|
||||
sizeof (short_data),
|
||||
&out_len, &error);
|
||||
g_assert_null (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* App data before secure channel */
|
||||
guint8 app_early[] = { 0x17, 0x03, 0x03, 0x00, 0x10,
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
|
||||
result = validity_tls_unwrap_response (&tls, app_early,
|
||||
sizeof (app_early),
|
||||
&out_len, &error);
|
||||
g_assert_null (result);
|
||||
g_assert_nonnull (error);
|
||||
g_clear_error (&error);
|
||||
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #1 — Flash parse requires PSK for private key
|
||||
*
|
||||
* Private key block (ID 4) is encrypted with PSK. Calling parse_flash
|
||||
* without first deriving PSK must fail (HMAC mismatch), proving the
|
||||
* ordering dependency. This catches the bug where flash_read SSM
|
||||
* parsed flash data BEFORE PSK derivation had occurred.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_parse_needs_psk (void)
|
||||
{
|
||||
ValidityTlsState tls_with_psk, tls_no_psk;
|
||||
validity_tls_init (&tls_with_psk);
|
||||
validity_tls_init (&tls_no_psk);
|
||||
|
||||
/* Derive PSK so we can build a valid encrypted private key block */
|
||||
validity_tls_derive_psk (&tls_with_psk);
|
||||
|
||||
/* Build a realistic flash image with a cert block + encrypted privkey block.
|
||||
* We use a minimal cert (just 16 bytes of dummy data) and a privkey block
|
||||
* that's encrypted with the proper PSK. */
|
||||
|
||||
/* Step 1: Build a cert body */
|
||||
guint8 cert_body[16];
|
||||
memset (cert_body, 0xAA, sizeof (cert_body));
|
||||
|
||||
/* Step 2: Build a private-key body encrypted with PSK */
|
||||
guint8 priv_plaintext[96]; /* d(32) + pad for block alignment */
|
||||
memset (priv_plaintext, 0xBB, sizeof (priv_plaintext));
|
||||
|
||||
/* Encrypt plaintext with PSK encryption key */
|
||||
guint8 iv[TLS_IV_SIZE];
|
||||
memset (iv, 0x11, TLS_IV_SIZE);
|
||||
gsize ct_len = sizeof (priv_plaintext);
|
||||
guint8 *ciphertext = g_malloc (TLS_IV_SIZE + ct_len);
|
||||
memcpy (ciphertext, iv, TLS_IV_SIZE);
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new ();
|
||||
int out_len, final_len;
|
||||
EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc (), NULL,
|
||||
tls_with_psk.psk_encryption_key, iv);
|
||||
EVP_CIPHER_CTX_set_padding (ctx, 0);
|
||||
EVP_EncryptUpdate (ctx, ciphertext + TLS_IV_SIZE, &out_len,
|
||||
priv_plaintext, ct_len);
|
||||
EVP_EncryptFinal_ex (ctx, ciphertext + TLS_IV_SIZE + out_len, &final_len);
|
||||
EVP_CIPHER_CTX_free (ctx);
|
||||
gsize enc_total = TLS_IV_SIZE + ct_len;
|
||||
|
||||
/* HMAC over (iv + ciphertext) with psk_validation_key */
|
||||
guint8 mac[TLS_HMAC_SIZE];
|
||||
unsigned int mac_len;
|
||||
HMAC (EVP_sha256 (),
|
||||
tls_with_psk.psk_validation_key, TLS_AES_KEY_SIZE,
|
||||
ciphertext, enc_total, mac, &mac_len);
|
||||
|
||||
/* Private key block payload: 0x02 || ciphertext || hmac */
|
||||
gsize priv_block_len = 1 + enc_total + TLS_HMAC_SIZE;
|
||||
guint8 *priv_block = g_malloc (priv_block_len);
|
||||
priv_block[0] = 0x02;
|
||||
memcpy (priv_block + 1, ciphertext, enc_total);
|
||||
memcpy (priv_block + 1 + enc_total, mac, TLS_HMAC_SIZE);
|
||||
g_free (ciphertext);
|
||||
|
||||
/* Build flash image: [cert_header][cert_body][priv_header][priv_body][end] */
|
||||
GByteArray *flash = g_byte_array_new ();
|
||||
|
||||
/* Cert block header: id=0x0003, size, sha256 hash */
|
||||
guint8 cert_hdr[TLS_FLASH_BLOCK_HEADER_SIZE];
|
||||
FP_WRITE_UINT16_LE (cert_hdr, TLS_FLASH_BLOCK_CERT);
|
||||
FP_WRITE_UINT16_LE (cert_hdr + 2, sizeof (cert_body));
|
||||
GChecksum *cs = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
gsize hash_len = 32;
|
||||
g_checksum_update (cs, cert_body, sizeof (cert_body));
|
||||
g_checksum_get_digest (cs, cert_hdr + 4, &hash_len);
|
||||
g_checksum_free (cs);
|
||||
g_byte_array_append (flash, cert_hdr, sizeof (cert_hdr));
|
||||
g_byte_array_append (flash, cert_body, sizeof (cert_body));
|
||||
|
||||
/* Priv block header */
|
||||
guint8 priv_hdr[TLS_FLASH_BLOCK_HEADER_SIZE];
|
||||
FP_WRITE_UINT16_LE (priv_hdr, TLS_FLASH_BLOCK_PRIVKEY);
|
||||
FP_WRITE_UINT16_LE (priv_hdr + 2, priv_block_len);
|
||||
cs = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
hash_len = 32;
|
||||
g_checksum_update (cs, priv_block, priv_block_len);
|
||||
g_checksum_get_digest (cs, priv_hdr + 4, &hash_len);
|
||||
g_checksum_free (cs);
|
||||
g_byte_array_append (flash, priv_hdr, sizeof (priv_hdr));
|
||||
g_byte_array_append (flash, priv_block, priv_block_len);
|
||||
|
||||
/* End marker */
|
||||
guint8 end_marker[4] = { 0xFF, 0xFF, 0x00, 0x00 };
|
||||
g_byte_array_append (flash, end_marker, sizeof (end_marker));
|
||||
|
||||
/* TEST: Without PSK, parse_flash must fail on the privkey block */
|
||||
GError *error = NULL;
|
||||
gboolean result = validity_tls_parse_flash (&tls_no_psk,
|
||||
flash->data, flash->len,
|
||||
&error);
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
/* Should fail with HMAC-related error since PSK is all zeros */
|
||||
g_clear_error (&error);
|
||||
|
||||
g_byte_array_free (flash, TRUE);
|
||||
g_free (priv_block);
|
||||
validity_tls_free (&tls_with_psk);
|
||||
validity_tls_free (&tls_no_psk);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #2 — READ_FLASH command format
|
||||
*
|
||||
* The READ_FLASH command must be exactly 13 bytes matching
|
||||
* python-validity: pack('<BBBHLL', 0x40, partition, 1, 0, addr, size).
|
||||
* Old code only sent 10 bytes, missing the access flag and reserved
|
||||
* field. This test verifies the command constant and expected layout.
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_cmd_format (void)
|
||||
{
|
||||
/* Verify the command byte */
|
||||
g_assert_cmpint (VCSFW_CMD_READ_FLASH, ==, 0x40);
|
||||
|
||||
/* Build the same command as validity_tls_flash_read_run_state does */
|
||||
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); /* reserved */
|
||||
FP_WRITE_UINT32_LE (&cmd[5], 0x0000); /* offset */
|
||||
FP_WRITE_UINT32_LE (&cmd[9], 0x1000); /* size */
|
||||
|
||||
/* Verify total size is 13 (not 10 like the old bug) */
|
||||
g_assert_cmpuint (sizeof (cmd), ==, 13);
|
||||
|
||||
/* Verify byte layout matches python-validity's pack('<BBBHLL', ...) */
|
||||
g_assert_cmpint (cmd[0], ==, 0x40); /* command */
|
||||
g_assert_cmpint (cmd[1], ==, 0x01); /* partition */
|
||||
g_assert_cmpint (cmd[2], ==, 0x01); /* access flag (was missing) */
|
||||
g_assert_cmpint (cmd[3], ==, 0x00); /* reserved lo */
|
||||
g_assert_cmpint (cmd[4], ==, 0x00); /* reserved hi */
|
||||
/* offset at bytes 5-8 (LE uint32 = 0) */
|
||||
g_assert_cmpint (cmd[5], ==, 0x00);
|
||||
g_assert_cmpint (cmd[6], ==, 0x00);
|
||||
g_assert_cmpint (cmd[7], ==, 0x00);
|
||||
g_assert_cmpint (cmd[8], ==, 0x00);
|
||||
/* size at bytes 9-12 (LE uint32 = 0x1000) */
|
||||
g_assert_cmpint (cmd[9], ==, 0x00);
|
||||
g_assert_cmpint (cmd[10], ==, 0x10);
|
||||
g_assert_cmpint (cmd[11], ==, 0x00);
|
||||
g_assert_cmpint (cmd[12], ==, 0x00);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #3 — Flash response has 6-byte header
|
||||
*
|
||||
* After vcsfw_cmd_send strips the 2-byte VCSFW status, the flash
|
||||
* response still contains a 6-byte header: [size:4 LE][unknown:2].
|
||||
* Actual flash data starts at offset 6. Old code passed the raw
|
||||
* response directly to parse_flash(), corrupting the block parsing.
|
||||
* This test verifies:
|
||||
* 1) The 6-byte header is correctly structured (size field matches)
|
||||
* 2) parse_flash works on correctly unwrapped data (offset +6)
|
||||
* 3) The raw response differs from the unwrapped payload, proving
|
||||
* that skipping the header is necessary
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_flash_response_header (void)
|
||||
{
|
||||
/* Build a minimal valid flash image: just an end marker */
|
||||
guint8 flash_data[] = { 0xFF, 0xFF, 0x00, 0x00 };
|
||||
|
||||
/* Wrap it in the response header format: [size:4 LE][unk:2][data] */
|
||||
guint32 data_size = sizeof (flash_data);
|
||||
guint8 response[6 + sizeof (flash_data)];
|
||||
FP_WRITE_UINT32_LE (response, data_size);
|
||||
response[4] = 0x00; /* unknown byte 1 */
|
||||
response[5] = 0x00; /* unknown byte 2 */
|
||||
memcpy (response + 6, flash_data, sizeof (flash_data));
|
||||
|
||||
/* Verify the unwrap logic: read size from offset 0, skip 6-byte header */
|
||||
guint32 read_size = FP_READ_UINT32_LE (response);
|
||||
g_assert_cmpuint (read_size, ==, sizeof (flash_data));
|
||||
|
||||
const guint8 *payload = response + 6;
|
||||
gsize payload_len = sizeof (response) - 6;
|
||||
g_assert_cmpuint (payload_len, ==, sizeof (flash_data));
|
||||
|
||||
/* Parsing the correctly unwrapped data should succeed (end marker only = no keys) */
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
GError *error = NULL;
|
||||
gboolean result = validity_tls_parse_flash (&tls, payload, payload_len, &error);
|
||||
/* Expected: fails because no keys, but NOT because of corrupt block headers */
|
||||
g_assert_false (result);
|
||||
g_assert_nonnull (error);
|
||||
g_assert_true (g_str_has_prefix (error->message, "TLS flash: incomplete key data"));
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
/* Verify the bug scenario: passing the raw response (with the 6-byte
|
||||
* header) gives DIFFERENT data to the parser than the correctly unwrapped
|
||||
* payload. The first 4 bytes of the raw response are the LE size field
|
||||
* (0x04 0x00 0x00 0x00), which would be misinterpreted as block_id=0x0004
|
||||
* (PRIVKEY block with size 0). This is a data corruption — the parser
|
||||
* receives wrong input either way, but the key point is that the raw
|
||||
* response and the unwrapped payload are NOT the same buffer content. */
|
||||
g_assert_cmpuint (sizeof (response), !=, payload_len);
|
||||
g_assert_true (memcmp (response, payload, payload_len) != 0);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #4 — TLS handshake expects raw TLS records
|
||||
*
|
||||
* parse_server_hello expects raw TLS records starting with a content
|
||||
* type byte (0x16 for Handshake). The old code used vcsfw_cmd_send
|
||||
* which strips 2 bytes of VCSFW status, corrupting the TLS record.
|
||||
* This test verifies that:
|
||||
* - A valid TLS Handshake record header is accepted
|
||||
* - Data prefixed with a 2-byte VCSFW status is rejected
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_server_hello_rejects_vcsfw_prefix (void)
|
||||
{
|
||||
/* Build a minimal valid TLS ServerHello record */
|
||||
guint8 server_hello_msg[] = {
|
||||
/* Handshake message: ServerHello (type 0x02) */
|
||||
0x02, /* type: ServerHello */
|
||||
0x00, 0x00, 0x26, /* length: 38 bytes */
|
||||
0x03, 0x03, /* version 1.2 */
|
||||
/* 32 bytes server_random */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
0x00, /* session_id length: 0 */
|
||||
0xC0, 0x05, /* cipher suite: 0xC005 */
|
||||
0x00, /* compression: none */
|
||||
};
|
||||
|
||||
gsize hs_len = sizeof (server_hello_msg);
|
||||
|
||||
/* Wrap in TLS record: content_type(1) + version(2) + length(2) + body */
|
||||
gsize raw_tls_len = 5 + hs_len;
|
||||
guint8 *raw_tls = g_malloc (raw_tls_len);
|
||||
raw_tls[0] = TLS_CONTENT_HANDSHAKE; /* 0x16 */
|
||||
raw_tls[1] = TLS_VERSION_MAJOR;
|
||||
raw_tls[2] = TLS_VERSION_MINOR;
|
||||
raw_tls[3] = (hs_len >> 8) & 0xff;
|
||||
raw_tls[4] = hs_len & 0xff;
|
||||
memcpy (raw_tls + 5, server_hello_msg, hs_len);
|
||||
|
||||
/* Test 1: parse_server_hello with raw TLS — should succeed */
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
tls.handshake_hash = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
GError *error = NULL;
|
||||
|
||||
gboolean result = validity_tls_parse_server_hello (&tls, raw_tls,
|
||||
raw_tls_len, &error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (result);
|
||||
/* Verify server_random was properly extracted */
|
||||
g_assert_cmpint (tls.server_random[0], ==, 0x01);
|
||||
g_assert_cmpint (tls.server_random[31], ==, 0x20);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
/* Test 2: Prepend a 2-byte VCSFW status (0x0000) — simulates what
|
||||
* vcsfw_cmd_send's cmd_receive_cb would have already STRIPPED.
|
||||
* But if the raw recv path is wrong and doesn't strip, the parser
|
||||
* gets [0x00, 0x00, 0x16, ...] — first byte 0x00 is not a valid
|
||||
* TLS content type, so parsing should behave differently. */
|
||||
gsize prefixed_len = 2 + raw_tls_len;
|
||||
guint8 *prefixed = g_malloc (prefixed_len);
|
||||
prefixed[0] = 0x00; /* VCSFW status lo */
|
||||
prefixed[1] = 0x00; /* VCSFW status hi */
|
||||
memcpy (prefixed + 2, raw_tls, raw_tls_len);
|
||||
|
||||
validity_tls_init (&tls);
|
||||
tls.handshake_hash = g_checksum_new (G_CHECKSUM_SHA256);
|
||||
|
||||
result = validity_tls_parse_server_hello (&tls, prefixed, prefixed_len,
|
||||
&error);
|
||||
/* With the 2-byte prefix, the first "record" starts at byte 0:
|
||||
* content_type=0x00 is NOT TLS_CONTENT_HANDSHAKE (0x16), so the
|
||||
* parser treats it as unknown content and either fails or skips it,
|
||||
* and the server_random will NOT match the expected values. */
|
||||
if (result)
|
||||
{
|
||||
/* Even if parsing didn't error, server_random should be wrong */
|
||||
gboolean random_ok = (tls.server_random[0] == 0x01 &&
|
||||
tls.server_random[31] == 0x20);
|
||||
g_assert_false (random_ok);
|
||||
}
|
||||
g_clear_error (&error);
|
||||
validity_tls_free (&tls);
|
||||
|
||||
g_free (raw_tls);
|
||||
g_free (prefixed);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Regression: Bug #5 — Client hello has 0x44 prefix (not VCSFW cmd)
|
||||
*
|
||||
* TLS handshake messages use 0x44000000 as a 4-byte prefix, NOT a
|
||||
* standard VCSFW command byte. This test verifies the prefix and that
|
||||
* the TLS record immediately follows (no VCSFW status expected in
|
||||
* response).
|
||||
* ================================================================ */
|
||||
static void
|
||||
test_client_hello_tls_prefix (void)
|
||||
{
|
||||
ValidityTlsState tls;
|
||||
validity_tls_init (&tls);
|
||||
|
||||
gsize out_len;
|
||||
guint8 *hello = validity_tls_build_client_hello (&tls, &out_len);
|
||||
g_assert_nonnull (hello);
|
||||
|
||||
/* Must start with 0x44 0x00 0x00 0x00 (TLS prefix, not VCSFW) */
|
||||
g_assert_cmpint (hello[0], ==, 0x44);
|
||||
g_assert_cmpint (hello[1], ==, 0x00);
|
||||
g_assert_cmpint (hello[2], ==, 0x00);
|
||||
g_assert_cmpint (hello[3], ==, 0x00);
|
||||
|
||||
/* Byte 4 must be TLS Handshake content type (0x16) */
|
||||
g_assert_cmpint (hello[4], ==, TLS_CONTENT_HANDSHAKE);
|
||||
|
||||
/* Bytes 5-6 must be TLS version 1.2 (0x0303) */
|
||||
g_assert_cmpint (hello[5], ==, TLS_VERSION_MAJOR);
|
||||
g_assert_cmpint (hello[6], ==, TLS_VERSION_MINOR);
|
||||
|
||||
/* The prefix (0x44) must NOT equal any VCSFW command byte.
|
||||
* Specifically, 0x44 != VCSFW_CMD_READ_FLASH (0x40) and
|
||||
* is not any known VCSFW command. This proves TLS messages
|
||||
* travel on a separate "channel". */
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_GET_VERSION);
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_READ_FLASH);
|
||||
g_assert_cmpint (hello[0], !=, VCSFW_CMD_GET_FW_INFO);
|
||||
|
||||
g_free (hello);
|
||||
validity_tls_free (&tls);
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/validity/tls/prf/deterministic", test_prf_deterministic);
|
||||
g_test_add_func ("/validity/tls/prf/output-length", test_prf_output_length);
|
||||
g_test_add_func ("/validity/tls/prf/short-output", test_prf_short_output);
|
||||
g_test_add_func ("/validity/tls/encrypt/roundtrip",
|
||||
test_encrypt_decrypt_roundtrip);
|
||||
g_test_add_func ("/validity/tls/encrypt/block-aligned",
|
||||
test_encrypt_block_aligned);
|
||||
g_test_add_func ("/validity/tls/decrypt/invalid", test_decrypt_invalid);
|
||||
g_test_add_func ("/validity/tls/psk/derivation", test_psk_derivation);
|
||||
g_test_add_func ("/validity/tls/psk/deterministic", test_psk_deterministic);
|
||||
g_test_add_func ("/validity/tls/flash/parse-empty", test_flash_parse_empty);
|
||||
g_test_add_func ("/validity/tls/flash/parse-truncated",
|
||||
test_flash_parse_truncated);
|
||||
g_test_add_func ("/validity/tls/init-free", test_init_free);
|
||||
g_test_add_func ("/validity/tls/client-hello", test_build_client_hello);
|
||||
g_test_add_func ("/validity/tls/unwrap/invalid", test_unwrap_invalid);
|
||||
|
||||
/* Regression tests for hardware-discovered bugs */
|
||||
g_test_add_func ("/validity/tls/regression/flash-parse-needs-psk",
|
||||
test_flash_parse_needs_psk);
|
||||
g_test_add_func ("/validity/tls/regression/flash-cmd-format",
|
||||
test_flash_cmd_format);
|
||||
g_test_add_func ("/validity/tls/regression/flash-response-header",
|
||||
test_flash_response_header);
|
||||
g_test_add_func ("/validity/tls/regression/server-hello-rejects-vcsfw-prefix",
|
||||
test_server_hello_rejects_vcsfw_prefix);
|
||||
g_test_add_func ("/validity/tls/regression/client-hello-tls-prefix",
|
||||
test_client_hello_tls_prefix);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
259
tests/validity/test_tls_hardware.py
Normal file
259
tests/validity/test_tls_hardware.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
#!/usr/bin/python3
|
||||
"""
|
||||
Hardware test for Validity TLS session management (Iteration 2).
|
||||
|
||||
Requires a real Validity/Synaptics sensor (06cb:009a or similar) that has
|
||||
been paired at least once (e.g. via python-validity or Windows driver).
|
||||
|
||||
Run with:
|
||||
sudo LD_LIBRARY_PATH=builddir/libfprint \
|
||||
GI_TYPELIB_PATH=builddir/libfprint \
|
||||
FP_DEVICE_EMULATION=0 \
|
||||
FP_DRIVERS_ALLOWLIST=validity \
|
||||
G_MESSAGES_DEBUG=all \
|
||||
python3 tests/validity/test_tls_hardware.py 2>&1
|
||||
|
||||
The test will:
|
||||
1. Enumerate and detect the validity sensor
|
||||
2. Open the device (triggers: GET_VERSION, CMD19, GET_FW_INFO,
|
||||
flash read, PSK derivation, flash parse, TLS handshake)
|
||||
3. Report whether TLS handshake succeeded or failed
|
||||
4. Close the device cleanly
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import gi
|
||||
gi.require_version('FPrint', '2.0')
|
||||
from gi.repository import FPrint, GLib
|
||||
|
||||
# Exit with error on any exception, including in callbacks
|
||||
sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))
|
||||
|
||||
# Ensure we're not in emulation mode
|
||||
if os.environ.get('FP_DEVICE_EMULATION') == '1':
|
||||
print('ERROR: FP_DEVICE_EMULATION=1 is set, this test needs real hardware')
|
||||
sys.exit(1)
|
||||
|
||||
# Ensure running as root (USB access)
|
||||
if os.geteuid() != 0:
|
||||
print('WARNING: Not running as root — USB access may fail')
|
||||
|
||||
# Collect debug log lines for analysis
|
||||
log_lines = []
|
||||
original_handler = None
|
||||
|
||||
def log_handler(log_domain, log_level, message, user_data):
|
||||
log_lines.append(message)
|
||||
# Also print to stderr for real-time visibility
|
||||
print(f' [{log_domain}] {message}', file=sys.stderr)
|
||||
|
||||
# Install log handler to capture libfprint debug output
|
||||
log_flags = (GLib.LogLevelFlags.LEVEL_DEBUG |
|
||||
GLib.LogLevelFlags.LEVEL_INFO |
|
||||
GLib.LogLevelFlags.LEVEL_MESSAGE |
|
||||
GLib.LogLevelFlags.LEVEL_WARNING |
|
||||
GLib.LogLevelFlags.LEVEL_CRITICAL)
|
||||
|
||||
for domain in ['libfprint', 'libfprint-SSM', 'libfprint-validity',
|
||||
'libfprint-device', 'libfprint-context']:
|
||||
GLib.log_set_handler(domain, log_flags, log_handler, None)
|
||||
|
||||
print('=== Validity TLS Hardware Test ===')
|
||||
print()
|
||||
|
||||
# Step 1: Enumerate devices
|
||||
c = FPrint.Context()
|
||||
c.enumerate()
|
||||
devices = c.get_devices()
|
||||
|
||||
if len(devices) == 0:
|
||||
print('FAIL: No fingerprint devices found')
|
||||
sys.exit(1)
|
||||
|
||||
d = devices[0]
|
||||
del devices
|
||||
|
||||
driver = d.get_driver()
|
||||
print(f'Found device: driver={driver}')
|
||||
|
||||
if driver != 'validity':
|
||||
print(f'SKIP: Expected validity driver, got {driver}')
|
||||
sys.exit(77) # meson skip code
|
||||
|
||||
# Step 2: Open device (this triggers the full TLS flow)
|
||||
print()
|
||||
print('Opening device (GET_VERSION → CMD19 → FW_INFO → Flash Read → PSK → TLS handshake)...')
|
||||
try:
|
||||
d.open_sync()
|
||||
print('Device opened successfully')
|
||||
except GLib.Error as e:
|
||||
print(f'FAIL: open_sync() failed: {e.message}')
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Analyze debug log for TLS progress
|
||||
print()
|
||||
print('=== TLS Progress Analysis ===')
|
||||
|
||||
checks = {
|
||||
'fwext_loaded': False,
|
||||
'fwext_not_loaded': False,
|
||||
'flash_read': False,
|
||||
'flash_bytes': None,
|
||||
'psk_derived': False,
|
||||
'psk_product': None,
|
||||
'flash_cert': False,
|
||||
'flash_privkey': False,
|
||||
'flash_ecdh': False,
|
||||
'keys_loaded': False,
|
||||
'tls_started': False,
|
||||
'server_hello': False,
|
||||
'handshake_done': False,
|
||||
'secure_session': False,
|
||||
'handshake_failed': None,
|
||||
'flash_parse_failed': None,
|
||||
'no_fwext_skip': False,
|
||||
}
|
||||
|
||||
for line in log_lines:
|
||||
if 'Firmware extension is loaded' in line:
|
||||
checks['fwext_loaded'] = True
|
||||
|
||||
if 'Firmware extension not loaded' in line:
|
||||
checks['fwext_not_loaded'] = True
|
||||
|
||||
if 'No firmware extension' in line:
|
||||
checks['no_fwext_skip'] = True
|
||||
if 'TLS flash read: got' in line:
|
||||
checks['flash_read'] = True
|
||||
m = re.search(r'got (\d+) bytes', line)
|
||||
if m:
|
||||
checks['flash_bytes'] = int(m.group(1))
|
||||
|
||||
if 'PSK derived from DMI' in line:
|
||||
checks['psk_derived'] = True
|
||||
m = re.search(r'product=(\S+)', line)
|
||||
if m:
|
||||
checks['psk_product'] = m.group(1)
|
||||
|
||||
if 'TLS flash: certificate loaded' in line:
|
||||
checks['flash_cert'] = True
|
||||
|
||||
if 'TLS flash: private key loaded' in line:
|
||||
checks['flash_privkey'] = True
|
||||
|
||||
if 'TLS flash: ECDH public key loaded' in line:
|
||||
checks['flash_ecdh'] = True
|
||||
|
||||
if 'TLS flash: all keys loaded' in line:
|
||||
checks['keys_loaded'] = True
|
||||
|
||||
if 'TLS ServerHello: cipher 0xC005' in line:
|
||||
checks['server_hello'] = True
|
||||
|
||||
if 'TLS handshake completed' in line:
|
||||
checks['handshake_done'] = True
|
||||
|
||||
if 'TLS session established' in line:
|
||||
checks['secure_session'] = True
|
||||
checks['tls_started'] = True
|
||||
|
||||
if 'TLS handshake failed' in line:
|
||||
checks['handshake_failed'] = line
|
||||
|
||||
if 'TLS flash parse failed' in line:
|
||||
checks['flash_parse_failed'] = line
|
||||
|
||||
if 'skipping TLS' in line.lower() or 'continuing without TLS' in line.lower():
|
||||
pass # noted but not a hard failure for this test
|
||||
|
||||
|
||||
# Report results
|
||||
def report(label, ok, detail=''):
|
||||
status = 'PASS' if ok else 'FAIL'
|
||||
extra = f' ({detail})' if detail else ''
|
||||
print(f' [{status}] {label}{extra}')
|
||||
|
||||
report('Firmware extension loaded',
|
||||
checks['fwext_loaded'],
|
||||
'NOT loaded' if checks['fwext_not_loaded'] else '')
|
||||
|
||||
report('Flash read executed',
|
||||
checks['flash_read'],
|
||||
f"{checks['flash_bytes']} bytes" if checks['flash_bytes'] else '')
|
||||
|
||||
report('PSK derived from DMI',
|
||||
checks['psk_derived'],
|
||||
checks['psk_product'] or '')
|
||||
|
||||
report('Certificate extracted from flash',
|
||||
checks['flash_cert'])
|
||||
|
||||
report('Private key decrypted from flash',
|
||||
checks['flash_privkey'])
|
||||
|
||||
report('ECDH public key extracted from flash',
|
||||
checks['flash_ecdh'])
|
||||
|
||||
report('All TLS keys loaded',
|
||||
checks['keys_loaded'])
|
||||
|
||||
report('ServerHello received (cipher 0xC005)',
|
||||
checks['server_hello'])
|
||||
|
||||
report('TLS handshake completed',
|
||||
checks['handshake_done'])
|
||||
|
||||
report('Secure TLS session established',
|
||||
checks['secure_session'])
|
||||
|
||||
if checks['handshake_failed']:
|
||||
print(f' [INFO] Handshake failure: {checks["handshake_failed"]}')
|
||||
|
||||
if checks['flash_parse_failed']:
|
||||
print(f' [INFO] Flash parse failure: {checks["flash_parse_failed"]}')
|
||||
|
||||
# Step 4: Close device
|
||||
print()
|
||||
print('Closing device...')
|
||||
d.close_sync()
|
||||
print('Device closed successfully')
|
||||
|
||||
del d
|
||||
del c
|
||||
|
||||
# Summary
|
||||
print()
|
||||
all_ok = (checks['fwext_loaded'] and checks['flash_read'] and
|
||||
checks['psk_derived'] and checks['keys_loaded'] and
|
||||
checks['handshake_done'] and checks['secure_session'])
|
||||
fwext_ok_keys_fail = (checks['fwext_loaded'] and checks['flash_read'] and
|
||||
not checks['keys_loaded'])
|
||||
no_fwext = checks['no_fwext_skip'] or checks['fwext_not_loaded']
|
||||
|
||||
if all_ok:
|
||||
print('=== RESULT: ALL TLS CHECKS PASSED ===')
|
||||
print('TLS session established with real hardware.')
|
||||
sys.exit(0)
|
||||
elif no_fwext:
|
||||
print('=== RESULT: FWEXT NOT LOADED ===')
|
||||
print('The firmware extension is not loaded on the sensor.')
|
||||
print('This is required for flash access and TLS handshake.')
|
||||
print()
|
||||
print('To resolve, pair the device first with python-validity:')
|
||||
print(' sudo validity-sensors-firmware # download/upload firmware')
|
||||
print(' sudo python3 -c "from validitysensor.init import open; open()"')
|
||||
print()
|
||||
print('The fwext_loaded check verified the driver correctly detects this.')
|
||||
sys.exit(0) # Not a driver bug — this is expected without fwext
|
||||
elif fwext_ok_keys_fail:
|
||||
print('=== RESULT: PARTIAL — Flash readable but keys incomplete ===')
|
||||
print('Flash read succeeded but TLS key material is missing or corrupt.')
|
||||
print('Re-pairing with python-validity may fix this.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('=== RESULT: SOME TLS CHECKS FAILED ===')
|
||||
sys.exit(1)
|
||||
Loading…
Add table
Reference in a new issue