sdcp-v2: Move SDCP support from using NSS to OpenSSL 3.0 and implement for egismoc

This commit is contained in:
Joshua Grisham
2025-08-28 21:01:54 +02:00
parent c74a0d4592
commit 7460e0dec4
31 changed files with 3593 additions and 2353 deletions

View File

@@ -226,19 +226,24 @@ fpi_image_device_set_bz3_threshold
<TITLE>Internal FpSdcpDevice</TITLE>
FpiSdcpClaim
FpSdcpDeviceClass
fpi_sdcp_claim_copy
fpi_sdcp_claim_free
fpi_sdcp_claim_get_type
fpi_sdcp_claim_new
fpi_sdcp_device_connect_complete
fpi_sdcp_claim_copy
fpi_sdcp_claim_free
fpi_sdcp_device_open_complete
fpi_sdcp_device_get_connect_data
fpi_sdcp_device_connect_complete
fpi_sdcp_device_get_reconnect_data
fpi_sdcp_device_reconnect_complete
fpi_sdcp_device_list_complete
fpi_sdcp_device_enroll_commit
fpi_sdcp_device_enroll_commit_complete
fpi_sdcp_device_enroll_ready
fpi_sdcp_device_enroll_set_nonce
fpi_sdcp_device_get_identify_data
fpi_sdcp_device_set_identify_data
fpi_sdcp_device_identify_retry
fpi_sdcp_device_identify_complete
fpi_sdcp_device_get_print_id
fpi_sdcp_device_set_print_id
</SECTION>
<SECTION>
@@ -249,6 +254,8 @@ fp_warn
fp_err
BUG_ON
BUG
fp_dbg_hex_dump_bytes
fp_dbg_hex_dump_gbytes
</SECTION>
<SECTION>

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,10 @@
/*
* Driver for Egis Technology (LighTuning) Match-On-Chip sensors
* Originally authored 2023 by Joshua Grisham <josh@joshuagrisham.com>
* Copyright (C) 2023-2025 Joshua Grisham <josh@joshuagrisham.com>
*
* Portions of code and logic inspired from the elanmoc libfprint driver
* which is copyright (C) 2021 Elan Microelectronics Inc (see elanmoc.c)
*
* Based on original reverse-engineering work by Joshua Grisham. The protocol has
* been reverse-engineered from captures of the official Windows driver, and by
* testing commands on the sensor with a multiplatform Python prototype driver:
* https://github.com/joshuagrisham/galaxy-book2-pro-linux/tree/main/fingerprint/
*
* 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
@@ -27,16 +22,18 @@
#pragma once
#include "fpi-device.h"
#include "fpi-ssm.h"
G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, FpDevice)
#include "fpi-sdcp-device.h"
G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC, FpSdcpDevice)
#define EGISMOC_DRIVER_FULLNAME "Egis Technology (LighTuning) Match-on-Chip"
#define EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 (1 << 0)
#define EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 (1 << 1)
#define EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 (1 << 2)
#define EGISMOC_DRIVER_MAX_ENROLL_STAGES_15 (1 << 3)
#define EGISMOC_EP_CMD_OUT (0x02 | FPI_USB_ENDPOINT_OUT)
#define EGISMOC_EP_CMD_IN (0x81 | FPI_USB_ENDPOINT_IN)
@@ -52,7 +49,11 @@ G_DECLARE_FINAL_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FPI, DEVICE_EGISMOC,
#define EGISMOC_MAX_ENROLL_STAGES_DEFAULT 10
#define EGISMOC_MAX_ENROLL_NUM 10
#define EGISMOC_FINGERPRINT_DATA_SIZE 32
#define EGISMOC_FINGER_ON_SENSOR_TIMEOUT_USEC (10 * G_USEC_PER_SEC)
#define EGISMOC_CONNECT_RESPONSE_PREFIX_SIZE 15
#define EGISMOC_IDENTIFY_RESPONSE_PREFIX_SIZE 14
#define EGISMOC_ENROLL_STARTING_RESPONSE_PREFIX_SIZE 14
#define EGISMOC_LIST_RESPONSE_PREFIX_SIZE 14
#define EGISMOC_LIST_RESPONSE_SUFFIX_SIZE 2
@@ -72,6 +73,9 @@ static gsize cmd_fw_version_len = sizeof (cmd_fw_version) / sizeof (cmd_fw_versi
static guchar rsp_fw_version_suffix[] = {0x90, 0x00};
static gsize rsp_fw_version_suffix_len = sizeof (rsp_fw_version_suffix) / sizeof (rsp_fw_version_suffix[0]);
static guchar rsp_sensor_has_finger_suffix[] = {0x90, 0x00, 0x90, 0x00};
static gsize rsp_sensor_has_finger_suffix_len = sizeof (rsp_sensor_has_finger_suffix) / sizeof (rsp_sensor_has_finger_suffix[0]);
static guchar cmd_list[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x19, 0x04, 0x00, 0x00, 0x01, 0x40};
static gsize cmd_list_len = sizeof (cmd_list) / sizeof (cmd_list[0]);
@@ -93,18 +97,19 @@ static gsize cmd_sensor_enroll_len = sizeof (cmd_sensor_enroll) / sizeof (cmd_se
static guchar cmd_enroll_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x01, 0x00, 0x00, 0x00, 0x20};
static gsize cmd_enroll_starting_len = sizeof (cmd_enroll_starting) / sizeof (cmd_enroll_starting[0]);
static guchar rsp_enroll_starting_suffix[] = {0x90, 0x00};
static gsize rsp_enroll_starting_suffix_len = sizeof (rsp_enroll_starting_suffix) / sizeof (rsp_enroll_starting_suffix[0]);
static guchar cmd_sensor_start_capture[] = {0x00, 0x00, 0x00, 0x04, 0x50, 0x16, 0x02, 0x01};
static gsize cmd_sensor_start_capture_len = sizeof (cmd_sensor_start_capture) / sizeof (cmd_sensor_start_capture[0]);
static guchar cmd_capture_post_wait_finger[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x80};
static gsize cmd_capture_post_wait_finger_len = sizeof (cmd_capture_post_wait_finger) / sizeof (cmd_capture_post_wait_finger[0]);
static guchar cmd_read_capture[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x02, 0x02, 0x00, 0x00, 0x02};
static gsize cmd_read_capture_len = sizeof (cmd_read_capture) / sizeof (cmd_read_capture[0]);
static guchar rsp_read_success_prefix[] = {0x00, 0x00, 0x00, 0x04};
static gsize rsp_read_success_prefix_len = sizeof (rsp_read_success_prefix) / sizeof (rsp_read_success_prefix[0]);
static guchar rsp_read_success_suffix[] = {0x90, 0x00};
static gsize rsp_read_success_suffix_len = sizeof (rsp_read_success_suffix) / sizeof (rsp_read_success_suffix[0]);
static guchar rsp_read_offcenter_prefix[] = {0x00, 0x00, 0x00, 0x04};
static gsize rsp_read_offcenter_prefix_len = sizeof (rsp_read_offcenter_prefix) / sizeof (rsp_read_offcenter_prefix[0]);
static guchar rsp_read_offcenter_suffix[] = {0x64, 0x91};
static gsize rsp_read_offcenter_suffix_len = sizeof (rsp_read_offcenter_suffix) / sizeof (rsp_read_offcenter_suffix[0]);
static guchar rsp_read_dirty_prefix[] = {0x00, 0x00, 0x00, 0x02, 0x64};
@@ -112,6 +117,8 @@ static gsize rsp_read_dirty_prefix_len = sizeof (rsp_read_dirty_prefix) / sizeof
static guchar cmd_commit_starting[] = {0x00, 0x00, 0x00, 0x07, 0x50, 0x16, 0x05, 0x00, 0x00, 0x00, 0x20};
static gsize cmd_commit_starting_len = sizeof (cmd_commit_starting) / sizeof (cmd_commit_starting[0]);
static guchar rsp_commit_success_suffix[] = {0x90, 0x00};
static gsize rsp_commit_success_suffix_len = sizeof (rsp_commit_success_suffix) / sizeof (rsp_commit_success_suffix[0]);
/* commands which exist on the device but are currently not used */
@@ -130,8 +137,13 @@ static gsize cmd_commit_starting_len = sizeof (cmd_commit_starting) / sizeof (cm
/* prefixes/suffixes and other things for dynamically created command payloads */
#define EGISMOC_CHECK_BYTES_LENGTH 2
#define EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET 46
#define EGISMOC_CMD_CHECK_SEPARATOR_LENGTH 32
static guchar cmd_sdcp_connect_prefix[] = {0x00, 0x00, 0x00, 0x6b, 0x50, 0x57, 0x01, 0x00, 0x00, 0x00, 0x62, 0x20};
static gsize cmd_sdcp_connect_prefix_len = sizeof (cmd_sdcp_connect_prefix) / sizeof (cmd_sdcp_connect_prefix[0]);
static guchar cmd_sdcp_connect_suffix[] = {0x00, 0x00};
static gsize cmd_sdcp_connect_suffix_len = sizeof (cmd_sdcp_connect_suffix) / sizeof (cmd_sdcp_connect_suffix[0]);
static guchar rsp_sdcp_connect_success_suffix[] = {0x90, 0x00};
static gsize rsp_sdcp_connect_success_suffix_len = sizeof (rsp_sdcp_connect_success_suffix) / sizeof (rsp_sdcp_connect_success_suffix[0]);
static guchar cmd_new_print_prefix[] = {0x00, 0x00, 0x00, 0x27, 0x50, 0x16, 0x03, 0x00, 0x00, 0x00, 0x20};
static gsize cmd_new_print_prefix_len = sizeof (cmd_new_print_prefix) / sizeof (cmd_new_print_prefix[0]);
@@ -169,6 +181,18 @@ typedef enum {
DEV_INIT_STATES,
} DeviceInitStates;
typedef enum {
CONNECT,
CONNECT_RESPONSE,
CONNECT_STATES,
} ConnectStates;
typedef enum {
WAIT_FINGER_NOT_ON_SENSOR,
WAIT_FINGER_ON_SENSOR,
WAIT_FINGER_STATES,
} WaitFingerStates;
typedef enum {
IDENTIFY_GET_ENROLLED_IDS,
IDENTIFY_CHECK_ENROLLED_NUM,
@@ -177,8 +201,6 @@ typedef enum {
IDENTIFY_WAIT_FINGER,
IDENTIFY_SENSOR_CHECK,
IDENTIFY_CHECK,
IDENTIFY_COMPLETE_SENSOR_RESET,
IDENTIFY_COMPLETE,
IDENTIFY_STATES,
} IdentifyStates;
@@ -194,11 +216,10 @@ typedef enum {
ENROLL_CAPTURE_SENSOR_RESET,
ENROLL_CAPTURE_SENSOR_START_CAPTURE,
ENROLL_CAPTURE_WAIT_FINGER,
ENROLL_CAPTURE_POST_WAIT_FINGER,
ENROLL_CAPTURE_READ_RESPONSE,
ENROLL_COMMIT_START,
ENROLL_COMMIT,
ENROLL_COMMIT_SENSOR_RESET,
ENROLL_COMPLETE,
ENROLL_STATES,
} EnrollStates;

View File

@@ -1,676 +0,0 @@
/*
* Virtual driver for SDCP device debugging
*
* Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
*
* 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
*/
/*
* This is a virtual test driver to test the basic SDCP functionality.
* It uses the test binaries from Microsoft, which were extended to allow
* a simple chat with the device.
* The environment variable contains the to be executed binary including
* arguments. This binary should be compiled from the code in
* https://github.com/Microsoft/SecureDeviceConnectionProtocol
* or, until it is merged upstream
* https://github.com/benzea/SecureDeviceConnectionProtocol
*
* Note that using this as an external executable has the advantage that we
* do not need to link against mbedtls or any other crypto library.
*/
#define FP_COMPONENT "virtual_sdcp"
#include "fpi-log.h"
#include "fpi-ssm.h"
#include "../fpi-sdcp-device.h"
#include <glib/gstdio.h>
#include <gio/gio.h>
struct _FpDeviceVirtualSdcp
{
FpSdcpDevice parent;
GSubprocess *proc;
GOutputStream *proc_stdin;
GInputStream *proc_stdout;
/* Only valid while a read/write is pending */
GByteArray *msg_out;
GByteArray *msg_in;
GByteArray *connect_msg;
};
G_DECLARE_FINAL_TYPE (FpDeviceVirtualSdcp, fpi_device_virtual_sdcp, FPI, DEVICE_VIRTUAL_SDCP, FpSdcpDevice)
G_DEFINE_TYPE (FpDeviceVirtualSdcp, fpi_device_virtual_sdcp, FP_TYPE_SDCP_DEVICE)
guint8 ca_1[] = {
0x30, 0x82, 0x03, 0xFD, 0x30, 0x82, 0x03, 0x82, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x13, 0x33,
0x00, 0x00, 0x00, 0x07, 0xE8, 0x9D, 0x61, 0x62, 0x4D, 0x46, 0x0F, 0x95, 0x00, 0x00, 0x00, 0x00,
0x00, 0x07, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x81,
0x84, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67,
0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65,
0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15,
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72,
0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x2E, 0x30, 0x2C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25,
0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x53, 0x65,
0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x50, 0x43, 0x41,
0x20, 0x32, 0x30, 0x31, 0x38, 0x30, 0x1E, 0x17, 0x0D, 0x31, 0x38, 0x30, 0x31, 0x33, 0x31, 0x31,
0x39, 0x35, 0x34, 0x35, 0x33, 0x5A, 0x17, 0x0D, 0x32, 0x38, 0x30, 0x31, 0x33, 0x31, 0x32, 0x30,
0x30, 0x34, 0x35, 0x33, 0x5A, 0x30, 0x7D, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57,
0x61, 0x73, 0x68, 0x69, 0x6E, 0x67, 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55,
0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06,
0x03, 0x55, 0x04, 0x0A, 0x13, 0x15, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20,
0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x27, 0x30, 0x25, 0x06,
0x03, 0x55, 0x04, 0x03, 0x13, 0x1E, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48, 0x65,
0x6C, 0x6C, 0x6F, 0x20, 0x31, 0x39, 0x42, 0x39, 0x32, 0x39, 0x36, 0x35, 0x20, 0x43, 0x41, 0x20,
0x32, 0x30, 0x31, 0x38, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xBE,
0x4B, 0x90, 0x6E, 0x24, 0xFC, 0xA1, 0x53, 0xC8, 0xA7, 0x3C, 0x70, 0xE8, 0x97, 0xCD, 0x1B, 0x31,
0xE4, 0x95, 0x91, 0x7A, 0x58, 0xA2, 0x86, 0xA8, 0x70, 0xF6, 0x09, 0x30, 0x77, 0x99, 0x3D, 0x10,
0xDF, 0xF7, 0x95, 0x0F, 0x68, 0x83, 0xE6, 0xA4, 0x11, 0x7C, 0xDA, 0x82, 0xE7, 0x0B, 0x8B, 0xF2,
0x9D, 0x6B, 0x5B, 0xF5, 0x3E, 0x77, 0xB4, 0xC1, 0x0E, 0x49, 0x00, 0x83, 0xBA, 0x94, 0xF8, 0xA3,
0x82, 0x01, 0xD7, 0x30, 0x82, 0x01, 0xD3, 0x30, 0x10, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01,
0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E,
0x04, 0x16, 0x04, 0x14, 0x13, 0x93, 0xC8, 0xCD, 0xF2, 0x23, 0x9A, 0x2D, 0xC6, 0x9B, 0x2A, 0xEB,
0x9A, 0xAB, 0x99, 0x0B, 0x56, 0x04, 0x5E, 0x7C, 0x30, 0x65, 0x06, 0x03, 0x55, 0x1D, 0x20, 0x04,
0x5E, 0x30, 0x5C, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00, 0x30, 0x52, 0x06, 0x0C, 0x2B,
0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x4C, 0x83, 0x7D, 0x01, 0x01, 0x30, 0x42, 0x30, 0x40, 0x06,
0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3A,
0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E,
0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x44, 0x6F, 0x63, 0x73, 0x2F,
0x52, 0x65, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x6F, 0x72, 0x79, 0x2E, 0x68, 0x74, 0x6D, 0x00, 0x30,
0x19, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0C, 0x1E, 0x0A,
0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x1D,
0x0F, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0F, 0x06, 0x03, 0x55, 0x1D, 0x13, 0x01, 0x01,
0xFF, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xFF, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x1D, 0x23, 0x04,
0x18, 0x30, 0x16, 0x80, 0x14, 0xDA, 0xCA, 0x4B, 0xD0, 0x4C, 0x56, 0x03, 0x27, 0x5F, 0x97, 0xEB,
0x75, 0xA3, 0x02, 0xC3, 0xBF, 0x45, 0x9C, 0xF8, 0xB1, 0x30, 0x68, 0x06, 0x03, 0x55, 0x1D, 0x1F,
0x04, 0x61, 0x30, 0x5F, 0x30, 0x5D, 0xA0, 0x5B, 0xA0, 0x59, 0x86, 0x57, 0x68, 0x74, 0x74, 0x70,
0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74,
0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x63, 0x72, 0x6C, 0x2F,
0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x25, 0x32, 0x30, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x25,
0x32, 0x30, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x25, 0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63,
0x65, 0x73, 0x25, 0x32, 0x30, 0x50, 0x43, 0x41, 0x25, 0x32, 0x30, 0x32, 0x30, 0x31, 0x38, 0x2E,
0x63, 0x72, 0x6C, 0x30, 0x75, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
0x59, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72,
0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73,
0x2F, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2F, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x25, 0x32,
0x30, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x25, 0x32, 0x30, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x25,
0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32, 0x30, 0x50, 0x43, 0x41, 0x25,
0x32, 0x30, 0x32, 0x30, 0x31, 0x38, 0x2E, 0x63, 0x72, 0x74, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86,
0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x03, 0x69, 0x00, 0x30, 0x66, 0x02, 0x31, 0x00, 0x87, 0xB6,
0x82, 0xF3, 0xDA, 0xBE, 0xB1, 0x7B, 0x98, 0x7D, 0x3D, 0x0A, 0x90, 0xA8, 0xF5, 0xBF, 0x15, 0xC3,
0xEE, 0x8A, 0x4E, 0xC0, 0x7B, 0x10, 0x1D, 0xA9, 0xE3, 0x0B, 0xEC, 0x2C, 0x53, 0x4E, 0xA7, 0xBD,
0xF1, 0x6C, 0xAD, 0x18, 0x55, 0xBA, 0x25, 0x73, 0x55, 0xB7, 0x5B, 0x12, 0x24, 0xF4, 0x02, 0x31,
0x00, 0xAF, 0x02, 0x9C, 0x4B, 0x92, 0xD0, 0x72, 0xA5, 0x80, 0xCA, 0x69, 0x2B, 0x38, 0x50, 0x64,
0xD8, 0x58, 0x9E, 0xEA, 0xD6, 0x35, 0xCF, 0x68, 0x98, 0x92, 0x81, 0x09, 0x61, 0xC2, 0xBD, 0xB1,
0x4C, 0x7F, 0xAE, 0x55, 0x7B, 0xFC, 0x22, 0xDD, 0xD6, 0xB7, 0x7C, 0xB5, 0xA8, 0x18, 0x5D, 0x33,
0x04
};
guint8 ca_2[] = {
0x30, 0x82, 0x04, 0x56, 0x30, 0x82, 0x03, 0xDC, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x13, 0x33,
0x00, 0x00, 0x00, 0x03, 0x6C, 0xCF, 0xED, 0xE2, 0x44, 0x70, 0x19, 0xBF, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03, 0x30, 0x81,
0x94, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67,
0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65,
0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15,
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72,
0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x3E, 0x30, 0x3C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x35,
0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x45, 0x43, 0x43, 0x20, 0x44, 0x65,
0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79,
0x20, 0x32, 0x30, 0x31, 0x37, 0x30, 0x1E, 0x17, 0x0D, 0x31, 0x38, 0x30, 0x31, 0x32, 0x35, 0x31,
0x39, 0x34, 0x39, 0x33, 0x38, 0x5A, 0x17, 0x0D, 0x33, 0x33, 0x30, 0x31, 0x32, 0x35, 0x31, 0x39,
0x35, 0x39, 0x33, 0x38, 0x5A, 0x30, 0x81, 0x84, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A,
0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67, 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03,
0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C,
0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74,
0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x2E, 0x30, 0x2C,
0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48,
0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69,
0x63, 0x65, 0x73, 0x20, 0x50, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x38, 0x30, 0x76, 0x30, 0x10,
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
0x03, 0x62, 0x00, 0x04, 0x1D, 0xDD, 0x08, 0x02, 0x03, 0x25, 0x75, 0x20, 0xE2, 0x71, 0x8B, 0xAD,
0x28, 0x09, 0x82, 0xE9, 0x06, 0xEE, 0x83, 0xC5, 0x3A, 0x6C, 0x4B, 0x71, 0x92, 0x50, 0x4E, 0x20,
0xE9, 0x72, 0xB4, 0xFC, 0x53, 0x2A, 0xEF, 0x5D, 0xCC, 0x9A, 0xB4, 0xCD, 0x76, 0xB8, 0x94, 0x97,
0x44, 0xB2, 0x71, 0x0E, 0xC9, 0xB1, 0x16, 0x03, 0xA1, 0x65, 0x2B, 0xB9, 0xE8, 0x5D, 0x5F, 0xF2,
0x30, 0x2E, 0xDD, 0xB1, 0x2B, 0x20, 0xFC, 0xBE, 0x00, 0x88, 0xEA, 0x1F, 0xA7, 0x7F, 0x99, 0x84,
0x98, 0x7C, 0x71, 0x3E, 0x4D, 0x34, 0x83, 0x69, 0x9B, 0x08, 0xCB, 0x78, 0xB2, 0x4B, 0xBD, 0xD7,
0x3E, 0xBE, 0x67, 0xA0, 0xA3, 0x82, 0x01, 0xFC, 0x30, 0x82, 0x01, 0xF8, 0x30, 0x10, 0x06, 0x09,
0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x1D,
0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xDA, 0xCA, 0x4B, 0xD0, 0x4C, 0x56, 0x03,
0x27, 0x5F, 0x97, 0xEB, 0x75, 0xA3, 0x02, 0xC3, 0xBF, 0x45, 0x9C, 0xF8, 0xB1, 0x30, 0x65, 0x06,
0x03, 0x55, 0x1D, 0x20, 0x04, 0x5E, 0x30, 0x5C, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00,
0x30, 0x52, 0x06, 0x0C, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x4C, 0x83, 0x7D, 0x01, 0x01,
0x30, 0x42, 0x30, 0x40, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x34,
0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F,
0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F,
0x44, 0x6F, 0x63, 0x73, 0x2F, 0x52, 0x65, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x6F, 0x72, 0x79, 0x2E,
0x68, 0x74, 0x6D, 0x00, 0x30, 0x19, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14,
0x02, 0x04, 0x0C, 0x1E, 0x0A, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30,
0x0B, 0x06, 0x03, 0x55, 0x1D, 0x0F, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0F, 0x06, 0x03,
0x55, 0x1D, 0x13, 0x01, 0x01, 0xFF, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xFF, 0x30, 0x1F, 0x06,
0x03, 0x55, 0x1D, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x14, 0xDA, 0x5B, 0xF1, 0x0E, 0x66,
0x47, 0xD1, 0x5D, 0x13, 0x5F, 0x5B, 0x7A, 0xEB, 0xEB, 0x5F, 0x01, 0x08, 0xB5, 0x49, 0x30, 0x7A,
0x06, 0x03, 0x55, 0x1D, 0x1F, 0x04, 0x73, 0x30, 0x71, 0x30, 0x6F, 0xA0, 0x6D, 0xA0, 0x6B, 0x86,
0x69, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72,
0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73,
0x2F, 0x63, 0x72, 0x6C, 0x2F, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x25, 0x32,
0x30, 0x45, 0x43, 0x43, 0x25, 0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32,
0x30, 0x52, 0x6F, 0x6F, 0x74, 0x25, 0x32, 0x30, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x25,
0x32, 0x30, 0x32, 0x30, 0x31, 0x37, 0x2E, 0x63, 0x72, 0x6C, 0x30, 0x81, 0x87, 0x06, 0x08, 0x2B,
0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x7B, 0x30, 0x79, 0x30, 0x77, 0x06, 0x08, 0x2B,
0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x6B, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F,
0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F,
0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2F, 0x4D,
0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x25, 0x32, 0x30, 0x45, 0x43, 0x43, 0x25, 0x32,
0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32, 0x30, 0x52, 0x6F, 0x6F, 0x74, 0x25,
0x32, 0x30, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x25, 0x32, 0x30,
0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32, 0x30, 0x32, 0x30, 0x31, 0x37,
0x2E, 0x63, 0x72, 0x74, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03,
0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, 0x56, 0x2A, 0xAD, 0x72, 0x4C, 0xB9, 0x8C, 0xB3, 0x23,
0x80, 0xF5, 0x5F, 0xF8, 0x21, 0x94, 0x66, 0x0F, 0x76, 0x77, 0xE2, 0x7B, 0x03, 0xDD, 0x30, 0x5E,
0xCB, 0x90, 0xCA, 0x78, 0xE6, 0x0B, 0x2D, 0x12, 0xE5, 0xF7, 0x67, 0x31, 0x58, 0x71, 0xE6, 0xF3,
0x64, 0xC1, 0x04, 0xB3, 0x8B, 0xE9, 0xE2, 0x02, 0x31, 0x00, 0xB9, 0x20, 0x61, 0xB9, 0xD0, 0x5E,
0x3A, 0xA4, 0xA2, 0x8A, 0xFE, 0x1D, 0xFC, 0x27, 0x61, 0x0B, 0x98, 0x16, 0x8C, 0x02, 0x9C, 0x20,
0x7F, 0xEE, 0xF3, 0xCB, 0x1F, 0x0A, 0x37, 0x62, 0xB1, 0x8E, 0xCE, 0xD9, 0x9A, 0x9E, 0xAC, 0xE6,
0x1A, 0xD4, 0xB8, 0xF1, 0xA8, 0x2B, 0xB1, 0xB4, 0x40, 0x9B
};
static void
msg_written_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
GOutputStream *stream = G_OUTPUT_STREAM (source_object);
FpiSsm *ssm = user_data;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (fpi_ssm_get_device (ssm));
g_clear_pointer (&self->msg_out, g_byte_array_unref);
g_assert (self->msg_out == NULL);
if (!g_output_stream_write_all_finish (stream, res, NULL, &error))
{
fpi_ssm_mark_failed (ssm, error);
return;
}
fpi_ssm_next_state (ssm);
}
static void
msg_received_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
GError *error = NULL;
GInputStream *stream = G_INPUT_STREAM (source_object);
FpiSsm *ssm = user_data;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (fpi_ssm_get_device (ssm));
gsize read;
g_assert (self->msg_out == NULL);
if (!g_input_stream_read_all_finish (stream, res, &read, &error) ||
read != self->msg_in->len)
{
g_clear_pointer (&self->msg_in, g_byte_array_unref);
if (!error)
error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Received EOF while reading from test binary.");
fpi_ssm_mark_failed (ssm, error);
return;
}
fpi_ssm_next_state (ssm);
}
enum {
SEND_MESSAGE,
RECV_MESSAGE,
SEND_RECV_STATES
};
static void
send_recv_ssm (FpiSsm *ssm, FpDevice *dev)
{
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
switch (fpi_ssm_get_cur_state (ssm))
{
case SEND_MESSAGE:
g_output_stream_write_all_async (self->proc_stdin,
self->msg_out->data,
self->msg_out->len,
G_PRIORITY_DEFAULT,
fpi_device_get_cancellable (FP_DEVICE (dev)),
msg_written_cb,
ssm);
break;
case RECV_MESSAGE:
g_input_stream_read_all_async (self->proc_stdout,
self->msg_in->data,
self->msg_in->len,
G_PRIORITY_DEFAULT,
fpi_device_get_cancellable (FP_DEVICE (dev)),
msg_received_cb,
ssm);
break;
}
}
static void
connect_2_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
g_autoptr(GBytes) recv_data = NULL;
g_autoptr(GBytes) r_d = NULL;
g_autoptr(FpiSdcpClaim) claim = NULL;
g_autoptr(GBytes) mac = NULL;
g_autoptr(GBytes) ca_1_bytes = NULL, ca_2_bytes = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
guint16 cert_size;
if (error)
{
fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), NULL, NULL, NULL, error);
g_clear_pointer (&self->connect_msg, g_byte_array_unref);
return;
}
memcpy (&cert_size, self->connect_msg->data + 32, 2);
g_byte_array_append (self->connect_msg, self->msg_in->data, self->msg_in->len);
g_clear_pointer (&self->msg_in, g_byte_array_unref);
/* Double check that the size is correct. */
g_assert (self->connect_msg->len == 32 + (2 + cert_size + 65 + 65 + 32 + 64 + 64) + 32);
recv_data = g_byte_array_free_to_bytes (g_steal_pointer (&self->connect_msg));
claim = fpi_sdcp_claim_new ();
r_d = g_bytes_new_from_bytes (recv_data, 0, 32);
claim->cert_m = g_bytes_new_from_bytes (recv_data, 34, cert_size);
claim->pk_d = g_bytes_new_from_bytes (recv_data, 34 + cert_size, 65);
claim->pk_f = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65, 65);
claim->h_f = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65, 32);
claim->s_m = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32, 64);
claim->s_d = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32 + 64, 64);
mac = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32 + 64 + 64, 32);
ca_1_bytes = g_bytes_new_static (ca_1, G_N_ELEMENTS (ca_1));
ca_2_bytes = g_bytes_new_static (ca_2, G_N_ELEMENTS (ca_2));
fpi_sdcp_device_set_intermediat_cas (FP_SDCP_DEVICE (dev),
ca_1_bytes,
ca_2_bytes);
fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), r_d, claim, mac, NULL);
}
static void
connect_1_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
guint16 cert_size;
if (error)
{
fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), NULL, NULL, NULL, error);
return;
}
g_clear_pointer (&self->connect_msg, g_byte_array_unref);
self->connect_msg = g_steal_pointer (&self->msg_in);
memcpy (&cert_size, self->connect_msg->data + 32, 2);
/* Nothing to send and the rest to receive. */
self->msg_out = g_byte_array_new ();
self->msg_in = g_byte_array_new ();
g_byte_array_set_size (self->msg_in, 32 + (2 + cert_size + 65 + 65 + 32 + 64 + 64) + 32 - self->connect_msg->len);
/* New SSM */
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect 2");
fpi_ssm_start (ssm, connect_2_cb);
}
static void
connect (FpSdcpDevice *dev)
{
g_autoptr(GBytes) r_h = NULL;
g_autoptr(GBytes) pk_h = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
FpiSsm *ssm;
G_DEBUG_HERE ();
g_assert (self->proc);
g_assert (self->connect_msg == NULL);
fpi_sdcp_device_get_connect_data (dev, &r_h, &pk_h);
self->msg_out = g_byte_array_new ();
g_byte_array_append (self->msg_out, (const guint8 *) "C", 1);
g_byte_array_append (self->msg_out,
g_bytes_get_data (r_h, NULL),
g_bytes_get_size (r_h));
g_byte_array_append (self->msg_out,
g_bytes_get_data (pk_h, NULL),
g_bytes_get_size (pk_h));
self->msg_in = g_byte_array_new ();
g_byte_array_set_size (self->msg_in, 34);
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect");
fpi_ssm_start (ssm, connect_1_cb);
}
static void
reconnect_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
g_autoptr(GBytes) mac = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
if (error)
{
fpi_sdcp_device_reconnect_complete (FP_SDCP_DEVICE (dev), mac, error);
return;
}
mac = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
fpi_sdcp_device_reconnect_complete (FP_SDCP_DEVICE (dev), mac, NULL);
}
static void
reconnect (FpSdcpDevice *dev)
{
g_autoptr(GBytes) r_h = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
FpiSsm *ssm;
G_DEBUG_HERE ();
g_assert (self->proc);
fpi_sdcp_device_get_reconnect_data (dev, &r_h);
self->msg_out = g_byte_array_new ();
g_byte_array_append (self->msg_out, (const guint8 *) "R", 1);
g_byte_array_append (self->msg_out,
g_bytes_get_data (r_h, NULL),
g_bytes_get_size (r_h));
self->msg_in = g_byte_array_new ();
g_byte_array_set_size (self->msg_in, 32);
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect 2");
fpi_ssm_start (ssm, reconnect_cb);
}
static void
enroll_begin_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
g_autoptr(GBytes) nonce = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
if (error)
{
fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), error);
return;
}
nonce = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
fpi_sdcp_device_enroll_set_nonce (FP_SDCP_DEVICE (dev), nonce);
/* Claim that we completed one enroll step. */
fpi_device_enroll_progress (dev, 1, NULL, NULL);
/* And signal that we are ready to commit. */
fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), NULL);
}
static void
enroll_begin (FpSdcpDevice *dev)
{
g_autoptr(GBytes) r_h = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
FpiSsm *ssm;
G_DEBUG_HERE ();
g_assert (self->proc);
fpi_sdcp_device_get_reconnect_data (dev, &r_h);
self->msg_out = g_byte_array_new ();
g_byte_array_append (self->msg_out, (const guint8 *) "E", 1);
/* Expect 32 byte nonce */
self->msg_in = g_byte_array_new ();
g_byte_array_set_size (self->msg_in, 32);
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "enroll_begin");
fpi_ssm_start (ssm, enroll_begin_cb);
}
static void
enroll_commit_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
g_clear_pointer (&self->msg_in, g_byte_array_unref);
if (error)
{
fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), error);
return;
}
/* Signal that we have committed. We don't expect a response
* from the virtual device (even though that is kind of broken).
*/
fpi_sdcp_device_enroll_commit_complete (FP_SDCP_DEVICE (dev), NULL);
}
static void
enroll_commit (FpSdcpDevice *dev, GBytes *id_in)
{
g_autoptr(GBytes) r_h = NULL;
g_autoptr(GBytes) id = id_in;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
FpiSsm *ssm;
G_DEBUG_HERE ();
g_assert (self->proc);
fpi_sdcp_device_get_reconnect_data (dev, &r_h);
self->msg_out = g_byte_array_new ();
self->msg_in = g_byte_array_new ();
if (id)
{
g_byte_array_append (self->msg_out, (const guint8 *) "F", 1);
g_byte_array_append (self->msg_out,
g_bytes_get_data (id, NULL),
g_bytes_get_size (id));
/* NOTE: No response from device, assume commit works. */
}
else
{
/* Cancel enroll (does not receive a reply) */
g_byte_array_append (self->msg_out, (const guint8 *) "G", 1);
/* NOTE: No response from device, assume cancellation works. */
}
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "enroll_commit");
fpi_ssm_start (ssm, enroll_commit_cb);
}
static void
identify_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
{
g_autoptr(GBytes) reply = NULL;
g_autoptr(GBytes) id = NULL;
g_autoptr(GBytes) mac = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
if (error)
{
fpi_sdcp_device_identify_complete (FP_SDCP_DEVICE (dev), NULL, NULL, error);
return;
}
reply = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
id = g_bytes_new_from_bytes (reply, 0, 32);
mac = g_bytes_new_from_bytes (reply, 32, 32);
fpi_sdcp_device_identify_complete (FP_SDCP_DEVICE (self), id, mac, NULL);
}
static void
identify (FpSdcpDevice *dev)
{
g_autoptr(GBytes) nonce = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
FpiSsm *ssm;
G_DEBUG_HERE ();
g_assert (self->proc);
fpi_sdcp_device_get_identify_data (dev, &nonce);
self->msg_out = g_byte_array_new ();
g_byte_array_append (self->msg_out, (const guint8 *) "I", 1);
g_byte_array_append (self->msg_out, g_bytes_get_data (nonce, NULL), g_bytes_get_size (nonce));
/* Expect 64 byte nonce */
self->msg_in = g_byte_array_new ();
g_byte_array_set_size (self->msg_in, 64);
ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "identify");
fpi_ssm_start (ssm, identify_cb);
}
static void
probe (FpDevice *dev)
{
g_auto(GStrv) argv = NULL;
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
GError *error = NULL;
const char *env;
/* We launch the test binary alread at probe time and quit only when
* the object is finalized. This allows testing reconnect properly.
*
* Also, we'll fail probe if something goes wrong executing it.
*/
env = fpi_device_get_virtual_env (FP_DEVICE (self));
if (!g_shell_parse_argv (env, NULL, &argv, &error))
goto out;
self->proc = g_subprocess_newv ((const char * const *) argv,
G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
&error);
if (!self->proc)
goto out;
self->proc_stdin = g_object_ref (g_subprocess_get_stdin_pipe (self->proc));
self->proc_stdout = g_object_ref (g_subprocess_get_stdout_pipe (self->proc));
out:
fpi_device_probe_complete (dev, "virtual-sdcp", NULL, error);
}
static void
dev_close (FpDevice *dev)
{
/* No-op, needs to be defined. */
fpi_device_close_complete (dev, NULL);
}
static void
fpi_device_virtual_sdcp_init (FpDeviceVirtualSdcp *self)
{
}
static void
fpi_device_virtual_sdcp_finalize (GObject *obj)
{
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (obj);
/* Just kill the subprocess, no need to be graceful here. */
if (self->proc)
g_subprocess_force_exit (self->proc);
g_clear_object (&self->proc);
g_clear_object (&self->proc_stdin);
g_clear_object (&self->proc_stdout);
G_OBJECT_CLASS (fpi_device_virtual_sdcp_parent_class)->finalize (obj);
}
static const FpIdEntry driver_ids[] = {
{ .virtual_envvar = "FP_VIRTUAL_SDCP" },
{ .virtual_envvar = NULL }
};
static void
fpi_device_virtual_sdcp_class_init (FpDeviceVirtualSdcpClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS (klass);
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpSdcpDeviceClass *sdcp_class = FP_SDCP_DEVICE_CLASS (klass);
obj_class->finalize = fpi_device_virtual_sdcp_finalize;
dev_class->id = FP_COMPONENT;
dev_class->full_name = "Virtual SDCP device talking to MS test code";
dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
dev_class->id_table = driver_ids;
dev_class->nr_enroll_stages = 1;
/* The SDCP base class may need to override this in the long run */
dev_class->probe = probe;
dev_class->close = dev_close;
sdcp_class->connect = connect;
sdcp_class->reconnect = reconnect;
sdcp_class->enroll_begin = enroll_begin;
sdcp_class->enroll_commit = enroll_commit;
sdcp_class->identify = identify;
}

