Compare commits

...

21 Commits

Author SHA1 Message Date
Joshua Grisham
d878136546 Merge remote-tracking branch 'libfprint/master' into feature/sdcp-v2 2025-10-20 19:14:46 +02:00
Joshua Grisham
31f9a250be sdcp: uncrustify 2025-09-13 13:13:19 +02:00
Joshua Grisham
df004970f1 egismoc: avoid double finalization of cancelled tasks 2025-09-13 13:11:41 +02:00
Joshua Grisham
21960019fc sdcp: Fail enroll and identify if there is no application_secret 2025-09-09 21:31:33 +02:00
Joshua Grisham
6086c9fb27 sdcp: Refactor test-sdcp-device-fake to virtual-sdcp to support testing with fprintd 2025-09-09 21:30:38 +02:00
Joshua Grisham
036ebd607a egismoc: uncrustify 2025-09-01 18:29:51 +02:00
Joshua Grisham
99ed5d139b sdcp: uncrustify 2025-09-01 18:29:03 +02:00
Joshua Grisham
74e15c793b sdcp: use GBytes for sdcp-data instead of GVariant 2025-09-01 01:37:06 +02:00
Joshua Grisham
e96f9c3618 sdcp: Make sure to always set error when failing and use UNTRUSTED error for SDCP failures 2025-08-31 22:17:52 +02:00
Joshua Grisham
05d0b13c28 egismoc: Add support for 1c7a:0584 2025-08-28 21:10:53 +02:00
Joshua Grisham
7460e0dec4 sdcp-v2: Move SDCP support from using NSS to OpenSSL 3.0 and implement for egismoc 2025-08-28 21:01:54 +02:00
Benjamin Berg
c74a0d4592 sdcp-device: Use a key DB for testing 2025-07-20 01:14:03 +02:00
Benjamin Berg
3c1c9acd71 sdcp-device: Use predictable keys and random numbers for testing
If FP_DEVICE_EMULATION is set, then switch to using predictable EC
ephemeral key and random numbers. This should allow recording and
replaying real device interactions using umockdev.
2025-07-20 01:12:48 +02:00
Benjamin Berg
6974e1bad4 port to low level EC key functions
This will allow creating a fixed key for testing
2025-07-20 01:11:20 +02:00
Benjamin Berg
89fae86e8c saner public key param 2025-07-20 01:09:58 +02:00
Benjamin Berg
a925b83456 fixups: use static OID data 2025-07-20 01:09:30 +02:00
Benjamin Berg
4e811f7b63 tests: Add SDCP virtual device test 2025-07-20 01:07:24 +02:00
Benjamin Berg
e3fab01e38 virtual-sdcp: Add a virtual SDCP device
This device is designed to talk to an external executable. This
executable can be provided/created using the test implementation from
Microsoft.
2025-07-20 01:04:54 +02:00
Benjamin Berg
f063e3787e sdcp: Add SDCP base class
This adds a base class for SDCP devices. Not all functionality has been
fully tested, in particular the code to verify the model certificate is
most likely broken or incomplete. One problem there is that there is no
code to find the root CA to trust.

See: #257
2025-07-20 01:03:16 +02:00
Benjamin Berg
ca89ead07d device: Add new FP_DEVICE_ERROR_UNTRUSTED error code
Throw a specific error when we are unable to verify the integrety of the
device.
2025-07-20 00:57:32 +02:00
Benjamin Berg
4582b3b825 print: Add an SDCP print type
For now it is identical to a RAW print. However, this is likely to
change in the future.
2025-07-20 00:56:30 +02:00
32 changed files with 4522 additions and 464 deletions

View File

@@ -80,9 +80,11 @@ usb:v1C7Ap0571*
# Supported by libfprint driver egismoc
usb:v1C7Ap0582*
usb:v1C7Ap0583*
usb:v1C7Ap0584*
usb:v1C7Ap0586*
usb:v1C7Ap0587*
usb:v1C7Ap05A1*
usb:v1C7Ap05A5*
ID_AUTOSUSPEND=1
ID_PERSIST=0
@@ -428,7 +430,6 @@ usb:v16D1p1027*
usb:v1C7Ap0300*
usb:v1C7Ap0575*
usb:v1C7Ap0576*
usb:v1C7Ap0584*
usb:v1C7Ap0577*
usb:v1C7Ap057E*
usb:v2541p0236*

View File

@@ -100,6 +100,12 @@ FP_TYPE_IMAGE_DEVICE
FpImageDevice
</SECTION>
<SECTION>
<FILE>fp-sdcp-device</FILE>
FP_TYPE_SDCP_DEVICE
FpSdcpDevice
</SECTION>
<SECTION>
<FILE>fp-print</FILE>
FP_TYPE_PRINT
@@ -215,6 +221,31 @@ fpi_image_device_retry_scan
fpi_image_device_set_bz3_threshold
</SECTION>
<SECTION>
<FILE>fpi-sdcp-device</FILE>
<TITLE>Internal FpSdcpDevice</TITLE>
FpiSdcpClaim
FpSdcpDeviceClass
fpi_sdcp_claim_get_type
fpi_sdcp_claim_new
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_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>
<FILE>fpi-log</FILE>
fp_dbg
@@ -223,6 +254,8 @@ fp_warn
fp_err
BUG_ON
BUG
fp_dbg_hex_dump_bytes
fp_dbg_hex_dump_gbytes
</SECTION>
<SECTION>

View File

@@ -28,6 +28,7 @@
<xi:include href="xml/fp-context.xml"/>
<xi:include href="xml/fp-device.xml"/>
<xi:include href="xml/fp-image-device.xml"/>
<xi:include href="xml/fp-sdcp-device.xml"/>
<xi:include href="xml/fp-print.xml"/>
<xi:include href="xml/fp-image.xml"/>
</part>
@@ -38,6 +39,7 @@
<title>Device methods for drivers</title>
<xi:include href="xml/fpi-device.xml"/>
<xi:include href="xml/fpi-image-device.xml"/>
<xi:include href="xml/fpi-sdcp-device.xml"/>
</chapter>
<chapter id="driver-helpers">

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

