mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2025-11-15 07:38:12 +00:00
sdcp: Refactor test-sdcp-device-fake to virtual-sdcp to support testing with fprintd
This commit is contained in:
331
libfprint/drivers/virtual-sdcp.c
Normal file
331
libfprint/drivers/virtual-sdcp.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Secure Device Connection Protocol (SDCP) support test utils
|
* Virtual driver test payloads for SDCP device debugging
|
||||||
|
*
|
||||||
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
@@ -19,11 +20,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "fpi-log.h"
|
#include "fpi-compat.h"
|
||||||
#include "fpi-sdcp.h"
|
|
||||||
#include "fpi-sdcp-device.h"
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
/* host keys */
|
/* host keys */
|
||||||
|
|
||||||
@@ -84,19 +81,12 @@ static const gchar application_secret_hex[] = "13330ba3135ecf5dc71cede01a8865407
|
|||||||
static const gchar reconnect_random_hex[] = "8a7451c1d3a8dca1c1330ca50d73454b351a49f46c8e9dcee15c964d295c31c9";
|
static const gchar reconnect_random_hex[] = "8a7451c1d3a8dca1c1330ca50d73454b351a49f46c8e9dcee15c964d295c31c9";
|
||||||
static const gchar reconnect_mac_hex[] = "bf3f3bb3bd6ecb2784c160f526f7bc3b3ca8faf5557194c48e0024a0493903c7";
|
static const gchar reconnect_mac_hex[] = "bf3f3bb3bd6ecb2784c160f526f7bc3b3ca8faf5557194c48e0024a0493903c7";
|
||||||
|
|
||||||
/* test verify_identify values */
|
|
||||||
|
|
||||||
static const gchar identify_nonce_hex[] = "3a1b506f5bec089059acefb9b44dfbdea7a599ee9aa267e5252664d60b798053";
|
|
||||||
static const gchar identify_enrollment_id_hex[] = "ef2055244e49c39beabdac49fdf4ee418605d195da23b202ba219a13831ae621";
|
|
||||||
static const gchar identify_mac_hex[] = "f0a5c5f261c2fe937d8b113857bc629cd07ca88edf991f69ca6fae5c332390f6";
|
|
||||||
|
|
||||||
/* test enrollment_id values */
|
/* test enrollment_id values */
|
||||||
|
|
||||||
static const gchar enrollment_nonce_hex[] = "c2101c44c9a667bba397e81f48b143398603e2c9335a68b409e1dbe71e005ca2";
|
static const gchar enrollment_nonce_hex[] = "c2101c44c9a667bba397e81f48b143398603e2c9335a68b409e1dbe71e005ca2";
|
||||||
static const gchar enrollment_enrollment_id_hex[] = "67109dc70a216331f1580ddac601915929c1ff6c9bcba6544ba572c660c3d91e";
|
static const gchar enrollment_id_hex[] = "67109dc70a216331f1580ddac601915929c1ff6c9bcba6544ba572c660c3d91e";
|
||||||
|
|
||||||
/******************************************************************************/
|
/* test verify_identify values */
|
||||||
|
|
||||||
GBytes *g_bytes_from_hex (const gchar *hex);
|
static const gchar identify_nonce_hex[] = "3a1b506f5bec089059acefb9b44dfbdea7a599ee9aa267e5252664d60b798053";
|
||||||
|
static const gchar identify_mac_hex[] = "53a723eef40713094a90c5ef9996cbd6ba268e30676cd7107705a6c3e3e1eff9";
|
||||||
FpiSdcpClaim *sdcp_test_claim (void);
|
|
||||||
@@ -144,6 +144,8 @@ driver_sources = {
|
|||||||
[ 'drivers/virtual-device.c' ],
|
[ 'drivers/virtual-device.c' ],
|
||||||
'virtual_device_storage' :
|
'virtual_device_storage' :
|
||||||
[ 'drivers/virtual-device-storage.c' ],
|
[ 'drivers/virtual-device-storage.c' ],
|
||||||
|
'virtual_sdcp' :
|
||||||
|
[ 'drivers/virtual-sdcp.c' ],
|
||||||
'synaptics' :
|
'synaptics' :
|
||||||
[ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ],
|
[ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ],
|
||||||
'goodixmoc' :
|
'goodixmoc' :
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ virtual_drivers = [
|
|||||||
'virtual_image',
|
'virtual_image',
|
||||||
'virtual_device',
|
'virtual_device',
|
||||||
'virtual_device_storage',
|
'virtual_device_storage',
|
||||||
|
'virtual_sdcp',
|
||||||
]
|
]
|
||||||
default_drivers = [
|
default_drivers = [
|
||||||
'upektc_img',
|
'upektc_img',
|
||||||
@@ -214,6 +215,7 @@ driver_helper_mapping = {
|
|||||||
'virtual_image' : [ 'virtual' ],
|
'virtual_image' : [ 'virtual' ],
|
||||||
'virtual_device' : [ 'virtual' ],
|
'virtual_device' : [ 'virtual' ],
|
||||||
'virtual_device_storage' : [ 'virtual' ],
|
'virtual_device_storage' : [ 'virtual' ],
|
||||||
|
'virtual_sdcp' : [ 'virtual', 'sdcp' ],
|
||||||
}
|
}
|
||||||
|
|
||||||
driver_helpers = []
|
driver_helpers = []
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ envs.set('FP_DRIVERS_ALLOWLIST', ':'.join([
|
|||||||
'virtual_image',
|
'virtual_image',
|
||||||
'virtual_device',
|
'virtual_device',
|
||||||
'virtual_device_storage',
|
'virtual_device_storage',
|
||||||
|
'virtual_sdcp',
|
||||||
]))
|
]))
|
||||||
|
|
||||||
envs.set('FP_PRINTS_PATH', meson.project_source_root() / 'examples' / 'prints')
|
envs.set('FP_PRINTS_PATH', meson.project_source_root() / 'examples' / 'prints')
|
||||||
@@ -91,6 +92,7 @@ if get_option('introspection')
|
|||||||
virtual_devices_tests = [
|
virtual_devices_tests = [
|
||||||
'virtual-image',
|
'virtual-image',
|
||||||
'virtual-device',
|
'virtual-device',
|
||||||
|
'virtual-sdcp',
|
||||||
]
|
]
|
||||||
|
|
||||||
unittest_inspector = find_program('unittest_inspector.py')
|
unittest_inspector = find_program('unittest_inspector.py')
|
||||||
@@ -265,12 +267,7 @@ if 'virtual_image' in drivers
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if 'sdcp' in driver_helpers
|
if 'sdcp' in driver_helpers
|
||||||
test_util_sources += [
|
|
||||||
'test-sdcp-device-fake.c',
|
|
||||||
'test-sdcp-utils.c',
|
|
||||||
]
|
|
||||||
unit_tests += [
|
unit_tests += [
|
||||||
'fp-sdcp-device',
|
|
||||||
'fpi-sdcp',
|
'fpi-sdcp',
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
* FpSdcpDevice Unit tests
|
|
||||||
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <libfprint/fprint.h>
|
|
||||||
|
|
||||||
#include "test-sdcp-device-fake.h"
|
|
||||||
#include "test-sdcp-utils.h"
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_set_get_print_id (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
g_autoptr(FpPrint) print = fp_print_new (device);
|
|
||||||
g_autoptr(GBytes) id1 = NULL;
|
|
||||||
g_autoptr(GBytes) id2 = NULL;
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
|
|
||||||
id1 = fpi_sdcp_generate_random (&error);
|
|
||||||
|
|
||||||
g_assert_nonnull (id1);
|
|
||||||
g_assert_no_error (error);
|
|
||||||
|
|
||||||
fpi_sdcp_device_set_print_id (print, id1);
|
|
||||||
fpi_sdcp_device_get_print_id (print, &id2);
|
|
||||||
|
|
||||||
g_assert_nonnull (id2);
|
|
||||||
g_assert_true (g_bytes_equal (id1, id2));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_identify (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
g_autoptr(GPtrArray) match_prints = g_ptr_array_new_with_free_func (g_object_unref);
|
|
||||||
g_autoptr(FpPrint) template_print = fp_print_new (device);
|
|
||||||
g_autoptr(FpPrint) print = NULL;
|
|
||||||
g_autoptr(FpPrint) matched_print = NULL;
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, &error));
|
|
||||||
|
|
||||||
print = fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error);
|
|
||||||
g_ptr_array_add (match_prints, g_object_ref_sink (print));
|
|
||||||
|
|
||||||
g_assert_true (fp_device_identify_sync (device, match_prints, NULL, NULL, NULL,
|
|
||||||
&matched_print, &print, &error));
|
|
||||||
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, &error));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_enroll (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
g_autoptr(FpPrint) template_print = fp_print_new (device);
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, &error));
|
|
||||||
|
|
||||||
g_assert_nonnull (fp_device_enroll_sync (device, template_print, NULL, NULL, NULL, &error));
|
|
||||||
g_assert_no_error (error);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, &error));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_list (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
g_autoptr(GPtrArray) prints = NULL;
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, &error));
|
|
||||||
|
|
||||||
prints = fp_device_list_prints_sync (device, NULL, &error);
|
|
||||||
|
|
||||||
g_assert_nonnull (prints);
|
|
||||||
g_assert_true (prints->len == 1);
|
|
||||||
|
|
||||||
/* TODO: Should we also check the print's "fpi-data" for the expected ID? */
|
|
||||||
|
|
||||||
g_assert_no_error (error);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, &error));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_reconnect (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
FpiSdcpDeviceFake *fake_device = FPI_SDCP_DEVICE_FAKE (device);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, NULL));
|
|
||||||
g_assert (fake_device->reconnect_called == FALSE);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, NULL));
|
|
||||||
|
|
||||||
/* open a second time to reconnect */
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, NULL));
|
|
||||||
g_assert (fake_device->reconnect_called == TRUE);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_open (void)
|
|
||||||
{
|
|
||||||
g_autoptr(FpDevice) device = g_object_new (FPI_TYPE_SDCP_DEVICE_FAKE, NULL);
|
|
||||||
|
|
||||||
g_assert_true (fp_device_open_sync (device, NULL, NULL));
|
|
||||||
g_assert_true (fp_device_close_sync (device, NULL, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char *argv[])
|
|
||||||
{
|
|
||||||
g_test_init (&argc, &argv, NULL);
|
|
||||||
|
|
||||||
g_test_add_func ("/sdcp_device/open", test_open);
|
|
||||||
g_test_add_func ("/sdcp_device/reconnect", test_reconnect);
|
|
||||||
g_test_add_func ("/sdcp_device/list", test_list);
|
|
||||||
g_test_add_func ("/sdcp_device/enroll", test_enroll);
|
|
||||||
g_test_add_func ("/sdcp_device/identify", test_identify);
|
|
||||||
g_test_add_func ("/sdcp_device/set_get_print_id", test_set_get_print_id);
|
|
||||||
|
|
||||||
return g_test_run ();
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,55 @@
|
|||||||
#define FP_COMPONENT "test_fpi_sdcp"
|
#define FP_COMPONENT "test_fpi_sdcp"
|
||||||
#include "fpi-log.h"
|
#include "fpi-log.h"
|
||||||
|
|
||||||
#include "test-sdcp-utils.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
|
static void
|
||||||
test_generate_enrollment_id (void)
|
test_generate_enrollment_id (void)
|
||||||
@@ -29,7 +77,7 @@ test_generate_enrollment_id (void)
|
|||||||
g_autoptr(GError) error = NULL;
|
g_autoptr(GError) error = NULL;
|
||||||
g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
|
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) nonce = g_bytes_from_hex (enrollment_nonce_hex);
|
||||||
g_autoptr(GBytes) expected_id = g_bytes_from_hex (enrollment_enrollment_id_hex);
|
g_autoptr(GBytes) expected_id = g_bytes_from_hex (enrollment_id_hex);
|
||||||
|
|
||||||
id = fpi_sdcp_generate_enrollment_id (application_secret, nonce, &error);
|
id = fpi_sdcp_generate_enrollment_id (application_secret, nonce, &error);
|
||||||
|
|
||||||
@@ -49,7 +97,7 @@ test_verify_identify (void)
|
|||||||
g_autoptr(GError) error = NULL;
|
g_autoptr(GError) error = NULL;
|
||||||
g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
|
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) nonce = g_bytes_from_hex (identify_nonce_hex);
|
||||||
g_autoptr(GBytes) id = g_bytes_from_hex (identify_enrollment_id_hex);
|
g_autoptr(GBytes) id = g_bytes_from_hex (enrollment_id_hex);
|
||||||
g_autoptr(GBytes) mac = g_bytes_from_hex (identify_mac_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_true (fpi_sdcp_verify_identify (application_secret, nonce, id, mac, &error));
|
||||||
@@ -78,7 +126,7 @@ test_verify_connect (void)
|
|||||||
g_autoptr(GBytes) host_random = g_bytes_from_hex (host_random_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) device_random = g_bytes_from_hex (device_random_hex);
|
||||||
g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
|
g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
|
||||||
FpiSdcpClaim *claim = sdcp_test_claim ();
|
FpiSdcpClaim *claim = get_fake_sdcp_claim ();
|
||||||
|
|
||||||
g_autoptr(GBytes) expected_application_secret = g_bytes_from_hex (application_secret_hex);
|
g_autoptr(GBytes) expected_application_secret = g_bytes_from_hex (application_secret_hex);
|
||||||
|
|
||||||
|
|||||||
@@ -1,197 +0,0 @@
|
|||||||
/*
|
|
||||||
* Virtual driver for SDCP unit testing
|
|
||||||
* Completes actions using example values from Microsoft's SDCP documentation
|
|
||||||
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define FP_COMPONENT "fake_test_sdcp_dev"
|
|
||||||
#include "fpi-log.h"
|
|
||||||
|
|
||||||
#include "test-sdcp-device-fake.h"
|
|
||||||
|
|
||||||
G_DEFINE_TYPE (FpiSdcpDeviceFake, fpi_sdcp_device_fake, FP_TYPE_SDCP_DEVICE)
|
|
||||||
|
|
||||||
static const FpIdEntry driver_ids[] = {
|
|
||||||
{ .virtual_envvar = "FP_VIRTUAL_FAKE_SDCP_DEVICE" },
|
|
||||||
{ .virtual_envvar = NULL }
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_identify (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
g_autoptr(GBytes) nonce = NULL;
|
|
||||||
g_autoptr(GBytes) identify_enrollment_id = g_bytes_from_hex (identify_enrollment_id_hex);
|
|
||||||
g_autoptr(GBytes) identify_mac = g_bytes_from_hex (identify_mac_hex);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Normally, a driver would fetch the identify data and then send it to the
|
|
||||||
* device's Identify command. In this fake device, we will just fetch and
|
|
||||||
* verify the nonce was generated but do nothing with it
|
|
||||||
*/
|
|
||||||
|
|
||||||
fpi_sdcp_device_get_identify_data (sdcp_device, &nonce);
|
|
||||||
|
|
||||||
g_assert (nonce);
|
|
||||||
g_assert (g_bytes_get_size (nonce) == SDCP_NONCE_SIZE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In emulation mode (FP_DEVICE_EMULATION=1), a different hard-coded nonce is
|
|
||||||
* set in fpi-sdcp-device, which was the same nonce used to generate both the
|
|
||||||
* identify_enrollment_id and identify_mac values provided here
|
|
||||||
*/
|
|
||||||
|
|
||||||
fpi_sdcp_device_identify_complete (sdcp_device, identify_enrollment_id, identify_mac, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_enroll_commit (FpSdcpDevice *sdcp_device, GBytes *id)
|
|
||||||
{
|
|
||||||
fpi_sdcp_device_enroll_commit_complete (sdcp_device, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_enroll (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
GBytes *enrollment_nonce = g_bytes_from_hex (enrollment_nonce_hex);
|
|
||||||
|
|
||||||
fpi_sdcp_device_enroll_commit (sdcp_device, enrollment_nonce, error);
|
|
||||||
|
|
||||||
g_bytes_unref (enrollment_nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_list (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
g_autoptr(GBytes) identify_enrollment_id = g_bytes_from_hex (identify_enrollment_id_hex);
|
|
||||||
GPtrArray *enrollment_ids = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
|
|
||||||
|
|
||||||
g_ptr_array_add (enrollment_ids, g_steal_pointer (&identify_enrollment_id));
|
|
||||||
|
|
||||||
for (gint i = 0; i < enrollment_ids->len; i++)
|
|
||||||
{
|
|
||||||
fp_dbg ("print %d:", i);
|
|
||||||
fp_dbg_hex_dump_gbytes (g_ptr_array_index (enrollment_ids, i));
|
|
||||||
}
|
|
||||||
|
|
||||||
fpi_sdcp_device_list_complete (sdcp_device, g_steal_pointer (&enrollment_ids), NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_reconnect (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
FpiSdcpDeviceFake *fake_device = FPI_SDCP_DEVICE_FAKE (sdcp_device);
|
|
||||||
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
g_autoptr(GBytes) random = NULL;
|
|
||||||
g_autoptr(GBytes) reconnect_mac = g_bytes_from_hex (reconnect_mac_hex);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Normally, a driver would fetch the reconnect data and then send it to the
|
|
||||||
* device's Reconnect command. In this fake device, we will just fetch and
|
|
||||||
* verify the random was generated but do nothing with it
|
|
||||||
*/
|
|
||||||
|
|
||||||
fpi_sdcp_device_get_reconnect_data (sdcp_device, &random);
|
|
||||||
|
|
||||||
g_assert (random);
|
|
||||||
g_assert (g_bytes_get_size (random) == SDCP_RANDOM_SIZE);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In emulation mode (FP_DEVICE_EMULATION=1), a different hard-coded random is
|
|
||||||
* set in fpi-sdcp-device, which was the same random used to generate the
|
|
||||||
* reconnect_mac value provided here
|
|
||||||
*/
|
|
||||||
|
|
||||||
fpi_sdcp_device_reconnect_complete (sdcp_device, reconnect_mac, error);
|
|
||||||
|
|
||||||
fake_device->reconnect_called = TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_connect (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
g_autoptr(GBytes) host_random = NULL;
|
|
||||||
g_autoptr(GBytes) host_public_key = NULL;
|
|
||||||
g_autoptr(GBytes) device_random = g_bytes_from_hex (device_random_hex);
|
|
||||||
g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
|
|
||||||
FpiSdcpClaim *claim = sdcp_test_claim ();
|
|
||||||
|
|
||||||
fp_device_open_sync (FP_DEVICE (sdcp_device), NULL, NULL);
|
|
||||||
|
|
||||||
fpi_sdcp_device_get_connect_data (sdcp_device, &host_random, &host_public_key);
|
|
||||||
|
|
||||||
g_assert (host_random);
|
|
||||||
g_assert (g_bytes_get_size (host_random) == SDCP_RANDOM_SIZE);
|
|
||||||
|
|
||||||
g_assert (host_public_key);
|
|
||||||
g_assert (g_bytes_get_size (host_public_key) == SDCP_PUBLIC_KEY_SIZE);
|
|
||||||
|
|
||||||
fpi_sdcp_device_connect_complete (sdcp_device, device_random, claim, connect_mac, error);
|
|
||||||
|
|
||||||
fpi_sdcp_claim_free (claim);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_close (FpDevice *device)
|
|
||||||
{
|
|
||||||
fpi_device_close_complete (device, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_open (FpSdcpDevice *sdcp_device)
|
|
||||||
{
|
|
||||||
fpi_sdcp_device_open_complete (sdcp_device, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_init (FpiSdcpDeviceFake *self)
|
|
||||||
{
|
|
||||||
G_DEBUG_HERE ();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fpi_sdcp_device_fake_class_init (FpiSdcpDeviceFakeClass *klass)
|
|
||||||
{
|
|
||||||
FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
|
|
||||||
FpSdcpDeviceClass *sdcp_dev_class = FP_SDCP_DEVICE_CLASS (klass);
|
|
||||||
|
|
||||||
dev_class->id = FP_COMPONENT;
|
|
||||||
dev_class->full_name = "Virtual SDCP test device";
|
|
||||||
dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
|
|
||||||
dev_class->id_table = driver_ids;
|
|
||||||
dev_class->nr_enroll_stages = 5;
|
|
||||||
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
|
||||||
|
|
||||||
sdcp_dev_class->ignore_device_certificate = FALSE;
|
|
||||||
sdcp_dev_class->ignore_device_signatures = FALSE;
|
|
||||||
|
|
||||||
sdcp_dev_class->open = fpi_sdcp_device_fake_open;
|
|
||||||
sdcp_dev_class->connect = fpi_sdcp_device_fake_connect;
|
|
||||||
sdcp_dev_class->reconnect = fpi_sdcp_device_fake_reconnect;
|
|
||||||
sdcp_dev_class->list = fpi_sdcp_device_fake_list;
|
|
||||||
sdcp_dev_class->enroll = fpi_sdcp_device_fake_enroll;
|
|
||||||
sdcp_dev_class->enroll_commit = fpi_sdcp_device_fake_enroll_commit;
|
|
||||||
sdcp_dev_class->identify = fpi_sdcp_device_fake_identify;
|
|
||||||
|
|
||||||
dev_class->close = fpi_sdcp_device_fake_close;
|
|
||||||
|
|
||||||
fpi_device_class_auto_initialize_features (dev_class);
|
|
||||||
dev_class->features |= FP_DEVICE_FEATURE_STORAGE;
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Virtual driver for SDCP unit testing
|
|
||||||
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "fpi-sdcp-device.h"
|
|
||||||
|
|
||||||
#include "test-sdcp-utils.h"
|
|
||||||
|
|
||||||
#define FPI_TYPE_SDCP_DEVICE_FAKE (fpi_sdcp_device_fake_get_type ())
|
|
||||||
G_DECLARE_FINAL_TYPE (FpiSdcpDeviceFake, fpi_sdcp_device_fake, FPI, SDCP_DEVICE_FAKE, FpSdcpDevice)
|
|
||||||
|
|
||||||
struct _FpiSdcpDeviceFake
|
|
||||||
{
|
|
||||||
FpDevice parent;
|
|
||||||
gboolean reconnect_called;
|
|
||||||
};
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Secure Device Connection Protocol (SDCP) support test utils
|
|
||||||
* Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
|
|
||||||
*
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This library is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public
|
|
||||||
* License along with this library; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "test-sdcp-utils.h"
|
|
||||||
|
|
||||||
static const guint8 from_hex_map[] = {
|
|
||||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
|
|
||||||
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
|
|
||||||
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, // @abcdef
|
|
||||||
};
|
|
||||||
|
|
||||||
GBytes *
|
|
||||||
g_bytes_from_hex (const gchar *hex)
|
|
||||||
{
|
|
||||||
g_autoptr(GBytes) res = NULL;
|
|
||||||
guint8 b0, b1;
|
|
||||||
gsize bytes_len = strlen (hex) / 2;
|
|
||||||
guint8 *bytes = g_malloc0 (bytes_len);
|
|
||||||
|
|
||||||
for (int i = 0; i < strlen (hex) - 1; i += 2)
|
|
||||||
{
|
|
||||||
b0 = ((guint8) hex[i + 0] & 0x1F) ^ 0x10;
|
|
||||||
b1 = ((guint8) hex[i + 1] & 0x1F) ^ 0x10;
|
|
||||||
bytes[i / 2] = (guint8) (from_hex_map[b0] << 4) | from_hex_map[b1];
|
|
||||||
}
|
|
||||||
|
|
||||||
res = g_bytes_new_take (bytes, bytes_len);
|
|
||||||
|
|
||||||
return g_steal_pointer (&res);
|
|
||||||
}
|
|
||||||
|
|
||||||
FpiSdcpClaim *
|
|
||||||
sdcp_test_claim (void)
|
|
||||||
{
|
|
||||||
FpiSdcpClaim *claim = g_new0 (FpiSdcpClaim, 1);
|
|
||||||
|
|
||||||
claim->model_certificate = g_bytes_from_hex (model_certificate_hex);
|
|
||||||
claim->device_public_key = g_bytes_from_hex (device_public_key_hex);
|
|
||||||
claim->firmware_public_key = g_bytes_from_hex (firmware_public_key_hex);
|
|
||||||
claim->firmware_hash = g_bytes_from_hex (firmware_hash_hex);
|
|
||||||
claim->model_signature = g_bytes_from_hex (model_signature_hex);
|
|
||||||
claim->device_signature = g_bytes_from_hex (device_signature_hex);
|
|
||||||
return g_steal_pointer (&claim);
|
|
||||||
}
|
|
||||||
169
tests/virtual-sdcp.py
Normal file
169
tests/virtual-sdcp.py
Normal 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))
|
||||||
Reference in New Issue
Block a user