View File

@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,38 +22,26 @@
#include "fpi-sdcp-device.h"
#include <nss.h>
#include <keyhi.h>
#include <keythi.h>
#include <pk11pub.h>
typedef struct
{
GError *enroll_pre_commit_error;
/* XXX: Do we want a separate SDCP session object?
*/
GPtrArray *intermediate_cas;
/* Host random for the connection */
guint8 host_random[32];
NSSInitContext *nss_init_context;
PK11SlotInfo *slot;
SECKEYPrivateKey *host_key_private;
SECKEYPublicKey *host_key_public;
/* Master secret is required for reconnects.
* TODO: We probably want to serialize this to disk so it can survive
* fprintd idle shutdown. */
PK11SymKey *master_secret;
PK11SymKey *mac_secret;
GBytes *host_private_key;
GBytes *host_public_key;
GBytes *host_random;
GBytes *reconnect_random;
GBytes *identify_nonce;
GVariant *data;
} FpSdcpDevicePrivate;
void fpi_sdcp_device_get_application_secret (FpSdcpDevice *self,
GBytes **application_secret);
void fpi_sdcp_device_set_application_secret (FpSdcpDevice *self,
GBytes *application_secret);
void fpi_sdcp_device_unset_application_secret (FpSdcpDevice *self);
void fpi_sdcp_device_open (FpSdcpDevice *self);
void fpi_sdcp_device_connect (FpSdcpDevice *self);
void fpi_sdcp_device_reconnect (FpSdcpDevice *self);
void fpi_sdcp_device_list (FpSdcpDevice *self);
void fpi_sdcp_device_enroll (FpSdcpDevice *self);
void fpi_sdcp_device_identify (FpSdcpDevice *self);