@@ -0,0 +1,331 @@
/*
* Virtual driver for SDCP device debugging
*
* 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
*/
/*
* This is a virtual driver to debug the SDCP-based drivers.
* This virtual driver does not use a socket listener but instead
* embeds the simulated logic of the fake "device" directly within
* the logic of each function. This driver also allows prints to be
* registered programmatically, making it possible to test libfprint
* and fprintd.
*
* This virtual driver will override FpSdcpDevice's dynamically
* generated cryptography values and instead replace them with
* pre-generated values taken from from Microsoft's sample client
* implementation. See:
* https://github.com/Microsoft/SecureDeviceConnectionProtocol
*/
#define FP_COMPONENT "virtual_sdcp"
#include "fpi-log.h"
#include "../fpi-sdcp.h"
#include "virtual-sdcp.h"
struct _FpDeviceVirtualSdcp
{
FpSdcpDevice parent;
GPtrArray *print_ids;
};
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)
/******************************************************************************/
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
};
static 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);
}
static FpiSdcpClaim *
get_fake_sdcp_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);
}
/******************************************************************************/
static void
dev_identify (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (sdcp_device);
g_autoptr(GBytes) enrollment_id = NULL;
g_autoptr(GBytes) identify_mac = NULL;
GBytes *identify_nonce = NULL;
if (self->print_ids->len > 0)
{
/*
* Pretend that the virtual device identified the first print.
* Since we used a pre-generated enrollment_id for it, we can also use the
* matching pre-generated test identify data for its identification.
*/
identify_nonce = g_bytes_from_hex (identify_nonce_hex);
enrollment_id = g_bytes_from_hex (enrollment_id_hex);
fpi_sdcp_device_set_identify_data (sdcp_device, identify_nonce);
identify_mac = g_bytes_from_hex (identify_mac_hex);
fpi_sdcp_device_identify_complete (sdcp_device, enrollment_id, identify_mac, NULL);
}
else
{
fpi_sdcp_device_identify_complete (sdcp_device, NULL, NULL,
fpi_device_error_new (FP_DEVICE_ERROR_DATA_NOT_FOUND));
}
}
static void
dev_enroll_commit (FpSdcpDevice *sdcp_device, GBytes *id)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (sdcp_device);
g_autoptr(GBytes) expected_first_print_id = NULL;
GBytes *print_id;
print_id = g_bytes_new (g_bytes_get_data (id, NULL), g_bytes_get_size (id));
/*
* If this is the first print, it is probably good if we make sure the
* internal API assigned it the expected pre-known ID
*/
if (self->print_ids->len == 0)
{
expected_first_print_id = g_bytes_from_hex (enrollment_id_hex);
if (!g_bytes_equal (print_id, expected_first_print_id))
{
fpi_sdcp_device_enroll_commit_complete (sdcp_device,
fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
"First enrolled print ID does not match expected value"));
return;
}
}
g_ptr_array_add (self->print_ids, g_steal_pointer (&print_id));
fpi_sdcp_device_enroll_commit_complete (sdcp_device, NULL);
}
static void
dev_enroll (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (sdcp_device);
FpDevice *device = FP_DEVICE (sdcp_device);
g_autoptr(GError) error = NULL;
g_autoptr(GBytes) nonce = NULL;
FpPrint *print;
fpi_device_get_enroll_data (device, &print);
if (self->print_ids->len == 0)
{
/* Use the hard-coded nonce for the first enrollment */
nonce = g_bytes_from_hex (enrollment_nonce_hex);
}
else
{
/* Generate a new nonce for all other enrollments */
nonce = fpi_sdcp_generate_random (&error);
if (error)
fpi_device_enroll_progress (device, 0, print, error);
}
fpi_device_enroll_progress (device, 1, print, NULL);
fpi_sdcp_device_enroll_commit (sdcp_device, nonce, error);
}
static void
dev_list (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (sdcp_device);
GPtrArray *print_ids = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
for (gint i = 0; i < self->print_ids->len; i++)
{
GBytes *print_id = g_ptr_array_index (self->print_ids, i);
fp_dbg ("print %d:", i);
fp_dbg_hex_dump_gbytes (print_id);
g_ptr_array_add (print_ids, g_bytes_new (g_bytes_get_data (print_id, NULL),
g_bytes_get_size (print_id)));
}
fpi_sdcp_device_list_complete (sdcp_device, g_steal_pointer (&print_ids), NULL);
}
static void
dev_reconnect (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
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);
}
static void
dev_connect (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
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);
g_autoptr(FpiSdcpClaim) claim = get_fake_sdcp_claim ();
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);
}
static void
dev_close (FpDevice *device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
fpi_device_close_complete (device, NULL);
}
static void
dev_open (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
fpi_sdcp_device_open_complete (sdcp_device, NULL);
}
static void
fpi_device_virtual_sdcp_finalize (GObject *object)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (object);
g_clear_pointer (&self->print_ids, g_ptr_array_unref);
G_OBJECT_CLASS (fpi_device_virtual_sdcp_parent_class)->finalize (object);
}
static void
fpi_device_virtual_sdcp_init (FpDeviceVirtualSdcp *self)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
/* Force FP_DEVICE_EMULATION=1 when using FpDeviceVirtualSdcp */
g_setenv ("FP_DEVICE_EMULATION", "1", TRUE);
self->print_ids = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
}
static const FpIdEntry driver_ids[] = {
{ .virtual_envvar = "FP_VIRTUAL_SDCP" },
{ .virtual_envvar = NULL }
};
static void
fpi_device_virtual_sdcp_class_init (FpDeviceVirtualSdcpClass *klass)
{
fp_dbg ("Virtual SDCP device: %s()", G_STRFUNC);
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
FpSdcpDeviceClass *sdcp_dev_class = FP_SDCP_DEVICE_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = fpi_device_virtual_sdcp_finalize;
dev_class->id = FP_COMPONENT;
dev_class->full_name = "Virtual SDCP device for debugging";
dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
dev_class->id_table = driver_ids;
dev_class->nr_enroll_stages = 1;
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 = dev_open;
sdcp_dev_class->connect = dev_connect;
if (!g_getenv ("FP_VIRTUAL_SDCP_NO_RECONNECT"))
sdcp_dev_class->reconnect = dev_reconnect;
sdcp_dev_class->list = dev_list;
sdcp_dev_class->enroll = dev_enroll;
sdcp_dev_class->enroll_commit = dev_enroll_commit;
sdcp_dev_class->identify = dev_identify;
dev_class->close = dev_close;
fpi_device_class_auto_initialize_features (dev_class);
dev_class->features |= FP_DEVICE_FEATURE_STORAGE;
}

View File

@@ -0,0 +1,92 @@
/*
* Virtual driver test payloads for SDCP device debugging
*
* 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"
/* 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 enrollment_id values */
static const gchar enrollment_nonce_hex[] = "c2101c44c9a667bba397e81f48b143398603e2c9335a68b409e1dbe71e005ca2";
static const gchar enrollment_id_hex[] = "67109dc70a216331f1580ddac601915929c1ff6c9bcba6544ba572c660c3d91e";
/* test verify_identify values */
static const gchar identify_nonce_hex[] = "3a1b506f5bec089059acefb9b44dfbdea7a599ee9aa267e5252664d60b798053";
static const gchar identify_mac_hex[] = "53a723eef40713094a90c5ef9996cbd6ba268e30676cd7107705a6c3e3e1eff9";

View File

@@ -143,6 +143,7 @@ typedef enum {
* @FP_DEVICE_ERROR_DATA_DUPLICATE: Enrolling template duplicates storaged templates
* @FP_DEVICE_ERROR_REMOVED: The device has been removed.
* @FP_DEVICE_ERROR_TOO_HOT: The device might be getting too hot
* @FP_DEVICE_ERROR_UNTRUSTED: Device cannot be trusted
*
* Error codes for device operations. More specific errors from other domains
* such as #G_IO_ERROR or #G_USB_DEVICE_ERROR may also be reported.
@@ -161,6 +162,7 @@ typedef enum {
/* Leave some room to add more DATA related errors */
FP_DEVICE_ERROR_REMOVED = 0x100,
FP_DEVICE_ERROR_TOO_HOT,
FP_DEVICE_ERROR_UNTRUSTED,
} FpDeviceError;
GQuark fp_device_retry_quark (void);

View File

@@ -605,7 +605,7 @@ fp_print_equal (FpPrint *self, FpPrint *other)
if (g_strcmp0 (self->device_id, other->device_id))
return FALSE;
if (self->type == FPI_PRINT_RAW)
if (self->type == FPI_PRINT_RAW || self->type == FPI_PRINT_SDCP)
{
return g_variant_equal (self->data, other->data);
}
@@ -870,7 +870,7 @@ fp_print_deserialize (const guchar *data,
g_ptr_array_add (result->prints, g_steal_pointer (&xyt));
}
}
else if (type == FPI_PRINT_RAW)
else if (type == FPI_PRINT_RAW || type == FPI_PRINT_SDCP)
{
g_autoptr(GVariant) fp_data = g_variant_get_child_value (print_data, 0);

View File

@@ -0,0 +1,46 @@
/*
* 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
* 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"
typedef struct
{
GBytes *host_private_key;
GBytes *host_public_key;
GBytes *host_random;
GBytes *reconnect_random;
GBytes *identify_nonce;
GBytes *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_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);

177
libfprint/fp-sdcp-device.c Normal file
View File

@@ -0,0 +1,177 @@
/*
* 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
* 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 "sdcp_device"
#include "fpi-log.h"
#include "fp-sdcp-device-private.h"
/**
* SECTION: fp-sdcp-device
* @title: FpSdcpDevice
* @short_description: SDCP device subclass
*
* This is a base class for devices implementing the SDCP security protocol.
*/
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpSdcpDevice, fp_sdcp_device, FP_TYPE_DEVICE)
enum {
PROP_0,
PROP_SDCP_DATA,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
/*******************************************************/
/* Callbacks/vfuncs */
static void
fp_sdcp_device_open (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
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
fp_sdcp_device_enroll (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
fpi_sdcp_device_enroll (self);
}
static void
fp_sdcp_device_identify (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
fpi_sdcp_device_identify (self);
}
/*********************************************************/
static void
fp_sdcp_device_finalize (GObject *object)
{
FpSdcpDevice *self = (FpSdcpDevice *) object;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
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_bytes_unref);
G_OBJECT_CLASS (fp_sdcp_device_parent_class)->finalize (object);
}
static void
fp_sdcp_device_get_property (GObject *object,
guint prop_id,
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_boxed (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_bytes_unref);
priv->data = g_value_dup_boxed (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
fp_sdcp_device_constructed (GObject *obj)
{
G_OBJECT_CLASS (fp_sdcp_device_parent_class)->constructed (obj);
}
static void
fp_sdcp_device_class_init (FpSdcpDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FpDeviceClass *fp_device_class = FP_DEVICE_CLASS (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;
properties[PROP_SDCP_DATA] =
g_param_spec_boxed ("sdcp-data",
"SDCP Data",
"SDCP-related device data that should be persisted and used with the "
"device during the current system boot",
G_TYPE_BYTES,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
g_object_class_install_properties (object_class, N_PROPS, properties);
fpi_device_class_auto_initialize_features (fp_device_class);
}
static void
fp_sdcp_device_init (FpSdcpDevice *self)
{
}

View File

@@ -0,0 +1,29 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* 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
*/
#pragma once
#include <fp-device.h>
G_BEGIN_DECLS
#define FP_TYPE_SDCP_DEVICE (fp_sdcp_device_get_type ())
G_DECLARE_DERIVABLE_TYPE (FpSdcpDevice, fp_sdcp_device, FP, SDCP_DEVICE, FpDevice)
G_END_DECLS

View File

@@ -190,6 +190,9 @@ fpi_device_error_new (FpDeviceError error)
case FP_DEVICE_ERROR_TOO_HOT:
msg = "Device disabled to prevent overheating.";
case FP_DEVICE_ERROR_UNTRUSTED:
msg = "Could not verify integrity of the device, it cannot be trusted!";
break;
default:

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

@@ -0,0 +1,57 @@
/*
* 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)

View File

@@ -11,11 +11,13 @@ G_BEGIN_DECLS
* @FPI_PRINT_UNDEFINED: Undefined type, this happens prior to enrollment
* @FPI_PRINT_RAW: A raw print where the data is directly compared
* @FPI_PRINT_NBIS: NBIS minutiae comparison
* @FPI_PRINT_SDCP: Print from an SDCP conforming device
*/
typedef enum {
FPI_PRINT_UNDEFINED = 0,
FPI_PRINT_RAW,
FPI_PRINT_NBIS,
FPI_PRINT_SDCP,
} FpiPrintType;
/**

980
libfprint/fpi-sdcp-device.c Normal file
View File

@@ -0,0 +1,980 @@
/*
* 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
* 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 "sdcp_device"
#include "fpi-log.h"
#include "fpi-compat.h"
#include "fpi-print.h"
#include "fp-sdcp-device-private.h"
#include "fpi-sdcp.h"
#include "fpi-sdcp-device.h"
/**
* SECTION: fpi-sdcp-device
* @title: Internal FpSdcpDevice
* @short_description: Internal SDCP device routines
*
* Internal SDCP handling routines. See #FpSdcpDevice for public routines.
*/
G_DEFINE_BOXED_TYPE (FpiSdcpClaim, fpi_sdcp_claim, fpi_sdcp_claim_copy, fpi_sdcp_claim_free)
/**
* fpi_sdcp_claim_new:
*
* Create an empty #FpiSdcpClaim to provide to the base class.
*
* Returns: (transfer full): A newly created #FpiSdcpClaim
*/
FpiSdcpClaim *
fpi_sdcp_claim_new (void)
{
FpiSdcpClaim *res = NULL;
res = g_new0 (FpiSdcpClaim, 1);
return res;
}
/**
* fpi_sdcp_claim_free:
* @claim: a #FpiSdcpClaim
*
* Release the memory used by an #FpiSdcpClaim.
*/
void
fpi_sdcp_claim_free (FpiSdcpClaim *claim)
{
g_return_if_fail (claim);
g_clear_pointer (&claim->model_certificate, g_bytes_unref);
g_clear_pointer (&claim->device_public_key, g_bytes_unref);
g_clear_pointer (&claim->firmware_public_key, g_bytes_unref);
g_clear_pointer (&claim->firmware_hash, g_bytes_unref);
g_clear_pointer (&claim->model_signature, g_bytes_unref);
g_clear_pointer (&claim->device_signature, g_bytes_unref);
g_free (claim);
}
/**
* fpi_sdcp_claim_copy:
* @other: The #FpiSdcpClaim to copy
*
* Create a (shallow) copy of a #FpiSdcpClaim.
*
* Returns: (transfer full): A newly created #FpiSdcpClaim
*/
FpiSdcpClaim *
fpi_sdcp_claim_copy (FpiSdcpClaim *other)
{
FpiSdcpClaim *res = NULL;
res = fpi_sdcp_claim_new ();
if (other->model_certificate)
res->model_certificate = g_bytes_ref (other->model_certificate);
if (other->device_public_key)
res->device_public_key = g_bytes_ref (other->device_public_key);
if (other->firmware_public_key)
res->firmware_public_key = g_bytes_ref (other->firmware_public_key);
if (other->firmware_hash)
res->firmware_hash = g_bytes_ref (other->firmware_hash);
if (other->model_signature)
res->model_signature = g_bytes_ref (other->model_signature);
if (other->device_signature)
res->device_signature = g_bytes_ref (other->device_signature);
return res;
}
/* Manually redefine what G_DEFINE_* macro does */
static inline gpointer
fp_sdcp_device_get_instance_private (FpSdcpDevice *self)
{
FpSdcpDeviceClass *sdcp_class = g_type_class_peek_static (FP_TYPE_SDCP_DEVICE);
return G_STRUCT_MEMBER_P (self,
g_type_class_get_instance_private_offset (sdcp_class));
}
/* Example values from Microsoft's SDCP documentation to use when testing (FP_DEVICE_EMULATION=1) */
static const guchar test_host_private_key[] = {
0x84, 0x00, 0xed, 0x14, 0x57, 0x9c, 0xdf, 0x11, 0x58, 0x64, 0x77, 0xe8, 0x36, 0xe8, 0xcb, 0x52,
0x70, 0x84, 0x41, 0xc1, 0xc2, 0xa4, 0x47, 0xc2, 0x18, 0xc5, 0xbb, 0xc2, 0xd1, 0x18, 0xfb, 0xc7
};
static const guchar test_host_public_key[] = {
0x04, 0x52, 0xf0, 0x56, 0xff, 0xb9, 0xc6, 0x72, 0x86, 0x54, 0x77, 0x1a, 0x36, 0x29, 0xb7, 0x70,
0x76, 0x7b, 0x19, 0xa2, 0x10, 0x6a, 0x49, 0x16, 0xfb, 0x81, 0xba, 0x06, 0xef, 0x67, 0x97, 0xc4,
0xa3, 0xdf, 0x67, 0x2a, 0xde, 0x0e, 0x91, 0x16, 0xd1, 0xab, 0xe2, 0x78, 0xa8, 0x22, 0x3a, 0xbd,
0xe4, 0x95, 0x8d, 0x62, 0xd4, 0xff, 0x68, 0x82, 0x15, 0x9f, 0x06, 0x17, 0xc6, 0xf8, 0xce, 0x10,
0xbf
};
static const gchar test_host_random[] = {
0xd8, 0x77, 0x40, 0x3a, 0xbe, 0x82, 0xf4, 0xd9, 0x7e, 0x14, 0x48, 0xc5, 0x05, 0x2d, 0x83, 0xa5,
0x32, 0xa4, 0x5e, 0x56, 0xef, 0x04, 0x9c, 0xbb, 0xf9, 0x81, 0x13, 0x75, 0x20, 0xe7, 0x13, 0xbf
};
static const gchar test_reconnect_random[] = {
0x8a, 0x74, 0x51, 0xc1, 0xd3, 0xa8, 0xdc, 0xa1, 0xc1, 0x33, 0x0c, 0xa5, 0x0d, 0x73, 0x45, 0x4b,
0x35, 0x1a, 0x49, 0xf4, 0x6c, 0x8e, 0x9d, 0xce, 0xe1, 0x5c, 0x96, 0x4d, 0x29, 0x5c, 0x31, 0xc9
};
static const gchar test_identify_nonce[] = {
0x3a, 0x1b, 0x50, 0x6f, 0x5b, 0xec, 0x08, 0x90, 0x59, 0xac, 0xef, 0xb9, 0xb4, 0x4d, 0xfb, 0xde,
0xa7, 0xa5, 0x99, 0xee, 0x9a, 0xa2, 0x67, 0xe5, 0x25, 0x26, 0x64, 0xd6, 0x0b, 0x79, 0x80, 0x53
};
/* FpiSdcpDevice */
/* Internal functions of FpSdcpDevice */
void
fpi_sdcp_device_get_application_secret (FpSdcpDevice *self,
GBytes **application_secret)
{
GBytes *data = NULL;
g_return_if_fail (*application_secret == NULL);
g_object_get (G_OBJECT (self), "sdcp-data", &data, NULL);
if (!data)
return;
*application_secret = g_steal_pointer (&data);
}
void
fpi_sdcp_device_set_application_secret (FpSdcpDevice *self,
GBytes *application_secret)
{
g_return_if_fail (application_secret);
g_object_set (G_OBJECT (self), "sdcp-data", application_secret, NULL);
}
void
fpi_sdcp_device_open (FpSdcpDevice *self)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
g_return_if_fail (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_OPEN);
cls->open (self);
}
void
fpi_sdcp_device_connect (FpSdcpDevice *self)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
GError *error = NULL;
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);
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
/* SDCP Connect: 3.i. Generate host ephemeral ECDH key pair */
fpi_sdcp_generate_host_key (&priv->host_private_key, &priv->host_public_key, &error);
if (error)
{
fpi_sdcp_device_connect_complete (self,
NULL, NULL, NULL,
error);
return;
}
/* SDCP Connect: 3.ii. Generate host random */
priv->host_random = fpi_sdcp_generate_random (&error);
if (error)
{
fpi_sdcp_device_connect_complete (self,
NULL, NULL, NULL,
error);
return;
}
}
else
{
/* Use Microsoft's SDCP documentation example values in emulation mode */
priv->host_private_key = g_bytes_new (test_host_private_key, sizeof (test_host_private_key));
priv->host_public_key = g_bytes_new (test_host_public_key, sizeof (test_host_public_key));
priv->host_random = g_bytes_new (test_host_random, sizeof (test_host_random));
}
/* SDCP Connect: 3.iii. Send the Connect message */
cls->connect (self);
return;
}
void
fpi_sdcp_device_reconnect (FpSdcpDevice *self)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
GError *error = NULL;
g_clear_pointer (&priv->reconnect_random, g_bytes_unref);
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
/* SDCP Reconnect: 2.i. Generate host random */
priv->reconnect_random = fpi_sdcp_generate_random (&error);
if (error)
{
fpi_sdcp_device_reconnect_complete (self, NULL, error);
return;
}
}
else
{
/* Use Microsoft's SDCP documentation example value in emulation mode */
priv->reconnect_random = g_bytes_new (test_reconnect_random, sizeof (test_reconnect_random));
}
/* SDCP Reconnect: 2.ii. Send the Reconnect message */
if (cls->reconnect)
cls->reconnect (self);
else
fpi_sdcp_device_connect (self);
}
void
fpi_sdcp_device_list (FpSdcpDevice *self)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
g_return_if_fail (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_LIST);
cls->list (self);
}
void
fpi_sdcp_device_enroll (FpSdcpDevice *self)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_autoptr(GBytes) application_secret = NULL;
FpPrint *print;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
g_return_if_fail (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_ENROLL);
fpi_sdcp_device_get_application_secret (self, &application_secret);
g_return_if_fail (application_secret != NULL);
fpi_device_get_enroll_data (FP_DEVICE (self), &print);
fpi_print_set_device_stored (print, FALSE);
g_object_set (print, "fpi-data", NULL, NULL);
cls->enroll (self);
}
void
fpi_sdcp_device_identify (FpSdcpDevice *self)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_autoptr(GBytes) application_secret = NULL;
FpiDeviceAction action;
GError *error = NULL;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_IDENTIFY || action == FPI_DEVICE_ACTION_VERIFY);
fpi_sdcp_device_get_application_secret (self, &application_secret);
g_return_if_fail (application_secret != NULL);
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
/* Generate a new nonce. */
priv->identify_nonce = fpi_sdcp_generate_random (&error);
if (error)
{
fpi_device_action_error (FP_DEVICE (self), error);
return;
}
}
else
{
/* Use Microsoft's SDCP documentation example value in emulation mode */
priv->identify_nonce = g_bytes_new (test_identify_nonce, sizeof (test_identify_nonce));
}
cls->identify (self);
}
/*********************************************************/
/* Private API */
/**
* fpi_sdcp_device_open_complete:
* @self: a #FpSdcpDevice fingerprint device
* @error: A #GError or %NULL on success
*
* Reports completion of open operation. Responsible for triggering SDCP connect
* or reconnect as necessary.
*/
void
fpi_sdcp_device_open_complete (FpSdcpDevice *self,
GError *error)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_autoptr(GBytes) application_secret = NULL;
if (!error)
{
fpi_sdcp_device_get_application_secret (self, &application_secret);
/* Try a reconnect if implemented and we already have an application_secret */
if (cls->reconnect && application_secret)
fpi_sdcp_device_reconnect (self);
/* Connect if we don't already have an application_secret */
else if (!application_secret)
fpi_sdcp_device_connect (self);
/* Complete open if we are already connected */
else
fpi_device_open_complete (FP_DEVICE (self), NULL);
}
else
{
fpi_device_open_complete (FP_DEVICE (self), error);
}
}
/**
* fp_sdcp_device_get_connect_data:
* @self: a #FpSdcpDevice fingerprint device
* @host_random: (out) (transfer full): The host-generated random
* @host_public_key: (out) (transfer full): The host public key
*
* Get data required to connect to (i.e. open) the device securely.
*/
void
fpi_sdcp_device_get_connect_data (FpSdcpDevice *self,
GBytes **host_random,
GBytes **host_public_key)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_return_if_fail (host_random != NULL);
g_return_if_fail (host_public_key != NULL);
g_return_if_fail (priv->host_random);
g_return_if_fail (priv->host_public_key);
*host_random = g_bytes_new_from_bytes (priv->host_random, 0,
g_bytes_get_size (priv->host_random));
*host_public_key = g_bytes_new_from_bytes (priv->host_public_key, 0,
g_bytes_get_size (priv->host_public_key));
}
/**
* fp_sdcp_device_get_reconnect_data:
* @self: a #FpSdcpDevice fingerprint device
* @reconnect_random: (out) (transfer full): The host-generated random
*
* Get data required to reconnect to (i.e. open) to the device securely.
*/
void
fpi_sdcp_device_get_reconnect_data (FpSdcpDevice *self,
GBytes **reconnect_random)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_return_if_fail (reconnect_random != NULL);
g_return_if_fail (priv->reconnect_random);
*reconnect_random = g_bytes_new_from_bytes (priv->reconnect_random, 0,
g_bytes_get_size (priv->reconnect_random));
}
/**
* fp_sdcp_device_get_identify_data:
* @self: a #FpSdcpDevice fingerprint device
* @nonce: (out) (transfer full): A new host-generated nonce
*
* Get data required to identify a new print.
*/
void
fpi_sdcp_device_get_identify_data (FpSdcpDevice *self,
GBytes **nonce)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_return_if_fail (nonce != NULL);
g_return_if_fail (priv->identify_nonce);
*nonce = g_bytes_new_from_bytes (priv->identify_nonce, 0,
g_bytes_get_size (priv->identify_nonce));
}
/**
* fp_sdcp_device_set_identify_data:
* @self: a #FpSdcpDevice fingerprint device
* @nonce: A driver-specified nonce
*
* Sets data required to identify a new print.
*
* Most drivers should not use this function, but instead use the automatically
* generated values retrieved from fpi_sdcp_device_get_identify_data() when
* executing the device-specific Identify command.
*
* In cases where a device's Identify command does not accept a randomly
* generated nonce, this function can be used to override the randomly generated
* nonce to the nonce that was actually sent to the device.
*/
void
fpi_sdcp_device_set_identify_data (FpSdcpDevice *self,
GBytes *nonce)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_return_if_fail (nonce != NULL);
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
priv->identify_nonce = g_steal_pointer (&nonce);
}
/**
* fpi_sdcp_device_connect_complete:
* @self: a #FpSdcpDevice fingerprint device
* @device_random: The device random
* @claim: The device #FpiSdcpClaim
* @mac: The MAC authenticating @claim
* @error: A #GError or %NULL on success
*
* Reports completion of connect operation. Responsible for performing SDCP key
* agreement, deriving secrets necessary for processing all other SDCP-related
* payloads, and verifying the device connection is trusted.
*/
void
fpi_sdcp_device_connect_complete (FpSdcpDevice *self,
GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
GError *error)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_autoptr(GBytes) application_secret = NULL;
FpiDeviceAction action;
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_OPEN);
g_return_if_fail (priv->host_private_key);
g_return_if_fail (priv->host_random);
if (error)
{
if (device_random || claim || mac)
{
g_clear_pointer (&device_random, g_bytes_unref);
g_clear_pointer (&claim, fpi_sdcp_claim_free);
g_clear_pointer (&mac, g_bytes_unref);
fp_warn ("Driver provided SDCP Connect information but also reported error.");
}
fpi_device_open_complete (FP_DEVICE (self), error);
return;
}
if (!device_random || !claim || !mac ||
(!claim->model_certificate || !claim->device_public_key || !claim->firmware_public_key ||
!claim->firmware_hash || !claim->model_signature || !claim->device_signature))
{
fp_dbg ("Driver did not provide all required information to callback; returning error instead.");
g_clear_pointer (&device_random, g_bytes_unref);
g_clear_pointer (&claim, fpi_sdcp_claim_free);
g_clear_pointer (&mac, g_bytes_unref);
fpi_device_open_complete (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver called connect complete with "
"incomplete arguments"));
return;
}
/* Verify connect and store the application_secret */
if (!fpi_sdcp_verify_connect (priv->host_private_key,
priv->host_random,
device_random,
claim,
mac,
!cls->ignore_device_certificate,
!cls->ignore_device_signatures,
&application_secret,
&error))
{
fpi_device_open_complete (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
"SDCP Connect verification failed: %s",
error->message));
return;
}
fpi_sdcp_device_set_application_secret (self, application_secret);
/* Clear no longer needed private data */
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);
fpi_device_open_complete (FP_DEVICE (self), NULL);
}
/**
* fpi_sdcp_device_reconnect_complete:
* @self: a #FpSdcpDevice fingerprint device
* @mac: The MAC authenticating @claim
* @error: A #GError or %NULL on success
*
* Reports completion of a reconnect (i.e. open) operation.
*/
void
fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
GBytes *mac,
GError *error)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_autoptr(GBytes) application_secret = NULL;
FpiDeviceAction action;
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_OPEN);
g_return_if_fail (priv->reconnect_random);
if (error)
{
if (mac)
{
fp_warn ("Driver provided a reconnect MAC but also reported an error.");
g_clear_pointer (&mac, g_bytes_unref);
}
/* Silently try a normal connect instead. */
fpi_sdcp_device_connect (self);
}
else if (mac)
{
fpi_sdcp_device_get_application_secret (self, &application_secret);
if (fpi_sdcp_verify_reconnect (application_secret, priv->reconnect_random, mac, &error))
{
fp_dbg ("SDCP Reconnect succeeded");
fpi_device_open_complete (FP_DEVICE (self), NULL);
}
else
{
fp_dbg ("SDCP Reconnect failed; doing a full connect.");
fpi_sdcp_device_connect (self);
}
}
else
{
fpi_device_open_complete (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
"Driver called reconnect complete with wrong arguments"));
}
/* Clear no longer needed private data */
g_clear_pointer (&priv->reconnect_random, g_bytes_unref);
}
/**
* fpi_sdcp_device_list_complete:
* @self: a #FpSdcpDevice fingerprint device
* @ids: A #GPtrArray of #GBytes of each SDCP enrollment ID stored on the device
* @error: A #GError or %NULL on success
*
* Convenience function to create the minimally required #FpPrint list for
* #FpSdcpDevice prints using the provided @ids, then uses that #FpPrint list to
* report completion of the list operation.
*
* If the device provides additional attributes that should be stored on each
* #FpPrint as part of the list operation, a #GPtrArray of #FpPrint can instead
* be created with the additional attributes and fpi_device_list_complete() can
* be used instead of this function.
*
* Please note that the @ids array will be freed using g_ptr_array_unref() and
* the elements are destroyed automatically. As such, you must use
* g_ptr_array_new_with_free_func() with `(GDestroyNotify) g_bytes_unref` as the
* free func when creating the #GPtrArray.
*/
void
fpi_sdcp_device_list_complete (FpSdcpDevice *self,
GPtrArray *ids,
GError *error)
{
g_autoptr(GPtrArray) prints = NULL;
gint prints_len = 0;
FpiDeviceAction action;
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_LIST);
if (error)
{
fpi_device_list_complete (FP_DEVICE (self), NULL, error);
return;
}
prints = g_ptr_array_new_with_free_func (g_object_unref);
/* Allow an empty array (prints_len=0) but if ids has been passed, use it */
if (ids)
prints_len = ids->len;
for (gint i = 0; i < prints_len; i++)
{
FpPrint *print = fp_print_new (FP_DEVICE (self));
fpi_print_set_type (print, FPI_PRINT_SDCP);
fpi_print_set_device_stored (print, FALSE);
fpi_sdcp_device_set_print_id (print, g_ptr_array_index (ids, i));
g_ptr_array_add (prints, g_object_ref_sink (print));
}
fpi_device_list_complete (FP_DEVICE (self), g_steal_pointer (&prints), NULL);
g_clear_pointer (&ids, g_ptr_array_unref);
}
/**
* fpi_sdcp_device_enroll_commit:
* @self: a #FpSdcpDevice fingerprint device
* @nonce: The device generated nonce
* @error: a #GError or %NULL on success
*
* Called when the print is ready to be committed to device memory.
* During enrollment, fpi_device_enroll_progress() must be called for each
* successful stage before the print can be committed.
* The @nonce generated by the device-specific EnrollmentNonce response must be
* provided in order for the enrollment ID to be generated.
* The driver's enroll_commit() vfunc will be triggered upon successfully
* generating the enrollment ID.
*/
void
fpi_sdcp_device_enroll_commit (FpSdcpDevice *self,
GBytes *nonce,
GError *error)
{
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
g_autoptr(GBytes) application_secret = NULL;
GBytes *id = NULL;
FpPrint *print;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
g_return_if_fail (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_ENROLL);
g_return_if_fail (nonce != NULL);
fpi_device_get_enroll_data (FP_DEVICE (self), &print);
fpi_sdcp_device_get_application_secret (self, &application_secret);
id = fpi_sdcp_generate_enrollment_id (application_secret, nonce, &error);
if (!id || error)
{
fp_warn ("Could not generate SDCP enrollment ID");
fpi_device_enroll_complete (FP_DEVICE (self), NULL, error);
g_object_set (print, "fpi-data", NULL, NULL);
return;
}
/* Set to true once committed */
fpi_print_set_device_stored (print, FALSE);
/* Attach the ID to the print */
fpi_sdcp_device_set_print_id (print, id);
cls->enroll_commit (self, id);
g_clear_pointer (&id, g_bytes_unref);
}
/**
* fpi_sdcp_device_enroll_commit_complete:
* @self: a #FpSdcpDevice fingerprint device
* @error: a #GError or %NULL on success
*
* Called when device has committed the given print to memory.
* This finalizes the enroll operation.
*/
void
fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
GError *error)
{
g_autoptr(GBytes) id = NULL;
FpPrint *print;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
g_return_if_fail (fpi_device_get_current_action (FP_DEVICE (self)) == FPI_DEVICE_ACTION_ENROLL);
if (error)
{
fpi_device_enroll_complete (FP_DEVICE (self), NULL, error);
return;
}
fpi_device_get_enroll_data (FP_DEVICE (self), &print);
fpi_sdcp_device_get_print_id (print, &id);
if (!id)
{
g_error ("Inconsistent state; the print must have the enrolled ID attached at this point");
return;
}
fpi_print_set_type (print, FPI_PRINT_SDCP);
fpi_print_set_device_stored (print, TRUE);
fpi_device_enroll_complete (FP_DEVICE (self), g_object_ref (print), NULL);
}
/**
* fpi_sdcp_device_identify_retry:
* @self: a #FpSdcpDevice fingerprint device
* @error: a #GError containing the retry condition
*
* Called when the device requires the finger to be presented again.
* This should not be called for a verified no-match, it should only
* be called if e.g. the finger was not centered properly or similar.
*
* Effectively this simply raises the error up. This function exists
* to bridge the difference in semantics that SDPC has from how
* libfprint works internally.
*/
void
fpi_sdcp_device_identify_retry (FpSdcpDevice *self,
GError *error)
{
FpiDeviceAction action;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_IDENTIFY || action == FPI_DEVICE_ACTION_VERIFY);
if (action == FPI_DEVICE_ACTION_VERIFY)
fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_ERROR, NULL, error);
else if (action == FPI_DEVICE_ACTION_IDENTIFY)
fpi_device_identify_report (FP_DEVICE (self), NULL, NULL, error);
}
/**
* fpi_sdcp_device_identify_complete:
* @self: a #FpSdcpDevice fingerprint device
* @id: (transfer none): the ID as reported by the device
* @mac: (transfer none): MAC authenticating the message
* @error: (transfer full): #GError if an error occured
*
* Called when device is done with the identification routine. The
* returned ID may be %NULL if none of the in-device templates matched.
*/
void
fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
GBytes *id,
GBytes *mac,
GError *error)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
g_autoptr(GBytes) application_secret = NULL;
FpPrint *identified_print;
FpiDeviceAction action;
g_return_if_fail (FP_IS_SDCP_DEVICE (self));
action = fpi_device_get_current_action (FP_DEVICE (self));
g_return_if_fail (action == FPI_DEVICE_ACTION_IDENTIFY || action == FPI_DEVICE_ACTION_VERIFY);
g_return_if_fail (priv->identify_nonce);
if (error)
{
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
fpi_device_action_error (FP_DEVICE (self), error);
return;
}
/* No error and no valid id/mac provided means that there was no match from the device */
if (!id || !mac || g_bytes_get_size (id) != SDCP_ENROLLMENT_ID_SIZE ||
g_bytes_get_size (mac) != SDCP_MAC_SIZE)
{
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
if (action == FPI_DEVICE_ACTION_VERIFY)
{
fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_FAIL, NULL, NULL);
fpi_device_verify_complete (FP_DEVICE (self), NULL);
}
else
{
fpi_device_identify_report (FP_DEVICE (self), NULL, NULL, NULL);
fpi_device_identify_complete (FP_DEVICE (self), NULL);
}
return;
}
fpi_sdcp_device_get_application_secret (self, &application_secret);
if (!fpi_sdcp_verify_identify (application_secret, priv->identify_nonce, id, mac, &error))
{
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
fpi_device_action_error (FP_DEVICE (self), error);
return;
}
/* Clear no longer needed private data */
g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
/* Create a new print */
identified_print = fp_print_new (FP_DEVICE (self));
fpi_print_set_type (identified_print, FPI_PRINT_SDCP);
/* Set to true once committed */
fpi_print_set_device_stored (identified_print, FALSE);
/* Attach the ID to the print */
fpi_sdcp_device_set_print_id (identified_print, id);
/* The surrounding API expects a match/no-match against a given set. */
if (action == FPI_DEVICE_ACTION_VERIFY)
{
FpPrint *print;
fpi_device_get_verify_data (FP_DEVICE (self), &print);
if (fp_print_equal (print, identified_print))
fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_SUCCESS, identified_print, NULL);
else
fpi_device_verify_report (FP_DEVICE (self), FPI_MATCH_FAIL, identified_print, NULL);
fpi_device_verify_complete (FP_DEVICE (self), NULL);
}
else
{
GPtrArray *prints;
gint i;
fpi_device_get_identify_data (FP_DEVICE (self), &prints);
for (i = 0; i < prints->len; i++)
{
FpPrint *print = g_ptr_array_index (prints, i);
if (fp_print_equal (print, identified_print))
{
fpi_device_identify_report (FP_DEVICE (self), print, identified_print, NULL);
fpi_device_identify_complete (FP_DEVICE (self), NULL);
return;
}
}
/* Print wasn't in database. */
fpi_device_identify_report (FP_DEVICE (self), NULL, identified_print, NULL);
fpi_device_identify_complete (FP_DEVICE (self), NULL);
}
}
/**
* fpi_sdcp_device_get_print_id:
* @print: an SDCP device #FpPrint
* @id: (out) (transfer full): the ID gotten from the @print data
*
* Gets the SDCP enrollment ID from the @print data.
*
* The returned @id may be %NULL if the data was not set or in the wrong format.
*/
void
fpi_sdcp_device_get_print_id (FpPrint *print,
GBytes **id)
{
g_autoptr(GVariant) id_var = NULL;
g_autoptr(GVariant) data = NULL;
const guint8 *id_data;
gsize id_len;
g_return_if_fail (print);
g_return_if_fail (*id == NULL);
g_object_get (G_OBJECT (print), "fpi-data", &data, NULL);
if (!data)
{
fp_warn ("SDCP print data has not been set.");
return;
}
if (!g_variant_check_format_string (data, "(@ay)", FALSE))
{
fp_warn ("SDCP print data is not in expected format.");
return;
}
g_variant_get (data, "(@ay)", &id_var);
id_data = g_variant_get_fixed_array (id_var, &id_len, sizeof (guint8));
*id = g_bytes_new (id_data, id_len);
}
/**
* fpi_sdcp_device_set_print_id:
* @print: an SDCP device #FpPrint
* @id: the ID to set in the @print data
*
* Sets the SDCP enrollment ID in the @print data.
*/
void
fpi_sdcp_device_set_print_id (FpPrint *print,
GBytes *id)
{
GVariant *id_var;
GVariant *data;
g_return_if_fail (print);
g_return_if_fail (id);
id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
g_bytes_get_data (id, NULL),
g_bytes_get_size (id),
1);
data = g_variant_new ("(@ay)", id_var);
g_object_set (G_OBJECT (print), "fpi-data", data, NULL);
}