View File

@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -32,15 +33,13 @@
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpSdcpDevice, fp_sdcp_device, FP_TYPE_DEVICE)
#if 0
/* XXX: We'll very likely want/need some properties on this class. */
enum {
PROP_0,
PROP_SDCP_DATA,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
#endif
/*******************************************************/
@@ -49,13 +48,16 @@ static void
fp_sdcp_device_open (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
/* Try a reconnect if we still have the mac secret. */
if (priv->mac_secret)
fpi_sdcp_device_reconnect (self);
else
fpi_sdcp_device_connect (self);
fpi_sdcp_device_open (self);
}
static void
fp_sdcp_device_list (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
fpi_sdcp_device_list (self);
}
static void
@@ -82,13 +84,12 @@ fp_sdcp_device_finalize (GObject *object)
FpSdcpDevice *self = (FpSdcpDevice *) object;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_clear_pointer (&priv->intermediate_cas, g_ptr_array_unref);
g_clear_pointer (&priv->slot, PK11_FreeSlot);
g_clear_pointer (&priv->host_key_private, SECKEY_DestroyPrivateKey);
g_clear_pointer (&priv->host_key_public, SECKEY_DestroyPublicKey);
g_clear_pointer (&priv->master_secret, PK11_FreeSymKey);
g_clear_pointer (&priv->mac_secret, PK11_FreeSymKey);
g_clear_pointer (&priv->nss_init_context, NSS_ShutdownContext);
g_clear_pointer (&priv->host_private_key, g_bytes_unref);
g_clear_pointer (&priv->host_public_key, g_bytes_unref);
g_clear_pointer (&priv->host_random, g_bytes_unref);
g_clear_pointer (&priv->reconnect_random, g_bytes_unref);
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
g_clear_pointer (&priv->data, g_variant_unref);
G_OBJECT_CLASS (fp_sdcp_device_parent_class)->finalize (object);
}
@@ -99,8 +100,36 @@ fp_sdcp_device_get_property (GObject *object,
GValue *value,
GParamSpec *pspec)
{
FpSdcpDevice *self = (FpSdcpDevice *) object;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
switch (prop_id)
{
case PROP_SDCP_DATA:
g_value_set_variant (value, priv->data);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
fp_sdcp_device_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (object);
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
switch (prop_id)
{
case PROP_SDCP_DATA:
g_clear_pointer (&priv->data, g_variant_unref);
priv->data = g_value_dup_variant (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -120,22 +149,30 @@ fp_sdcp_device_class_init (FpSdcpDeviceClass *klass)
object_class->finalize = fp_sdcp_device_finalize;
object_class->get_property = fp_sdcp_device_get_property;
object_class->set_property = fp_sdcp_device_set_property;
object_class->constructed = fp_sdcp_device_constructed;
fp_device_class->open = fp_sdcp_device_open;
fp_device_class->list = fp_sdcp_device_list;
fp_device_class->enroll = fp_sdcp_device_enroll;
fp_device_class->verify = fp_sdcp_device_identify;
fp_device_class->identify = fp_sdcp_device_identify;
#if 0
properties[PROP_SDCP_DATA] =
g_param_spec_variant ("sdcp-data",
"SDCP Data",
"SDCP-related device data that should be persisted and used with the "
"device during the current system boot",
G_VARIANT_TYPE_ANY,
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
g_object_class_install_properties (object_class, N_PROPS, properties);
#endif
fpi_device_class_auto_initialize_features (fp_device_class);
}
static void
fp_sdcp_device_init (FpSdcpDevice *self)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
priv->intermediate_cas = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
}

56
libfprint/fpi-log.c Normal file
View File

@@ -0,0 +1,56 @@
/*
* FpiLog - Internal logging functions
* Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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
*/
#include "fpi-log.h"
#undef fp_dbg_hex_dump_bytes
#undef fp_dbg_hex_dump_gbytes
void
fp_dbg_hex_dump_bytes (const gchar *log_domain,
const guint8 *buf,
gsize len)
{
g_autoptr(GString) line = NULL;
line = g_string_new ("");
for (gint i = 0; i < len; i++)
{
g_string_append_printf (line, "%02x ", buf[i]);
if ((i + 1) % 16 == 0)
{
g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", line->str);
g_string_set_size (line, 0);
}
}
if (line->len)
g_log (log_domain, G_LOG_LEVEL_DEBUG, "%s", line->str);
}
void
fp_dbg_hex_dump_gbytes (const gchar *log_domain,
GBytes *gbytes)
{
gsize len = 0;
const guint8 *buf = g_bytes_get_data (gbytes, &len);
fp_dbg_hex_dump_bytes (log_domain, buf, len);
}

View File

@@ -96,3 +96,32 @@
* Same as BUG_ON() but is always true.
*/
#define BUG() BUG_ON (1)
/*
* Custom-defined logging functions are wrapped in macros for convenience so
* that the caller does not have to pass G_LOG_DOMAIN every time.
*/
void fp_dbg_hex_dump_bytes (const gchar *log_domain,
const guint8 *buf,
gsize len);
/**
* fp_dbg_hex_dump_bytes:
* @buf: Bytes buffer to dump
* @len: Length of @buf to dump
*
* Prints hex dump of @buf to fp_dbg()
*/
#define fp_dbg_hex_dump_bytes(buf, len) fp_dbg_hex_dump_bytes (G_LOG_DOMAIN, buf, len)
void fp_dbg_hex_dump_gbytes (const gchar *log_domain,
GBytes *gbytes);
/**
* fp_dbg_hex_dump_gbytes:
* @gbytes: #GBytes to dump
*
* Prints hex dump of @gbytes to fp_dbg()
*/
#define fp_dbg_hex_dump_gbytes(gbytes) fp_dbg_hex_dump_gbytes (G_LOG_DOMAIN, gbytes)

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,18 +20,29 @@
#pragma once
#include <glib-2.0/glib.h>
#include "fpi-device.h"
#include "fp-sdcp-device.h"
#define SDCP_PUBLIC_KEY_SIZE 65
#define SDCP_APPLICATION_SECRET_SIZE 32
#define SDCP_RANDOM_SIZE 32
#define SDCP_MAC_SIZE 32
#define SDCP_NONCE_SIZE 32
#define SDCP_ENROLLMENT_ID_SIZE 32
#define SDCP_SIGNATURE_SIZE 64
/**
* FpiSdcpClaim:
* @cert_m: The per-model ECDSA certificate (x509 ASN.1 DER encoded)
* @pk_d: The device public key (65 bytes)
* @pk_f: The firmware public key (65 bytes)
* @h_f: The firmware hash
* @s_m: Signature over @pk_d using the per-model private key (64 bytes)
* @s_d: Signature over h_f and pk_f using the device private key (64 bytes)
* @model_certificate: Microsoft-issued per-model certificate encoded in x509
* ASN.1 DER format (`cert_m`)
* @device_public_key: The per-device ECDSA public key (`pk_d`)
* @firmware_public_key: The ephemeral public key generated by the device
* firmware (`pk_f`)
* @firmware_hash: Hash of the firmware and firmware public key (`h_f`)
* @model_signature: Device public key signed by the model key (`s_m`)
* @device_signature: Firmware hash and public key signed by the device private
* key (`s_d`)
*
* Structure to hold the claim as produced by the device during a secure
* connect. See the SDCP specification for more details.
@@ -41,12 +53,12 @@
struct _FpiSdcpClaim
{
/*< public >*/
GBytes *cert_m;
GBytes *pk_d;
GBytes *pk_f;
GBytes *h_f;
GBytes *s_m;
GBytes *s_d;
GBytes *model_certificate; /* cert_m */
GBytes *device_public_key; /* pk_d */
GBytes *firmware_public_key; /* pk_f */
GBytes *firmware_hash; /* h_f */
GBytes *model_signature; /* s_m */
GBytes *device_signature; /* s_d */
};
typedef struct _FpiSdcpClaim FpiSdcpClaim;
@@ -60,30 +72,48 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSdcpClaim, fpi_sdcp_claim_free)
/**
* FpSdcpDeviceClass:
* @connect: Establish SDCP connection. Similar to open in #FpDeviceClass
* but called connect to mirror the SDCP specification.
* @ignore_device_certificate: Set to %TRUE to skip validating the device's
* #FpiSdcpClaim.model_certificate against the SDCP truststore.
* @ignore_device_signatures: Set to %TRUE to skip verifying the device's
* #FpiSdcpClaim.model_signature and #FpiSdcpClaim.device_signature.
* @open: Open the device. Similar to #FpDeviceClass.open except that
* completion with fpi_sdcp_device_open_complete() will also take care of
* executing @connect and @reconnect as necessary.
* @connect: Establish SDCP connection.
* @reconnect: Perform a faster reconnect. Drivers do not need to provide this
* function. If reconnect fails, then a normal connect will be tried.
* @enroll_begin: Start the enrollment procedure. In the absence of an error,
* the driver must call fpi_sdcp_device_enroll_set_nonce() at any point. It
* also must report enrollment progress using fpi_device_enroll_progress().
* It should also store available metadata about the print in device memory.
* The operation is completed with fpi_sdcp_device_enroll_ready().
* @enroll_commit: Complete the enrollment procedure. This commits the newly
* enrolled print to the device memory. Will only be called if enroll_begin
* succeeded. The passed id may be %NULL, in that case the driver must
* abort the enrollment process. id is owned by the base class and remains
* valid throughout the operation.
* @list: List prints stored on the device. The driver must create a #GPtrArray
* of #GBytes with each enrollment ID stored on the device and use it to call
* fpi_sdcp_device_list_complete() in order to complete the operation.
* @enroll: Start the enrollment procedure and capture all samples. The driver
* must report enrollment progress using fpi_device_enroll_progress(). It
* should also store available metadata about the print in device memory. The
* driver must call fpi_sdcp_device_enroll_commit() when all enrollment stages
* are complete and the print is ready to be commited to the device.
* @enroll_commit: Commit the newly-enrolled print to the device memory using
* the passed id. id may be %NULL, in which case the driver must abort the
* enrollment process. id is owned by the base class and remains valid
* throughout the operation. On completion, the driver must call
* fpi_sdcp_device_enroll_commit_complete().
* @identify: Start identification process. On completion, the driver must call
* fpi_sdcp_device_identify_complete(). To request the user to retry the
* fpi_sdcp_device_identify_retry() function is used.
*
*
* These are the main entry points for drivers implementing SDCP.
*
* Drivers *must* eventually call the corresponding function to finish the
* operation.
*
* The following #FpDeviceClass entry points are also compatible and can be set
* on the #FpDeviceClass if supported for a given device:
* - #FpDeviceClass.probe
* - #FpDeviceClass.close
* - #FpDeviceClass.delete
* - #FpDeviceClass.clear_storage
* - #FpDeviceClass.cancel
* - #FpDeviceClass.suspend
* - #FpDeviceClass.resume
*
* XXX: Is the use of fpi_device_action_error() acceptable?
*
* Drivers *must* also handle cancellation properly for any long running
@@ -97,46 +127,59 @@ struct _FpSdcpDeviceClass
{
FpDeviceClass parent_class;
void (*connect) (FpSdcpDevice *dev);
void (*reconnect) (FpSdcpDevice *dev);
void (*close) (FpSdcpDevice *dev);
void (*enroll_begin) (FpSdcpDevice *dev);
void (*enroll_commit) (FpSdcpDevice *dev,
gboolean ignore_device_certificate;
gboolean ignore_device_signatures;
void (*open) (FpSdcpDevice *sdcp_device);
void (*connect) (FpSdcpDevice *sdcp_device);
void (*reconnect) (FpSdcpDevice *sdcp_device);
void (*list) (FpSdcpDevice *sdcp_device);
void (*enroll) (FpSdcpDevice *sdcp_device);
void (*enroll_commit) (FpSdcpDevice *sdcp_device,
GBytes *id);
void (*identify) (FpSdcpDevice *dev);
void (*identify) (FpSdcpDevice *sdcp_device);
};
void fpi_sdcp_device_set_intermediat_cas (FpSdcpDevice *self,
GBytes *ca_1,
GBytes *ca_2);
void fpi_sdcp_device_open_complete (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_get_connect_data (FpSdcpDevice *self,
GBytes **r_h,
GBytes **pk_h);
GBytes **host_random,
GBytes **host_public_key);
void fpi_sdcp_device_connect_complete (FpSdcpDevice *self,
GBytes *r_d,
GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
GError *error);
void fpi_sdcp_device_get_reconnect_data (FpSdcpDevice *self,
GBytes **r_h);
GBytes **reconnect_random);
void fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
GBytes *mac,
GError *error);
void fpi_sdcp_device_enroll_set_nonce (FpSdcpDevice *self,
GBytes *nonce);
void fpi_sdcp_device_enroll_ready (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_list_complete (FpSdcpDevice *self,
GPtrArray *ids,
GError *error);
void fpi_sdcp_device_enroll_commit (FpSdcpDevice *self,
GBytes *nonce,
GError *error);
void fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_get_identify_data (FpSdcpDevice *self,
GBytes **nonce);
void fpi_sdcp_device_set_identify_data (FpSdcpDevice *self,
GBytes *nonce);
void fpi_sdcp_device_identify_retry (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
GBytes *id,
GBytes *mac,
GError *error);
void fpi_sdcp_device_get_print_id (FpPrint *print,
GBytes **id);
void fpi_sdcp_device_set_print_id (FpPrint *print,
GBytes *id);

1053
libfprint/fpi-sdcp.c Normal file

File diff suppressed because it is too large Load Diff

132
libfprint/fpi-sdcp.h Normal file
View File

@@ -0,0 +1,132 @@
/*
* Secure Device Connection Protocol (SDCP) support implementation
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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 "fpi-compat.h"
#include "fpi-sdcp-device.h"
/**
* fpi_sdcp_generate_host_key:
* @private_key: (out) (transfer full): The host private key (sk_h)
* @public_key: (out) (transfer full): The host public key (pk_h)
* @error: (out): #GError in case the out values are %NULL
*
* Function to generate a new ephemeral ECDH key pair for use with SDCP.
**/
void fpi_sdcp_generate_host_key (GBytes **private_key,
GBytes **public_key,
GError **error);
/**
* fpi_sdcp_generate_random:
* @error: (out): #GError in case the return value is %NULL
*
* Returns: A new #GBytes with a secure random of length %SDCP_RANDOM_SIZE
**/
GBytes *fpi_sdcp_generate_random (GError **error);
/**
* fpi_sdcp_verify_connect:
* @host_private_key: Private key generated using fpi_sdcp_generate_host_key() (sk_h)
* @host_random: Random generated using fpi_sdcp_generate_random() (r_h)
* @device_random: The random provided in the device's ConnectResponse (r_d)
* @claim: #FpiSdcpClaim provided in the device's ConnectResponse (c)
* @mac: The MAC provided in the device's ConnectResponse (m)
* @validate_certificate: If the model certificate (cert_m) should be parsed and
* its trust chain validated as issued from Microsoft's well-known issuers
* @verify_signatures: If the model signature (s_m) and device signature (s_d)
* should be validated against the certificate and keys provided in the claim
* @application_secret: (out) (transfer full): A new #GBytes with the derived
* application secret (s) of length %SDCP_APPLICATION_SECRET_SIZE
* @error: (out): #GError in case the return value is %NULL
*
* High level function which internally handles the derivation of all necessary
* SDCP-related keys and secrets from the device's ConnectResponse and derives
* the application secret for use with all other SDCP-related functions.
*
* This function will also perform a validation of the ConnectResponse MAC and
* optionally perform additional verifications based on the provided
* @validate_certificate and @verify_signatures booleans. If any of these these
* validations fail then %NULL will be returned, indicating that the SDCP secure
* connection channel could not be established.
*
* Returns: %TRUE if the @application_secret was successfully derived and the
* ConnectResponse has been successfully verified
**/
gboolean fpi_sdcp_verify_connect (GBytes *host_private_key,
GBytes *host_random,
GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
gboolean validate_certificate,
gboolean verify_signatures,
GBytes **application_secret,
GError **error);
/**
* fpi_sdcp_verify_reconnect:
* @application_secret: The host's derived application secret (s)
* @random: The host-generated random sent to the device's Reconnect command (r)
* @mac: The MAC provided in the device's ReconnectResponse (m)
* @error: (out): #GError in case the return value is %FALSE
*
* Verifies the SDCP ReconnectResponse.
*
* Returns: %TRUE if the ReconnectResponse is verified successfully
**/
gboolean fpi_sdcp_verify_reconnect (GBytes *application_secret,
GBytes *random,
GBytes *mac,
GError **error);
/**
* fpi_sdcp_verify_identify:
* @application_secret: The host's derived application secret (s)
* @nonce: The host-generated nonce sent to the device's Identify command (n)
* @id: The ID provided in the device's AuthorizedIdentity (id)
* @mac: The MAC provided in the device's AuthorizedIdentity (m)
* @error: (out): #GError in case the return value is %FALSE
*
* Verifies the SDCP ReconnectResponse.
*
* Returns: %TRUE if the ReconnectResponse is verified successfully
**/
gboolean fpi_sdcp_verify_identify (GBytes *application_secret,
GBytes *nonce,
GBytes *id,
GBytes *mac,
GError **error);
/**
* fpi_sdcp_generate_enrollment_id:
* @application_secret: The host's derived application secret (s)
* @nonce: The nonce received from the device in response to the EnrollBegin
* command (n)
* @error: (out): #GError in case the return value is %NULL
*
* Generates a new id for use with the device's EnrollCommit command.
*
* Returns: A new #GBytes with the generated enrollment id of length
* %SDCP_ENROLLMENT_ID_SIZE
**/
GBytes *fpi_sdcp_generate_enrollment_id (GBytes *application_secret,
GBytes *nonce,
GError **error);

View File

@@ -21,6 +21,7 @@ libfprint_private_sources = [
'fpi-device.c',
'fpi-image-device.c',
'fpi-image.c',
'fpi-log.c',
'fpi-print.c',
'fpi-ssm.c',
'fpi-usb-transfer.c',
@@ -47,8 +48,8 @@ libfprint_private_headers = [
'fpi-log.h',
'fpi-minutiae.h',
'fpi-print.h',
'fpi-usb-transfer.h',
'fpi-ssm.h',
'fpi-usb-transfer.h',
] + spi_headers
nbis_sources = [
@@ -143,8 +144,6 @@ driver_sources = {
[ 'drivers/virtual-device.c' ],
'virtual_device_storage' :
[ 'drivers/virtual-device-storage.c' ],
'virtual_sdcp' :
[ 'drivers/virtual-sdcp.c' ],
'synaptics' :
[ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ],
'goodixmoc' :
@@ -158,8 +157,6 @@ driver_sources = {
}
helper_sources = {
'sdcp' :
[ 'fp-sdcp-device.c', 'fpi-sdcp-device.c' ],
'aeslib' :
[ 'drivers/aeslib.c' ],
'aesx660' :
@@ -168,6 +165,8 @@ helper_sources = {
[ 'drivers/aes3k.c' ],
'openssl' :
[ ],
'sdcp' :
[ ],
'udev' :
[ ],
'virtual' :
@@ -183,6 +182,24 @@ foreach helper : driver_helpers
drivers_sources += helper_sources[helper]
endforeach
subdir('sdcp')
if 'sdcp' in driver_helpers
libfprint_sources += [
'fp-sdcp-device.c',
]
libfprint_private_sources += [
'fpi-sdcp.c',
'fpi-sdcp-device.c',
sdcp_truststore_resource_c,
]
libfprint_public_headers += [
'fp-sdcp-device.h',
]
libfprint_private_headers += [
'fpi-sdcp.h',
'fpi-sdcp-device.h',
]
endif
fp_enums = gnome.mkenums_simple('fp-enums',
sources: libfprint_public_headers,

View File

@@ -0,0 +1,25 @@
#!/usr/bin/python3
import os
import sys
if len(sys.argv) != 3:
print("generate-gresource.py: Generates SDCP Truststore GResource XML file from certificates in ./truststore/*.pem")
print("Usage: generate-gresource.py <libfprint_sdcp_source_dir> <output_xml_file>")
sys.exit(1)
gresource_prefix = "/org/freedesktop/fprint/sdcp"
relative_folder = "truststore"
full_folder = os.path.join(sys.argv[1], relative_folder)
output = sys.argv[2]
files = [f for f in os.listdir(full_folder) if os.path.isfile(os.path.join(full_folder, f)) and f.endswith('.pem')]
with open(output, 'w') as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
f.write('<gresources>\n')
f.write(f' <gresource prefix="{gresource_prefix}">\n')
for file in files:
f.write(f' <file compressed="true">{relative_folder}/{file}</file>\n')
f.write(' </gresource>\n')
f.write('</gresources>\n')

View File

@@ -0,0 +1,32 @@
sdcp_truststore_gresource_xml = custom_target('sdcp-truststore.gresource',
input : 'generate-gresource.py',
output : 'sdcp-truststore.gresource.xml',
command : [find_program('python3'), '@INPUT@', meson.current_source_dir(), '@OUTPUT@'],
)
sdcp_truststore_resource_h = custom_target('fpi-sdcp-truststore-resource.h',
input : sdcp_truststore_gresource_xml,
output : 'fpi-sdcp-truststore-resource.h',
command : ['glib-compile-resources',
'--target=@OUTPUT@',
'--sourcedir=' + meson.current_source_dir(),
'--internal',
'--generate',
'--c-name', 'fpi_sdcp_truststore',
'--manual-register',
'@INPUT@']
)
sdcp_truststore_resource_c = custom_target('fpi-sdcp-truststore-resource.c',
depends : [sdcp_truststore_resource_h],
input : sdcp_truststore_gresource_xml,
output : 'fpi-sdcp-truststore-resource.c',
command : ['glib-compile-resources',
'--target=@OUTPUT@',
'--sourcedir=' + meson.current_source_dir(),
'--internal',
'--generate',
'--c-name', 'fpi_sdcp_truststore',
'--manual-register',
'@INPUT@']
)

View File

@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDIDCCAqWgAwIBAgIQKs9yK9kUXqlMVB+fSF1UMjAKBggqhkjOPQQDAzCBlDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE+MDwGA1UEAxM1TWlj
cm9zb2Z0IEVDQyBEZXZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
MTcwHhcNMTcxMTA5MTk0MDQ4WhcNNDIxMTA5MTk0ODE5WjCBlDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE+MDwGA1UEAxM1TWljcm9zb2Z0IEVD
QyBEZXZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAARiivDX0DS0EXoGlfbd2PwxSC87Cszr6/aAjSx6pMwU
4kzXcId0dhrjSkPSIO5UCz50ggQGQiTwqRzyhM44FlEyzbzl6OHGDwR1vAg3wdmm
WEXWySzyAZKsfkwg0G7bPkijgbkwgbYwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFBTaW/EOZkfRXRNfW3rr618BCLVJMBAGCSsGAQQBgjcV
AQQDAgEAMGUGA1UdIAReMFwwBgYEVR0gADBSBgwrBgEEAYI3TIN9AQEwQjBABggr
BgEFBQcCARY0aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1Jl
cG9zaXRvcnkuaHRtADAKBggqhkjOPQQDAwNpADBmAjEAxxAFFL8juLXiulXvZgBQ
pGGPCcV2Tr3CorZ4p/uO2/rtBemqhL3CjKAm40VlhEz8AjEArE5fhA54SEDjoTwZ
VUosaqXa8ych31qjZI+e1ttbOPebAZTt9ac7+lTzJcLTEQch
-----END CERTIFICATE-----

View File

@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwzCCA0qgAwIBAgITMwAAAAuNaMBkOddK4wAAAAAACzAKBggqhkjOPQQDAjCB
hDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEuMCwGA1UEAxMl
V2luZG93cyBIZWxsbyBTZWN1cmUgRGV2aWNlcyBQQ0EgMjAxODAeFw0yMTEyMDky
MzI1MDFaFw0zMDEyMDkyMzM1MDFaMFYxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVN
aWNyb3NvZnQgQ29ycG9yYXRpb24xJzAlBgNVBAMTHldpbmRvd3MgSGVsbG8gMjA5
NkFEQ0MgQ0EgMjAyMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJWgPvl44Gei
RrTuA3f1eT60pAlBWM7ym7WSchqz3hge1WS8RUxPVedu0f7MCe/R6O6RVjV7HWq4
c6jo9FwoiAGjggHGMIIBwjAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUvzdI
40pjLelTo7qJApjAaUcqmbkwVAYDVR0gBE0wSzBJBgRVHSAAMEEwPwYIKwYBBQUH
AgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0
b3J5Lmh0bTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTaykvQTFYDJ1+X63WjAsO/RZz4
sTBoBgNVHR8EYTBfMF2gW6BZhldodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
b3BzL2NybC9XaW5kb3dzJTIwSGVsbG8lMjBTZWN1cmUlMjBEZXZpY2VzJTIwUENB
JTIwMjAxOC5jcmwwdQYIKwYBBQUHAQEEaTBnMGUGCCsGAQUFBzAChllodHRwOi8v
d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL1dpbmRvd3MlMjBIZWxsbyUy
MFNlY3VyZSUyMERldmljZXMlMjBQQ0ElMjAyMDE4LmNydDAKBggqhkjOPQQDAgNn
ADBkAjAeGyYlzf+uBQXI/EW84I5CGFbo/U6dL4k1Y83f2p94d0wrNwjUb/yprb4+
L9+OKfQCME8PgRyJQxsvsne+WI6gr0ZzJilotoiRdvDzlMK4+hx5TGJWTV17AsAo
z2330epHQQ==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEVjCCA9ygAwIBAgITMwAAAANsz+3iRHAZvwAAAAAAAzAKBggqhkjOPQQDAzCB
lDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE+MDwGA1UEAxM1
TWljcm9zb2Z0IEVDQyBEZXZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
IDIwMTcwHhcNMTgwMTI1MTk0OTM4WhcNMzMwMTI1MTk1OTM4WjCBhDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEuMCwGA1UEAxMlV2luZG93cyBI
ZWxsbyBTZWN1cmUgRGV2aWNlcyBQQ0EgMjAxODB2MBAGByqGSM49AgEGBSuBBAAi
A2IABB3dCAIDJXUg4nGLrSgJgukG7oPFOmxLcZJQTiDpcrT8UyrvXcyatM12uJSX
RLJxDsmxFgOhZSu56F1f8jAu3bErIPy+AIjqH6d/mYSYfHE+TTSDaZsIy3iyS73X
Pr5noKOCAfwwggH4MBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTaykvQTFYD
J1+X63WjAsO/RZz4sTBlBgNVHSAEXjBcMAYGBFUdIAAwUgYMKwYBBAGCN0yDfQEB
MEIwQAYIKwYBBQUHAgEWNGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
RG9jcy9SZXBvc2l0b3J5Lmh0bQAwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEw
CwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUFNpb8Q5m
R9FdE19beuvrXwEItUkwegYDVR0fBHMwcTBvoG2ga4ZpaHR0cDovL3d3dy5taWNy
b3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwRUNDJTIwRGV2aWNlcyUy
MFJvb3QlMjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMTcuY3JsMIGHBggr
BgEFBQcBAQR7MHkwdwYIKwYBBQUHMAKGa2h0dHA6Ly93d3cubWljcm9zb2Z0LmNv
bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwRUNDJTIwRGV2aWNlcyUyMFJvb3Ql
MjBDZXJ0aWZpY2F0ZSUyMEF1dGhvcml0eSUyMDIwMTcuY3J0MAoGCCqGSM49BAMD
A2gAMGUCMFYqrXJMuYyzI4D1X/ghlGYPdnfiewPdMF7LkMp45gstEuX3ZzFYcebz
ZMEEs4vp4gIxALkgYbnQXjqkoor+HfwnYQuYFowCnCB/7vPLHwo3YrGOztmanqzm
GtS48agrsbRAmw==
-----END CERTIFICATE-----

View File

@@ -112,7 +112,6 @@ virtual_drivers = [
'virtual_image',
'virtual_device',
'virtual_device_storage',
'virtual_sdcp',
]
default_drivers = [
'upektc_img',
@@ -209,12 +208,12 @@ driver_helper_mapping = {
'aes2660' : [ 'aeslib', 'aesx660' ],
'aes3500' : [ 'aeslib', 'aes3k' ],
'aes4000' : [ 'aeslib', 'aes3k' ],
'uru4000' : [ 'openssl' ],
'egismoc' : [ 'sdcp' ],
'elanspi' : [ 'udev' ],
'uru4000' : [ 'openssl' ],
'virtual_image' : [ 'virtual' ],
'virtual_device' : [ 'virtual' ],
'virtual_device_storage' : [ 'virtual' ],
'virtual_sdcp' : [ 'sdcp' ],
}
driver_helpers = []

Binary file not shown.

View File

@@ -1,17 +1,21 @@
P: /devices/pci0000:00/0000:00:14.0/usb3/3-5
N: bus/usb/003/012=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005
N: bus/usb/003/002=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005
E: BUSNUM=003
E: CURRENT_TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd:
E: DEVNAME=/dev/bus/usb/003/012
E: DEVNUM=012
E: CURRENT_TAGS=:snap_cups_ippeveprinter:snap_android-platform-tools_fastboot:snap_android-platform-tools_adb:snap_cups_cupsd:
E: DEVNAME=/dev/bus/usb/003/002
E: DEVNUM=002
E: DEVTYPE=usb_device
E: DRIVER=usb
E: ID_AUTOSUSPEND=1
E: ID_BUS=usb
E: ID_MODEL=ETU905A80-E
E: ID_MODEL_ENC=ETU905A80-E
E: ID_MODEL_ID=0582
E: ID_PATH=pci-0000:00:14.0-usb-0:5
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_5
E: ID_PATH_WITH_USB_REVISION=pci-0000:00:14.0-usbv2-0:5
E: ID_PERSIST=0
E: ID_PROCESSING=1
E: ID_REVISION=1181
E: ID_SERIAL=EGIS_ETU905A80-E_0E7828PBS393
E: ID_SERIAL_SHORT=0E7828PBS393
@@ -30,10 +34,10 @@ E: ID_VENDOR_ENC=EGIS
E: ID_VENDOR_FROM_DATABASE=LighTuning Technology Inc.
E: ID_VENDOR_ID=1c7a
E: MAJOR=189
E: MINOR=267
E: MINOR=257
E: PRODUCT=1c7a/582/1181
E: SUBSYSTEM=usb
E: TAGS=:snap_cups_ippeveprinter:snap_cups_cupsd:
E: TAGS=:snap_cups_ippeveprinter:snap_android-platform-tools_fastboot:snap_cups_cupsd:snap_android-platform-tools_adb:
E: TYPE=255/0/0
A: authorized=1\n
A: avoid_reset_quirk=0\n
@@ -50,8 +54,8 @@ A: bmAttributes=a0\n
A: busnum=3\n
A: configuration=
H: descriptors=12010002FF0000407A1C820581110102030109022700010100A0320904000003FF000000070581020002000705020200020007058303400005
A: dev=189:267\n
A: devnum=12\n
A: dev=189:257\n
A: devnum=2\n
A: devpath=5\n
L: driver=../../../../../bus/usb/drivers/usb
L: firmware_node=../../../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51/device:52/device:57
@@ -66,20 +70,20 @@ A: physical_location/lid=no\n
A: physical_location/panel=unknown\n
A: physical_location/vertical_position=center\n
L: port=../3-0:1.0/usb3-port5
A: power/active_duration=1425996\n
A: power/active_duration=2329204\n
A: power/async=enabled\n
A: power/autosuspend=2\n
A: power/autosuspend_delay_ms=2000\n
A: power/connected_duration=1426656\n
A: power/control=on\n
A: power/level=on\n
A: power/persist=0\n
A: power/connected_duration=96447632\n
A: power/control=auto\n
A: power/level=auto\n
A: power/persist=1\n
A: power/runtime_active_kids=0\n
A: power/runtime_active_time=1426124\n
A: power/runtime_enabled=forbidden\n
A: power/runtime_active_time=2345067\n
A: power/runtime_enabled=enabled\n
A: power/runtime_status=active\n
A: power/runtime_suspended_time=0\n
A: power/runtime_usage=1\n
A: power/runtime_suspended_time=94056717\n
A: power/runtime_usage=0\n
A: power/wakeup=disabled\n
A: power/wakeup_abort_count=\n
A: power/wakeup_active=\n
@@ -96,13 +100,13 @@ A: rx_lanes=1\n
A: serial=0E7828PBS393\n
A: speed=480\n
A: tx_lanes=1\n
A: urbnum=2803\n
A: urbnum=10257\n
A: version= 2.00\n
P: /devices/pci0000:00/0000:00:14.0/usb3
N: bus/usb/003/001=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C
N: bus/usb/003/001=12010002090001406B1D020015060302010109021900010100E0000904000001090000000705810304000C
E: BUSNUM=003
E: CURRENT_TAGS=:seat:snap_cups_cupsd:snap_cups_ippeveprinter:
E: CURRENT_TAGS=:snap_cups_ippeveprinter:seat:snap_android-platform-tools_fastboot:snap_cups_cupsd:snap_android-platform-tools_adb:
E: DEVNAME=/dev/bus/usb/003/001
E: DEVNUM=001
E: DEVTYPE=usb_device
@@ -116,28 +120,28 @@ E: ID_MODEL_FROM_DATABASE=2.0 root hub
E: ID_MODEL_ID=0002
E: ID_PATH=pci-0000:00:14.0
E: ID_PATH_TAG=pci-0000_00_14_0
E: ID_REVISION=0602
E: ID_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
E: ID_REVISION=0615
E: ID_SERIAL=Linux_6.15.1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
E: ID_SERIAL_SHORT=0000:00:14.0
E: ID_USB_INTERFACES=:090000:
E: ID_USB_MODEL=xHCI_Host_Controller
E: ID_USB_MODEL_ENC=xHCI\x20Host\x20Controller
E: ID_USB_MODEL_ID=0002
E: ID_USB_REVISION=0602
E: ID_USB_SERIAL=Linux_6.2.0-34-generic_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
E: ID_USB_REVISION=0615
E: ID_USB_SERIAL=Linux_6.15.1_xhci-hcd_xHCI_Host_Controller_0000:00:14.0
E: ID_USB_SERIAL_SHORT=0000:00:14.0
E: ID_USB_VENDOR=Linux_6.2.0-34-generic_xhci-hcd
E: ID_USB_VENDOR_ENC=Linux\x206.2.0-34-generic\x20xhci-hcd
E: ID_USB_VENDOR=Linux_6.15.1_xhci-hcd
E: ID_USB_VENDOR_ENC=Linux\x206.15.1\x20xhci-hcd
E: ID_USB_VENDOR_ID=1d6b
E: ID_VENDOR=Linux_6.2.0-34-generic_xhci-hcd
E: ID_VENDOR_ENC=Linux\x206.2.0-34-generic\x20xhci-hcd
E: ID_VENDOR=Linux_6.15.1_xhci-hcd
E: ID_VENDOR_ENC=Linux\x206.15.1\x20xhci-hcd
E: ID_VENDOR_FROM_DATABASE=Linux Foundation
E: ID_VENDOR_ID=1d6b
E: MAJOR=189
E: MINOR=256
E: PRODUCT=1d6b/2/602
E: PRODUCT=1d6b/2/615
E: SUBSYSTEM=usb
E: TAGS=:snap_cups_cupsd:seat:snap_cups_ippeveprinter:
E: TAGS=:snap_cups_ippeveprinter:seat:snap_android-platform-tools_fastboot:snap_cups_cupsd:snap_android-platform-tools_adb:
E: TYPE=9/0/1
A: authorized=1\n
A: authorized_default=1\n
@@ -150,11 +154,11 @@ A: bMaxPacketSize0=64\n
A: bMaxPower=0mA\n
A: bNumConfigurations=1\n
A: bNumInterfaces= 1\n
A: bcdDevice=0602\n
A: bcdDevice=0615\n
A: bmAttributes=e0\n
A: busnum=3\n
A: configuration=
H: descriptors=12010002090001406B1D020002060302010109021900010100E0000904000001090000000705810304000C
H: descriptors=12010002090001406B1D020015060302010109021900010100E0000904000001090000000705810304000C
A: dev=189:256\n
A: devnum=1\n
A: devpath=0\n
@@ -164,20 +168,20 @@ A: idProduct=0002\n
A: idVendor=1d6b\n
A: interface_authorized_default=1\n
A: ltm_capable=no\n
A: manufacturer=Linux 6.2.0-34-generic xhci-hcd\n
A: manufacturer=Linux 6.15.1 xhci-hcd\n
A: maxchild=12\n
A: power/active_duration=337953872\n
A: power/active_duration=2362684\n
A: power/async=enabled\n
A: power/autosuspend=0\n
A: power/autosuspend_delay_ms=0\n
A: power/connected_duration=337978524\n
A: power/connected_duration=96447784\n
A: power/control=auto\n
A: power/level=auto\n
A: power/runtime_active_kids=1\n
A: power/runtime_active_time=337962424\n
A: power/runtime_active_time=2368910\n
A: power/runtime_enabled=enabled\n
A: power/runtime_status=active\n
A: power/runtime_suspended_time=616\n
A: power/runtime_suspended_time=94033284\n
A: power/runtime_usage=0\n
A: power/wakeup=disabled\n
A: power/wakeup_abort_count=\n
@@ -195,12 +199,15 @@ A: rx_lanes=1\n
A: serial=0000:00:14.0\n
A: speed=480\n
A: tx_lanes=1\n
A: urbnum=4969\n
A: urbnum=7078\n
A: version= 2.00\n
P: /devices/pci0000:00/0000:00:14.0
E: DRIVER=xhci_hcd
E: ID_AUTOSUSPEND=1
E: ID_MODEL_FROM_DATABASE=Alder Lake PCH USB 3.2 xHCI Host Controller
E: ID_PATH=pci-0000:00:14.0
E: ID_PATH_TAG=pci-0000_00_14_0
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
@@ -214,10 +221,15 @@ E: SUBSYSTEM=pci
A: ari_enabled=0\n
A: broken_parity_status=0\n
A: class=0x0c0330\n
H: config=8680ED51060490020130030C000080000400161D6000000000000000000000000000000000000000000000004D1470C8000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C1080000000000000000000000059087007805E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F010112000000
H: config=8680ED51060490020130030C000080000400161D6000000000000000000000000000000000000000000000004D1470C8000000007000000000000000FF010000FD0134A089C27F8000000000000000003F6DD80F000000000000000000000000316000000000000000000000000000000180C2C10800000000000000000000000590B7001804E0FE000000000000000009B014F01000400100000000C10A080000080E00001800008F50020000010000090000018680C00009001014000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000B50F010112000000
A: consistent_dma_mask_bits=64\n
A: d3cold_allowed=1\n
A: dbc=disabled\n
A: dbc_bInterfaceProtocol=01\n
A: dbc_bcdDevice=0010\n
A: dbc_idProduct=0010\n
A: dbc_idVendor=1d6b\n
A: dbc_poll_interval_ms=64\n
A: device=0x51ed\n
A: dma_mask_bits=64\n
L: driver=../../../bus/pci/drivers/xhci_hcd
@@ -227,32 +239,39 @@ L: firmware_node=../../LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:51
A: index=7\n
L: iommu=../../virtual/iommu/dmar1
L: iommu_group=../../../kernel/iommu_groups/8
A: irq=145\n
A: irq=133\n
A: label=Onboard - Other\n
A: local_cpulist=0-15\n
A: local_cpus=ffff\n
A: modalias=pci:v00008086d000051EDsv0000144Dsd0000C870bc0Csc03i30\n
A: msi_bus=1\n
A: msi_irqs/145=msi\n
A: msi_irqs/133=msi\n
A: msi_irqs/134=msi\n
A: msi_irqs/135=msi\n
A: msi_irqs/136=msi\n
A: msi_irqs/137=msi\n
A: msi_irqs/138=msi\n
A: msi_irqs/139=msi\n
A: msi_irqs/140=msi\n
A: numa_node=-1\n
A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 9 2112 9\nxHCI ring segments 26 34 4096 34\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 32 128 1\nbuffer-32 0 0 32 0\n
A: pools=poolinfo - 0.1\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\nxHCI 1KB stream ctx arrays 0 0 1024 0\nxHCI 256 byte stream ctx arrays 0 0 256 0\nxHCI input/output contexts 6 7 2112 7\nxHCI ring segments 27 27 4096 27\nbuffer-2048 0 0 2048 0\nbuffer-512 0 0 512 0\nbuffer-128 0 0 128 0\nbuffer-32 0 0 32 0\n
A: power/async=enabled\n
A: power/control=auto\n
A: power/runtime_active_kids=1\n
A: power/runtime_active_time=337964621\n
A: power/runtime_active_time=2376480\n
A: power/runtime_enabled=enabled\n
A: power/runtime_status=active\n
A: power/runtime_suspended_time=438\n
A: power/runtime_suspended_time=94028360\n
A: power/runtime_usage=0\n
A: power/wakeup=enabled\n
A: power/wakeup_abort_count=0\n
A: power/wakeup_active=0\n
A: power/wakeup_active_count=7\n
A: power/wakeup_active_count=1\n
A: power/wakeup_count=0\n
A: power/wakeup_expire_count=7\n
A: power/wakeup_last_time_ms=336554844\n
A: power/wakeup_max_time_ms=105\n
A: power/wakeup_total_time_ms=721\n
A: power/wakeup_expire_count=1\n
A: power/wakeup_last_time_ms=41666464\n
A: power/wakeup_max_time_ms=101\n
A: power/wakeup_total_time_ms=101\n
A: power_state=D0\n
A: resource=0x000000601d160000 0x000000601d16ffff 0x0000000000140204\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n0x0000000000000000 0x0000000000000000 0x0000000000000000\n
A: revision=0x01\n

View File

@@ -14,9 +14,6 @@ envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint')
# random numbers rather than proper ones)
envs.set('FP_DEVICE_EMULATION', '1')
# Path to SDCP virtual device binary, only used for virtual-sdcp test
envs.set('SDCP_VIRT_BINARY', get_option('sdcp_virt_binary'))
# Set a colon-separated list of native drivers we enable in tests
envs.set('FP_DRIVERS_ALLOWLIST', ':'.join([
'virtual_image',
@@ -55,9 +52,9 @@ drivers_tests = [
'nb1010',
'egis0570',
'egismoc',
'egismoc-05a1',
'egismoc-0586',
'egismoc-0587',
# 'egismoc-05a1', # commented out until new capture with SDCP support can be provided
# 'egismoc-0586', # commented out until new capture with SDCP support can be provided
# 'egismoc-0587', # commented out until new capture with SDCP support can be provided
'fpcmoc',
'realtek',
'realtek-5816',
@@ -249,13 +246,10 @@ else
endforeach
endif
test_utils = static_library('fprint-test-utils',
sources: [
'test-utils.c',
'test-device-fake.c',
],
dependencies: libfprint_private_dep,
install: false)
test_util_sources = [
'test-utils.c',
'test-device-fake.c',
]
unit_tests = [
'fpi-device',
@@ -270,6 +264,22 @@ if 'virtual_image' in drivers
]
endif
if 'sdcp' in driver_helpers
test_util_sources += [
'test-sdcp-device-fake.c',
'test-sdcp-utils.c',
]
unit_tests += [
'fp-sdcp-device',
'fpi-sdcp',
]
endif
test_utils = static_library('fprint-test-utils',
sources: test_util_sources,
dependencies: libfprint_private_dep,
install: false)
unit_tests_deps = { 'fpi-assembling' : [cairo_dep] }
foreach test_name: unit_tests

Binary file not shown.

Binary file not shown.

143
tests/test-fp-sdcp-device.c Normal file
View File

@@ -0,0 +1,143 @@
/*
* FpSdcpDevice Unit tests
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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
*/
#include <libfprint/fprint.h>
#include "test-sdcp-device-fake.h"
#include "test-sdcp-utils.h"
static void
test_set_get_print_id (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
g_autoptr(FpPrint) print = fp_print_new (device);
g_autoptr(GBytes) id1 = NULL;
g_autoptr(GBytes) id2 = NULL;
g_autoptr(GError) error = NULL;
id1 = fpi_sdcp_generate_random (&error);
g_assert_nonnull (id1);
g_assert_no_error (error);
fpi_sdcp_device_set_print_id (print, id1);
fpi_sdcp_device_get_print_id (print, &id2);
g_assert_nonnull (id2);
g_assert_true (g_bytes_equal (id1, id2));
}
static void
test_identify (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
g_autoptr(GPtrArray) match_prints = g_ptr_array_new_with_free_func (g_object_unref);
g_autoptr(FpPrint) template_print = fp_print_new (device);
g_autoptr(FpPrint) print = NULL;
g_autoptr(FpPrint) matched_print = NULL;
g_autoptr(GError) error = NULL;
g_assert_true (fp_device_open_sync (device, NULL, &error));
print = fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);
g_ptr_array_add (match_prints, g_object_ref_sink (print));
g_assert_true (fp_device_identify_sync (device, match_prints, NULL, NULL, NULL,
&matched_print, &print, &error));
g_assert_true (fp_device_close_sync (device, NULL, &error));
}
static void
test_enroll (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
g_autoptr(FpPrint) template_print = fp_print_new (device);
g_autoptr(GError) error = NULL;
g_assert_true (fp_device_open_sync (device, NULL, &error));
g_assert_nonnull (fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error));
g_assert_no_error (error);
g_assert_true (fp_device_close_sync (device, NULL, &error));
}
static void
test_list (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
g_autoptr(GPtrArray) prints = NULL;
g_autoptr(GError) error = NULL;
g_assert_true (fp_device_open_sync (device, NULL, &error));
prints = fp_device_list_prints_sync (device, NULL, &error);
g_assert_nonnull (prints);
g_assert_true (prints->len == 1);
/* TODO: Should we also check the print's "fpi-data" for the expected ID? */
g_assert_no_error (error);
g_assert_true (fp_device_close_sync (device, NULL, &error));
}
static void
test_reconnect (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
FpiSdcpDeviceFake *fake_device = FPI_SDCP_DEVICE_FAKE (device);
g_assert_true (fp_device_open_sync (device, NULL, NULL));
g_assert (fake_device->reconnect_called == FALSE);
g_assert_true (fp_device_close_sync (device, NULL, NULL));
/* open a second time to reconnect */
g_assert_true (fp_device_open_sync (device, NULL, NULL));
g_assert (fake_device->reconnect_called == TRUE);
g_assert_true (fp_device_close_sync (device, NULL, NULL));
}
static void
test_open (void)
{
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
g_assert_true (fp_device_open_sync (device, NULL, NULL));
g_assert_true (fp_device_close_sync (device, NULL, NULL));
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/sdcp_device/open", test_open);
g_test_add_func ("/sdcp_device/reconnect", test_reconnect);
g_test_add_func ("/sdcp_device/list", test_list);
g_test_add_func ("/sdcp_device/enroll", test_enroll);
g_test_add_func ("/sdcp_device/identify", test_identify);
g_test_add_func ("/sdcp_device/set_get_print_id", test_set_get_print_id);
return g_test_run ();
}

162
tests/test-fpi-sdcp.c Normal file
View File

@@ -0,0 +1,162 @@
/*
* Secure Device Connection Protocol (SDCP) support unit tests
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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
*/
#define FP_COMPONENT "test_fpi_sdcp"
#include "fpi-log.h"
#include "test-sdcp-utils.h"
static void
test_generate_enrollment_id (void)
{
g_autoptr(GBytes) id = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
g_autoptr(GBytes) nonce = g_bytes_from_hex (enrollment_nonce_hex);
g_autoptr(GBytes) expected_id = g_bytes_from_hex (enrollment_enrollment_id_hex);
id = fpi_sdcp_generate_enrollment_id (application_secret, nonce, &error);
fp_dbg ("id:");
fp_dbg_hex_dump_gbytes (id);
fp_dbg ("expected:");
fp_dbg_hex_dump_gbytes (expected_id);
g_assert (g_bytes_equal (expected_id, id));
g_assert_null (error);
}
static void
test_verify_identify (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
g_autoptr(GBytes) nonce = g_bytes_from_hex (identify_nonce_hex);
g_autoptr(GBytes) id = g_bytes_from_hex (identify_enrollment_id_hex);
g_autoptr(GBytes) mac = g_bytes_from_hex (identify_mac_hex);
g_assert_true (fpi_sdcp_verify_identify (application_secret, nonce, id, mac, &error));
g_assert_null (error);
}
static void
test_verify_reconnect (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
g_autoptr(GBytes) random = g_bytes_from_hex (reconnect_random_hex);
g_autoptr(GBytes) mac = g_bytes_from_hex (reconnect_mac_hex);
g_assert_true (fpi_sdcp_verify_reconnect (application_secret, random, mac, &error));
g_assert_null (error);
}
static void
test_verify_connect (void)
{
g_autoptr(GBytes) application_secret = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) host_private_key = g_bytes_from_hex (host_private_key_hex);
g_autoptr(GBytes) host_random = g_bytes_from_hex (host_random_hex);
g_autoptr(GBytes) device_random = g_bytes_from_hex (device_random_hex);
g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
FpiSdcpClaim *claim = sdcp_test_claim ();
g_autoptr(GBytes) expected_application_secret = g_bytes_from_hex (application_secret_hex);
g_assert_true (fpi_sdcp_verify_connect (host_private_key,
host_random,
device_random,
claim,
connect_mac,
TRUE,
TRUE,
&application_secret,
&error));
g_assert_null (error);
g_assert (g_bytes_get_size (application_secret) == SDCP_APPLICATION_SECRET_SIZE);
fp_dbg ("application_secret:");
fp_dbg_hex_dump_gbytes (application_secret);
fp_dbg ("expected:");
fp_dbg_hex_dump_gbytes (expected_application_secret);
g_assert_true (g_bytes_equal (expected_application_secret, application_secret));
fpi_sdcp_claim_free (claim);
}
static void
test_generate_random (void)
{
g_autoptr(GBytes) random = NULL;
g_autoptr(GError) error = NULL;
random = fpi_sdcp_generate_random (&error);
g_assert_null (error);
g_assert (g_bytes_get_size (random) == SDCP_RANDOM_SIZE);
fp_dbg ("random:");
fp_dbg_hex_dump_gbytes (random);
}
static void
test_generate_host_key (void)
{
g_autoptr(GBytes) private_key = NULL;
g_autoptr(GBytes) public_key = NULL;
g_autoptr(GError) error = NULL;
gsize len = 0;
fpi_sdcp_generate_host_key (&private_key, &public_key, &error);
g_assert_null (error);
g_bytes_get_data (private_key, &len);
g_assert (len == 32);
fp_dbg ("private_key:");
fp_dbg_hex_dump_gbytes (private_key);
g_bytes_get_data (public_key, &len);
g_assert (len == SDCP_PUBLIC_KEY_SIZE);
fp_dbg ("public_key:");
fp_dbg_hex_dump_gbytes (public_key);
}
int
main (int argc, char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/sdcp/generate_host_key", test_generate_host_key);
g_test_add_func ("/sdcp/generate_random", test_generate_random);
g_test_add_func ("/sdcp/verify_connect", test_verify_connect);
g_test_add_func ("/sdcp/verify_reconnect", test_verify_reconnect);
g_test_add_func ("/sdcp/verify_identify", test_verify_identify);
g_test_add_func ("/sdcp/generate_enrollment_id", test_generate_enrollment_id);
return g_test_run ();
}

View File

@@ -0,0 +1,196 @@
/*
* Virtual driver for SDCP unit testing
* Completes actions using example values from Microsoft's SDCP documentation
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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
*/
#define FP_COMPONENT "fake_test_sdcp_dev"
#include "fpi-log.h"
#include "test-sdcp-device-fake.h"
G_DEFINE_TYPE (FpiSdcpDeviceFake, fpi_sdcp_device_fake, FP_TYPE_SDCP_DEVICE)
static const FpIdEntry driver_ids[] = {
{ .virtual_envvar = "FP_VIRTUAL_FAKE_SDCP_DEVICE" },
{ .virtual_envvar = NULL }
};
static void
fpi_sdcp_device_fake_identify (FpSdcpDevice *sdcp_device)
{
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) nonce = NULL;
g_autoptr(GBytes) identify_enrollment_id = g_bytes_from_hex (identify_enrollment_id_hex);
g_autoptr(GBytes) identify_mac = g_bytes_from_hex (identify_mac_hex);
/*
* Normally, a driver would fetch the identify data and then send it to the
* device's Identify command. In this fake device, we will just fetch and
* verify the nonce was generated but do nothing with it
*/
fpi_sdcp_device_get_identify_data (sdcp_device, &nonce);
g_assert (nonce);
g_assert (g_bytes_get_size (nonce) == SDCP_NONCE_SIZE);
/*
* In emulation mode (FP_DEVICE_EMULATION=1), a different hard-coded nonce is
* set in fpi-sdcp-device, which was the same nonce used to generate both the
* identify_enrollment_id and identify_mac values provided here
*/
fpi_sdcp_device_identify_complete (sdcp_device, identify_enrollment_id, identify_mac, error);
}
static void
fpi_sdcp_device_fake_enroll_commit (FpSdcpDevice *sdcp_device, GBytes *id)
{
fpi_sdcp_device_enroll_commit_complete (sdcp_device, NULL);
}
static void
fpi_sdcp_device_fake_enroll (FpSdcpDevice *sdcp_device)
{
g_autoptr(GError) error = NULL;
GBytes *enrollment_nonce = g_bytes_from_hex (enrollment_nonce_hex);
fpi_sdcp_device_enroll_commit (sdcp_device, enrollment_nonce, error);
g_bytes_unref (enrollment_nonce);
}
static void
fpi_sdcp_device_fake_list (FpSdcpDevice *sdcp_device)
{
g_autoptr(GBytes) identify_enrollment_id = g_bytes_from_hex (identify_enrollment_id_hex);
GPtrArray *enrollment_ids = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
g_ptr_array_add (enrollment_ids, g_steal_pointer (&identify_enrollment_id));
for (gint i = 0; i < enrollment_ids->len; i++)
{
fp_dbg ("print %d:", i);
fp_dbg_hex_dump_gbytes (g_ptr_array_index (enrollment_ids, i));
}
fpi_sdcp_device_list_complete (sdcp_device, g_steal_pointer (&enrollment_ids), NULL);
}
static void
fpi_sdcp_device_fake_reconnect (FpSdcpDevice *sdcp_device)
{
FpiSdcpDeviceFake *fake_device = FPI_SDCP_DEVICE_FAKE (sdcp_device);
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) random = NULL;
g_autoptr(GBytes) reconnect_mac = g_bytes_from_hex (reconnect_mac_hex);
/*
* Normally, a driver would fetch the reconnect data and then send it to the
* device's Reconnect command. In this fake device, we will just fetch and
* verify the random was generated but do nothing with it
*/
fpi_sdcp_device_get_reconnect_data (sdcp_device, &random);
g_assert (random);
g_assert (g_bytes_get_size (random) == SDCP_RANDOM_SIZE);
/*
* In emulation mode (FP_DEVICE_EMULATION=1), a different hard-coded random is
* set in fpi-sdcp-device, which was the same random used to generate the
* reconnect_mac value provided here
*/
fpi_sdcp_device_reconnect_complete (sdcp_device, reconnect_mac, error);
fake_device->reconnect_called = TRUE;
}
static void
fpi_sdcp_device_fake_connect (FpSdcpDevice *sdcp_device)
{
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) host_random = NULL;
g_autoptr(GBytes) host_public_key = NULL;
g_autoptr(GBytes) device_random = g_bytes_from_hex (device_random_hex);
g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
FpiSdcpClaim *claim = sdcp_test_claim ();
fp_device_open_sync (FP_DEVICE (sdcp_device), NULL, NULL);
fpi_sdcp_device_get_connect_data (sdcp_device, &host_random, &host_public_key);
g_assert (host_random);
g_assert (g_bytes_get_size (host_random) == SDCP_RANDOM_SIZE);
g_assert (host_public_key);
g_assert (g_bytes_get_size (host_public_key) == SDCP_PUBLIC_KEY_SIZE);
fpi_sdcp_device_connect_complete (sdcp_device, device_random, claim, connect_mac, error);
fpi_sdcp_claim_free (claim);
}
static void
fpi_sdcp_device_fake_close (FpDevice *device)
{
fpi_device_close_complete (device, NULL);
}
static void
fpi_sdcp_device_fake_open (FpSdcpDevice *sdcp_device)
{
fpi_sdcp_device_open_complete (sdcp_device, NULL);
}
static void
fpi_sdcp_device_fake_init (FpiSdcpDeviceFake *self)
{
G_DEBUG_HERE ();
}
static void
fpi_sdcp_device_fake_class_init (FpiSdcpDeviceFakeClass *klass)
{
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpSdcpDeviceClass *sdcp_dev_class = FP_SDCP_DEVICE_CLASS (klass);
dev_class->id = FP_COMPONENT;
dev_class->full_name = "Virtual SDCP test device";
dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
dev_class->id_table = driver_ids;
dev_class->nr_enroll_stages = 5;
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
sdcp_dev_class->ignore_device_certificate = FALSE;
sdcp_dev_class->ignore_device_signatures = FALSE;
sdcp_dev_class->open = fpi_sdcp_device_fake_open;
sdcp_dev_class->connect = fpi_sdcp_device_fake_connect;
sdcp_dev_class->reconnect = fpi_sdcp_device_fake_reconnect;
sdcp_dev_class->list = fpi_sdcp_device_fake_list;
sdcp_dev_class->enroll = fpi_sdcp_device_fake_enroll;
sdcp_dev_class->enroll_commit = fpi_sdcp_device_fake_enroll_commit;
sdcp_dev_class->identify = fpi_sdcp_device_fake_identify;
dev_class->close = fpi_sdcp_device_fake_close;
fpi_device_class_auto_initialize_features (dev_class);
dev_class->features |= FP_DEVICE_FEATURE_STORAGE;
}

View File

@@ -0,0 +1,33 @@
/*
* Virtual driver for SDCP unit testing
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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 "fpi-sdcp-device.h"
#include "test-sdcp-utils.h"
#define FPI_TYPE_SDCP_DEVICE_FAKE (fpi_sdcp_device_fake_get_type ())
G_DECLARE_FINAL_TYPE (FpiSdcpDeviceFake, fpi_sdcp_device_fake, FPI, SDCP_DEVICE_FAKE, FpSdcpDevice)
struct _FpiSdcpDeviceFake
{
FpDevice parent;
gboolean reconnect_called;
};

60
tests/test-sdcp-utils.c Normal file
View File

@@ -0,0 +1,60 @@
/*
* Secure Device Connection Protocol (SDCP) support test utils
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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
*/
#include "test-sdcp-utils.h"
static const guint8 from_hex_map[] =
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // @abcdef
};
GBytes *
g_bytes_from_hex (const gchar *hex)
{
g_autoptr(GBytes) res = NULL;
guint8 b0, b1;
gsize bytes_len = strlen (hex) / 2;
guint8 *bytes = g_malloc0 (bytes_len);
for (int i = 0; i < strlen (hex) - 1; i += 2)
{
b0 = ((guint8)hex[i+0] & 0x1F) ^ 0x10;
b1 = ((guint8)hex[i+1] & 0x1F) ^ 0x10;
bytes[i/2] = (guint8)(from_hex_map[b0] << 4) | from_hex_map[b1];
}
res = g_bytes_new_take (bytes, bytes_len);
return g_steal_pointer (&res);
}
FpiSdcpClaim *
sdcp_test_claim (void)
{
FpiSdcpClaim *claim = g_new0 (FpiSdcpClaim, 1);
claim->model_certificate = g_bytes_from_hex (model_certificate_hex);
claim->device_public_key = g_bytes_from_hex (device_public_key_hex);
claim->firmware_public_key = g_bytes_from_hex (firmware_public_key_hex);
claim->firmware_hash = g_bytes_from_hex (firmware_hash_hex);
claim->model_signature = g_bytes_from_hex (model_signature_hex);
claim->device_signature = g_bytes_from_hex (device_signature_hex);
return g_steal_pointer (&claim);
}

102
tests/test-sdcp-utils.h Normal file
View File

@@ -0,0 +1,102 @@
/*
* Secure Device Connection Protocol (SDCP) support test utils
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
*
* 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 "fpi-log.h"
#include "fpi-sdcp.h"
#include "fpi-sdcp-device.h"
/******************************************************************************/
/* host keys */
static const gchar host_private_key_hex[] = "8400ed14579cdf11586477e836e8cb52708441c1c2a447c218c5bbc2d118fbc7";
static const gchar host_public_key_hex[] = "0452f056ffb9c6728654771a3629b770767b19a2106a4916fb81ba06ef6797c4"
"a3df672ade0e9116d1abe278a8223abde4958d62d4ff6882159f0617c6f8ce10"
"bf";
static const gchar host_random_hex[] = "d877403abe82f4d97e1448c5052d83a532a45e56ef049cbbf981137520e713bf";
/* device keys */
static const gchar device_random_hex[] = "6e2f6c1abef2a1973fb1315a17e209fdb0c78520f1fd6a85d294d7aeb40a04a7";
static const gchar model_certificate_hex[] = "30820323308202caa00302010202133300000004c45d661d6eed040d00000000"
"0004300a06082a8648ce3d0403023056310b3009060355040613025553311e30"
"1c060355040a13154d6963726f736f667420436f72706f726174696f6e312730"
"250603550403131e57696e646f77732048656c6c6f2032303936414443432043"
"412032303231301e170d3232303130343231303033355a170d32333034303432"
"31303033355a301c311a3018060355040313115365637572652042494f205365"
"6e736f723059301306072a8648ce3d020106082a8648ce3d0301070342000414"
"cfc287f872a2b7d3339e0b31390e3ca688e61165eaa6687c959270e07666b1fa"
"19e3efaf1750d134a886d494424fe471970c4b06838408a18d1f5d57735dd7a3"
"8201af308201ab30750603551d11046e306ca46a30683132303006082b060104"
"82376402132436423045413344382d383339372d343444332d383043392d3930"
"324130414346344343303132303006082b060104823764011324413944423730"
"32362d433646462d344341462d423134382d393133454641333730423933301d"
"0603551d0e04160414db6d66d642b7236d5e6bb2ec2186decda98067b6301f06"
"03551d23041830168014bf3748e34a632de953a3ba890298c069472a99b9305f"
"0603551d1f045830563054a052a050864e687474703a2f2f7777772e6d696372"
"6f736f66742e636f6d2f706b696f70732f63726c2f57696e646f777325323048"
"656c6c6f25323032303936414443432532304341253230323032312e63726c30"
"6c06082b060105050701010460305e305c06082b060105050730028650687474"
"703a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f636572"
"74732f57696e646f777325323048656c6c6f2532303230393641444343253230"
"4341253230323032312e637274300c0603551d130101ff040230003015060355"
"1d25040e300c060a2b0601040182374c2b01300a06082a8648ce3d0403020347"
"003044022077553ef520f732e03cd740c8cf807e6366e12918bc581f75bfe0f1"
"95b7b1fd4f0220324e25b93b9da7538b797a624272b21b7cc0e96ea487924250"
"4677600450f283";
static const gchar device_public_key_hex[] = "04e2787890a684f95b96b9a2316ca8d3d33d4d79ff4c89dc6f9e888e973990d1"
"d3154133dcc8bd33b99af9dbf0673390d404d092498a3f214cd93f9b9f28fb5f"
"66";
static const gchar firmware_public_key_hex[] = "04f06a84ab51a3a6e8ff46868f91dd720e4cdad21f2e090d11e8f9bfc2ea19ee"
"1b5eac850b4532968a9399f76cd779e7723e8c2ca73b597c0df5f73b94a36f2b"
"6c";
static const gchar firmware_hash_hex[] = "c3bf47ea1f4a4a605470313cacb3a44f4a461f68c6faeab07e737610cb5ac835";
static const gchar model_signature_hex[] = "febe6ba3107813e185f05189e69ae79d9f7a40802582d94324459844c8b97ec6"
"c5daed5462276cb8a193c33e350424b0305d63d79a93a9188dcfc0cb5595f6c1";
static const gchar device_signature_hex[] = "10cc57dd8dafb463510a7327a5fca49b698e999b36448e2023eaf0dff0b0d4a3"
"4f1caf4e872b77364a0a00d7476554d0324c4cc931937e232a0315837d696c06";
static const gchar connect_mac_hex[] = "422bc475a78f972bae842a28e5ad721207457fcbd9a1a3aaf71587c07b84d247";
/* expected application_secret based on above */
static const gchar application_secret_hex[] = "13330ba3135ecf5dc71cede01a886540771efab35c8ba053902b2c1ee7de6efe";
/* test verify_reconnect values */
static const gchar reconnect_random_hex[] = "8a7451c1d3a8dca1c1330ca50d73454b351a49f46c8e9dcee15c964d295c31c9";
static const gchar reconnect_mac_hex[] = "bf3f3bb3bd6ecb2784c160f526f7bc3b3ca8faf5557194c48e0024a0493903c7";
/* test verify_identify values */
static const gchar identify_nonce_hex[] = "3a1b506f5bec089059acefb9b44dfbdea7a599ee9aa267e5252664d60b798053";
static const gchar identify_enrollment_id_hex[] = "ef2055244e49c39beabdac49fdf4ee418605d195da23b202ba219a13831ae621";
static const gchar identify_mac_hex[] = "f0a5c5f261c2fe937d8b113857bc629cd07ca88edf991f69ca6fae5c332390f6";
/* test enrollment_id values */
static const gchar enrollment_nonce_hex[] = "c2101c44c9a667bba397e81f48b143398603e2c9335a68b409e1dbe71e005ca2";
static const gchar enrollment_enrollment_id_hex[] = "67109dc70a216331f1580ddac601915929c1ff6c9bcba6544ba572c660c3d91e";
/******************************************************************************/
GBytes *g_bytes_from_hex (const gchar *hex);
FpiSdcpClaim *sdcp_test_claim (void);

View File

@@ -1,144 +0,0 @@
#!/usr/bin/env python3
import sys
try:
import gi
import os
from gi.repository import GLib, Gio
import unittest
import subprocess
import shutil
import tempfile
except Exception as e:
print("Missing dependencies: %s" % str(e))
sys.exit(77)
FPrint = None
# Re-run the test with the passed wrapper if set
wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER')
if wrapper:
wrap_cmd = wrapper.split(' ') + [sys.executable, os.path.abspath(__file__)] + \
sys.argv[1:]
os.unsetenv('LIBFPRINT_TEST_WRAPPER')
sys.exit(subprocess.check_call(wrap_cmd))
# Only permit loading virtual_sdcp driver for tests in this file
os.environ['FP_DRIVERS_WHITELIST'] = 'virtual_sdcp'
if hasattr(os.environ, 'MESON_SOURCE_ROOT'):
root = os.environ['MESON_SOURCE_ROOT']
else:
root = os.path.join(os.path.dirname(__file__), '..')
ctx = GLib.main_context_default()
class VirtualSDCP(unittest.TestCase):
@classmethod
def setUpClass(cls):
os.environ['FP_VIRTUAL_SDCP'] = os.environ['SDCP_VIRT_BINARY']
cls.ctx = FPrint.Context()
cls.dev = None
for dev in cls.ctx.get_devices():
cls.dev = dev
break
assert cls.dev is not None, "You need to compile with virtual_sdcp for testing"
@classmethod
def tearDownClass(cls):
del cls.dev
del cls.ctx
def setUp(self):
pass
def tearDown(self):
pass
def enroll(self, progress_cb=None):
# Enroll another print
template = FPrint.Print.new(self.dev)
template.props.finger = FPrint.Finger.LEFT_THUMB
template.props.username = "testuser"
template.props.description = "test print"
datetime = GLib.DateTime.new_now_local()
date = GLib.Date()
date.set_dmy(*datetime.get_ymd()[::-1])
template.props.enroll_date = date
return self.dev.enroll_sync(template, None, progress_cb, None)
def test_connect(self):
self.dev.open_sync()
self.dev.close_sync()
def test_reconnect(self):
# Ensure device was opened once before, this may be a reconnect if
# it is the same process as another test.
self.dev.open_sync()
self.dev.close_sync()
# Check that a reconnect happens on next open. To know about this, we
# need to parse check log messages for that.
success = [False]
def log_func(domain, level, msg):
print("log: '%s', '%s', '%s'" % (str(domain), str(level), msg))
if msg == 'Reconnect succeeded':
success[0] = True
# Call default handler
GLib.log_default_handler(domain, level, msg)
handler_id = GLib.log_set_handler('libfprint-sdcp_device', GLib.LogLevelFlags.LEVEL_DEBUG, log_func)
self.dev.open_sync()
self.dev.close_sync()
GLib.log_remove_handler('libfprint-sdcp_device', handler_id)
assert success[0]
def test_enroll(self):
self.dev.open_sync()
# Must return a print
assert isinstance(self.enroll(), FPrint.Print)
self.dev.close_sync()
def test_verify(self):
self.dev.open_sync()
# Enroll a new print (will be the last), check that it verifies
p = self.enroll()
match, dev_print = self.dev.verify_sync(p)
assert match
# The returned "device" print is identical
assert p.equal(dev_print)
# We can do the same with it
match, dev_print2 = self.dev.verify_sync(dev_print)
assert match
# Now, enroll a new print, causing the old one to not match anymore
# (the device always claims to see the last enrolled print).
self.enroll()
match, dev_print = self.dev.verify_sync(p)
assert match is False
self.dev.close_sync()
if __name__ == '__main__':
try:
gi.require_version('FPrint', '2.0')
from gi.repository import FPrint
except Exception as e:
print("Missing dependencies: %s" % str(e))
sys.exit(77)
# avoid writing to stderr
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))