185
libfprint/fpi-sdcp-device.h Normal file
View File

@@ -0,0 +1,185 @@
/*
* 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
* 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-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:
* @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.
*
* Note all of these may simply be memory views into a larger #GBytes created
* using g_bytes_new_from_bytes().
*/
struct _FpiSdcpClaim
{
/*< public >*/
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;
GType fpi_sdcp_claim_get_type (void) G_GNUC_CONST;
FpiSdcpClaim *fpi_sdcp_claim_new (void);
FpiSdcpClaim *fpi_sdcp_claim_copy (FpiSdcpClaim *other);
void fpi_sdcp_claim_free (FpiSdcpClaim *claim);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSdcpClaim, fpi_sdcp_claim_free)
/**
* FpSdcpDeviceClass:
* @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.
* @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
* operation (i.e. any operation that requires capturing). It is entirely fine
* to ignore cancellation requests for short operations (e.g. open/close).
*
* This API is solely intended for drivers. It is purely internal and neither
* API nor ABI stable.
*/
struct _FpSdcpDeviceClass
{
FpDeviceClass parent_class;
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 *sdcp_device);
};
void fpi_sdcp_device_open_complete (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_get_connect_data (FpSdcpDevice *self,
GBytes **host_random,
GBytes **host_public_key);
void fpi_sdcp_device_connect_complete (FpSdcpDevice *self,
GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
GError *error);
void fpi_sdcp_device_get_reconnect_data (FpSdcpDevice *self,
GBytes **reconnect_random);
void fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
GBytes *mac,
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);

1079
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,6 +144,8 @@ 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' :
@@ -164,6 +167,8 @@ helper_sources = {
[ 'drivers/aes3k.c' ],
'openssl' :
[ ],
'sdcp' :
[ ],
'udev' :
[ ],
'virtual' :
@@ -179,6 +184,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,8 +112,8 @@ virtual_drivers = [
'virtual_image',
'virtual_device',
'virtual_device_storage',
'virtual_sdcp',
]
default_drivers = [
'upektc_img',
'vfs5011',
@@ -209,11 +209,13 @@ 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' : [ 'virtual', 'sdcp' ],
}
driver_helpers = []
@@ -272,6 +274,13 @@ foreach i : driver_helpers
error('OpenSSL is required for @0@ and possibly others'.format(driver))
endif
optional_deps += openssl_dep
elif i == 'sdcp'
openssl_dep = dependency('openssl', version: '>= 3.0.8', required: false)
if not openssl_dep.found()
error('OpenSSL >= 3.0.8 is required for SDCP support (@0@ and possibly others)'.format(driver))
endif
optional_deps += openssl_dep
elif i == 'udev'
install_udev_rules = true

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

@@ -19,6 +19,7 @@ envs.set('FP_DRIVERS_ALLOWLIST', ':'.join([
'virtual_image',
'virtual_device',
'virtual_device_storage',
'virtual_sdcp',
]))
envs.set('FP_PRINTS_PATH', meson.project_source_root() / 'examples' / 'prints')
@@ -52,9 +53,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',
@@ -91,6 +92,7 @@ if get_option('introspection')
virtual_devices_tests = [
'virtual-image',
'virtual-device',
'virtual-sdcp',
]
unittest_inspector = find_program('unittest_inspector.py')
@@ -131,6 +133,7 @@ if get_option('introspection')
suite: ut_suite,
depends: libfprint_typelib,
env: envs,
workdir: meson.current_source_dir(),
)
endforeach
@@ -181,6 +184,7 @@ if get_option('introspection')
meson.current_source_dir() / driver_test,
],
env: driver_envs,
workdir: meson.current_source_dir(),
suite: ['drivers'],
timeout: 15,
depends: libfprint_typelib,
@@ -244,13 +248,10 @@ else
endforeach
endif
test_utils = static_library('fprint-test-utils',
sources: [
test_util_sources = [
'test-utils.c',
'test-device-fake.c',
],
dependencies: libfprint_private_dep,
install: false)
]
unit_tests = [
'fpi-device',
@@ -265,6 +266,17 @@ if 'virtual_image' in drivers
]
endif
if 'sdcp' in driver_helpers
unit_tests += [
'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

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

@@ -0,0 +1,210 @@
/*
* 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 "fpi-sdcp.h"
#include "fpi-sdcp-device.h"
/* We can re-use the test payloads from virtual-sdcp */
#include "drivers/virtual-sdcp.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
};
static 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);
}
static FpiSdcpClaim *
get_fake_sdcp_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);
}
/******************************************************************************/
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_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 (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 = get_fake_sdcp_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 ();
}

169
tests/virtual-sdcp.py Normal file
View File

@@ -0,0 +1,169 @@
#!/usr/bin/env python3
import sys
try:
import gi
import os
from gi.repository import GLib
import unittest
except Exception as e:
print("Missing dependencies: %s" % str(e))
sys.exit(77)
FPrint = None
# 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 VirtualSDCPBase(unittest.TestCase):
@classmethod
def setUpClass(cls):
os.environ['FP_VIRTUAL_SDCP'] = '1'
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):
self.ctx = FPrint.Context()
self.assertIsNotNone(self.dev)
self.assertFalse(self.dev.is_open())
self.dev.open_sync()
self.assertTrue(self.dev.is_open())
def tearDown(self):
self.dev.close_sync()
self.assertFalse(self.dev.is_open())
del self.ctx
class VirtualSDCP(VirtualSDCPBase):
def test_connect(self):
# Nothing to do here since setUp and tearDown will open and close the device
pass
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.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 == 'SDCP 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()
GLib.log_remove_handler('libfprint-sdcp_device', handler_id)
assert success[0]
def test_list(self):
prints = self.dev.list_prints_sync()
assert len(prints) == 0
def test_enroll_list_verify(self):
# Set up a new print
template = FPrint.Print.new(self.dev)
template.set_finger(FPrint.Finger.LEFT_THUMB)
# Enroll the new print
new_print = self.dev.enroll_sync(template, None, None, None)
self.assertIsInstance(new_print, FPrint.Print)
# Get the print list again and ensure there is exactly 1 print
prints = self.dev.list_prints_sync()
self.assertTrue(len(prints) == 1)
# Ensure the one print from list is the same as new_print
self.assertTrue(prints[0].equal(new_print))
# Verify new_print
verify_res, verify_print = self.dev.verify_sync(prints[0])
self.assertTrue(verify_res)
self.assertTrue(verify_print.equal(prints[0]))
# Set up a second new print
template = FPrint.Print.new(self.dev)
template.set_finger(FPrint.Finger.LEFT_INDEX)
# Enroll the second print
new_print2 = self.dev.enroll_sync(template, None, None, None)
self.assertIsInstance(new_print2, FPrint.Print)
# Get the print list again and ensure there is exactly 2 prints
prints = self.dev.list_prints_sync()
self.assertTrue(len(prints) == 2)
# Ensure the second print from list is the same as new_print2
self.assertTrue(prints[1].equal(new_print2))
class VirtualSDCPNoReconnect(VirtualSDCPBase):
@classmethod
def setUpClass(cls):
os.environ['FP_VIRTUAL_SDCP_NO_RECONNECT'] = '1'
super().setUpClass()
def test_connect(self):
# Nothing to do here since setUp and tearDown will open and close the device
pass
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.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 == 'SDCP 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()
GLib.log_remove_handler('libfprint-sdcp_device', handler_id)
# Ensure that we did NOT see "SDCP Reconnect succeeded" in the log
assert success[0] == False
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)
unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))