diff --git a/doc/libfprint-2-sections.txt b/doc/libfprint-2-sections.txt
index b582f2c2..756ca794 100644
--- a/doc/libfprint-2-sections.txt
+++ b/doc/libfprint-2-sections.txt
@@ -226,19 +226,24 @@ fpi_image_device_set_bz3_threshold
Internal FpSdcpDevice
FpiSdcpClaim
FpSdcpDeviceClass
-fpi_sdcp_claim_copy
-fpi_sdcp_claim_free
fpi_sdcp_claim_get_type
fpi_sdcp_claim_new
-fpi_sdcp_device_connect_complete
+fpi_sdcp_claim_copy
+fpi_sdcp_claim_free
+fpi_sdcp_device_open_complete
fpi_sdcp_device_get_connect_data
+fpi_sdcp_device_connect_complete
fpi_sdcp_device_get_reconnect_data
fpi_sdcp_device_reconnect_complete
+fpi_sdcp_device_list_complete
+fpi_sdcp_device_enroll_commit
fpi_sdcp_device_enroll_commit_complete
-fpi_sdcp_device_enroll_ready
-fpi_sdcp_device_enroll_set_nonce
+fpi_sdcp_device_get_identify_data
+fpi_sdcp_device_set_identify_data
fpi_sdcp_device_identify_retry
fpi_sdcp_device_identify_complete
+fpi_sdcp_device_get_print_id
+fpi_sdcp_device_set_print_id
@@ -249,6 +254,8 @@ fp_warn
fp_err
BUG_ON
BUG
+fp_dbg_hex_dump_bytes
+fp_dbg_hex_dump_gbytes
diff --git a/libfprint/drivers/egismoc/egismoc.c b/libfprint/drivers/egismoc/egismoc.c
index 8f35a67f..7bf71a54 100644
--- a/libfprint/drivers/egismoc/egismoc.c
+++ b/libfprint/drivers/egismoc/egismoc.c
@@ -1,15 +1,10 @@
/*
* Driver for Egis Technology (LighTuning) Match-On-Chip sensors
- * Originally authored 2023 by Joshua Grisham
+ * Copyright (C) 2023-2025 Joshua Grisham
*
* 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
@@ -26,6 +21,7 @@
*/
#define FP_COMPONENT "egismoc"
+#include "fpi-log.h"
#include
#include
@@ -42,19 +38,24 @@ struct _FpiDeviceEgisMoc
FpiSsm *task_ssm;
FpiSsm *cmd_ssm;
FpiUsbTransfer *cmd_transfer;
- GCancellable *interrupt_cancellable;
GPtrArray *enrolled_ids;
+ GBytes *enrollment_nonce;
gint max_enroll_stages;
+ FpiSsm *wait_finger_ssm;
+ gint64 wait_finger_start;
+ GCancellable *interrupt_cancellable;
+ gboolean dev_init_done;
};
-G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_DEVICE);
+G_DEFINE_TYPE (FpiDeviceEgisMoc, fpi_device_egismoc, FP_TYPE_SDCP_DEVICE);
static const FpIdEntry egismoc_id_table[] = {
{ .vid = 0x1c7a, .pid = 0x0582, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 },
- { .vid = 0x1c7a, .pid = 0x0583, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 },
+ { .vid = 0x1c7a, .pid = 0x0583, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_15 },
{ .vid = 0x1c7a, .pid = 0x0586, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x0587, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE1 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_20 },
{ .vid = 0x1c7a, .pid = 0x05a1, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 },
+ { .vid = 0x1c7a, .pid = 0x05a5, .driver_data = EGISMOC_DRIVER_CHECK_PREFIX_TYPE2 | EGISMOC_DRIVER_MAX_ENROLL_STAGES_15 },
{ .vid = 0, .pid = 0, .driver_data = 0 }
};
@@ -74,45 +75,12 @@ typedef struct egismoc_enroll_print
int stage;
} EnrollPrint;
-static void
-egismoc_finger_on_sensor_cb (FpiUsbTransfer *transfer,
- FpDevice *device,
- gpointer userdata,
- GError *error)
+typedef struct egismoc_identify_print
{
- fp_dbg ("Finger on sensor callback");
- fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
-
- g_return_if_fail (transfer->ssm);
- if (error)
- fpi_ssm_mark_failed (transfer->ssm, error);
- else
- fpi_ssm_next_state (transfer->ssm);
-}
-
-static void
-egismoc_wait_finger_on_sensor (FpiSsm *ssm,
- FpDevice *device)
-{
- fp_dbg ("Wait for finger on sensor");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
-
- g_autoptr(FpiUsbTransfer) transfer = fpi_usb_transfer_new (device);
-
- fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN,
- EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH);
- transfer->ssm = ssm;
- /* Interrupt on this device always returns 1 byte short; this is expected */
- transfer->short_is_error = FALSE;
-
- fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
-
- fpi_usb_transfer_submit (g_steal_pointer (&transfer),
- EGISMOC_USB_INTERRUPT_TIMEOUT,
- self->interrupt_cancellable,
- egismoc_finger_on_sensor_cb,
- NULL);
-}
+ GBytes *id;
+ GBytes *mac;
+ GError *error;
+} IdentifyPrint;
static gboolean
egismoc_validate_response_prefix (const guchar *buffer_in,
@@ -380,52 +348,108 @@ egismoc_exec_cmd (FpDevice *device,
}
static void
-egismoc_set_print_data (FpPrint *print,
- const gchar *device_print_id,
- const gchar *user_id)
-{
- GVariant *print_id_var = NULL;
- GVariant *fpi_data = NULL;
- g_autofree gchar *fill_user_id = NULL;
-
- if (user_id)
- fill_user_id = g_strdup (user_id);
- else
- fill_user_id = g_strndup (device_print_id, EGISMOC_FINGERPRINT_DATA_SIZE);
-
- fpi_print_fill_from_user_id (print, fill_user_id);
-
- fpi_print_set_type (print, FPI_PRINT_RAW);
- fpi_print_set_device_stored (print, TRUE);
-
- g_object_set (print, "description", fill_user_id, NULL);
-
- print_id_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
- device_print_id,
- EGISMOC_FINGERPRINT_DATA_SIZE,
- sizeof (guchar));
- fpi_data = g_variant_new ("(@ay)", print_id_var);
- g_object_set (print, "fpi-data", fpi_data, NULL);
-}
-
-static GPtrArray *
-egismoc_get_enrolled_prints (FpDevice *device)
+egismoc_wait_finger_ssm_done (FpiSsm *ssm,
+ FpDevice *device,
+ GError *error)
{
+ fp_dbg ("Wait for finger SSM done");
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
- g_autoptr(GPtrArray) result = g_ptr_array_new_with_free_func (g_object_unref);
+ /* wait_finger_ssm is going to be freed by completion of SSM */
+ g_assert (!self->wait_finger_ssm || self->wait_finger_ssm == ssm);
- if (!self->enrolled_ids)
- return g_steal_pointer (&result);
+ self->wait_finger_ssm = NULL;
+ self->wait_finger_start = 0;
- for (guint i = 0; i < self->enrolled_ids->len; i++)
+ if (error)
+ fpi_device_action_error (device, error);
+}
+
+static void
+egismoc_finger_on_sensor_cb (FpiUsbTransfer *transfer,
+ FpDevice *device,
+ gpointer userdata,
+ GError *error)
+{
+ fp_dbg ("Finger on sensor callback");
+
+ g_return_if_fail (transfer->ssm);
+
+ if (error) {
+ fpi_ssm_mark_failed (transfer->ssm, error);
+ return;
+ }
+
+ /* finger is "present" when buffer begins with "SIGE" and ends in valid suffix */
+ if (memcmp (transfer->buffer, egismoc_read_prefix, 4) == 0 &&
+ egismoc_validate_response_suffix (transfer->buffer,
+ transfer->actual_length,
+ rsp_sensor_has_finger_suffix,
+ rsp_sensor_has_finger_suffix_len))
{
- FpPrint *print = fp_print_new (device);
- egismoc_set_print_data (print, g_ptr_array_index (self->enrolled_ids, i), NULL);
- g_ptr_array_add (result, g_object_ref_sink (print));
+ fpi_device_report_finger_status (device, FP_FINGER_STATUS_PRESENT);
+ fpi_ssm_next_state (transfer->ssm);
}
+ else
+ {
+ fpi_ssm_jump_to_state (transfer->ssm, WAIT_FINGER_NOT_ON_SENSOR);
+ }
+}
- return g_steal_pointer (&result);
+static void
+egismoc_wait_finger_run_state (FpiSsm *ssm,
+ FpDevice *device)
+{
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ g_autoptr(FpiUsbTransfer) transfer = NULL;
+
+ switch (fpi_ssm_get_cur_state (ssm))
+ {
+ case WAIT_FINGER_NOT_ON_SENSOR:
+ if (self->wait_finger_start + EGISMOC_FINGER_ON_SENSOR_TIMEOUT_USEC > g_get_monotonic_time ())
+ {
+ transfer = fpi_usb_transfer_new (device);
+ fpi_usb_transfer_fill_interrupt (transfer, EGISMOC_EP_CMD_INTERRUPT_IN,
+ EGISMOC_USB_INTERRUPT_IN_RECV_LENGTH);
+
+ transfer->ssm = ssm;
+ /* Interrupt on this device always returns 1 byte short; this is expected */
+ transfer->short_is_error = FALSE;
+
+ fpi_usb_transfer_submit (g_steal_pointer (&transfer),
+ EGISMOC_USB_INTERRUPT_TIMEOUT,
+ self->interrupt_cancellable,
+ egismoc_finger_on_sensor_cb,
+ NULL);
+ }
+ else
+ {
+ fpi_ssm_mark_failed (ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "Timed out trying to detect "
+ "finger on sensor"));
+ }
+ break;
+
+ case WAIT_FINGER_ON_SENSOR:
+ fpi_ssm_mark_completed (ssm);
+ fpi_ssm_next_state (self->task_ssm);
+ break;
+ }
+}
+
+static void
+egismoc_wait_finger_on_sensor (FpDevice *device)
+{
+ fp_dbg ("Wait for finger on sensor");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+
+ self->wait_finger_start = g_get_monotonic_time ();
+
+ fpi_device_report_finger_status (device, FP_FINGER_STATUS_NEEDED);
+
+ g_assert (self->wait_finger_ssm == NULL);
+ self->wait_finger_ssm = fpi_ssm_new (device, egismoc_wait_finger_run_state, WAIT_FINGER_STATES);
+ fpi_ssm_start (self->wait_finger_ssm, egismoc_wait_finger_ssm_done);
}
static void
@@ -436,6 +460,10 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device,
{
fp_dbg ("List callback");
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ const guint8 *data;
+ guchar *enrollment_id = NULL;
+ FpiByteReader reader;
+ gboolean read = TRUE;
if (error)
{
@@ -446,32 +474,29 @@ egismoc_list_fill_enrolled_ids_cb (FpDevice *device,
g_clear_pointer (&self->enrolled_ids, g_ptr_array_unref);
self->enrolled_ids = g_ptr_array_new_with_free_func (g_free);
- FpiByteReader reader;
- gboolean read = TRUE;
-
fpi_byte_reader_init (&reader, buffer_in, length_in);
read &= fpi_byte_reader_set_pos (&reader, EGISMOC_LIST_RESPONSE_PREFIX_SIZE);
/*
- * Each fingerprint ID will be returned in this response as a 32 byte array
+ * Each enrollment_id will be returned in this response as a 32 byte array
* The other stuff in the payload is 16 bytes long, so if there is at least 1
* print then the length should be at least 16+32=48 bytes long
*/
while (read)
{
- const guint8 *data;
- g_autofree gchar *print_id = NULL;
-
- read &= fpi_byte_reader_get_data (&reader, EGISMOC_FINGERPRINT_DATA_SIZE,
- &data);
+ read &= fpi_byte_reader_get_data (&reader, SDCP_ENROLLMENT_ID_SIZE, &data);
if (!read)
break;
- print_id = g_strndup ((gchar *) data, EGISMOC_FINGERPRINT_DATA_SIZE);
- fp_dbg ("Device fingerprint %0d: %.*s", self->enrolled_ids->len + 1,
- EGISMOC_FINGERPRINT_DATA_SIZE, print_id);
- g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&print_id));
+ enrollment_id = g_malloc0 (SDCP_ENROLLMENT_ID_SIZE);
+ memcpy (enrollment_id, data, SDCP_ENROLLMENT_ID_SIZE);
+
+ fp_dbg ("Device ID %0d:", self->enrolled_ids->len + 1);
+ fp_dbg_hex_dump_bytes (enrollment_id, SDCP_ENROLLMENT_ID_SIZE);
+
+ g_ptr_array_add (self->enrolled_ids, g_steal_pointer (&enrollment_id));
+ g_free (enrollment_id);
}
fp_info ("Number of currently enrolled fingerprints on the device is %d",
@@ -485,7 +510,9 @@ static void
egismoc_list_run_state (FpiSsm *ssm,
FpDevice *device)
{
- g_autoptr(GPtrArray) enrolled_prints = NULL;
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+ g_autoptr(GPtrArray) ids = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
@@ -495,21 +522,27 @@ egismoc_list_run_state (FpiSsm *ssm,
break;
case LIST_RETURN_ENROLLED_PRINTS:
- enrolled_prints = egismoc_get_enrolled_prints (device);
- fpi_device_list_complete (device, g_steal_pointer (&enrolled_prints), NULL);
+ ids = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
+ for (gint i = 0; i < self->enrolled_ids->len; i++)
+ {
+ GBytes *id = g_bytes_new (g_ptr_array_index (self->enrolled_ids, i),
+ SDCP_ENROLLMENT_ID_SIZE);
+ g_ptr_array_add (ids, g_steal_pointer (&id));
+ }
+ fpi_sdcp_device_list_complete (sdcp_device, g_steal_pointer (&ids), NULL);
fpi_ssm_next_state (ssm);
break;
}
}
static void
-egismoc_list (FpDevice *device)
+egismoc_list (FpSdcpDevice *sdcp_device)
{
fp_dbg ("List");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (sdcp_device);
g_assert (self->task_ssm == NULL);
- self->task_ssm = fpi_ssm_new (device,
+ self->task_ssm = fpi_ssm_new (FP_DEVICE (sdcp_device),
egismoc_list_run_state,
LIST_STATES);
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
@@ -523,12 +556,7 @@ egismoc_get_delete_cmd (FpDevice *device,
fp_dbg ("Get delete command");
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
g_auto(FpiByteWriter) writer = {0};
- g_autoptr(GVariant) print_data = NULL;
- g_autoptr(GVariant) print_data_id_var = NULL;
- const guchar *print_data_id = NULL;
- gsize print_data_id_len = 0;
- g_autofree gchar *print_description = NULL;
- g_autofree guchar *enrolled_print_id = NULL;
+ g_autoptr(GBytes) enrollment_id = NULL;
g_autofree guchar *result = NULL;
gboolean written = TRUE;
@@ -552,11 +580,9 @@ egismoc_get_delete_cmd (FpDevice *device,
else if (self->enrolled_ids)
num_to_delete = self->enrolled_ids->len;
- const gsize body_length = sizeof (guchar) * EGISMOC_FINGERPRINT_DATA_SIZE *
- num_to_delete;
+ const gsize body_length = sizeof (guchar) * SDCP_ENROLLMENT_ID_SIZE * num_to_delete;
/* total_length is the 6 various bytes plus prefix and body payload */
- const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len +
- body_length;
+ const gsize total_length = (sizeof (guchar) * 6) + cmd_delete_prefix_len + body_length;
/* pre-fill entire payload with 00s */
fpi_byte_writer_init_with_size (&writer, total_length, TRUE);
@@ -598,32 +624,26 @@ egismoc_get_delete_cmd (FpDevice *device,
written &= fpi_byte_writer_put_uint8 (&writer, num_to_delete * 0x20);
}
- /* append desired 32-byte fingerprint IDs */
- /* if passed a delete_print then fetch its data from the FpPrint */
+ /* append desired enrollment_id(s) */
+
+ /* if passed a delete_print then fetch its ID from the FpPrint */
if (delete_print)
{
- g_object_get (delete_print, "description", &print_description, NULL);
- g_object_get (delete_print, "fpi-data", &print_data, NULL);
-
- if (!g_variant_check_format_string (print_data, "(@ay)", FALSE))
+ fpi_sdcp_device_get_print_id (delete_print, &enrollment_id);
+ if (!enrollment_id)
{
fpi_ssm_mark_failed (self->task_ssm,
- fpi_device_error_new (FP_DEVICE_ERROR_DATA_INVALID));
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
+ "Print data missing ID"));
return NULL;
}
- g_variant_get (print_data, "(@ay)", &print_data_id_var);
- print_data_id = g_variant_get_fixed_array (print_data_id_var,
- &print_data_id_len, sizeof (guchar));
+ fp_dbg ("Delete enrollment ID:");
+ fp_dbg_hex_dump_gbytes (enrollment_id);
- if (!g_str_has_prefix (print_description, "FP"))
- fp_dbg ("Fingerprint '%s' was not created by libfprint; deleting anyway.",
- print_description);
-
- fp_info ("Delete fingerprint %s (%s)", print_description, print_data_id);
-
- written &= fpi_byte_writer_put_data (&writer, print_data_id,
- EGISMOC_FINGERPRINT_DATA_SIZE);
+ written &= fpi_byte_writer_put_data (&writer,
+ g_bytes_get_data (enrollment_id, NULL),
+ g_bytes_get_size (enrollment_id));
}
/* Otherwise assume this is a "clear" - just loop through and append all enrolled IDs */
else if (self->enrolled_ids)
@@ -632,7 +652,7 @@ egismoc_get_delete_cmd (FpDevice *device,
{
written &= fpi_byte_writer_put_data (&writer,
g_ptr_array_index (self->enrolled_ids, i),
- EGISMOC_FINGERPRINT_DATA_SIZE);
+ SDCP_ENROLLMENT_ID_SIZE);
}
}
@@ -679,7 +699,7 @@ egismoc_delete_cb (FpDevice *device,
{
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Unsupported delete action."));
+ "Unsupported delete action"));
}
}
else
@@ -694,8 +714,10 @@ static void
egismoc_delete_run_state (FpiSsm *ssm,
FpDevice *device)
{
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
g_autofree guchar *payload = NULL;
gsize payload_length = 0;
+ GError *error = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
@@ -707,10 +729,24 @@ egismoc_delete_run_state (FpiSsm *ssm,
case DELETE_DELETE:
if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_DELETE)
- payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm),
- &payload_length);
+ {
+ payload = egismoc_get_delete_cmd (device, fpi_ssm_get_data (ssm),
+ &payload_length);
+ }
else
- payload = egismoc_get_delete_cmd (device, NULL, &payload_length);
+ {
+ if (self->enrolled_ids->len == 0)
+ {
+ error = fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_NOT_FOUND,
+ "Clear attempted when there are no prints "
+ "currently stored on the device");
+ fpi_device_delete_complete (device, error);
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ return;
+ }
+
+ payload = egismoc_get_delete_cmd (device, NULL, &payload_length);
+ }
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length,
g_free, egismoc_delete_cb);
@@ -749,6 +785,95 @@ egismoc_delete (FpDevice *device)
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
}
+static void
+egismoc_enroll_commit_complete_cb (FpDevice *device,
+ guchar *buffer_in,
+ gsize length_in,
+ GError *error)
+{
+ fp_dbg ("Enroll commit complete callback");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+
+ if (error)
+ {
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ fpi_sdcp_device_enroll_commit_complete (sdcp_device, error);
+ return;
+ }
+
+ fpi_sdcp_device_enroll_commit_complete (sdcp_device, NULL);
+ fpi_ssm_next_state (self->task_ssm);
+}
+
+static void
+egismoc_enroll_commit_cb (FpDevice *device,
+ guchar *buffer_in,
+ gsize length_in,
+ GError *error)
+{
+ fp_dbg ("Enroll commit callback");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+
+ g_clear_pointer (&self->enrollment_nonce, g_bytes_unref);
+
+ if (error)
+ {
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ fpi_sdcp_device_enroll_commit_complete (sdcp_device, error);
+ return;
+ }
+
+ if (!egismoc_validate_response_suffix (buffer_in,
+ length_in,
+ rsp_commit_success_suffix,
+ rsp_commit_success_suffix_len))
+ {
+ g_propagate_error (&error, fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
+ "Enrollment was rejected by the device"));
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ fpi_sdcp_device_enroll_commit_complete (sdcp_device, error);
+ return;
+ }
+
+ egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len,
+ NULL, egismoc_enroll_commit_complete_cb);
+}
+
+static void
+egismoc_enroll_commit (FpSdcpDevice *sdcp_device,
+ GBytes *id)
+{
+ fp_dbg ("Enroll commit");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (sdcp_device);
+ g_auto(FpiByteWriter) writer = {0};
+ g_autoptr(GError) error = NULL;
+ const guint8 *new_id;
+ gsize new_id_len = 0;
+ gsize payload_len = 0;
+
+ fpi_byte_writer_init (&writer);
+ if (!fpi_byte_writer_put_data (&writer, cmd_new_print_prefix,
+ cmd_new_print_prefix_len))
+ goto out_fail;
+
+ new_id = g_bytes_get_data (id, &new_id_len);
+
+ if (!fpi_byte_writer_put_data (&writer, new_id, new_id_len))
+ goto out_fail;
+
+ payload_len = fpi_byte_writer_get_size (&writer);
+ egismoc_exec_cmd (FP_DEVICE (self), fpi_byte_writer_reset_and_get_data (&writer),
+ payload_len, g_free, egismoc_enroll_commit_cb);
+ return;
+
+out_fail:
+ g_propagate_error (&error, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ fpi_sdcp_device_enroll_commit_complete (sdcp_device, error);
+}
+
static void
egismoc_enroll_status_report (FpDevice *device,
EnrollPrint *enroll_print,
@@ -808,11 +933,7 @@ egismoc_read_capture_cb (FpDevice *device,
}
/* Check that the read payload indicates "success" */
- if (egismoc_validate_response_prefix (buffer_in,
- length_in,
- rsp_read_success_prefix,
- rsp_read_success_prefix_len) &&
- egismoc_validate_response_suffix (buffer_in,
+ if (egismoc_validate_response_suffix (buffer_in,
length_in,
rsp_read_success_suffix,
rsp_read_success_suffix_len))
@@ -825,11 +946,7 @@ egismoc_read_capture_cb (FpDevice *device,
/* If not success then the sensor can either report "off center" or "sensor is dirty" */
/* "Off center" */
- if (egismoc_validate_response_prefix (buffer_in,
- length_in,
- rsp_read_offcenter_prefix,
- rsp_read_offcenter_prefix_len) &&
- egismoc_validate_response_suffix (buffer_in,
+ if (egismoc_validate_response_suffix (buffer_in,
length_in,
rsp_read_offcenter_suffix,
rsp_read_offcenter_suffix_len))
@@ -858,6 +975,42 @@ egismoc_read_capture_cb (FpDevice *device,
fpi_ssm_jump_to_state (self->task_ssm, ENROLL_CAPTURE_SENSOR_RESET);
}
+static void
+egismoc_enroll_starting_cb (FpDevice *device,
+ guchar *buffer_in,
+ gsize length_in,
+ GError *error)
+{
+ fp_dbg ("Enroll starting callback");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ g_autofree gchar *enrollment_nonce_hex = NULL;
+
+ if (error)
+ {
+ fpi_ssm_mark_failed (self->task_ssm, error);
+ return;
+ }
+
+ if (!egismoc_validate_response_suffix (buffer_in,
+ length_in,
+ rsp_enroll_starting_suffix,
+ rsp_enroll_starting_suffix_len))
+ {
+ fpi_ssm_mark_failed (self->task_ssm,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
+ "Invalid response when starting enrollment"));
+ return;
+ }
+
+ /* clear and fetch SDCP device enrollment nonce from response */
+ g_clear_pointer (&self->enrollment_nonce, g_bytes_unref);
+ self->enrollment_nonce = g_bytes_new (buffer_in
+ + EGISMOC_ENROLL_STARTING_RESPONSE_PREFIX_SIZE,
+ SDCP_NONCE_SIZE);
+
+ fpi_ssm_next_state (self->task_ssm);
+}
+
static void
egismoc_enroll_check_cb (FpDevice *device,
guchar *buffer_in,
@@ -909,15 +1062,14 @@ egismoc_get_check_cmd (FpDevice *device,
* 3) Hard-coded prefix (cmd_check_prefix)
* 4) 2-byte size indiciator, 20*Number of enrolled identifiers without plus 9
* ((enrolled_ids->len + 1) * 0x20)
- * 5) Hard-coded 32 * 0x00 bytes
+ * 5) SDCP Identify nonce (always hard-coded 32 * 0x00 bytes on these devices)
* 6) All of the currently registered prints in their 32-byte device identifiers
* (enrolled_list)
* 7) Hard-coded suffix (cmd_check_suffix)
*/
g_assert (self->enrolled_ids);
- const gsize body_length = sizeof (guchar) * self->enrolled_ids->len *
- EGISMOC_FINGERPRINT_DATA_SIZE;
+ const gsize body_length = sizeof (guchar) * self->enrolled_ids->len * SDCP_ENROLLMENT_ID_SIZE;
/* prefix length can depend on the type */
const gsize check_prefix_length = (fpi_device_get_driver_data (device) &
@@ -929,7 +1081,7 @@ egismoc_get_check_cmd (FpDevice *device,
* the body payload */
const gsize total_length = (sizeof (guchar) * 6)
+ check_prefix_length
- + EGISMOC_CMD_CHECK_SEPARATOR_LENGTH
+ + SDCP_NONCE_SIZE
+ body_length
+ cmd_check_suffix_len;
@@ -983,15 +1135,15 @@ egismoc_get_check_cmd (FpDevice *device,
(self->enrolled_ids->len + 1) * 0x20);
}
- /* add 00s "separator" to offset position */
- written &= fpi_byte_writer_change_pos (&writer,
- EGISMOC_CMD_CHECK_SEPARATOR_LENGTH);
+ /* skip ahead to leave Identify nonce as 00s (always 00s for egismoc devices) */
+ written &= fpi_byte_writer_change_pos (&writer, SDCP_NONCE_SIZE);
+ /* add each of the enrolled IDs */
for (guint i = 0; i < self->enrolled_ids->len && written; i++)
{
written &= fpi_byte_writer_put_data (&writer,
g_ptr_array_index (self->enrolled_ids, i),
- EGISMOC_FINGERPRINT_DATA_SIZE);
+ SDCP_ENROLLMENT_ID_SIZE);
}
/* command suffix */
@@ -1009,13 +1161,11 @@ static void
egismoc_enroll_run_state (FpiSsm *ssm,
FpDevice *device)
{
- g_auto(FpiByteWriter) writer = {0};
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
EnrollPrint *enroll_print = fpi_ssm_get_data (ssm);
g_autofree guchar *payload = NULL;
gsize payload_length = 0;
- g_autofree gchar *device_print_id = NULL;
- g_autofree gchar *user_id = NULL;
switch (fpi_ssm_get_cur_state (ssm))
{
@@ -1046,7 +1196,7 @@ egismoc_enroll_run_state (FpiSsm *ssm,
break;
case ENROLL_WAIT_FINGER:
- egismoc_wait_finger_on_sensor (ssm, device);
+ egismoc_wait_finger_on_sensor (device);
break;
case ENROLL_SENSOR_CHECK:
@@ -1062,7 +1212,7 @@ egismoc_enroll_run_state (FpiSsm *ssm,
case ENROLL_START:
egismoc_exec_cmd (device, cmd_enroll_starting, cmd_enroll_starting_len,
- NULL, egismoc_task_ssm_next_state_cb);
+ NULL, egismoc_enroll_starting_cb);
break;
case ENROLL_CAPTURE_SENSOR_RESET:
@@ -1077,7 +1227,12 @@ egismoc_enroll_run_state (FpiSsm *ssm,
break;
case ENROLL_CAPTURE_WAIT_FINGER:
- egismoc_wait_finger_on_sensor (ssm, device);
+ egismoc_wait_finger_on_sensor (device);
+ break;
+
+ case ENROLL_CAPTURE_POST_WAIT_FINGER:
+ egismoc_exec_cmd (device, cmd_capture_post_wait_finger, cmd_capture_post_wait_finger_len,
+ NULL, egismoc_task_ssm_next_state_cb);
break;
case ENROLL_CAPTURE_READ_RESPONSE:
@@ -1091,49 +1246,18 @@ egismoc_enroll_run_state (FpiSsm *ssm,
break;
case ENROLL_COMMIT:
- user_id = fpi_print_generate_user_id (enroll_print->print);
- fp_dbg ("New fingerprint ID: %s", user_id);
-
- device_print_id = g_strndup (user_id, EGISMOC_FINGERPRINT_DATA_SIZE);
- egismoc_set_print_data (enroll_print->print, device_print_id, user_id);
-
- fpi_byte_writer_init (&writer);
- if (!fpi_byte_writer_put_data (&writer, cmd_new_print_prefix,
- cmd_new_print_prefix_len))
- {
- fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
- break;
- }
- if (!fpi_byte_writer_put_data (&writer, (guint8 *) device_print_id,
- EGISMOC_FINGERPRINT_DATA_SIZE))
- {
- fpi_ssm_mark_failed (ssm, fpi_device_error_new (FP_DEVICE_ERROR_PROTO));
- break;
- }
-
- payload_length = fpi_byte_writer_get_size (&writer);
- egismoc_exec_cmd (device, fpi_byte_writer_reset_and_get_data (&writer),
- payload_length,
- g_free, egismoc_task_ssm_next_state_cb);
- break;
-
- case ENROLL_COMMIT_SENSOR_RESET:
- egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len,
- NULL, egismoc_task_ssm_next_state_cb);
- break;
-
- case ENROLL_COMPLETE:
- egismoc_enroll_status_report (device, enroll_print, ENROLL_STATUS_COMPLETE, NULL);
- fpi_ssm_next_state (ssm);
+ g_assert (self->enrollment_nonce);
+ fpi_sdcp_device_enroll_commit (sdcp_device, self->enrollment_nonce, NULL);
break;
}
}
static void
-egismoc_enroll (FpDevice *device)
+egismoc_enroll (FpSdcpDevice *sdcp_device)
{
fp_dbg ("Enroll");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (sdcp_device);
+ FpDevice *device = FP_DEVICE (sdcp_device);
EnrollPrint *enroll_print = g_new0 (EnrollPrint, 1);
fpi_device_get_enroll_data (device, &enroll_print->print);
@@ -1145,6 +1269,34 @@ egismoc_enroll (FpDevice *device)
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
}
+static void
+egismoc_identify_complete_cb (FpDevice *device,
+ guchar *buffer_in,
+ gsize length_in,
+ GError *error)
+{
+ fp_dbg ("Identify complete callback");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+ IdentifyPrint *identify_print = fpi_ssm_get_data (self->task_ssm);
+
+ if (error)
+ {
+ fpi_device_action_error (device, error);
+ goto out;
+ }
+
+ fpi_sdcp_device_identify_complete (sdcp_device, identify_print->id, identify_print->mac,
+ identify_print->error);
+
+ fpi_ssm_next_state (self->task_ssm);
+
+out:
+ g_clear_pointer (&identify_print->id, g_bytes_unref);
+ g_clear_pointer (&identify_print->mac, g_bytes_unref);
+ g_clear_pointer (&identify_print, g_free);
+}
+
static void
egismoc_identify_check_cb (FpDevice *device,
guchar *buffer_in,
@@ -1153,16 +1305,17 @@ egismoc_identify_check_cb (FpDevice *device,
{
fp_dbg ("Identify check callback");
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
- gchar device_print_id[EGISMOC_FINGERPRINT_DATA_SIZE];
- FpPrint *print = NULL;
- FpPrint *verify_print = NULL;
- GPtrArray *prints;
- gboolean found = FALSE;
- guint index;
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+ IdentifyPrint *identify_print = fpi_ssm_get_data (self->task_ssm);
+ g_autofree guchar *nonce_buf = NULL;
+ g_autoptr(GBytes) nonce = NULL;
+
+ g_return_if_fail (identify_print->id == NULL);
+ g_return_if_fail (identify_print->mac == NULL);
if (error)
{
- fpi_ssm_mark_failed (self->task_ssm, error);
+ fpi_device_action_error (device, error);
return;
}
@@ -1173,77 +1326,40 @@ egismoc_identify_check_cb (FpDevice *device,
rsp_identify_match_suffix_len))
{
/*
- On success, there is a 32 byte array of "something"(?) in chars 14-45
- and then the 32 byte array ID of the matched print comes as chars 46-77
+ * egismoc devices always use 00s for the identify nonce, so we should set
+ * it here instead of using the default randomly generated nonce
*/
- memcpy (device_print_id,
- buffer_in + EGISMOC_IDENTIFY_RESPONSE_PRINT_ID_OFFSET,
- EGISMOC_FINGERPRINT_DATA_SIZE);
+ nonce_buf = g_malloc0 (SDCP_NONCE_SIZE);
+ nonce = g_bytes_new (nonce_buf, SDCP_NONCE_SIZE);
+ fpi_sdcp_device_set_identify_data (sdcp_device, g_steal_pointer (&nonce));
- /* Create a new print from this device_print_id and then see if it matches
- * the one indicated
+ /*
+ Normally for SDCP the "Authorized Identity" response should be (id,m)
+ but on egismoc devices there is a prefix, followed by (m,id) (yes, it
+ is backwards), followed by a suffix.
*/
- print = fp_print_new (device);
- egismoc_set_print_data (print, device_print_id, NULL);
-
- if (!print)
- {
- fpi_ssm_mark_failed (self->task_ssm,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
- "Failed to build a print from "
- "device response."));
- return;
- }
-
- fp_info ("Identify successful for: %s", fp_print_get_description (print));
-
- if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY)
- {
- fpi_device_get_identify_data (device, &prints);
- found = g_ptr_array_find_with_equal_func (prints,
- print,
- (GEqualFunc) fp_print_equal,
- &index);
-
- if (found)
- fpi_device_identify_report (device, g_ptr_array_index (prints, index), print, NULL);
- else
- fpi_device_identify_report (device, NULL, print, NULL);
- }
- else
- {
- fpi_device_get_verify_data (device, &verify_print);
- fp_info ("Verifying against: %s", fp_print_get_description (verify_print));
-
- if (fp_print_equal (verify_print, print))
- fpi_device_verify_report (device, FPI_MATCH_SUCCESS, print, NULL);
- else
- fpi_device_verify_report (device, FPI_MATCH_FAIL, print, NULL);
- }
+ identify_print->mac = g_bytes_new (buffer_in
+ + EGISMOC_IDENTIFY_RESPONSE_PREFIX_SIZE,
+ SDCP_MAC_SIZE);
+ identify_print->id = g_bytes_new (buffer_in
+ + EGISMOC_IDENTIFY_RESPONSE_PREFIX_SIZE
+ + SDCP_MAC_SIZE,
+ SDCP_ENROLLMENT_ID_SIZE);
}
- /* If device was successfully read but it was a "not matched" */
- else if (egismoc_validate_response_suffix (buffer_in,
- length_in,
- rsp_identify_notmatch_suffix,
- rsp_identify_notmatch_suffix_len))
+ /* If device was not successfully read (not a valid "not matched") */
+ else if (!egismoc_validate_response_suffix (buffer_in,
+ length_in,
+ rsp_identify_notmatch_suffix,
+ rsp_identify_notmatch_suffix_len))
{
- fp_info ("Print was not identified by the device");
-
- if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_VERIFY)
- fpi_device_verify_report (device, FPI_MATCH_FAIL, NULL, NULL);
- else
- fpi_device_identify_report (device, NULL, NULL, NULL);
- }
- else
- {
- fpi_ssm_mark_failed (self->task_ssm,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Unrecognized response from device."));
- return;
+ g_propagate_error (&identify_print->error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
+ "Unrecognized response from device"));
}
- fpi_ssm_next_state (self->task_ssm);
-}
+ egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len,
+ NULL, egismoc_identify_complete_cb);
+ }
static void
egismoc_identify_run_state (FpiSsm *ssm,
@@ -1282,7 +1398,7 @@ egismoc_identify_run_state (FpiSsm *ssm,
break;
case IDENTIFY_WAIT_FINGER:
- egismoc_wait_finger_on_sensor (ssm, device);
+ egismoc_wait_finger_on_sensor (device);
break;
case IDENTIFY_SENSOR_CHECK:
@@ -1295,43 +1411,209 @@ egismoc_identify_run_state (FpiSsm *ssm,
egismoc_exec_cmd (device, g_steal_pointer (&payload), payload_length,
g_free, egismoc_identify_check_cb);
break;
-
- case IDENTIFY_COMPLETE_SENSOR_RESET:
- egismoc_exec_cmd (device, cmd_sensor_reset, cmd_sensor_reset_len,
- NULL, egismoc_task_ssm_next_state_cb);
- break;
-
- /*
- * In Windows, the driver seems at this point to then immediately take
- * another read from the sensor; this is suspected to be an on-chip
- * "verify". However, because the user's finger is still on the sensor from
- * the identify, then it seems to always return positive. We will consider
- * this extra step unnecessary and just skip it in this driver. This driver
- * will instead handle matching of the FpPrint from the gallery in the
- * "verify" case of the callback egismoc_identify_check_cb.
- */
- case IDENTIFY_COMPLETE:
- if (fpi_device_get_current_action (device) == FPI_DEVICE_ACTION_IDENTIFY)
- fpi_device_identify_complete (device, NULL);
- else
- fpi_device_verify_complete (device, NULL);
-
- fpi_ssm_mark_completed (ssm);
- break;
}
}
static void
-egismoc_identify_verify (FpDevice *device)
+egismoc_identify (FpSdcpDevice *sdcp_device)
{
- fp_dbg ("Identify or Verify");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ fp_dbg ("Identify");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (sdcp_device);
+ IdentifyPrint *identify_print = g_new0 (IdentifyPrint, 1);
g_assert (self->task_ssm == NULL);
- self->task_ssm = fpi_ssm_new (device, egismoc_identify_run_state, IDENTIFY_STATES);
+ self->task_ssm = fpi_ssm_new (FP_DEVICE (sdcp_device),
+ egismoc_identify_run_state,
+ IDENTIFY_STATES);
+ fpi_ssm_set_data (self->task_ssm, g_steal_pointer (&identify_print), NULL);
fpi_ssm_start (self->task_ssm, egismoc_task_ssm_done);
}
+/*
+ * Validates and uses the SDCP "ConnectResponse" payload to establish a secure
+ * device connection which can then be used to generate enrollment IDs and
+ * verify identities as per SDCP.
+ */
+static void
+egismoc_connect_cb (FpDevice *device,
+ guchar *buffer_in,
+ gsize length_in,
+ GError *error)
+{
+ fp_dbg ("SDCP ConnectResponse callback");
+ FpSdcpDevice *sdcp_device = FP_SDCP_DEVICE (device);
+ g_autoptr(GBytes) device_random = NULL;
+ gsize model_certificate_len = 0;
+ g_autoptr(FpiSdcpClaim) claim = NULL;
+ g_autoptr(GBytes) mac = NULL;
+ int pos = EGISMOC_CONNECT_RESPONSE_PREFIX_SIZE;
+
+ if (error)
+ {
+ fpi_sdcp_device_connect_complete (sdcp_device, NULL, NULL, NULL, error);
+ return;
+ }
+
+ /* Check that the read payload indicates "success" */
+ if (!egismoc_validate_response_suffix (buffer_in,
+ length_in,
+ rsp_sdcp_connect_success_suffix,
+ rsp_sdcp_connect_success_suffix_len))
+ {
+ fpi_sdcp_device_connect_complete (sdcp_device, NULL, NULL, NULL,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
+ "Device responded with failure "
+ "instead of SDCP ConnectResponse"));
+ return;
+ }
+
+ /* buf len should be at least larger than all required parts (plus a cert) */
+ if (length_in <= SDCP_RANDOM_SIZE
+ + SDCP_PUBLIC_KEY_SIZE
+ + SDCP_PUBLIC_KEY_SIZE
+ + SDCP_RANDOM_SIZE
+ + SDCP_SIGNATURE_SIZE
+ + SDCP_SIGNATURE_SIZE
+ + SDCP_MAC_SIZE)
+ {
+ fpi_sdcp_device_connect_complete (sdcp_device, NULL, NULL, NULL,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
+ "Device SDCP ConnectResponse "
+ "was not long enough"));
+ return;
+ }
+
+
+ /*
+ * Parse ConnectResponse parts; unfortunately these devices return a somewhat
+ * non-standard ConnectResponse as there are two bytes indicating cert_m's
+ * length which must be handled.
+ */
+ claim = g_new0 (FpiSdcpClaim, 1);
+
+ /* r_d */
+ device_random = g_bytes_new (buffer_in + pos, SDCP_RANDOM_SIZE);
+ pos += SDCP_RANDOM_SIZE;
+
+ /* next two bytes are an unsigned short giving the cert_m length */
+ model_certificate_len = buffer_in[pos] << 8 | buffer_in[pos + 1];
+ pos += 2;
+
+ /* cert_m bytes based on length fetched above */
+ claim->model_certificate = g_bytes_new (buffer_in + pos, model_certificate_len);
+ pos += model_certificate_len;
+
+ /* pk_d */
+ claim->device_public_key = g_bytes_new (buffer_in + pos, SDCP_PUBLIC_KEY_SIZE);
+ pos += SDCP_PUBLIC_KEY_SIZE;
+
+ /* pk_f */
+ claim->firmware_public_key = g_bytes_new (buffer_in + pos, SDCP_PUBLIC_KEY_SIZE);
+ pos += SDCP_PUBLIC_KEY_SIZE;
+
+ /* h_f */
+ claim->firmware_hash = g_bytes_new (buffer_in + pos, SDCP_MAC_SIZE);
+ pos += SDCP_MAC_SIZE;
+
+ /* s_m */
+ claim->model_signature = g_bytes_new (buffer_in + pos, SDCP_SIGNATURE_SIZE);
+ pos += SDCP_SIGNATURE_SIZE;
+
+ /* s_d */
+ claim->device_signature = g_bytes_new (buffer_in + pos, SDCP_SIGNATURE_SIZE);
+ pos += SDCP_SIGNATURE_SIZE;
+
+ /* m */
+ mac = g_bytes_new (buffer_in + pos, SDCP_MAC_SIZE);
+ pos += SDCP_MAC_SIZE;
+
+ /* Derive SDCP keys and establish secured connection */
+ fpi_sdcp_device_connect_complete (sdcp_device, device_random, claim, mac, error);
+}
+
+static void
+egismoc_connect (FpSdcpDevice *sdcp_device)
+{
+ fp_dbg ("Connect");
+ FpDevice *device = FP_DEVICE (sdcp_device);
+ g_auto(FpiByteWriter) writer = {0};
+ gboolean written = TRUE;
+ g_autoptr(GError) error = NULL;
+
+ g_autoptr(GBytes) host_random = NULL;
+ const guchar *host_random_ptr;
+ gsize host_random_len = 0;
+
+ g_autoptr(GBytes) host_public_key = NULL;
+ const guchar *host_public_key_ptr;
+ gsize host_public_key_len = 0;
+
+ fpi_sdcp_device_get_connect_data (sdcp_device, &host_random, &host_public_key);
+ if (!host_random || !host_public_key)
+ {
+ fpi_sdcp_device_connect_complete (sdcp_device, NULL, NULL, NULL,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
+ "Failed to get SDCP Connect data"));
+ return;
+ }
+
+ host_random_ptr = g_bytes_get_data (host_random, &host_random_len);
+ host_public_key_ptr = g_bytes_get_data (host_public_key, &host_public_key_len);
+
+ const int length = cmd_sdcp_connect_prefix_len
+ + host_random_len
+ + host_public_key_len
+ + cmd_sdcp_connect_suffix_len;
+
+ fpi_byte_writer_init_with_size (&writer, length, TRUE);
+
+ written &= fpi_byte_writer_put_data (&writer, cmd_sdcp_connect_prefix,
+ cmd_sdcp_connect_prefix_len);
+
+ written &= fpi_byte_writer_put_data (&writer, host_random_ptr,
+ host_random_len);
+
+ written &= fpi_byte_writer_put_data (&writer, host_public_key_ptr,
+ host_public_key_len);
+
+ written &= fpi_byte_writer_put_data (&writer, cmd_sdcp_connect_suffix,
+ cmd_sdcp_connect_suffix_len);
+
+ if (!written)
+ {
+ fpi_sdcp_device_connect_complete (sdcp_device, NULL, NULL, NULL,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
+ "Failed to write SDCP Connect payload"));
+ return;
+ }
+
+ /* Execute the egismoc SDCP "Connect" command */
+ egismoc_exec_cmd (device,
+ fpi_byte_writer_reset_and_get_data (&writer), length, g_free,
+ egismoc_connect_cb);
+}
+
+static void
+egismoc_dev_init_done (FpiSsm *ssm,
+ FpDevice *device,
+ GError *error)
+{
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+
+ if (error)
+ {
+ g_usb_device_release_interface (
+ fpi_device_get_usb_device (device), 0, 0, NULL);
+ egismoc_task_ssm_done (ssm, device, error);
+ return;
+ }
+
+ egismoc_task_ssm_done (ssm, device, NULL);
+ fpi_sdcp_device_open_complete (FP_SDCP_DEVICE (device), NULL);
+
+ self->dev_init_done = TRUE;
+}
+
static void
egismoc_fw_version_cb (FpDevice *device,
guchar *buffer_in,
@@ -1360,7 +1642,7 @@ egismoc_fw_version_cb (FpDevice *device,
fpi_ssm_mark_failed (self->task_ssm,
fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
"Device firmware response "
- "was not valid."));
+ "was not valid"));
return;
}
@@ -1380,23 +1662,6 @@ egismoc_fw_version_cb (FpDevice *device,
fpi_ssm_next_state (self->task_ssm);
}
-static void
-egismoc_dev_init_done (FpiSsm *ssm,
- FpDevice *device,
- GError *error)
-{
- if (error)
- {
- g_usb_device_release_interface (
- fpi_device_get_usb_device (device), 0, 0, NULL);
- egismoc_task_ssm_done (ssm, device, error);
- return;
- }
-
- egismoc_task_ssm_done (ssm, device, NULL);
- fpi_device_open_complete (device, NULL);
-}
-
static void
egismoc_dev_init_handler (FpiSsm *ssm,
FpDevice *device)
@@ -1463,6 +1728,82 @@ egismoc_dev_init_handler (FpiSsm *ssm,
NULL);
}
+static void
+egismoc_open (FpSdcpDevice *sdcp_device)
+{
+ fp_dbg ("Opening device");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (sdcp_device);
+ FpDevice *device = FP_DEVICE (sdcp_device);
+ GError *error = NULL;
+
+ self->interrupt_cancellable = g_cancellable_new ();
+
+ if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error))
+ {
+ fpi_sdcp_device_open_complete (sdcp_device, error);
+ return;
+ }
+
+ if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device),
+ 0, 0, &error))
+ {
+ fpi_sdcp_device_open_complete (sdcp_device, error);
+ return;
+ }
+
+ if (self->dev_init_done)
+ {
+ fpi_sdcp_device_open_complete (FP_SDCP_DEVICE (device), NULL);
+ }
+ else
+ {
+ g_assert (self->task_ssm == NULL);
+ self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES);
+ fpi_ssm_start (self->task_ssm, egismoc_dev_init_done);
+ }
+}
+
+static void
+egismoc_cancel (FpDevice *device)
+{
+ fp_dbg ("Cancel");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+
+ g_cancellable_cancel (self->interrupt_cancellable);
+ g_clear_object (&self->interrupt_cancellable);
+ self->interrupt_cancellable = g_cancellable_new ();
+
+ /* Terminate ongoing action if cancel is called */
+ if (self->task_ssm)
+ fpi_ssm_mark_failed (self->task_ssm, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "Operation was cancelled"));
+}
+
+static void
+egismoc_suspend (FpDevice *device)
+{
+ fp_dbg ("Suspend");
+
+ egismoc_cancel (device);
+ g_cancellable_cancel (fpi_device_get_cancellable (device));
+ fpi_device_suspend_complete (device, NULL);
+}
+
+static void
+egismoc_close (FpDevice *device)
+{
+ fp_dbg ("Closing device");
+ FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
+ GError *error = NULL;
+
+ egismoc_cancel (device);
+ g_clear_object (&self->interrupt_cancellable);
+
+ g_usb_device_release_interface (fpi_device_get_usb_device (device),
+ 0, 0, &error);
+ fpi_device_close_complete (device, error);
+}
+
static void
egismoc_probe (FpDevice *device)
{
@@ -1470,8 +1811,7 @@ egismoc_probe (FpDevice *device)
GError *error = NULL;
g_autofree gchar *serial = NULL;
FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
-
- fp_dbg ("%s enter --> ", G_STRFUNC);
+ guint64 driver_data;
/* Claim usb interface */
usb_dev = fpi_device_get_usb_device (device);
@@ -1515,8 +1855,11 @@ egismoc_probe (FpDevice *device)
return;
}
- if (fpi_device_get_driver_data (device) & EGISMOC_DRIVER_MAX_ENROLL_STAGES_20)
- self->max_enroll_stages = 20;
+ driver_data = fpi_device_get_driver_data (device);
+ if (driver_data & EGISMOC_DRIVER_MAX_ENROLL_STAGES_20)
+ self->max_enroll_stages = 20;
+ else if (driver_data & EGISMOC_DRIVER_MAX_ENROLL_STAGES_15)
+ self->max_enroll_stages = 15;
else
self->max_enroll_stages = EGISMOC_MAX_ENROLL_STAGES_DEFAULT;
@@ -1528,69 +1871,6 @@ egismoc_probe (FpDevice *device)
fpi_device_probe_complete (device, serial, NULL, error);
}
-static void
-egismoc_open (FpDevice *device)
-{
- fp_dbg ("Opening device");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
- GError *error = NULL;
-
- self->interrupt_cancellable = g_cancellable_new ();
-
- if (!g_usb_device_reset (fpi_device_get_usb_device (device), &error))
- {
- fpi_device_open_complete (device, error);
- return;
- }
-
- if (!g_usb_device_claim_interface (fpi_device_get_usb_device (device),
- 0, 0, &error))
- {
- fpi_device_open_complete (device, error);
- return;
- }
-
- g_assert (self->task_ssm == NULL);
- self->task_ssm = fpi_ssm_new (device, egismoc_dev_init_handler, DEV_INIT_STATES);
- fpi_ssm_start (self->task_ssm, egismoc_dev_init_done);
-}
-
-static void
-egismoc_cancel (FpDevice *device)
-{
- fp_dbg ("Cancel");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
-
- g_cancellable_cancel (self->interrupt_cancellable);
- g_clear_object (&self->interrupt_cancellable);
- self->interrupt_cancellable = g_cancellable_new ();
-}
-
-static void
-egismoc_suspend (FpDevice *device)
-{
- fp_dbg ("Suspend");
-
- egismoc_cancel (device);
- g_cancellable_cancel (fpi_device_get_cancellable (device));
- fpi_device_suspend_complete (device, NULL);
-}
-
-static void
-egismoc_close (FpDevice *device)
-{
- fp_dbg ("Closing device");
- FpiDeviceEgisMoc *self = FPI_DEVICE_EGISMOC (device);
- GError *error = NULL;
-
- egismoc_cancel (device);
- g_clear_object (&self->interrupt_cancellable);
-
- g_usb_device_release_interface (fpi_device_get_usb_device (device),
- 0, 0, &error);
- fpi_device_close_complete (device, error);
-}
-
static void
fpi_device_egismoc_init (FpiDeviceEgisMoc *self)
{
@@ -1601,6 +1881,7 @@ static void
fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *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 = EGISMOC_DRIVER_FULLNAME;
@@ -1612,18 +1893,23 @@ fpi_device_egismoc_class_init (FpiDeviceEgisMocClass *klass)
/* device should be "always off" unless being used */
dev_class->temp_hot_seconds = 0;
+ sdcp_dev_class->open = egismoc_open;
+ sdcp_dev_class->connect = egismoc_connect;
+ sdcp_dev_class->list = egismoc_list;
+ sdcp_dev_class->enroll = egismoc_enroll;
+ sdcp_dev_class->enroll_commit = egismoc_enroll_commit;
+ sdcp_dev_class->identify = egismoc_identify;
+
dev_class->probe = egismoc_probe;
- dev_class->open = egismoc_open;
dev_class->cancel = egismoc_cancel;
dev_class->suspend = egismoc_suspend;
dev_class->close = egismoc_close;
- dev_class->identify = egismoc_identify_verify;
- dev_class->verify = egismoc_identify_verify;
- dev_class->enroll = egismoc_enroll;
dev_class->delete = egismoc_delete;
dev_class->clear_storage = egismoc_clear_storage;
- dev_class->list = egismoc_list;
fpi_device_class_auto_initialize_features (dev_class);
dev_class->features |= FP_DEVICE_FEATURE_DUPLICATES_CHECK;
+
+ sdcp_dev_class->ignore_device_certificate = FALSE;
+ sdcp_dev_class->ignore_device_signatures = FALSE;
}
diff --git a/libfprint/drivers/egismoc/egismoc.h b/libfprint/drivers/egismoc/egismoc.h
index ef31d2cd..06f74102 100644
--- a/libfprint/drivers/egismoc/egismoc.h
+++ b/libfprint/drivers/egismoc/egismoc.h
@@ -1,15 +1,10 @@
/*
* Driver for Egis Technology (LighTuning) Match-On-Chip sensors
- * Originally authored 2023 by Joshua Grisham
+ * Copyright (C) 2023-2025 Joshua Grisham
*
* 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;
diff --git a/libfprint/drivers/virtual-sdcp.c b/libfprint/drivers/virtual-sdcp.c
deleted file mode 100644
index 95431da7..00000000
--- a/libfprint/drivers/virtual-sdcp.c
+++ /dev/null
@@ -1,676 +0,0 @@
-/*
- * Virtual driver for SDCP device debugging
- *
- * Copyright (C) 2020 Benjamin Berg
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/*
- * This is a virtual test driver to test the basic SDCP functionality.
- * It uses the test binaries from Microsoft, which were extended to allow
- * a simple chat with the device.
- * The environment variable contains the to be executed binary including
- * arguments. This binary should be compiled from the code in
- * https://github.com/Microsoft/SecureDeviceConnectionProtocol
- * or, until it is merged upstream
- * https://github.com/benzea/SecureDeviceConnectionProtocol
- *
- * Note that using this as an external executable has the advantage that we
- * do not need to link against mbedtls or any other crypto library.
- */
-
-#define FP_COMPONENT "virtual_sdcp"
-
-#include "fpi-log.h"
-#include "fpi-ssm.h"
-
-#include "../fpi-sdcp-device.h"
-
-#include
-#include
-
-struct _FpDeviceVirtualSdcp
-{
- FpSdcpDevice parent;
-
- GSubprocess *proc;
- GOutputStream *proc_stdin;
- GInputStream *proc_stdout;
-
- /* Only valid while a read/write is pending */
- GByteArray *msg_out;
- GByteArray *msg_in;
-
- GByteArray *connect_msg;
-};
-
-G_DECLARE_FINAL_TYPE (FpDeviceVirtualSdcp, fpi_device_virtual_sdcp, FPI, DEVICE_VIRTUAL_SDCP, FpSdcpDevice)
-G_DEFINE_TYPE (FpDeviceVirtualSdcp, fpi_device_virtual_sdcp, FP_TYPE_SDCP_DEVICE)
-
-guint8 ca_1[] = {
- 0x30, 0x82, 0x03, 0xFD, 0x30, 0x82, 0x03, 0x82, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x13, 0x33,
- 0x00, 0x00, 0x00, 0x07, 0xE8, 0x9D, 0x61, 0x62, 0x4D, 0x46, 0x0F, 0x95, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x07, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x30, 0x81,
- 0x84, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
- 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67,
- 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65,
- 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15,
- 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72,
- 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x2E, 0x30, 0x2C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25,
- 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x53, 0x65,
- 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x50, 0x43, 0x41,
- 0x20, 0x32, 0x30, 0x31, 0x38, 0x30, 0x1E, 0x17, 0x0D, 0x31, 0x38, 0x30, 0x31, 0x33, 0x31, 0x31,
- 0x39, 0x35, 0x34, 0x35, 0x33, 0x5A, 0x17, 0x0D, 0x32, 0x38, 0x30, 0x31, 0x33, 0x31, 0x32, 0x30,
- 0x30, 0x34, 0x35, 0x33, 0x5A, 0x30, 0x7D, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06,
- 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57,
- 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67, 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55,
- 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06,
- 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20,
- 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x27, 0x30, 0x25, 0x06,
- 0x03, 0x55, 0x04, 0x03, 0x13, 0x1E, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48, 0x65,
- 0x6C, 0x6C, 0x6F, 0x20, 0x31, 0x39, 0x42, 0x39, 0x32, 0x39, 0x36, 0x35, 0x20, 0x43, 0x41, 0x20,
- 0x32, 0x30, 0x31, 0x38, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02,
- 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xBE,
- 0x4B, 0x90, 0x6E, 0x24, 0xFC, 0xA1, 0x53, 0xC8, 0xA7, 0x3C, 0x70, 0xE8, 0x97, 0xCD, 0x1B, 0x31,
- 0xE4, 0x95, 0x91, 0x7A, 0x58, 0xA2, 0x86, 0xA8, 0x70, 0xF6, 0x09, 0x30, 0x77, 0x99, 0x3D, 0x10,
- 0xDF, 0xF7, 0x95, 0x0F, 0x68, 0x83, 0xE6, 0xA4, 0x11, 0x7C, 0xDA, 0x82, 0xE7, 0x0B, 0x8B, 0xF2,
- 0x9D, 0x6B, 0x5B, 0xF5, 0x3E, 0x77, 0xB4, 0xC1, 0x0E, 0x49, 0x00, 0x83, 0xBA, 0x94, 0xF8, 0xA3,
- 0x82, 0x01, 0xD7, 0x30, 0x82, 0x01, 0xD3, 0x30, 0x10, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01,
- 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E,
- 0x04, 0x16, 0x04, 0x14, 0x13, 0x93, 0xC8, 0xCD, 0xF2, 0x23, 0x9A, 0x2D, 0xC6, 0x9B, 0x2A, 0xEB,
- 0x9A, 0xAB, 0x99, 0x0B, 0x56, 0x04, 0x5E, 0x7C, 0x30, 0x65, 0x06, 0x03, 0x55, 0x1D, 0x20, 0x04,
- 0x5E, 0x30, 0x5C, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00, 0x30, 0x52, 0x06, 0x0C, 0x2B,
- 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x4C, 0x83, 0x7D, 0x01, 0x01, 0x30, 0x42, 0x30, 0x40, 0x06,
- 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x34, 0x68, 0x74, 0x74, 0x70, 0x3A,
- 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E,
- 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x44, 0x6F, 0x63, 0x73, 0x2F,
- 0x52, 0x65, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x6F, 0x72, 0x79, 0x2E, 0x68, 0x74, 0x6D, 0x00, 0x30,
- 0x19, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0C, 0x1E, 0x0A,
- 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x0B, 0x06, 0x03, 0x55, 0x1D,
- 0x0F, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0F, 0x06, 0x03, 0x55, 0x1D, 0x13, 0x01, 0x01,
- 0xFF, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xFF, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x1D, 0x23, 0x04,
- 0x18, 0x30, 0x16, 0x80, 0x14, 0xDA, 0xCA, 0x4B, 0xD0, 0x4C, 0x56, 0x03, 0x27, 0x5F, 0x97, 0xEB,
- 0x75, 0xA3, 0x02, 0xC3, 0xBF, 0x45, 0x9C, 0xF8, 0xB1, 0x30, 0x68, 0x06, 0x03, 0x55, 0x1D, 0x1F,
- 0x04, 0x61, 0x30, 0x5F, 0x30, 0x5D, 0xA0, 0x5B, 0xA0, 0x59, 0x86, 0x57, 0x68, 0x74, 0x74, 0x70,
- 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74,
- 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x63, 0x72, 0x6C, 0x2F,
- 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x25, 0x32, 0x30, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x25,
- 0x32, 0x30, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x25, 0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63,
- 0x65, 0x73, 0x25, 0x32, 0x30, 0x50, 0x43, 0x41, 0x25, 0x32, 0x30, 0x32, 0x30, 0x31, 0x38, 0x2E,
- 0x63, 0x72, 0x6C, 0x30, 0x75, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04,
- 0x69, 0x30, 0x67, 0x30, 0x65, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86,
- 0x59, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72,
- 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73,
- 0x2F, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2F, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x25, 0x32,
- 0x30, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x25, 0x32, 0x30, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x25,
- 0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32, 0x30, 0x50, 0x43, 0x41, 0x25,
- 0x32, 0x30, 0x32, 0x30, 0x31, 0x38, 0x2E, 0x63, 0x72, 0x74, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86,
- 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02, 0x03, 0x69, 0x00, 0x30, 0x66, 0x02, 0x31, 0x00, 0x87, 0xB6,
- 0x82, 0xF3, 0xDA, 0xBE, 0xB1, 0x7B, 0x98, 0x7D, 0x3D, 0x0A, 0x90, 0xA8, 0xF5, 0xBF, 0x15, 0xC3,
- 0xEE, 0x8A, 0x4E, 0xC0, 0x7B, 0x10, 0x1D, 0xA9, 0xE3, 0x0B, 0xEC, 0x2C, 0x53, 0x4E, 0xA7, 0xBD,
- 0xF1, 0x6C, 0xAD, 0x18, 0x55, 0xBA, 0x25, 0x73, 0x55, 0xB7, 0x5B, 0x12, 0x24, 0xF4, 0x02, 0x31,
- 0x00, 0xAF, 0x02, 0x9C, 0x4B, 0x92, 0xD0, 0x72, 0xA5, 0x80, 0xCA, 0x69, 0x2B, 0x38, 0x50, 0x64,
- 0xD8, 0x58, 0x9E, 0xEA, 0xD6, 0x35, 0xCF, 0x68, 0x98, 0x92, 0x81, 0x09, 0x61, 0xC2, 0xBD, 0xB1,
- 0x4C, 0x7F, 0xAE, 0x55, 0x7B, 0xFC, 0x22, 0xDD, 0xD6, 0xB7, 0x7C, 0xB5, 0xA8, 0x18, 0x5D, 0x33,
- 0x04
-};
-guint8 ca_2[] = {
- 0x30, 0x82, 0x04, 0x56, 0x30, 0x82, 0x03, 0xDC, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x13, 0x33,
- 0x00, 0x00, 0x00, 0x03, 0x6C, 0xCF, 0xED, 0xE2, 0x44, 0x70, 0x19, 0xBF, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x03, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03, 0x30, 0x81,
- 0x94, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13,
- 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67,
- 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65,
- 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15,
- 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72,
- 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x3E, 0x30, 0x3C, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x35,
- 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x45, 0x43, 0x43, 0x20, 0x44, 0x65,
- 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69,
- 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79,
- 0x20, 0x32, 0x30, 0x31, 0x37, 0x30, 0x1E, 0x17, 0x0D, 0x31, 0x38, 0x30, 0x31, 0x32, 0x35, 0x31,
- 0x39, 0x34, 0x39, 0x33, 0x38, 0x5A, 0x17, 0x0D, 0x33, 0x33, 0x30, 0x31, 0x32, 0x35, 0x31, 0x39,
- 0x35, 0x39, 0x33, 0x38, 0x5A, 0x30, 0x81, 0x84, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04,
- 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0A,
- 0x57, 0x61, 0x73, 0x68, 0x69, 0x6E, 0x67, 0x74, 0x6F, 0x6E, 0x31, 0x10, 0x30, 0x0E, 0x06, 0x03,
- 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6D, 0x6F, 0x6E, 0x64, 0x31, 0x1E, 0x30, 0x1C,
- 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x15, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74,
- 0x20, 0x43, 0x6F, 0x72, 0x70, 0x6F, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x31, 0x2E, 0x30, 0x2C,
- 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x20, 0x48,
- 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x44, 0x65, 0x76, 0x69,
- 0x63, 0x65, 0x73, 0x20, 0x50, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x38, 0x30, 0x76, 0x30, 0x10,
- 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
- 0x03, 0x62, 0x00, 0x04, 0x1D, 0xDD, 0x08, 0x02, 0x03, 0x25, 0x75, 0x20, 0xE2, 0x71, 0x8B, 0xAD,
- 0x28, 0x09, 0x82, 0xE9, 0x06, 0xEE, 0x83, 0xC5, 0x3A, 0x6C, 0x4B, 0x71, 0x92, 0x50, 0x4E, 0x20,
- 0xE9, 0x72, 0xB4, 0xFC, 0x53, 0x2A, 0xEF, 0x5D, 0xCC, 0x9A, 0xB4, 0xCD, 0x76, 0xB8, 0x94, 0x97,
- 0x44, 0xB2, 0x71, 0x0E, 0xC9, 0xB1, 0x16, 0x03, 0xA1, 0x65, 0x2B, 0xB9, 0xE8, 0x5D, 0x5F, 0xF2,
- 0x30, 0x2E, 0xDD, 0xB1, 0x2B, 0x20, 0xFC, 0xBE, 0x00, 0x88, 0xEA, 0x1F, 0xA7, 0x7F, 0x99, 0x84,
- 0x98, 0x7C, 0x71, 0x3E, 0x4D, 0x34, 0x83, 0x69, 0x9B, 0x08, 0xCB, 0x78, 0xB2, 0x4B, 0xBD, 0xD7,
- 0x3E, 0xBE, 0x67, 0xA0, 0xA3, 0x82, 0x01, 0xFC, 0x30, 0x82, 0x01, 0xF8, 0x30, 0x10, 0x06, 0x09,
- 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, 0x01, 0x00, 0x30, 0x1D,
- 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xDA, 0xCA, 0x4B, 0xD0, 0x4C, 0x56, 0x03,
- 0x27, 0x5F, 0x97, 0xEB, 0x75, 0xA3, 0x02, 0xC3, 0xBF, 0x45, 0x9C, 0xF8, 0xB1, 0x30, 0x65, 0x06,
- 0x03, 0x55, 0x1D, 0x20, 0x04, 0x5E, 0x30, 0x5C, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1D, 0x20, 0x00,
- 0x30, 0x52, 0x06, 0x0C, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x4C, 0x83, 0x7D, 0x01, 0x01,
- 0x30, 0x42, 0x30, 0x40, 0x06, 0x08, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x02, 0x01, 0x16, 0x34,
- 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F,
- 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F,
- 0x44, 0x6F, 0x63, 0x73, 0x2F, 0x52, 0x65, 0x70, 0x6F, 0x73, 0x69, 0x74, 0x6F, 0x72, 0x79, 0x2E,
- 0x68, 0x74, 0x6D, 0x00, 0x30, 0x19, 0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14,
- 0x02, 0x04, 0x0C, 0x1E, 0x0A, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30,
- 0x0B, 0x06, 0x03, 0x55, 0x1D, 0x0F, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0F, 0x06, 0x03,
- 0x55, 0x1D, 0x13, 0x01, 0x01, 0xFF, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xFF, 0x30, 0x1F, 0x06,
- 0x03, 0x55, 0x1D, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x14, 0xDA, 0x5B, 0xF1, 0x0E, 0x66,
- 0x47, 0xD1, 0x5D, 0x13, 0x5F, 0x5B, 0x7A, 0xEB, 0xEB, 0x5F, 0x01, 0x08, 0xB5, 0x49, 0x30, 0x7A,
- 0x06, 0x03, 0x55, 0x1D, 0x1F, 0x04, 0x73, 0x30, 0x71, 0x30, 0x6F, 0xA0, 0x6D, 0xA0, 0x6B, 0x86,
- 0x69, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72,
- 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73,
- 0x2F, 0x63, 0x72, 0x6C, 0x2F, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x25, 0x32,
- 0x30, 0x45, 0x43, 0x43, 0x25, 0x32, 0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32,
- 0x30, 0x52, 0x6F, 0x6F, 0x74, 0x25, 0x32, 0x30, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
- 0x61, 0x74, 0x65, 0x25, 0x32, 0x30, 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x25,
- 0x32, 0x30, 0x32, 0x30, 0x31, 0x37, 0x2E, 0x63, 0x72, 0x6C, 0x30, 0x81, 0x87, 0x06, 0x08, 0x2B,
- 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x7B, 0x30, 0x79, 0x30, 0x77, 0x06, 0x08, 0x2B,
- 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x6B, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F,
- 0x77, 0x77, 0x77, 0x2E, 0x6D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x2E, 0x63, 0x6F,
- 0x6D, 0x2F, 0x70, 0x6B, 0x69, 0x6F, 0x70, 0x73, 0x2F, 0x63, 0x65, 0x72, 0x74, 0x73, 0x2F, 0x4D,
- 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x25, 0x32, 0x30, 0x45, 0x43, 0x43, 0x25, 0x32,
- 0x30, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x73, 0x25, 0x32, 0x30, 0x52, 0x6F, 0x6F, 0x74, 0x25,
- 0x32, 0x30, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x25, 0x32, 0x30,
- 0x41, 0x75, 0x74, 0x68, 0x6F, 0x72, 0x69, 0x74, 0x79, 0x25, 0x32, 0x30, 0x32, 0x30, 0x31, 0x37,
- 0x2E, 0x63, 0x72, 0x74, 0x30, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03,
- 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, 0x56, 0x2A, 0xAD, 0x72, 0x4C, 0xB9, 0x8C, 0xB3, 0x23,
- 0x80, 0xF5, 0x5F, 0xF8, 0x21, 0x94, 0x66, 0x0F, 0x76, 0x77, 0xE2, 0x7B, 0x03, 0xDD, 0x30, 0x5E,
- 0xCB, 0x90, 0xCA, 0x78, 0xE6, 0x0B, 0x2D, 0x12, 0xE5, 0xF7, 0x67, 0x31, 0x58, 0x71, 0xE6, 0xF3,
- 0x64, 0xC1, 0x04, 0xB3, 0x8B, 0xE9, 0xE2, 0x02, 0x31, 0x00, 0xB9, 0x20, 0x61, 0xB9, 0xD0, 0x5E,
- 0x3A, 0xA4, 0xA2, 0x8A, 0xFE, 0x1D, 0xFC, 0x27, 0x61, 0x0B, 0x98, 0x16, 0x8C, 0x02, 0x9C, 0x20,
- 0x7F, 0xEE, 0xF3, 0xCB, 0x1F, 0x0A, 0x37, 0x62, 0xB1, 0x8E, 0xCE, 0xD9, 0x9A, 0x9E, 0xAC, 0xE6,
- 0x1A, 0xD4, 0xB8, 0xF1, 0xA8, 0x2B, 0xB1, 0xB4, 0x40, 0x9B
-};
-
-
-static void
-msg_written_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- GError *error = NULL;
- GOutputStream *stream = G_OUTPUT_STREAM (source_object);
- FpiSsm *ssm = user_data;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (fpi_ssm_get_device (ssm));
-
- g_clear_pointer (&self->msg_out, g_byte_array_unref);
- g_assert (self->msg_out == NULL);
-
- if (!g_output_stream_write_all_finish (stream, res, NULL, &error))
- {
- fpi_ssm_mark_failed (ssm, error);
- return;
- }
-
- fpi_ssm_next_state (ssm);
-}
-
-static void
-msg_received_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- GError *error = NULL;
- GInputStream *stream = G_INPUT_STREAM (source_object);
- FpiSsm *ssm = user_data;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (fpi_ssm_get_device (ssm));
- gsize read;
-
- g_assert (self->msg_out == NULL);
-
- if (!g_input_stream_read_all_finish (stream, res, &read, &error) ||
- read != self->msg_in->len)
- {
- g_clear_pointer (&self->msg_in, g_byte_array_unref);
-
- if (!error)
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Received EOF while reading from test binary.");
-
- fpi_ssm_mark_failed (ssm, error);
- return;
- }
-
- fpi_ssm_next_state (ssm);
-}
-
-enum {
- SEND_MESSAGE,
- RECV_MESSAGE,
- SEND_RECV_STATES
-};
-
-static void
-send_recv_ssm (FpiSsm *ssm, FpDevice *dev)
-{
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
-
- switch (fpi_ssm_get_cur_state (ssm))
- {
- case SEND_MESSAGE:
- g_output_stream_write_all_async (self->proc_stdin,
- self->msg_out->data,
- self->msg_out->len,
- G_PRIORITY_DEFAULT,
- fpi_device_get_cancellable (FP_DEVICE (dev)),
- msg_written_cb,
- ssm);
- break;
-
- case RECV_MESSAGE:
- g_input_stream_read_all_async (self->proc_stdout,
- self->msg_in->data,
- self->msg_in->len,
- G_PRIORITY_DEFAULT,
- fpi_device_get_cancellable (FP_DEVICE (dev)),
- msg_received_cb,
- ssm);
- break;
- }
-}
-
-static void
-connect_2_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- g_autoptr(GBytes) recv_data = NULL;
- g_autoptr(GBytes) r_d = NULL;
- g_autoptr(FpiSdcpClaim) claim = NULL;
- g_autoptr(GBytes) mac = NULL;
- g_autoptr(GBytes) ca_1_bytes = NULL, ca_2_bytes = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- guint16 cert_size;
-
- if (error)
- {
- fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), NULL, NULL, NULL, error);
- g_clear_pointer (&self->connect_msg, g_byte_array_unref);
- return;
- }
-
- memcpy (&cert_size, self->connect_msg->data + 32, 2);
- g_byte_array_append (self->connect_msg, self->msg_in->data, self->msg_in->len);
- g_clear_pointer (&self->msg_in, g_byte_array_unref);
- /* Double check that the size is correct. */
- g_assert (self->connect_msg->len == 32 + (2 + cert_size + 65 + 65 + 32 + 64 + 64) + 32);
- recv_data = g_byte_array_free_to_bytes (g_steal_pointer (&self->connect_msg));
-
- claim = fpi_sdcp_claim_new ();
- r_d = g_bytes_new_from_bytes (recv_data, 0, 32);
- claim->cert_m = g_bytes_new_from_bytes (recv_data, 34, cert_size);
- claim->pk_d = g_bytes_new_from_bytes (recv_data, 34 + cert_size, 65);
- claim->pk_f = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65, 65);
- claim->h_f = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65, 32);
- claim->s_m = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32, 64);
- claim->s_d = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32 + 64, 64);
- mac = g_bytes_new_from_bytes (recv_data, 34 + cert_size + 65 + 65 + 32 + 64 + 64, 32);
-
- ca_1_bytes = g_bytes_new_static (ca_1, G_N_ELEMENTS (ca_1));
- ca_2_bytes = g_bytes_new_static (ca_2, G_N_ELEMENTS (ca_2));
-
- fpi_sdcp_device_set_intermediat_cas (FP_SDCP_DEVICE (dev),
- ca_1_bytes,
- ca_2_bytes);
-
- fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), r_d, claim, mac, NULL);
-}
-
-static void
-connect_1_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- guint16 cert_size;
-
- if (error)
- {
- fpi_sdcp_device_connect_complete (FP_SDCP_DEVICE (dev), NULL, NULL, NULL, error);
- return;
- }
-
- g_clear_pointer (&self->connect_msg, g_byte_array_unref);
- self->connect_msg = g_steal_pointer (&self->msg_in);
-
- memcpy (&cert_size, self->connect_msg->data + 32, 2);
-
- /* Nothing to send and the rest to receive. */
- self->msg_out = g_byte_array_new ();
- self->msg_in = g_byte_array_new ();
- g_byte_array_set_size (self->msg_in, 32 + (2 + cert_size + 65 + 65 + 32 + 64 + 64) + 32 - self->connect_msg->len);
-
- /* New SSM */
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect 2");
- fpi_ssm_start (ssm, connect_2_cb);
-}
-
-static void
-connect (FpSdcpDevice *dev)
-{
- g_autoptr(GBytes) r_h = NULL;
- g_autoptr(GBytes) pk_h = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- FpiSsm *ssm;
-
- G_DEBUG_HERE ();
-
- g_assert (self->proc);
- g_assert (self->connect_msg == NULL);
-
- fpi_sdcp_device_get_connect_data (dev, &r_h, &pk_h);
-
- self->msg_out = g_byte_array_new ();
- g_byte_array_append (self->msg_out, (const guint8 *) "C", 1);
- g_byte_array_append (self->msg_out,
- g_bytes_get_data (r_h, NULL),
- g_bytes_get_size (r_h));
- g_byte_array_append (self->msg_out,
- g_bytes_get_data (pk_h, NULL),
- g_bytes_get_size (pk_h));
-
- self->msg_in = g_byte_array_new ();
- g_byte_array_set_size (self->msg_in, 34);
-
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect");
- fpi_ssm_start (ssm, connect_1_cb);
-}
-
-static void
-reconnect_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- g_autoptr(GBytes) mac = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
-
- if (error)
- {
- fpi_sdcp_device_reconnect_complete (FP_SDCP_DEVICE (dev), mac, error);
- return;
- }
-
- mac = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
-
- fpi_sdcp_device_reconnect_complete (FP_SDCP_DEVICE (dev), mac, NULL);
-}
-
-static void
-reconnect (FpSdcpDevice *dev)
-{
- g_autoptr(GBytes) r_h = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- FpiSsm *ssm;
-
- G_DEBUG_HERE ();
-
- g_assert (self->proc);
-
- fpi_sdcp_device_get_reconnect_data (dev, &r_h);
-
- self->msg_out = g_byte_array_new ();
- g_byte_array_append (self->msg_out, (const guint8 *) "R", 1);
- g_byte_array_append (self->msg_out,
- g_bytes_get_data (r_h, NULL),
- g_bytes_get_size (r_h));
-
- self->msg_in = g_byte_array_new ();
- g_byte_array_set_size (self->msg_in, 32);
-
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "connect 2");
- fpi_ssm_start (ssm, reconnect_cb);
-}
-
-static void
-enroll_begin_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- g_autoptr(GBytes) nonce = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
-
- if (error)
- {
- fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), error);
- return;
- }
-
- nonce = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
-
- fpi_sdcp_device_enroll_set_nonce (FP_SDCP_DEVICE (dev), nonce);
-
- /* Claim that we completed one enroll step. */
- fpi_device_enroll_progress (dev, 1, NULL, NULL);
-
- /* And signal that we are ready to commit. */
- fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), NULL);
-}
-
-static void
-enroll_begin (FpSdcpDevice *dev)
-{
- g_autoptr(GBytes) r_h = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- FpiSsm *ssm;
-
- G_DEBUG_HERE ();
-
- g_assert (self->proc);
-
- fpi_sdcp_device_get_reconnect_data (dev, &r_h);
-
- self->msg_out = g_byte_array_new ();
- g_byte_array_append (self->msg_out, (const guint8 *) "E", 1);
-
- /* Expect 32 byte nonce */
- self->msg_in = g_byte_array_new ();
- g_byte_array_set_size (self->msg_in, 32);
-
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "enroll_begin");
- fpi_ssm_start (ssm, enroll_begin_cb);
-}
-
-static void
-enroll_commit_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
-
- g_clear_pointer (&self->msg_in, g_byte_array_unref);
-
- if (error)
- {
- fpi_sdcp_device_enroll_ready (FP_SDCP_DEVICE (dev), error);
- return;
- }
-
- /* Signal that we have committed. We don't expect a response
- * from the virtual device (even though that is kind of broken).
- */
- fpi_sdcp_device_enroll_commit_complete (FP_SDCP_DEVICE (dev), NULL);
-}
-
-static void
-enroll_commit (FpSdcpDevice *dev, GBytes *id_in)
-{
- g_autoptr(GBytes) r_h = NULL;
- g_autoptr(GBytes) id = id_in;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- FpiSsm *ssm;
-
- G_DEBUG_HERE ();
-
- g_assert (self->proc);
-
- fpi_sdcp_device_get_reconnect_data (dev, &r_h);
-
- self->msg_out = g_byte_array_new ();
- self->msg_in = g_byte_array_new ();
- if (id)
- {
- g_byte_array_append (self->msg_out, (const guint8 *) "F", 1);
- g_byte_array_append (self->msg_out,
- g_bytes_get_data (id, NULL),
- g_bytes_get_size (id));
-
- /* NOTE: No response from device, assume commit works. */
- }
- else
- {
- /* Cancel enroll (does not receive a reply) */
- g_byte_array_append (self->msg_out, (const guint8 *) "G", 1);
-
- /* NOTE: No response from device, assume cancellation works. */
- }
-
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "enroll_commit");
- fpi_ssm_start (ssm, enroll_commit_cb);
-}
-
-static void
-identify_cb (FpiSsm *ssm, FpDevice *dev, GError *error)
-{
- g_autoptr(GBytes) reply = NULL;
- g_autoptr(GBytes) id = NULL;
- g_autoptr(GBytes) mac = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
-
- if (error)
- {
- fpi_sdcp_device_identify_complete (FP_SDCP_DEVICE (dev), NULL, NULL, error);
- return;
- }
-
- reply = g_byte_array_free_to_bytes (g_steal_pointer (&self->msg_in));
- id = g_bytes_new_from_bytes (reply, 0, 32);
- mac = g_bytes_new_from_bytes (reply, 32, 32);
-
- fpi_sdcp_device_identify_complete (FP_SDCP_DEVICE (self), id, mac, NULL);
-}
-
-static void
-identify (FpSdcpDevice *dev)
-{
- g_autoptr(GBytes) nonce = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- FpiSsm *ssm;
-
- G_DEBUG_HERE ();
-
- g_assert (self->proc);
-
- fpi_sdcp_device_get_identify_data (dev, &nonce);
-
- self->msg_out = g_byte_array_new ();
- g_byte_array_append (self->msg_out, (const guint8 *) "I", 1);
- g_byte_array_append (self->msg_out, g_bytes_get_data (nonce, NULL), g_bytes_get_size (nonce));
-
- /* Expect 64 byte nonce */
- self->msg_in = g_byte_array_new ();
- g_byte_array_set_size (self->msg_in, 64);
-
- ssm = fpi_ssm_new_full (FP_DEVICE (dev), send_recv_ssm, SEND_RECV_STATES, SEND_RECV_STATES, "identify");
- fpi_ssm_start (ssm, identify_cb);
-}
-
-static void
-probe (FpDevice *dev)
-{
- g_auto(GStrv) argv = NULL;
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (dev);
- GError *error = NULL;
- const char *env;
-
- /* We launch the test binary alread at probe time and quit only when
- * the object is finalized. This allows testing reconnect properly.
- *
- * Also, we'll fail probe if something goes wrong executing it.
- */
- env = fpi_device_get_virtual_env (FP_DEVICE (self));
-
- if (!g_shell_parse_argv (env, NULL, &argv, &error))
- goto out;
-
- self->proc = g_subprocess_newv ((const char * const *) argv,
- G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
- &error);
- if (!self->proc)
- goto out;
-
- self->proc_stdin = g_object_ref (g_subprocess_get_stdin_pipe (self->proc));
- self->proc_stdout = g_object_ref (g_subprocess_get_stdout_pipe (self->proc));
-
-
-out:
- fpi_device_probe_complete (dev, "virtual-sdcp", NULL, error);
-}
-
-static void
-dev_close (FpDevice *dev)
-{
- /* No-op, needs to be defined. */
- fpi_device_close_complete (dev, NULL);
-}
-
-static void
-fpi_device_virtual_sdcp_init (FpDeviceVirtualSdcp *self)
-{
-}
-
-static void
-fpi_device_virtual_sdcp_finalize (GObject *obj)
-{
- FpDeviceVirtualSdcp *self = FPI_DEVICE_VIRTUAL_SDCP (obj);
-
- /* Just kill the subprocess, no need to be graceful here. */
- if (self->proc)
- g_subprocess_force_exit (self->proc);
-
- g_clear_object (&self->proc);
- g_clear_object (&self->proc_stdin);
- g_clear_object (&self->proc_stdout);
-
- G_OBJECT_CLASS (fpi_device_virtual_sdcp_parent_class)->finalize (obj);
-}
-
-static const FpIdEntry driver_ids[] = {
- { .virtual_envvar = "FP_VIRTUAL_SDCP" },
- { .virtual_envvar = NULL }
-};
-
-static void
-fpi_device_virtual_sdcp_class_init (FpDeviceVirtualSdcpClass *klass)
-{
- GObjectClass *obj_class = G_OBJECT_CLASS (klass);
- FpDeviceClass *dev_class = FP_DEVICE_CLASS (klass);
- FpSdcpDeviceClass *sdcp_class = FP_SDCP_DEVICE_CLASS (klass);
-
- obj_class->finalize = fpi_device_virtual_sdcp_finalize;
-
- dev_class->id = FP_COMPONENT;
- dev_class->full_name = "Virtual SDCP device talking to MS test code";
- dev_class->type = FP_DEVICE_TYPE_VIRTUAL;
- dev_class->id_table = driver_ids;
- dev_class->nr_enroll_stages = 1;
-
- /* The SDCP base class may need to override this in the long run */
- dev_class->probe = probe;
- dev_class->close = dev_close;
-
- sdcp_class->connect = connect;
- sdcp_class->reconnect = reconnect;
-
- sdcp_class->enroll_begin = enroll_begin;
- sdcp_class->enroll_commit = enroll_commit;
-
- sdcp_class->identify = identify;
-}
diff --git a/libfprint/fp-sdcp-device-private.h b/libfprint/fp-sdcp-device-private.h
index 249c33c9..aa29445e 100644
--- a/libfprint/fp-sdcp-device-private.h
+++ b/libfprint/fp-sdcp-device-private.h
@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg
+ * Copyright (C) 2025 Joshua Grisham
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -21,38 +22,26 @@
#include "fpi-sdcp-device.h"
-#include
-#include
-#include
-#include
-
typedef struct
{
- GError *enroll_pre_commit_error;
-
- /* XXX: Do we want a separate SDCP session object?
- */
-
- GPtrArray *intermediate_cas;
-
- /* Host random for the connection */
- guint8 host_random[32];
-
- NSSInitContext *nss_init_context;
- PK11SlotInfo *slot;
- SECKEYPrivateKey *host_key_private;
- SECKEYPublicKey *host_key_public;
-
- /* Master secret is required for reconnects.
- * TODO: We probably want to serialize this to disk so it can survive
- * fprintd idle shutdown. */
- PK11SymKey *master_secret;
- PK11SymKey *mac_secret;
-
+ GBytes *host_private_key;
+ GBytes *host_public_key;
+ GBytes *host_random;
+ GBytes *reconnect_random;
+ GBytes *identify_nonce;
+ GVariant *data;
} FpSdcpDevicePrivate;
+void fpi_sdcp_device_get_application_secret (FpSdcpDevice *self,
+ GBytes **application_secret);
+void fpi_sdcp_device_set_application_secret (FpSdcpDevice *self,
+ GBytes *application_secret);
+void fpi_sdcp_device_unset_application_secret (FpSdcpDevice *self);
+
+void fpi_sdcp_device_open (FpSdcpDevice *self);
void fpi_sdcp_device_connect (FpSdcpDevice *self);
void fpi_sdcp_device_reconnect (FpSdcpDevice *self);
+void fpi_sdcp_device_list (FpSdcpDevice *self);
void fpi_sdcp_device_enroll (FpSdcpDevice *self);
void fpi_sdcp_device_identify (FpSdcpDevice *self);
diff --git a/libfprint/fp-sdcp-device.c b/libfprint/fp-sdcp-device.c
index 4cdac4e5..5c0d02cc 100644
--- a/libfprint/fp-sdcp-device.c
+++ b/libfprint/fp-sdcp-device.c
@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg
+ * Copyright (C) 2025 Joshua Grisham
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -32,15 +33,13 @@
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (FpSdcpDevice, fp_sdcp_device, FP_TYPE_DEVICE)
-#if 0
-/* XXX: We'll very likely want/need some properties on this class. */
enum {
PROP_0,
+ PROP_SDCP_DATA,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
-#endif
/*******************************************************/
@@ -49,13 +48,16 @@ static void
fp_sdcp_device_open (FpDevice *device)
{
FpSdcpDevice *self = FP_SDCP_DEVICE (device);
- FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- /* Try a reconnect if we still have the mac secret. */
- if (priv->mac_secret)
- fpi_sdcp_device_reconnect (self);
- else
- fpi_sdcp_device_connect (self);
+ fpi_sdcp_device_open (self);
+}
+
+static void
+fp_sdcp_device_list (FpDevice *device)
+{
+ FpSdcpDevice *self = FP_SDCP_DEVICE (device);
+
+ fpi_sdcp_device_list (self);
}
static void
@@ -82,13 +84,12 @@ fp_sdcp_device_finalize (GObject *object)
FpSdcpDevice *self = (FpSdcpDevice *) object;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- g_clear_pointer (&priv->intermediate_cas, g_ptr_array_unref);
- g_clear_pointer (&priv->slot, PK11_FreeSlot);
- g_clear_pointer (&priv->host_key_private, SECKEY_DestroyPrivateKey);
- g_clear_pointer (&priv->host_key_public, SECKEY_DestroyPublicKey);
- g_clear_pointer (&priv->master_secret, PK11_FreeSymKey);
- g_clear_pointer (&priv->mac_secret, PK11_FreeSymKey);
- g_clear_pointer (&priv->nss_init_context, NSS_ShutdownContext);
+ g_clear_pointer (&priv->host_private_key, g_bytes_unref);
+ g_clear_pointer (&priv->host_public_key, g_bytes_unref);
+ g_clear_pointer (&priv->host_random, g_bytes_unref);
+ g_clear_pointer (&priv->reconnect_random, g_bytes_unref);
+ g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
+ g_clear_pointer (&priv->data, g_variant_unref);
G_OBJECT_CLASS (fp_sdcp_device_parent_class)->finalize (object);
}
@@ -99,8 +100,36 @@ fp_sdcp_device_get_property (GObject *object,
GValue *value,
GParamSpec *pspec)
{
+ FpSdcpDevice *self = (FpSdcpDevice *) object;
+ FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
+
switch (prop_id)
{
+ case PROP_SDCP_DATA:
+ g_value_set_variant (value, priv->data);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+fp_sdcp_device_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ FpSdcpDevice *self = FP_SDCP_DEVICE (object);
+ FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_SDCP_DATA:
+ g_clear_pointer (&priv->data, g_variant_unref);
+ priv->data = g_value_dup_variant (value);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -120,22 +149,30 @@ fp_sdcp_device_class_init (FpSdcpDeviceClass *klass)
object_class->finalize = fp_sdcp_device_finalize;
object_class->get_property = fp_sdcp_device_get_property;
+ object_class->set_property = fp_sdcp_device_set_property;
object_class->constructed = fp_sdcp_device_constructed;
fp_device_class->open = fp_sdcp_device_open;
+ fp_device_class->list = fp_sdcp_device_list;
fp_device_class->enroll = fp_sdcp_device_enroll;
fp_device_class->verify = fp_sdcp_device_identify;
fp_device_class->identify = fp_sdcp_device_identify;
-#if 0
+ properties[PROP_SDCP_DATA] =
+ g_param_spec_variant ("sdcp-data",
+ "SDCP Data",
+ "SDCP-related device data that should be persisted and used with the "
+ "device during the current system boot",
+ G_VARIANT_TYPE_ANY,
+ NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
g_object_class_install_properties (object_class, N_PROPS, properties);
-#endif
+
+ fpi_device_class_auto_initialize_features (fp_device_class);
}
static void
fp_sdcp_device_init (FpSdcpDevice *self)
{
- FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
-
- priv->intermediate_cas = g_ptr_array_new_with_free_func ((GDestroyNotify) g_bytes_unref);
}
diff --git a/libfprint/fpi-log.c b/libfprint/fpi-log.c
new file mode 100644
index 00000000..679b2570
--- /dev/null
+++ b/libfprint/fpi-log.c
@@ -0,0 +1,56 @@
+/*
+ * FpiLog - Internal logging functions
+ * Copyright (C) 2020 Benjamin Berg
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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);
+}
diff --git a/libfprint/fpi-log.h b/libfprint/fpi-log.h
index cd8f1bd5..1222854e 100644
--- a/libfprint/fpi-log.h
+++ b/libfprint/fpi-log.h
@@ -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)
diff --git a/libfprint/fpi-sdcp-device.c b/libfprint/fpi-sdcp-device.c
index f594cca0..19683f09 100644
--- a/libfprint/fpi-sdcp-device.c
+++ b/libfprint/fpi-sdcp-device.c
@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg
+ * Copyright (C) 2025 Joshua Grisham
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -20,18 +21,17 @@
#define FP_COMPONENT "sdcp_device"
#include "fpi-log.h"
-#include
-#include
-
#include "fpi-compat.h"
-#include "fp-sdcp-device-private.h"
-#include "fpi-sdcp-device.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
+ * @short_description: Internal SDCP device routines
*
* Internal SDCP handling routines. See #FpSdcpDevice for public routines.
*/
@@ -39,19 +39,6 @@
G_DEFINE_BOXED_TYPE (FpiSdcpClaim, fpi_sdcp_claim, fpi_sdcp_claim_copy, fpi_sdcp_claim_free)
-/*
- * Static, but could be created at runtime using:
- * oid_data = SECOID_FindOIDByTag (SEC_OID_SECG_EC_SECP256R1);
- * ec_parameters_der.len = oid_data->oid.len + 2;
- * ec_parameters_der.data = ec_params_data = g_malloc0 (oid_data->oid.len + 2);
- * ec_parameters_der.data[0] = SEC_ASN1_OBJECT_ID;
- * ec_parameters_der.data[1] = oid_data->oid.len;
- */
-const SECItem SDCPECParamsDER = {
- .len = 10,
- .data = (guint8[]){ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 }
-};
-
/**
* fpi_sdcp_claim_new:
*
@@ -76,16 +63,16 @@ fpi_sdcp_claim_new (void)
* Release the memory used by an #FpiSdcpClaim.
*/
void
-fpi_sdcp_claim_free (FpiSdcpClaim * claim)
+fpi_sdcp_claim_free (FpiSdcpClaim *claim)
{
g_return_if_fail (claim);
- g_clear_pointer (&claim->cert_m, g_bytes_unref);
- g_clear_pointer (&claim->pk_d, g_bytes_unref);
- g_clear_pointer (&claim->pk_f, g_bytes_unref);
- g_clear_pointer (&claim->h_f, g_bytes_unref);
- g_clear_pointer (&claim->s_m, g_bytes_unref);
- g_clear_pointer (&claim->s_d, g_bytes_unref);
+ 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);
}
@@ -105,48 +92,22 @@ fpi_sdcp_claim_copy (FpiSdcpClaim *other)
res = fpi_sdcp_claim_new ();
- if (other->cert_m)
- res->cert_m = g_bytes_ref (other->cert_m);
- if (other->pk_d)
- res->pk_d = g_bytes_ref (other->pk_d);
- if (other->pk_f)
- res->pk_f = g_bytes_ref (other->pk_f);
- if (other->h_f)
- res->h_f = g_bytes_ref (other->h_f);
- if (other->s_m)
- res->s_m = g_bytes_ref (other->s_m);
- if (other->s_d)
- res->s_d = g_bytes_ref (other->s_d);
+ 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;
}
-static void
-dump_bytes (GBytes *d)
-{
- g_autoptr(GString) line = NULL;
- const guint8 *dump_data;
- gsize dump_len;
-
- dump_data = g_bytes_get_data (d, &dump_len);
-
- line = g_string_new ("");
- /* Dump the buffer. */
- for (gint i = 0; i < dump_len; i++)
- {
- g_string_append_printf (line, "%02x ", dump_data[i]);
- if ((i + 1) % 16 == 0)
- {
- g_debug ("%s", line->str);
- g_string_set_size (line, 0);
- }
- }
-
- if (line->len)
- g_debug ("%s", line->str);
-
-}
-
/* Manually redefine what G_DEFINE_* macro does */
static inline gpointer
fp_sdcp_device_get_instance_private (FpSdcpDevice *self)
@@ -157,376 +118,146 @@ fp_sdcp_device_get_instance_private (FpSdcpDevice *self)
g_type_class_get_instance_private_offset (sdcp_class));
}
-/**
- * fpi_sdcp_generate_random:
- * @buffer: Buffer to place random bytes into
- * @len: Number of bytes to generate
- * @error: Error out
- *
- * Returns: #TRUE on success
- **/
-FP_GNUC_ACCESS (write_only, 1, 2)
-static gboolean
-fpi_sdcp_generate_random (guint8 *buffer, gsize len, GError **error)
-{
- /* Just use a counter in emulation mode. Not random, but all
- * we need is something predictable and not repeating immediately.
- */
- if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
- {
- static guint8 emulation_rand = 0;
- gsize i;
-
- for (i = 0; i < len; i++)
- {
- buffer[i] = emulation_rand;
- emulation_rand += 1;
- }
-
- return TRUE;
- }
-
- /* Generating random numbers is basic enough to assume it works */
- if (PK11_GenerateRandom (buffer, len) != SECSuccess)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Error generating random numbers using NSS!"));
- return FALSE;
- }
-
- return TRUE;
-}
-
-/**
- * fpi_sdcp_kdf:
- * @self: The #FpSdcpDevice
- * @baseKey: The key to base it on
- * @data_a: (nullable): First data segment to concatenate
- * @data_b: (nullable): Second data segment to concatenate
- * @out_key_2: (nullable) (out): Second output key or %NULL.
- * @error: (out): #GError in case the return value is %NULL
- *
- * Convenience function to calculate a KDF with a specific label
- * and up to two data segments that are concatinated. The returned
- * keys will be 32bytes (256bit) in length. If @out_key_2 is set
- * then two keys will be generated.
- *
- * Returns: A new #PK11SymKey of length @bitlength
- **/
-static PK11SymKey *
-fpi_sdcp_kdf (FpSdcpDevice *self,
- PK11SymKey *baseKey,
- const gchar *label,
- GBytes *data_a,
- GBytes *data_b,
- PK11SymKey **out_key_2,
- GError **error)
-{
- PK11SymKey * res = NULL;
- CK_SP800_108_KDF_PARAMS kdf_params;
- CK_SP800_108_COUNTER_FORMAT counter_format;
- CK_SP800_108_DKM_LENGTH_FORMAT length_format;
- CK_DERIVED_KEY additional_key;
- CK_ATTRIBUTE additional_key_attrs[2];
- CK_ULONG attr_type, attr_len;
- CK_OBJECT_HANDLE out_key_handle = 0;
- CK_PRF_DATA_PARAM data_param[5];
- SECItem params;
-
- kdf_params.prfType = CKM_SHA256_HMAC;
- kdf_params.pDataParams = data_param;
-
- /* First item is the counter */
- counter_format.bLittleEndian = FALSE;
- counter_format.ulWidthInBits = 32;
- data_param[0].type = CK_SP800_108_ITERATION_VARIABLE;
- data_param[0].pValue = &counter_format;
- data_param[0].ulValueLen = sizeof (counter_format);
- kdf_params.ulNumberOfDataParams = 1;
-
- /* Then the label */
- data_param[kdf_params.ulNumberOfDataParams].type = CK_SP800_108_BYTE_ARRAY;
- data_param[kdf_params.ulNumberOfDataParams].pValue = (guint8 *) label;
- data_param[kdf_params.ulNumberOfDataParams].ulValueLen = strlen (label) + 1;
- kdf_params.ulNumberOfDataParams += 1;
-
- /* Then the context a */
- if (data_a)
- {
- data_param[kdf_params.ulNumberOfDataParams].type = CK_SP800_108_BYTE_ARRAY;
- data_param[kdf_params.ulNumberOfDataParams].pValue = (guint8 *) g_bytes_get_data (data_a, NULL);
- data_param[kdf_params.ulNumberOfDataParams].ulValueLen = g_bytes_get_size (data_a);
- kdf_params.ulNumberOfDataParams += 1;
- }
-
- /* Then the context b */
- if (data_b)
- {
- data_param[kdf_params.ulNumberOfDataParams].type = CK_SP800_108_BYTE_ARRAY;
- data_param[kdf_params.ulNumberOfDataParams].pValue = (guint8 *) g_bytes_get_data (data_b, NULL);
- data_param[kdf_params.ulNumberOfDataParams].ulValueLen = g_bytes_get_size (data_b);
- kdf_params.ulNumberOfDataParams += 1;
- }
-
- /* And the output length */
- length_format.dkmLengthMethod = CK_SP800_108_DKM_LENGTH_SUM_OF_KEYS;
- length_format.bLittleEndian = FALSE;
- length_format.ulWidthInBits = 32;
- data_param[kdf_params.ulNumberOfDataParams].type = CK_SP800_108_DKM_LENGTH;
- data_param[kdf_params.ulNumberOfDataParams].pValue = &length_format;
- data_param[kdf_params.ulNumberOfDataParams].ulValueLen = sizeof (length_format);
- kdf_params.ulNumberOfDataParams += 1;
-
- kdf_params.ulAdditionalDerivedKeys = 0;
- kdf_params.pAdditionalDerivedKeys = NULL;
- if (out_key_2)
- {
- attr_type = CKK_SHA256_HMAC;
- attr_len = 256 / 8;
- additional_key_attrs[0].type = CKA_KEY_TYPE;
- additional_key_attrs[0].pValue = &attr_type;
- additional_key_attrs[0].ulValueLen = sizeof (attr_type);
- additional_key_attrs[1].type = CKA_VALUE_LEN;
- additional_key_attrs[1].pValue = &attr_len;
- additional_key_attrs[1].ulValueLen = sizeof (attr_len);
-
- additional_key.pTemplate = additional_key_attrs;
- additional_key.ulAttributeCount = 2;
- additional_key.phKey = &out_key_handle;
-
- kdf_params.ulAdditionalDerivedKeys = 1;
- kdf_params.pAdditionalDerivedKeys = &additional_key;
- }
-
- params.len = sizeof (kdf_params);
- params.data = (guint8 *) &kdf_params;
- res = PK11_Derive (baseKey,
- CKM_SP800_108_COUNTER_KDF,
- ¶ms,
- CKM_SHA256_HMAC,
- CKA_SIGN,
- 256 / 8);
- if (!res)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Error deriving secret (label: %s): %d",
- label,
- PORT_GetError ()));
- }
-
- if (out_key_2)
- *out_key_2 = PK11_SymKeyFromHandle (PK11_GetSlotFromKey (baseKey), res, CKO_DATA, CKM_NULL, out_key_handle, FALSE, NULL);
-
- return res;
-}
-
-/**
- * fpi_sdcp_mac:
- * @self: The #FpSdcpDevice
- * @baseKey: The key to base it on
- * @data_a: (nullable): Data segment a to concatenate
- * @data_b: (nullable): Data segment b to concatenate
- * @error: (out): #GError in case the return value is %NULL
- *
- * Convenience function to calculate a MAC with a specific label
- * and a generic data segments that is concatenated.
- *
- * Returns: A new #PK11SymKey
- **/
-static GBytes *
-fpi_sdcp_mac (FpSdcpDevice *self,
- const gchar *label,
- GBytes *data_a,
- GBytes *data_b,
- GError **error)
-{
- g_autoptr(GBytes) res = NULL;
- FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- SECStatus r;
- SECItem input, output;
- g_autofree guint8 *data = NULL;
- gsize label_len;
- gsize data_a_len = 0;
- gsize data_b_len = 0;
- gsize length;
-
- label_len = strlen (label) + 1;
- length = label_len;
- if (data_a)
- data_a_len = g_bytes_get_size (data_a);
- if (data_b)
- data_b_len = g_bytes_get_size (data_b);
-
- length += data_a_len + data_b_len;
-
- data = g_malloc (length);
-
- memcpy (data, label, label_len);
- if (data_a)
- memcpy (data + label_len, g_bytes_get_data (data_a, NULL), data_a_len);
- if (data_b)
- memcpy (data + label_len + data_a_len, g_bytes_get_data (data_b, NULL), data_b_len);
-
- input.len = length;
- input.data = data;
- output.len = 32;
- output.data = g_malloc0 (32);
- res = g_bytes_new_take (output.data, output.len);
-
- r = PK11_SignWithSymKey (priv->mac_secret,
- CKM_SHA256_HMAC,
- NULL,
- &output,
- &input);
- if (r != SECSuccess)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Error calculating MAC (label: %s): %d",
- label,
- r));
- }
-
- return g_steal_pointer (&res);
-}
+/* 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)
+{
+ g_autoptr(GVariant) data = NULL;
+ g_autoptr(GVariant) application_secret_var = NULL;
+ const guint8 *application_secret_data;
+ gsize application_secret_len = 0;
+
+ g_return_if_fail (*application_secret == NULL);
+
+ g_object_get (G_OBJECT (self), "sdcp-data", &data, NULL);
+
+ if (!data)
+ return;
+
+ if (!g_variant_check_format_string (data, "(@ay)", FALSE))
+ {
+ fp_warn ("SDCP data is not in expected format.");
+ return;
+ }
+
+ g_variant_get (data, "(@ay)", &application_secret_var);
+
+ application_secret_data = g_variant_get_fixed_array (application_secret_var,
+ &application_secret_len,
+ sizeof (guint8));
+
+ *application_secret = g_bytes_new (application_secret_data, application_secret_len);
+}
+
+void
+fpi_sdcp_device_set_application_secret (FpSdcpDevice *self,
+ GBytes *application_secret)
+{
+ GVariant *application_secret_var;
+ GVariant *data;
+
+ g_return_if_fail (application_secret);
+
+ application_secret_var = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+ g_bytes_get_data (application_secret, NULL),
+ g_bytes_get_size (application_secret),
+ sizeof (guint8));
+ data = g_variant_new ("(@ay)", application_secret_var);
+
+ g_object_set (G_OBJECT (self), "sdcp-data", data, NULL);
+}
+
+void
+fpi_sdcp_device_unset_application_secret (FpSdcpDevice *self)
+{
+ g_object_set (G_OBJECT (self), "sdcp-data", 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)
{
- G_GNUC_UNUSED g_autofree void * ec_params_data = NULL;
FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
GError *error = NULL;
- SECStatus r = SECSuccess;
+ 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);
- /* Disable loading p11-kit's user configuration */
- g_setenv ("P11_KIT_NO_USER_CONFIG", "1", TRUE);
-
- /* Initialise NSS; Same as NSS_NoDB_Init but using a context. */
- if (!priv->nss_init_context)
+ if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
- priv->nss_init_context = NSS_InitContext ("", "", "", "", NULL,
- NSS_INIT_READONLY |
- NSS_INIT_NOCERTDB |
- NSS_INIT_NOMODDB |
- NSS_INIT_FORCEOPEN |
- NSS_INIT_NOROOTINIT |
- NSS_INIT_OPTIMIZESPACE);
- }
- if (!priv->nss_init_context)
- goto nss_error;
-
- g_clear_pointer (&priv->slot, PK11_FreeSlot);
- g_clear_pointer (&priv->host_key_private, SECKEY_DestroyPrivateKey);
- g_clear_pointer (&priv->host_key_public, SECKEY_DestroyPublicKey);
- priv->host_key_private = NULL;
-
- /* SDCP Connect: 3.i. Generate an ephemeral ECDH key pair */
- /* Look up the OID data for our curve. */
-
- /* Just use a counter in emulation mode. Not random, but all
- * we need is something predictable and not repeating immediately.
- */
- if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") == 0)
- {
- /* To generate, use the #if 0 code below and remove the readOnly flag */
- priv->slot = SECMOD_OpenUserDB ("configdir='sdcp-key-db' tokenDescription='libfprint CI testing' flags=readOnly");
- if (!priv->slot)
+ /* 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)
{
- g_message ("Could not open key DB for testing");
- exit (77);
+ fpi_sdcp_device_connect_complete (self,
+ NULL, NULL, NULL,
+ error);
+ return;
}
-#if 0
- if (PK11_NeedUserInit (priv->slot))
- if (PK11_InitPin (priv->slot, "", "") != SECSuccess)
- goto nss_error;
-
- if (priv->slot == NULL)
- goto nss_error;
- g_debug ("logged in: %i, need: %i", PK11_IsLoggedIn (priv->slot, NULL), PK11_NeedLogin (priv->slot));
- g_debug ("read only: %i", PK11_IsReadOnly (priv->slot));
- g_debug ("need user init: %i", PK11_NeedUserInit (priv->slot));
- //PK11_SetPasswordFunc (pwfunc);
-
- /* SDCP Connect: 3.i. Generate an ephemeral ECDH key pair */
- /* Look up the OID data for our curve. */
- oid_data = SECOID_FindOIDByTag (SEC_OID_SECG_EC_SECP256R1);
- if (!oid_data)
- goto nss_error;
-
- priv->host_key_private = PK11_GenerateKeyPair (priv->slot, CKM_EC_KEY_PAIR_GEN,
- (SECItem *) &SDCPECParamsDER,
- &priv->host_key_public,
- TRUE, FALSE,
- NULL);
-
- PK11_SetPrivateKeyNickname (priv->host_key_private, "CI testing");
- PK11_SetPublicKeyNickname (priv->host_key_public, "CI testing");
-#else
-
- g_assert (!PK11_NeedUserInit (priv->slot));
- g_assert (PK11_IsReadOnly (priv->slot));
-
- SECKEYPrivateKeyList *priv_key_list = NULL;
- SECKEYPublicKeyList *pub_key_list = NULL;
-
- priv_key_list = PK11_ListPrivKeysInSlot (priv->slot, (char *) "CI testing", NULL);
- pub_key_list = PK11_ListPublicKeysInSlot (priv->slot, (char *) "CI testing");
- g_assert (priv_key_list != NULL && pub_key_list != NULL);
- g_assert (!PR_CLIST_IS_EMPTY (&priv_key_list->list) && !PR_CLIST_IS_EMPTY (&pub_key_list->list));
-
- priv->host_key_private = SECKEY_CopyPrivateKey (((SECKEYPrivateKeyListNode *) PR_LIST_HEAD (&priv_key_list->list))->key);
- priv->host_key_public = SECKEY_CopyPublicKey (((SECKEYPublicKeyListNode *) PR_LIST_HEAD (&pub_key_list->list))->key);
-
- SECKEY_DestroyPrivateKeyList (priv_key_list);
- SECKEY_DestroyPublicKeyList (pub_key_list);
-#endif
+ /* 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
{
- /* Create a slot for PK11 operation */
- priv->slot = PK11_GetBestSlot (CKM_EC_KEY_PAIR_GEN, NULL);
- if (priv->slot == NULL)
- goto nss_error;
-
- priv->host_key_private = PK11_GenerateKeyPair (priv->slot, CKM_EC_KEY_PAIR_GEN,
- (SECItem *) &SDCPECParamsDER,
- &priv->host_key_public,
- FALSE, TRUE,
- NULL);
- }
-
- if (r != SECSuccess)
- goto nss_error;
-
- /* SDCP Connect: 3.ii. Generate host random */
- if (!fpi_sdcp_generate_random (priv->host_random, sizeof (priv->host_random), &error))
- {
- fpi_sdcp_device_connect_complete (self,
- NULL, NULL, NULL,
- error);
- return;
+ /* 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;
-
-nss_error:
- if (r == SECSuccess)
- r = PORT_GetError ();
- fpi_sdcp_device_connect_complete (self,
- NULL, NULL, NULL,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Error calling NSS crypto routine: %d", r));
}
void
@@ -536,11 +267,22 @@ fpi_sdcp_device_reconnect (FpSdcpDevice *self)
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
GError *error = NULL;
- /* SDCP Reconnect: 2.i. Generate host random */
- if (!fpi_sdcp_generate_random (priv->host_random, sizeof (priv->host_random), &error))
+ g_clear_pointer (&priv->reconnect_random, g_bytes_unref);
+
+ if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
- fpi_sdcp_device_reconnect_complete (self, NULL, error);
- return;
+ /* 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 */
@@ -550,6 +292,17 @@ fpi_sdcp_device_reconnect (FpSdcpDevice *self)
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)
{
@@ -567,7 +320,7 @@ fpi_sdcp_device_enroll (FpSdcpDevice *self)
/* For enrollment, all we need to do is start the process. But just to be sure,
* clear a bit of internal state.
*/
- cls->enroll_begin (self);
+ cls->enroll (self);
}
void
@@ -583,11 +336,22 @@ fpi_sdcp_device_identify (FpSdcpDevice *self)
g_return_if_fail (action == FPI_DEVICE_ACTION_IDENTIFY || action == FPI_DEVICE_ACTION_VERIFY);
- /* Generate a new nonce. */
- if (!fpi_sdcp_generate_random (priv->host_random, sizeof (priv->host_random), &error))
+ g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
+
+ if (g_strcmp0 (g_getenv ("FP_DEVICE_EMULATION"), "1") != 0)
{
- fpi_device_action_error (FP_DEVICE (self), error);
- return;
+ /* 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);
@@ -597,72 +361,92 @@ fpi_sdcp_device_identify (FpSdcpDevice *self)
/* Private API */
/**
- * fp_sdcp_device_set_intermediate_cas:
- * @self: The #FpSdcpDevice
- * @ca_1: (transfer none): DER encoded intermediate CA certificate #1
- * @ca_2: (transfer none): DER encoded intermediate CA certificate #2
+ * fpi_sdcp_device_open_complete:
+ * @self: a #FpSdcpDevice fingerprint device
+ * @error: A #GError or %NULL on success
*
- * Set the intermediate CAs used by the device.
+ * Reports completion of open operation. Responsible for triggering SDCP connect
+ * or reconnect as necessary.
*/
void
-fpi_sdcp_device_set_intermediat_cas (FpSdcpDevice *self,
- GBytes *ca_1,
- GBytes *ca_2)
+fpi_sdcp_device_open_complete (FpSdcpDevice *self,
+ 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;
- g_ptr_array_set_size (priv->intermediate_cas, 0);
- if (ca_1)
- g_ptr_array_add (priv->intermediate_cas, g_bytes_ref (ca_1));
- if (ca_2)
- g_ptr_array_add (priv->intermediate_cas, g_bytes_ref (ca_2));
+ 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);
+ }
}
-/* FIXME: This is (transfer full), but other getters have (transfer none)
-* for drivers. Kind of inconsistent, but it is convenient here. */
/**
* fp_sdcp_device_get_connect_data:
- * @r_h: (out) (transfer full): The host random
- * @pk_h: (out) (transfer full): The host public key
+ * @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 **r_h,
- GBytes **pk_h)
+ GBytes **host_random,
+ GBytes **host_public_key)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- g_return_if_fail (r_h != NULL);
- g_return_if_fail (pk_h != NULL);
+ 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);
- *r_h = g_bytes_new (priv->host_random, sizeof (priv->host_random));
-
- g_assert (priv->host_key_public->u.ec.publicValue.len == 65);
- *pk_h = g_bytes_new (priv->host_key_public->u.ec.publicValue.data, priv->host_key_public->u.ec.publicValue.len);
+ *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:
- * @r_h: (out) (transfer full): The host random
+ * @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 **r_h)
+ GBytes **reconnect_random)
{
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- g_return_if_fail (r_h != NULL);
+ g_return_if_fail (reconnect_random != NULL);
+ g_return_if_fail (priv->reconnect_random);
- *r_h = g_bytes_new (priv->host_random, sizeof (priv->host_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:
- * @r_h: (out) (transfer full): The host random
+ * @self: a #FpSdcpDevice fingerprint device
+ * @nonce: (out) (transfer full): A new host-generated nonce
*
* Get data required to identify a new print.
*/
@@ -673,420 +457,123 @@ fpi_sdcp_device_get_identify_data (FpSdcpDevice *self,
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 (priv->host_random, sizeof (priv->host_random));
+ *nonce = g_bytes_new_from_bytes (priv->identify_nonce, 0,
+ g_bytes_get_size (priv->identify_nonce));
}
-/* Returns the certificates public key after validation. */
-static SECKEYPublicKey *
-fpi_sdcp_validate_cert (FpSdcpDevice *self,
- FpiSdcpClaim *claim,
- GError **error)
+/**
+ * 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);
- CERTValInParam in_params[1] = { 0, };
- CERTValOutParam out_params[2] = { 0, };
- const void *cert_m_data;
- gsize cert_m_length;
- CERTCertDBHandle *cert_db = NULL;
- CERTCertificate *cert_m = NULL;
- g_autoptr(GPtrArray) intermediate_cas = NULL;
- PLArenaPool *res_arena = NULL;
- SECKEYPublicKey *res = NULL;
- SECStatus r;
- gint i;
+ g_return_if_fail (nonce != NULL);
- g_debug ("cert_m:");
- dump_bytes (claim->cert_m);
- cert_m_data = g_bytes_get_data (claim->cert_m, &cert_m_length);
- cert_m = CERT_DecodeCertFromPackage ((char *) cert_m_data, cert_m_length);
- if (!cert_m)
- {
- /* So, the MS test client we use for the virtual-sdcp driver does not return
- * a certificate (yeah ... why?). This special case is purely for testing
- * purposes and should be removed by fixing the test client!
- */
- if (g_str_equal (fp_device_get_driver (FP_DEVICE (self)), "virtual_sdcp") && cert_m_length == 65)
- {
- /* Create a new public key directly from the buffer rather than from the certificate. */
- res_arena = PORT_NewArena (DER_DEFAULT_CHUNKSIZE);
- g_assert (res_arena);
+ g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
- res = (SECKEYPublicKey *) PORT_ArenaZAlloc (res_arena, sizeof (SECKEYPublicKey));
- g_assert (res);
-
- res->arena = res_arena;
- res->pkcs11Slot = 0;
- res->pkcs11ID = CK_INVALID_HANDLE;
-
- res->keyType = ecKey;
- res->u.ec.DEREncodedParams = SDCPECParamsDER;
- res->u.ec.publicValue.len = 65;
- res->u.ec.publicValue.data = (guint8 *) PORT_ArenaAlloc (res->arena, 65);
- memcpy (res->u.ec.publicValue.data, cert_m_data, 65);
-
- goto out;
- }
-
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Failed to read cert_m: %d", PORT_GetError ()));
- goto out;
- }
-
-#if 0
- /* The following code would be a better way of specifying the intermediate CAs
- * (instead of inserting them into the certificate store), but it does not
- * work because the feature has simply not been implemented in PKIX.
- * The code here is left purely as a reference and warning.
- */
- CERTCertList *intermediate_cas = NULL;
-
- /* Setup list for the intermediate CAs. */
- intermediate_cas = CERT_NewCertList ();
- for (i = 0; i < priv->intermediate_cas->len; i++)
- {
- CERTCertificate *cert = NULL;
- const void *data;
- gsize length;
-
- data = g_bytes_get_data ((GBytes *) g_ptr_array_index (priv->intermediate_cas, i),
- &length);
- cert = CERT_DecodeCertFromPackage ((char *) data, length);
- if (!cert)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Failed to read intermediate cert: %d", PORT_GetError ()));
- goto out;
- }
- /* Adding takes the reference. */
- r = CERT_AddCertToListTail (intermediate_cas, cert);
-
- if (r != SECSuccess)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Failed to add cert to cert list: %d", r));
- goto out;
- }
- }
-#endif
-
- /* Import intermediate certificates into cert DB. */
- cert_db = CERT_GetDefaultCertDB ();
- if (!cert_db)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "No default certificate DB!"));
- goto out;
- }
-
- intermediate_cas = g_ptr_array_new_full (priv->intermediate_cas->len, g_free);
- for (i = 0; i < priv->intermediate_cas->len; i++)
- {
- gsize length;
- SECItem *item = NULL;
-
- item = g_new0 (SECItem, 1);
- item->type = siDERCertBuffer;
- item->data = (guint8 *) g_bytes_get_data ((GBytes *) g_ptr_array_index (priv->intermediate_cas, i),
- &length);
- item->len = length;
- g_ptr_array_add (intermediate_cas, item);
- }
- r = CERT_ImportCerts (cert_db, certUsageVerifyCA,
- intermediate_cas->len, (SECItem **) intermediate_cas->pdata,
- NULL,
- FALSE, FALSE, NULL);
- if (r != SECSuccess)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Failed to import intermediate CAs: %d", PORT_GetError ()));
- goto out;
- }
-
- /* We assume we have the root CA in the system store already. */
- in_params[0].type = cert_pi_end;
-
- out_params[0].type = cert_po_end; // cert_po_errorLog;
- out_params[0].value.pointer.log = NULL;
- out_params[1].type = cert_po_end;
-
- r = CERT_PKIXVerifyCert (cert_m,
- certUsageAnyCA, /* XXX: is this correct? */
- in_params,
- out_params,
- NULL);
- if (r != SECSuccess)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
- "Failed to verify device certificate: %d", PORT_GetError ()));
- goto out;
- }
-
- /* All seems good, extract the public key in order to return it. */
- res = CERT_ExtractPublicKey (cert_m);
- if (!res)
- {
- g_propagate_error (error,
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Failed to extract public key from certificate: %d", PORT_GetError ()));
- goto out;
- }
-
-out:
- g_clear_pointer (&cert_m, CERT_DestroyCertificate);
- if (out_params[0].value.pointer.log)
- PORT_FreeArena (out_params[0].value.pointer.log->arena, FALSE);
- return res;
+ priv->identify_nonce = g_steal_pointer (&nonce);
}
-/* FIXME: How to provide intermediate CAs provided? Same call or separate channel? */
/**
* fpi_sdcp_device_connect_complete:
* @self: a #FpSdcpDevice fingerprint device
- * @r_d: The device random nonce
+ * @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 (i.e. open) operation.
+ * 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 *r_d,
+ GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
GError *error)
{
- g_autoptr(GBytes) r_h = NULL;
- g_autoptr(GBytes) claim_hash_bytes = NULL;
- g_autoptr(GBytes) claim_mac = NULL;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- SECKEYPublicKey firmware_key_public = { 0, };
- SECKEYPublicKey device_key_public = { 0, };
- SECKEYPublicKey *model_key_public = NULL;
- HASHContext *hash_ctx;
- guint8 hash_out[SHA256_LENGTH];
- guint hash_len = 0;
+ FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
+ g_autoptr(GBytes) application_secret = NULL;
FpiDeviceAction action;
- PK11SymKey *a = NULL;
- PK11SymKey *enc_secret = NULL;
- gsize length;
- SECItem sig, hash;
- SECStatus r;
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 (r_d || claim || mac)
+ if (device_random || claim || mac)
{
- g_warning ("Driver provided connect information but also reported error.");
- g_clear_pointer (&r_d, g_bytes_unref);
+ 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 (!r_d || !claim || !mac ||
- (!claim->cert_m || !claim->pk_d || !claim->pk_f || !claim->h_f || !claim->s_m || !claim->s_d))
+ 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))
{
- g_warning ("Driver did not provide all required information to callback, returning error instead.");
- g_clear_pointer (&r_d, g_bytes_unref);
+ 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."));
+ "Driver called connect complete with "
+ "incomplete arguments"));
return;
}
- /* Device key is of same type as host key */
- g_assert (g_bytes_get_size (claim->pk_f) == 65);
- firmware_key_public.keyType = ecKey;
- firmware_key_public.u.ec.DEREncodedParams = SDCPECParamsDER;
- firmware_key_public.u.ec.publicValue.len = 65;
- firmware_key_public.u.ec.publicValue.data = (guint8 *) g_bytes_get_data (claim->pk_f, NULL);
-
- /* SDCP Connect: 5.i. Perform key agreement */
- a = PK11_PubDeriveWithKDF (priv->host_key_private,
- &firmware_key_public,
- TRUE,
- NULL,
- NULL,
- CKM_ECDH1_DERIVE,
- CKM_SP800_108_COUNTER_KDF,
- CKA_DERIVE,
- 32, /* 256 bit (HMAC) secret */
- CKD_NULL,
- NULL,
- NULL);
-
- if (!a)
+ /* 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))
{
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Error doing key agreement: %d", PORT_GetError ());
- goto out;
+ fpi_device_open_complete (FP_DEVICE (self), error);
+ return;
}
- /* SDCP Connect: 5.ii. Derive master secret */
- g_clear_pointer (&priv->master_secret, PK11_FreeSymKey);
+ fpi_sdcp_device_set_application_secret (self, application_secret);
- r_h = g_bytes_new (priv->host_random, sizeof (priv->host_random));
+ /* 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);
- priv->master_secret = fpi_sdcp_kdf (self,
- a,
- "master secret",
- r_h,
- r_d,
- NULL,
- &error);
- if (!priv->master_secret)
- goto out;
-
- /* SDCP Connect: 5.iii. Derive MAC secret and symetric key */
-
- /* NOTE: symetric key is never used, as such we just don't derive it! */
- g_clear_pointer (&priv->mac_secret, PK11_FreeSymKey);
- priv->mac_secret = fpi_sdcp_kdf (self,
- priv->master_secret,
- "application keys",
- NULL,
- NULL,
- &enc_secret,
- &error);
- if (!priv->mac_secret)
- goto out;
-
- /* SDCP Connect: 5.iv. Validate the MAC over H(claim) */
- hash_ctx = HASH_Create (HASH_AlgSHA256);
- if (!hash_ctx)
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Could not create hash context");
- goto out;
- }
- HASH_Begin (hash_ctx);
- HASH_Update (hash_ctx, g_bytes_get_data (claim->cert_m, NULL), g_bytes_get_size (claim->cert_m));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->pk_d, NULL), g_bytes_get_size (claim->pk_d));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->pk_f, NULL), g_bytes_get_size (claim->pk_f));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->h_f, NULL), g_bytes_get_size (claim->h_f));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->s_m, NULL), g_bytes_get_size (claim->s_m));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->s_d, NULL), g_bytes_get_size (claim->s_d));
- HASH_End (hash_ctx, hash_out, &hash_len, sizeof (hash_out));
- g_clear_pointer (&hash_ctx, HASH_Destroy);
- g_assert (hash_len == sizeof (hash_out));
-
- claim_hash_bytes = g_bytes_new (hash_out, sizeof (hash_out));
- g_debug ("H(c):");
- dump_bytes (claim_hash_bytes);
-
- claim_mac = fpi_sdcp_mac (self, "connect", claim_hash_bytes, NULL, &error);
- if (!claim_mac)
- goto out;
-
- g_debug ("MAC(s, \"connect\"||H(c)):");
- dump_bytes (claim_mac);
-
- if (!g_bytes_equal (mac, claim_mac))
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
- "Device MAC over H(c) is incorrect.");
- goto out;
- }
-
- /* SDCP Connect: 5.v. Unpack the claim (SKIP, already done) */
- /* SDCP Connect: 5.vi. Verify claim */
-
- /* First, validate the certificate (and return its public key). */
- model_key_public = fpi_sdcp_validate_cert (self, claim, &error);
- if (!model_key_public)
- goto out;
-
- /* Verify(pk_m, H(pk_d), s_m) */
- sig.data = (guint8 *) g_bytes_get_data (claim->s_m, &length);
- sig.len = length;
- memset (hash_out, 0, sizeof (hash_out));
- r = PK11_HashBuf (HASH_GetHashOidTagByHashType (HASH_AlgSHA256),
- hash_out,
- g_bytes_get_data (claim->pk_d, NULL),
- g_bytes_get_size (claim->pk_d));
- if (r != SECSuccess)
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Failed to hash device public key!");
- goto out;
- }
-
- hash.data = hash_out;
- hash.len = sizeof (hash_out);
- r = PK11_Verify (model_key_public, &sig, &hash, NULL);
- if (r != SECSuccess)
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
- "Verification of device public key failed: %d", PORT_GetError ());
- goto out;
- }
-
- device_key_public.keyType = ecKey;
- device_key_public.u.ec.DEREncodedParams = SDCPECParamsDER;
- device_key_public.u.ec.publicValue.len = g_bytes_get_size (claim->pk_d);
- device_key_public.u.ec.publicValue.data = (guint8 *) g_bytes_get_data (claim->pk_d, NULL);
-
- /* Verify(pk_d, H(C001||h_f||pk_f), s_d) */
- sig.data = (guint8 *) g_bytes_get_data (claim->s_d, &length);
- sig.len = length;
-
- hash_ctx = HASH_Create (HASH_AlgSHA256);
- if (!hash_ctx)
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Could not create hash context");
- goto out;
- }
- HASH_Begin (hash_ctx);
- HASH_Update (hash_ctx, (guint8 *) "\xC0\x01", 2);
- HASH_Update (hash_ctx, g_bytes_get_data (claim->h_f, NULL), g_bytes_get_size (claim->h_f));
- HASH_Update (hash_ctx, g_bytes_get_data (claim->pk_f, NULL), g_bytes_get_size (claim->pk_f));
- HASH_End (hash_ctx, hash_out, &hash_len, sizeof (hash_out));
- g_clear_pointer (&hash_ctx, HASH_Destroy);
- g_assert (hash_len == sizeof (hash_out));
-
- hash.data = hash_out;
- hash.len = sizeof (hash_out);
- r = PK11_Verify (&device_key_public, &sig, &hash, NULL);
- if (r != SECSuccess)
- {
- error = fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
- "Verification of boot process failed: %d", PORT_GetError ());
- goto out;
- }
-
- /* XXX/FIXME: We should be checking H(f) against a list of compromised firmwares.
- * We would need a way to distribute and load it though.
- */
-
-out:
- g_clear_pointer (&a, PK11_FreeSymKey);
- g_clear_pointer (&enc_secret, PK11_FreeSymKey);
- g_clear_pointer (&model_key_public, SECKEY_DestroyPublicKey);
-
- if (error)
- g_clear_pointer (&priv->mac_secret, PK11_FreeSymKey);
-
- fpi_device_open_complete (FP_DEVICE (self), error);
+ fpi_device_open_complete (FP_DEVICE (self), NULL);
}
/**
@@ -1102,20 +589,21 @@ fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
GBytes *mac,
GError *error)
{
- g_autoptr(GError) err = NULL;
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)
{
- g_warning ("Driver provided a MAC but also reported an error.");
- g_bytes_unref (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. */
@@ -1123,27 +611,16 @@ fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
}
else if (mac)
{
- g_autoptr(GBytes) mac_verify = NULL;
- g_autoptr(GBytes) host_random = NULL;
+ fpi_sdcp_device_get_application_secret (self, &application_secret);
- /* We got a MAC, so we can check whether the device
- * still agrees with us on the shared secret. */
- host_random = g_bytes_new (priv->host_random, sizeof (priv->host_random));
- mac_verify = fpi_sdcp_mac (self, "reconnect", host_random, NULL, &err);
- if (!mac_verify)
+ if (fpi_sdcp_verify_reconnect (application_secret, priv->reconnect_random, mac, &error))
{
- fpi_device_open_complete (FP_DEVICE (self), g_steal_pointer (&err));
- return;
- }
-
- if (g_bytes_equal (mac, mac_verify))
- {
- g_debug ("Reconnect succeeded");
+ fp_dbg ("SDCP Reconnect succeeded");
fpi_device_open_complete (FP_DEVICE (self), NULL);
}
else
{
- g_message ("Fast reconnect with SDCP device failed, doing a full connect.");
+ fp_dbg ("SDCP Reconnect failed; doing a full connect.");
fpi_sdcp_device_connect (self);
}
}
@@ -1151,125 +628,131 @@ fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
{
fpi_device_open_complete (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Driver called reconnect complete with wrong arguments."));
+ "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_enroll_set_nonce:
+ * fpi_sdcp_device_list_complete:
* @self: a #FpSdcpDevice fingerprint device
- * @nonce: The device generated nonce
+ * @ids: A #GPtrArray of #GBytes of each SDCP enrollment ID stored on the device
+ * @error: A #GError or %NULL on success
*
- * Called during enroll to inform the SDCP base class about the nonce
- * that the device chose. This can be called at any point, but must be
- * called before calling fpi_sdcp_device_enroll_ready().
+ * 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_enroll_set_nonce (FpSdcpDevice *self,
- GBytes *nonce)
+fpi_sdcp_device_list_complete (FpSdcpDevice *self,
+ GPtrArray *ids,
+ GError *error)
{
- g_autoptr(GBytes) id = NULL;
- GVariant *id_var;
- FpPrint *print;
- GVariant *data;
+ g_autoptr(GPtrArray) prints = NULL;
+ gint prints_len = 0;
+ FpiDeviceAction action;
- 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);
+ action = fpi_device_get_current_action (FP_DEVICE (self));
- g_return_if_fail (nonce || g_bytes_get_size (nonce) != 32);
-
- fpi_device_get_enroll_data (FP_DEVICE (self), &print);
-
- id = fpi_sdcp_mac (self, "enroll", nonce, NULL, NULL);
- if (!id)
- {
- g_warning ("Could not generate enroll MAC");
- return;
- }
-
- 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);
-
- /* Set to true once committed */
- fpi_print_set_device_stored (print, FALSE);
-
- /* Attach the ID to the print */
- g_object_set (print, "fpi-data", data, NULL);
-}
-
-/**
- * fpi_sdcp_device_enroll_ready:
- * @self: a #FpSdcpDevice fingerprint device
- * @error: a #GError or %NULL on success
- *
- * Called when the print is ready to be committed to device memory.
- * For each enroll step, fpi_device_enroll_progress() must first
- * be called until the enroll is ready to be committed.
- */
-void
-fpi_sdcp_device_enroll_ready (FpSdcpDevice *self,
- GError *error)
-{
- g_autoptr(GVariant) data = NULL;
- g_autoptr(GVariant) id_var = NULL;
- g_autoptr(GBytes) id = NULL;
- FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- FpSdcpDeviceClass *cls = FP_SDCP_DEVICE_GET_CLASS (self);
- 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_device_get_enroll_data (FP_DEVICE (self), &print);
+ 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");
+
+ /* clear potentially non-functioning application_secret */
+ fpi_sdcp_device_unset_application_secret (self);
+
fpi_device_enroll_complete (FP_DEVICE (self), NULL, error);
g_object_set (print, "fpi-data", NULL, NULL);
return;
}
- /* TODO: The following will need to ensure that the ID has been generated */
+ /* Set to true once committed */
+ fpi_print_set_device_stored (print, FALSE);
- g_object_get (G_OBJECT (print), "fpi-data", &data, NULL);
+ /* Attach the ID to the print */
+ fpi_sdcp_device_set_print_id (print, id);
- if (data)
- {
- const guint8 *id_data;
- gsize id_len;
+ cls->enroll_commit (self, id);
- g_variant_get (data,
- "(@ay)",
- &id_var);
-
- id_data = g_variant_get_fixed_array (id_var, &id_len, 1);
- id = g_bytes_new (id_data, id_len);
- }
-
- g_debug ("ID/enroll mac:");
- dump_bytes (id);
-
- if (!id)
- {
- g_warning ("Driver failed to call fpi_sdcp_device_enroll_set_nonce, aborting enroll.");
-
- /* NOTE: Cancel the enrollment, i.e. don't commit */
- priv->enroll_pre_commit_error = fpi_device_error_new_msg (FP_DEVICE_ERROR_PROTO,
- "Device/driver did not provide a nonce as required by protocol, aborting enroll!");
- cls->enroll_commit (self, NULL);
- }
- else
- {
- cls->enroll_commit (self, g_steal_pointer (&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.
@@ -1278,27 +761,12 @@ void
fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
GError *error)
{
- g_autoptr(GVariant) data = NULL;
- FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
+ 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 (priv->enroll_pre_commit_error)
- {
- if (error)
- {
- g_warning ("Cancelling enroll after error failed with: %s", error->message);
- g_error_free (error);
- }
-
- fpi_device_enroll_complete (FP_DEVICE (self),
- NULL,
- g_steal_pointer (&priv->enroll_pre_commit_error));
- return;
- }
-
if (error)
{
fpi_device_enroll_complete (FP_DEVICE (self), NULL, error);
@@ -1306,10 +774,11 @@ fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
}
fpi_device_get_enroll_data (FP_DEVICE (self), &print);
- g_object_get (G_OBJECT (print), "fpi-data", &data, NULL);
- if (!data)
+
+ 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");
+ g_error ("Inconsistent state; the print must have the enrolled ID attached at this point");
return;
}
@@ -1365,68 +834,72 @@ fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
GBytes *mac,
GError *error)
{
- g_autoptr(GBytes) mac_verify = NULL;
- g_autoptr(GBytes) host_random = NULL;
FpSdcpDevicePrivate *priv = fp_sdcp_device_get_instance_private (self);
- GError *err = NULL;
+ g_autoptr(GBytes) application_secret = NULL;
FpPrint *identified_print;
- GVariant *id_var;
- GVariant *data;
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;
}
- if (!id || !mac || g_bytes_get_size (id) != 32 || g_bytes_get_size (mac) != 32)
+ /* 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)
{
- fpi_device_action_error (FP_DEVICE (self),
- fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
- "Driver returned incorrect ID/MAC for identify operation"));
+ 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;
}
- host_random = g_bytes_new (priv->host_random, sizeof (priv->host_random));
- mac_verify = fpi_sdcp_mac (self, "identify", host_random, id, &err);
- if (!mac_verify)
- {
- fpi_device_action_error (FP_DEVICE (self),
- err);
- return;
- }
+ fpi_sdcp_device_get_application_secret (self, &application_secret);
- if (!g_bytes_equal (mac, mac_verify))
+ if (!fpi_sdcp_verify_identify (application_secret, priv->identify_nonce, id, mac, &error))
{
+ g_clear_pointer (&priv->identify_nonce, g_bytes_unref);
+
+ /* clear potentially non-functioning application_secret */
+ fpi_sdcp_device_unset_application_secret (self);
+
fpi_device_action_error (FP_DEVICE (self),
fpi_device_error_new_msg (FP_DEVICE_ERROR_UNTRUSTED,
- "Reported match from the device cannot be trusted!"));
+ "SDCP AuthorizedIdentity verification "
+ "failed: %s",
+ error->message));
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);
- fpi_print_set_device_stored (identified_print, TRUE);
-
- 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);
/* Set to true once committed */
fpi_print_set_device_stored (identified_print, FALSE);
/* Attach the ID to the print */
- g_object_set (identified_print, "fpi-data", data, NULL);
+ fpi_sdcp_device_set_print_id (identified_print, id);
/* The surrounding API expects a match/no-match against a given set. */
@@ -1467,3 +940,71 @@ fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
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);
+}
diff --git a/libfprint/fpi-sdcp-device.h b/libfprint/fpi-sdcp-device.h
index d5a4fcf7..7de1d2a0 100644
--- a/libfprint/fpi-sdcp-device.h
+++ b/libfprint/fpi-sdcp-device.h
@@ -1,6 +1,7 @@
/*
* FpSdcpDevice - A base class for SDCP enabled devices
* Copyright (C) 2020 Benjamin Berg
+ * Copyright (C) 2025 Joshua Grisham
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,18 +20,29 @@
#pragma once
-#include
#include "fpi-device.h"
+
#include "fp-sdcp-device.h"
+#define SDCP_PUBLIC_KEY_SIZE 65
+#define SDCP_APPLICATION_SECRET_SIZE 32
+#define SDCP_RANDOM_SIZE 32
+#define SDCP_MAC_SIZE 32
+#define SDCP_NONCE_SIZE 32
+#define SDCP_ENROLLMENT_ID_SIZE 32
+#define SDCP_SIGNATURE_SIZE 64
+
/**
* FpiSdcpClaim:
- * @cert_m: The per-model ECDSA certificate (x509 ASN.1 DER encoded)
- * @pk_d: The device public key (65 bytes)
- * @pk_f: The firmware public key (65 bytes)
- * @h_f: The firmware hash
- * @s_m: Signature over @pk_d using the per-model private key (64 bytes)
- * @s_d: Signature over h_f and pk_f using the device private key (64 bytes)
+ * @model_certificate: Microsoft-issued per-model certificate encoded in x509
+ * ASN.1 DER format (`cert_m`)
+ * @device_public_key: The per-device ECDSA public key (`pk_d`)
+ * @firmware_public_key: The ephemeral public key generated by the device
+ * firmware (`pk_f`)
+ * @firmware_hash: Hash of the firmware and firmware public key (`h_f`)
+ * @model_signature: Device public key signed by the model key (`s_m`)
+ * @device_signature: Firmware hash and public key signed by the device private
+ * key (`s_d`)
*
* Structure to hold the claim as produced by the device during a secure
* connect. See the SDCP specification for more details.
@@ -41,12 +53,12 @@
struct _FpiSdcpClaim
{
/*< public >*/
- GBytes *cert_m;
- GBytes *pk_d;
- GBytes *pk_f;
- GBytes *h_f;
- GBytes *s_m;
- GBytes *s_d;
+ GBytes *model_certificate; /* cert_m */
+ GBytes *device_public_key; /* pk_d */
+ GBytes *firmware_public_key; /* pk_f */
+ GBytes *firmware_hash; /* h_f */
+ GBytes *model_signature; /* s_m */
+ GBytes *device_signature; /* s_d */
};
typedef struct _FpiSdcpClaim FpiSdcpClaim;
@@ -60,30 +72,48 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSdcpClaim, fpi_sdcp_claim_free)
/**
* FpSdcpDeviceClass:
- * @connect: Establish SDCP connection. Similar to open in #FpDeviceClass
- * but called connect to mirror the SDCP specification.
+ * @ignore_device_certificate: Set to %TRUE to skip validating the device's
+ * #FpiSdcpClaim.model_certificate against the SDCP truststore.
+ * @ignore_device_signatures: Set to %TRUE to skip verifying the device's
+ * #FpiSdcpClaim.model_signature and #FpiSdcpClaim.device_signature.
+ * @open: Open the device. Similar to #FpDeviceClass.open except that
+ * completion with fpi_sdcp_device_open_complete() will also take care of
+ * executing @connect and @reconnect as necessary.
+ * @connect: Establish SDCP connection.
* @reconnect: Perform a faster reconnect. Drivers do not need to provide this
* function. If reconnect fails, then a normal connect will be tried.
- * @enroll_begin: Start the enrollment procedure. In the absence of an error,
- * the driver must call fpi_sdcp_device_enroll_set_nonce() at any point. It
- * also must report enrollment progress using fpi_device_enroll_progress().
- * It should also store available metadata about the print in device memory.
- * The operation is completed with fpi_sdcp_device_enroll_ready().
- * @enroll_commit: Complete the enrollment procedure. This commits the newly
- * enrolled print to the device memory. Will only be called if enroll_begin
- * succeeded. The passed id may be %NULL, in that case the driver must
- * abort the enrollment process. id is owned by the base class and remains
- * valid throughout the operation.
+ * @list: List prints stored on the device. The driver must create a #GPtrArray
+ * of #GBytes with each enrollment ID stored on the device and use it to call
+ * fpi_sdcp_device_list_complete() in order to complete the operation.
+ * @enroll: Start the enrollment procedure and capture all samples. The driver
+ * must report enrollment progress using fpi_device_enroll_progress(). It
+ * should also store available metadata about the print in device memory. The
+ * driver must call fpi_sdcp_device_enroll_commit() when all enrollment stages
+ * are complete and the print is ready to be commited to the device.
+ * @enroll_commit: Commit the newly-enrolled print to the device memory using
+ * the passed id. id may be %NULL, in which case the driver must abort the
+ * enrollment process. id is owned by the base class and remains valid
+ * throughout the operation. On completion, the driver must call
+ * fpi_sdcp_device_enroll_commit_complete().
* @identify: Start identification process. On completion, the driver must call
* fpi_sdcp_device_identify_complete(). To request the user to retry the
* fpi_sdcp_device_identify_retry() function is used.
*
- *
* These are the main entry points for drivers implementing SDCP.
*
* Drivers *must* eventually call the corresponding function to finish the
* operation.
*
+ * The following #FpDeviceClass entry points are also compatible and can be set
+ * on the #FpDeviceClass if supported for a given device:
+ * - #FpDeviceClass.probe
+ * - #FpDeviceClass.close
+ * - #FpDeviceClass.delete
+ * - #FpDeviceClass.clear_storage
+ * - #FpDeviceClass.cancel
+ * - #FpDeviceClass.suspend
+ * - #FpDeviceClass.resume
+ *
* XXX: Is the use of fpi_device_action_error() acceptable?
*
* Drivers *must* also handle cancellation properly for any long running
@@ -97,46 +127,59 @@ struct _FpSdcpDeviceClass
{
FpDeviceClass parent_class;
- void (*connect) (FpSdcpDevice *dev);
- void (*reconnect) (FpSdcpDevice *dev);
- void (*close) (FpSdcpDevice *dev);
- void (*enroll_begin) (FpSdcpDevice *dev);
- void (*enroll_commit) (FpSdcpDevice *dev,
+ gboolean ignore_device_certificate;
+ gboolean ignore_device_signatures;
+
+ void (*open) (FpSdcpDevice *sdcp_device);
+ void (*connect) (FpSdcpDevice *sdcp_device);
+ void (*reconnect) (FpSdcpDevice *sdcp_device);
+ void (*list) (FpSdcpDevice *sdcp_device);
+ void (*enroll) (FpSdcpDevice *sdcp_device);
+ void (*enroll_commit) (FpSdcpDevice *sdcp_device,
GBytes *id);
- void (*identify) (FpSdcpDevice *dev);
+ void (*identify) (FpSdcpDevice *sdcp_device);
};
-void fpi_sdcp_device_set_intermediat_cas (FpSdcpDevice *self,
- GBytes *ca_1,
- GBytes *ca_2);
+void fpi_sdcp_device_open_complete (FpSdcpDevice *self,
+ GError *error);
void fpi_sdcp_device_get_connect_data (FpSdcpDevice *self,
- GBytes **r_h,
- GBytes **pk_h);
+ GBytes **host_random,
+ GBytes **host_public_key);
void fpi_sdcp_device_connect_complete (FpSdcpDevice *self,
- GBytes *r_d,
+ GBytes *device_random,
FpiSdcpClaim *claim,
GBytes *mac,
GError *error);
void fpi_sdcp_device_get_reconnect_data (FpSdcpDevice *self,
- GBytes **r_h);
+ GBytes **reconnect_random);
void fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
GBytes *mac,
GError *error);
-void fpi_sdcp_device_enroll_set_nonce (FpSdcpDevice *self,
- GBytes *nonce);
-void fpi_sdcp_device_enroll_ready (FpSdcpDevice *self,
- GError *error);
+void fpi_sdcp_device_list_complete (FpSdcpDevice *self,
+ GPtrArray *ids,
+ GError *error);
+
+void fpi_sdcp_device_enroll_commit (FpSdcpDevice *self,
+ GBytes *nonce,
+ GError *error);
void fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_get_identify_data (FpSdcpDevice *self,
GBytes **nonce);
+void fpi_sdcp_device_set_identify_data (FpSdcpDevice *self,
+ GBytes *nonce);
void fpi_sdcp_device_identify_retry (FpSdcpDevice *self,
GError *error);
void fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
GBytes *id,
GBytes *mac,
GError *error);
+
+void fpi_sdcp_device_get_print_id (FpPrint *print,
+ GBytes **id);
+void fpi_sdcp_device_set_print_id (FpPrint *print,
+ GBytes *id);
diff --git a/libfprint/fpi-sdcp.c b/libfprint/fpi-sdcp.c
new file mode 100644
index 00000000..c3d8ef17
--- /dev/null
+++ b/libfprint/fpi-sdcp.c
@@ -0,0 +1,1053 @@
+/*
+ * Secure Device Connection Protocol (SDCP) support implementation
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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"
+#include "fpi-log.h"
+
+#include "fpi-sdcp.h"
+#include "sdcp/fpi-sdcp-truststore-resource.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define SDCP_PRIVATE_KEY_SIZE 32
+#define SDCP_KEY_AGREEMENT_SIZE 32
+#define SDCP_MASTER_SECRET_SIZE 32
+#define SDCP_APPLICATION_KEYS_SIZE 64
+
+/******************************************************************************/
+
+static void
+print_openssl_errors (void)
+{
+ gulong e = 0;
+ e = ERR_get_error ();
+ while (e != 0)
+ {
+ fp_dbg ("OpenSSL error: %s", ERR_error_string (e, NULL));
+ e = ERR_get_error ();
+ }
+}
+
+static void
+print_certificate (X509 *certificate)
+{
+ BIO *bio = BIO_new (BIO_s_mem ());
+ BUF_MEM *bio_mem = NULL;
+
+ X509_print (bio, certificate);
+ BIO_get_mem_ptr (bio, &bio_mem);
+ fp_dbg ("SDCP Device reported the following model certificate:\n%.*s\n",
+ (int) bio_mem->length, bio_mem->data);
+ BIO_free (bio);
+}
+
+/******************************************************************************/
+
+static gboolean
+fpi_sdcp_verify_signature (EVP_PKEY *pkey,
+ const gchar *label,
+ GBytes *data_a,
+ GBytes *data_b,
+ GBytes *signature,
+ GError **error)
+{
+ EVP_MD_CTX *mdctx = NULL;
+ EVP_PKEY_CTX *pctx = NULL;
+
+ mdctx = EVP_MD_CTX_create ();
+ pctx = EVP_PKEY_CTX_new(pkey, NULL);
+ EVP_MD_CTX_set_pkey_ctx(mdctx, pctx);
+
+ if (!EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256 (), NULL, pkey))
+ goto out_error;
+
+ if (label)
+ if (!EVP_DigestVerifyUpdate (mdctx, label, strlen (label)))
+ goto out_error;
+
+ if (data_a)
+ if (!EVP_DigestVerifyUpdate (mdctx,
+ g_bytes_get_data (data_a, NULL),
+ g_bytes_get_size (data_a)))
+ goto out_error;
+
+ if (data_b)
+ if (!EVP_DigestVerifyUpdate (mdctx,
+ g_bytes_get_data (data_b, NULL),
+ g_bytes_get_size (data_b)))
+ goto out_error;
+
+ if (!EVP_DigestVerifyUpdate (mdctx,
+ g_bytes_get_data (signature, NULL),
+ g_bytes_get_size (signature)))
+ goto out_error;
+
+ EVP_PKEY_CTX_free (pctx);
+ EVP_MD_CTX_free (mdctx);
+
+ return TRUE;
+
+out_error:
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error verifying signature for label '%s'",
+ label));
+ print_openssl_errors ();
+ g_clear_pointer (&pctx, EVP_PKEY_CTX_free);
+ g_clear_pointer (&mdctx, EVP_MD_CTX_free);
+ return FALSE;
+}
+
+static X509_STORE *truststore = NULL;
+
+static X509_STORE *
+fpi_sdcp_get_truststore (GError **error)
+{
+ g_autoptr(GResource) truststore_resource = NULL;
+ const gchar* truststore_resource_path = "/org/freedesktop/fprint/sdcp/truststore/";
+ char **trustcert_names = NULL;
+ gchar *trustcert_path = NULL;
+ GBytes *trustcert_gb = NULL;
+ const gchar *trustcert_ptr;
+ gsize trustcert_len = 0;
+ BIO *bio = NULL;
+ X509 *trustcert = NULL;
+
+ if (truststore)
+ return truststore;
+
+ truststore = X509_STORE_new ();
+ if (!truststore)
+ {
+ fp_dbg ("Failed initializing SDCP X509 certificate store");
+ goto out_error;
+ }
+
+ fpi_sdcp_truststore_register_resource ();
+ truststore_resource = fpi_sdcp_truststore_get_resource ();
+ trustcert_names = g_resources_enumerate_children (truststore_resource_path,
+ G_RESOURCE_LOOKUP_FLAGS_NONE, error);
+ if (*error)
+ {
+ fp_dbg ("Error loading SDCP truststore certificates: %s", (*error)->message);
+ goto out;
+ }
+ for (int i = 0; trustcert_names[i]; i++)
+ {
+ fp_dbg ("Adding certificate to SDCP truststore: %s", trustcert_names[i]);
+ trustcert_path = g_strconcat (truststore_resource_path, trustcert_names[i], NULL);
+ trustcert_gb = g_resource_lookup_data (truststore_resource, trustcert_path, 0, error);
+ if (*error)
+ {
+ fp_dbg ("Error loading SDCP truststore certificate '%s': %s",
+ trustcert_names[i], (*error)->message);
+ goto out;
+ }
+ g_free (trustcert_path);
+
+ trustcert_ptr = g_bytes_get_data (trustcert_gb, &trustcert_len);
+ //fp_dbg ("%s:\n%s", trustcert_names[i], trustcert_ptr);
+
+ bio = BIO_new (BIO_s_mem ());
+ if (BIO_write (bio, trustcert_ptr, trustcert_len) != trustcert_len)
+ {
+ fp_dbg ("Failed reading '%s' to buffer", trustcert_names[i]);
+ goto out_error;
+ }
+ g_bytes_unref (trustcert_gb);
+ trustcert = PEM_read_bio_X509 (bio, NULL, NULL, NULL);
+ //print_certificate (trustcert);
+ if (!X509_STORE_add_cert (truststore, trustcert))
+ {
+ fp_dbg ("Failed adding '%s' to X509 store", trustcert_names[i]);
+ goto out_error;
+ }
+ BIO_free (bio);
+ X509_free (trustcert);
+ }
+
+ g_strfreev (trustcert_names);
+ g_resource_unref (truststore_resource);
+
+ return truststore;
+
+out_error:
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error setting up certificate verification"));
+ print_openssl_errors ();
+ goto out;
+out:
+ g_clear_pointer (&trustcert_names, g_strfreev);
+ g_clear_pointer (&trustcert, X509_free);
+ return NULL;
+}
+
+static gboolean
+fpi_sdcp_verify_certificate (X509 *certificate,
+ GError **error)
+{
+ X509_STORE *sdcp_truststore = NULL;
+ X509_VERIFY_PARAM *param = NULL;
+ X509_STORE_CTX *ctx = NULL;
+
+ sdcp_truststore = fpi_sdcp_get_truststore (error);
+ if (*error)
+ goto out_error;
+
+ param = X509_VERIFY_PARAM_new ();
+ if (!param)
+ goto out_error;
+
+ /*
+ * Most model certificates expire after one year and are not renewed via a
+ * vendor firmware update, so we should ignore the validity time check.
+ */
+ if (!X509_VERIFY_PARAM_set_flags (param, X509_V_FLAG_NO_CHECK_TIME))
+ goto out_error;
+
+ /* Do not allow broken certificates */
+ if (!X509_VERIFY_PARAM_set_flags (param, X509_V_FLAG_X509_STRICT))
+ goto out_error;
+
+ /* set X509_V_FLAG_PARTIAL_CHAIN if we want to skip adding all root and intermediate certs */
+ /*
+ if (!X509_VERIFY_PARAM_set_flags (param, X509_V_FLAG_PARTIAL_CHAIN))
+ goto out_error;
+ */
+
+ if (!X509_STORE_set1_param (sdcp_truststore, param))
+ goto out_error;
+
+ X509_VERIFY_PARAM_free (param);
+ ctx = X509_STORE_CTX_new ();
+
+ if(!X509_STORE_CTX_init (ctx, sdcp_truststore, certificate, NULL))
+ goto out_verify_error;
+
+ if (!X509_verify_cert (ctx))
+ goto out_verify_error;
+
+ X509_STORE_CTX_free (ctx);
+
+ return TRUE;
+
+out_error:
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error setting up certificate verification"));
+ print_openssl_errors ();
+ goto out;
+out_verify_error:
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error verifying model certificate"));
+ fp_dbg ("OpenSSL verification error: %s",
+ X509_verify_cert_error_string (X509_STORE_CTX_get_error (ctx)));
+out:
+ g_clear_pointer (¶m, X509_VERIFY_PARAM_free);
+ g_clear_pointer (&ctx, X509_STORE_CTX_free);
+ return FALSE;
+}
+
+static GBytes *
+fpi_sdcp_kdf (GBytes *key,
+ const gchar *label,
+ GBytes *context_a,
+ GBytes *context_b,
+ gsize length,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ guint8 *secret = NULL;
+ EVP_KDF *kdf;
+ EVP_KDF_CTX *kdf_ctx;
+ OSSL_PARAM params[8];
+ int i = 0;
+
+ g_assert (key);
+ g_assert (strlen (label) > 0);
+ g_assert (length > 0);
+
+ params[i++] = OSSL_PARAM_construct_utf8_string (OSSL_KDF_PARAM_MODE, (gchar *) "counter", 0);
+ params[i++] = OSSL_PARAM_construct_utf8_string (OSSL_KDF_PARAM_MAC, (gchar *) "HMAC", 0);
+ params[i++] = OSSL_PARAM_construct_utf8_string (OSSL_KDF_PARAM_DIGEST, (gchar *) "SHA2-256", 0);
+
+ params[i++] = OSSL_PARAM_construct_octet_string (OSSL_KDF_PARAM_KEY,
+ (void *) (g_bytes_get_data (key, NULL)),
+ g_bytes_get_size (key));
+
+ params[i++] = OSSL_PARAM_construct_octet_string (OSSL_KDF_PARAM_SALT,
+ (void *) label,
+ strlen (label));
+
+ if (context_a)
+ params[i++] = OSSL_PARAM_construct_octet_string (OSSL_KDF_PARAM_INFO,
+ (void *) (g_bytes_get_data (context_a, NULL)),
+ g_bytes_get_size (context_a));
+
+ if (context_b)
+ params[i++] = OSSL_PARAM_construct_octet_string (OSSL_KDF_PARAM_INFO,
+ (void *) (g_bytes_get_data (context_b, NULL)),
+ g_bytes_get_size (context_b));
+
+ params[i++] = OSSL_PARAM_construct_end ();
+
+ kdf = EVP_KDF_fetch (NULL, "KBKDF", NULL);
+ kdf_ctx = EVP_KDF_CTX_new (kdf);
+
+ secret = g_malloc0 (length);
+ if (!EVP_KDF_derive (kdf_ctx, secret, length, params))
+ {
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error during key derivation "
+ "for label '%s'", label));
+ g_free (secret);
+ EVP_KDF_CTX_free (kdf_ctx);
+ EVP_KDF_free (kdf);
+ print_openssl_errors ();
+ return NULL;
+ }
+
+ EVP_KDF_CTX_free (kdf_ctx);
+ EVP_KDF_free (kdf);
+
+ res = g_bytes_new_take (secret, length);
+
+ return g_steal_pointer (&res);
+}
+
+static GBytes *
+fpi_sdcp_mac (GBytes *application_secret,
+ const gchar *label,
+ GBytes *data_a,
+ GBytes *data_b,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+
+ EVP_MAC *hmac;
+ EVP_MAC_CTX *hmac_ctx;
+ guint8 *mac = NULL;
+ gsize mac_len = 0;
+
+ OSSL_PARAM params[] = {
+ OSSL_PARAM_construct_utf8_string (OSSL_MAC_PARAM_DIGEST, (gchar *) "SHA256", 0),
+ OSSL_PARAM_construct_end (),
+ };
+
+ hmac = EVP_MAC_fetch (NULL, "hmac", NULL);
+ hmac_ctx = EVP_MAC_CTX_new (hmac);
+
+ if (!EVP_MAC_init (hmac_ctx,
+ g_bytes_get_data (application_secret, NULL),
+ g_bytes_get_size (application_secret),
+ params))
+ goto out_error;
+
+ if (label)
+ if (!EVP_MAC_update (hmac_ctx, (guchar *) label, strlen (label) + 1)) /* +1 due to unsigned */
+ goto out_error;
+
+ if (data_a)
+ if (!EVP_MAC_update (hmac_ctx, g_bytes_get_data (data_a, NULL), g_bytes_get_size (data_a)))
+ goto out_error;
+
+ if (data_b)
+ if (!EVP_MAC_update (hmac_ctx, g_bytes_get_data (data_b, NULL), g_bytes_get_size (data_b)))
+ goto out_error;
+
+ mac = g_malloc0 (SDCP_MAC_SIZE);
+ if (!EVP_MAC_final (hmac_ctx, mac, &mac_len, SDCP_MAC_SIZE))
+ goto out_error;
+
+ if (mac_len != SDCP_MAC_SIZE)
+ goto out_error;
+
+ EVP_MAC_CTX_free (hmac_ctx);
+ EVP_MAC_free (hmac);
+
+ res = g_bytes_new_take (mac, mac_len);
+ return g_steal_pointer (&res);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error generating MAC for label '%s'",
+ label));
+ print_openssl_errors ();
+ g_clear_pointer (&mac, g_free);
+ g_clear_pointer (&hmac_ctx, EVP_MAC_CTX_free);
+ g_clear_pointer (&hmac, EVP_MAC_free);
+ return NULL;
+}
+
+static GBytes *
+fpi_sdcp_get_private_key (EVP_PKEY *pkey,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ BIGNUM *priv_bn = NULL;
+ guint8 *priv = NULL;
+ gsize priv_len = 0;
+
+ g_assert_nonnull (pkey);
+
+ if (!EVP_PKEY_get_bn_param (pkey, OSSL_PKEY_PARAM_PRIV_KEY, &priv_bn))
+ goto out_error;
+
+ priv_len = BN_num_bytes (priv_bn);
+ if (priv_len != SDCP_PRIVATE_KEY_SIZE)
+ goto out_error;
+
+ priv = g_malloc0 (priv_len);
+ if (!BN_bn2bin (priv_bn, priv))
+ goto out_error;
+
+ BN_clear_free (priv_bn);
+
+ res = g_bytes_new_take (priv, priv_len);
+
+ return g_steal_pointer (&res);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error getting private key bytes"));
+ print_openssl_errors ();
+ g_clear_pointer (&priv, g_free);
+ g_clear_pointer (&priv_bn, BN_clear_free);
+ return NULL;
+}
+
+static GBytes *
+fpi_sdcp_get_public_key (EVP_PKEY *pkey,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ guint8 *pub = NULL;
+ gsize pub_len = 0;
+
+ g_assert_nonnull (pkey);
+
+ pub = g_malloc0 (SDCP_PUBLIC_KEY_SIZE);
+ if (!EVP_PKEY_get_octet_string_param (pkey, OSSL_PKEY_PARAM_PUB_KEY,
+ pub, SDCP_PUBLIC_KEY_SIZE, &pub_len))
+ goto out_error;
+
+ if (pub_len != SDCP_PUBLIC_KEY_SIZE)
+ goto out_error;
+
+ res = g_bytes_new_take (pub, pub_len);
+
+ return g_steal_pointer (&res);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error getting public key bytes"));
+ print_openssl_errors ();
+ g_clear_pointer (&pub, g_free);
+ return NULL;
+}
+
+static EVP_PKEY *
+fpi_sdcp_get_private_pkey (GBytes *private_key,
+ GError **error)
+{
+ BIGNUM *private_key_bn = NULL;
+ EC_GROUP *group = NULL;
+ EC_POINT *public_key_point = NULL;
+ g_autofree guint8 *public_key_buf = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *key = NULL;
+ OSSL_PARAM_BLD *param_bld = NULL;
+ OSSL_PARAM *params = NULL;
+
+ if (g_bytes_get_size (private_key) != SDCP_PRIVATE_KEY_SIZE)
+ goto out_error;
+
+ /* import private key as a BIGNUM */
+ private_key_bn = BN_bin2bn (g_bytes_get_data (private_key, NULL),
+ g_bytes_get_size (private_key),
+ NULL);
+
+ /* set up public key based on imported private_key_bn */
+ group = EC_GROUP_new_by_curve_name (NID_X9_62_prime256v1);
+ public_key_point = EC_POINT_new (group);
+ if (!EC_POINT_mul (group, public_key_point, private_key_bn, NULL, NULL, NULL))
+ goto out_error;
+ public_key_buf = g_malloc0 (SDCP_PUBLIC_KEY_SIZE);
+ if (!EC_POINT_point2oct (group, public_key_point, POINT_CONVERSION_UNCOMPRESSED,
+ public_key_buf, SDCP_PUBLIC_KEY_SIZE, NULL))
+ goto out_error;
+ EC_POINT_free (public_key_point);
+ EC_GROUP_free (group);
+
+ /* set up parameters */
+
+ param_bld = OSSL_PARAM_BLD_new ();
+ if (!OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME,
+ SN_X9_62_prime256v1, sizeof (SN_X9_62_prime256v1)))
+ goto out_error;
+ if (!OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY,
+ public_key_buf, SDCP_PUBLIC_KEY_SIZE))
+ goto out_error;
+ if (!OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, private_key_bn))
+ goto out_error;
+
+ params = OSSL_PARAM_BLD_to_param (param_bld);
+
+ /* import pkey from params */
+ ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL);
+ if (!EVP_PKEY_fromdata_init (ctx))
+ goto out_error;
+ EVP_PKEY_fromdata (ctx, &key, EVP_PKEY_KEYPAIR, params);
+
+ EVP_PKEY_CTX_free (ctx);
+ OSSL_PARAM_free (params);
+ OSSL_PARAM_BLD_free (param_bld);
+ BN_clear_free (private_key_bn);
+
+ return g_steal_pointer (&key);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error getting private key"));
+ print_openssl_errors ();
+ g_clear_pointer (&key, EVP_PKEY_free);
+ g_clear_pointer (&ctx, EVP_PKEY_CTX_free);
+ g_clear_pointer (¶ms, OSSL_PARAM_free);
+ g_clear_pointer (¶m_bld, OSSL_PARAM_BLD_free);
+ g_clear_pointer (&public_key_point, EC_POINT_free);
+ g_clear_pointer (&group, EC_GROUP_free);
+ g_clear_pointer (&private_key_bn, BN_clear_free);
+ return NULL;
+}
+
+static EVP_PKEY *
+fpi_sdcp_get_public_pkey (GBytes *public_key,
+ GError **error)
+{
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *key = NULL;
+ OSSL_PARAM params[3];
+
+ params[0] = OSSL_PARAM_construct_utf8_string (OSSL_PKEY_PARAM_GROUP_NAME,
+ (char *) SN_X9_62_prime256v1,
+ sizeof (SN_X9_62_prime256v1));
+ params[1] = OSSL_PARAM_construct_octet_string (OSSL_PKEY_PARAM_PUB_KEY,
+ (char *) g_bytes_get_data (public_key, NULL),
+ g_bytes_get_size (public_key));
+ params[2] = OSSL_PARAM_construct_end ();
+
+ ctx = EVP_PKEY_CTX_new_from_name (NULL, "EC", NULL);
+ if (!ctx)
+ goto out_error;
+
+ if (!EVP_PKEY_fromdata_init (ctx))
+ goto out_error;
+
+ if (!EVP_PKEY_fromdata(ctx, &key, EVP_PKEY_PUBLIC_KEY, params))
+ goto out_error;
+
+ EVP_PKEY_CTX_free (ctx);
+
+ return g_steal_pointer (&key);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error getting public key"));
+ print_openssl_errors ();
+ g_clear_pointer (&key, EVP_PKEY_free);
+ g_clear_pointer (&ctx, EVP_PKEY_CTX_free);
+ return NULL;
+}
+
+static GBytes *
+fpi_sdcp_hash_claim (FpiSdcpClaim *claim,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ EVP_MD_CTX *sh256_ctx;
+ EVP_MD *sha256;
+ guint8 *hash = NULL;
+ guint hash_len = 0;
+
+ sh256_ctx = EVP_MD_CTX_create ();
+ sha256 = EVP_MD_fetch (NULL, "SHA256", NULL);
+ if (!EVP_DigestInit_ex (sh256_ctx, sha256, NULL))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->model_certificate, NULL),
+ g_bytes_get_size (claim->model_certificate)))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->device_public_key, NULL),
+ g_bytes_get_size (claim->device_public_key)))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->firmware_public_key, NULL),
+ g_bytes_get_size (claim->firmware_public_key)))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->firmware_hash, NULL),
+ g_bytes_get_size (claim->firmware_hash)))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->model_signature, NULL),
+ g_bytes_get_size (claim->model_signature)))
+ goto out_error;
+
+ if (!EVP_DigestUpdate (sh256_ctx,
+ g_bytes_get_data (claim->device_signature, NULL),
+ g_bytes_get_size (claim->device_signature)))
+ goto out_error;
+
+ hash = g_malloc0 (EVP_MAX_MD_SIZE);
+ if (!EVP_DigestFinal_ex (sh256_ctx, hash, &hash_len))
+ goto out_error;
+
+ EVP_MD_CTX_free (sh256_ctx);
+ EVP_MD_free (sha256);
+
+ res = g_bytes_new_take (hash, hash_len);
+ return g_steal_pointer (&res);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error hashing the SDCP claim"));
+ print_openssl_errors ();
+ g_clear_pointer (&sh256_ctx, EVP_MD_CTX_free);
+ g_clear_pointer (&sha256, EVP_MD_free);
+ g_clear_pointer (&hash, g_free);
+ return NULL;
+}
+
+static GBytes *
+fpi_sdcp_key_agreement (GBytes *host_private_key,
+ GBytes *firmware_public_key,
+ GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_PKEY *host_pkey = NULL;
+ EVP_PKEY *device_pkey = NULL;
+ guint8 *key_agreement = NULL;
+ gsize key_agreement_len = 0;
+
+ host_pkey = fpi_sdcp_get_private_pkey (host_private_key, error);
+ if (*error)
+ goto out;
+
+ device_pkey = fpi_sdcp_get_public_pkey (firmware_public_key, error);
+ if (*error)
+ goto out;
+
+ ctx = EVP_PKEY_CTX_new_from_pkey (NULL, host_pkey, NULL);
+
+ if (!EVP_PKEY_derive_init (ctx))
+ goto out_error;
+
+ if (!EVP_PKEY_derive_set_peer (ctx, device_pkey))
+ goto out_error;
+
+ /* Get the size by passing NULL as the buffer */
+ if (!EVP_PKEY_derive (ctx, NULL, &key_agreement_len))
+ goto out_error;
+
+ if (key_agreement_len != SDCP_KEY_AGREEMENT_SIZE)
+ goto out_error;
+
+ /* Then get the derived shared secret using the fetched size */
+ key_agreement = g_malloc0 (key_agreement_len);
+ if (!EVP_PKEY_derive (ctx, key_agreement, &key_agreement_len))
+ goto out_error;
+
+ EVP_PKEY_CTX_free (ctx);
+ EVP_PKEY_free (device_pkey);
+ EVP_PKEY_free (host_pkey);
+
+ res = g_bytes_new_take (key_agreement, key_agreement_len);
+
+ return g_steal_pointer (&res);
+
+out_error:
+ g_propagate_error (error, fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error deriving key agreement"));
+ print_openssl_errors ();
+out:
+ g_clear_pointer (&key_agreement, g_free);
+ g_clear_pointer (&ctx, EVP_PKEY_CTX_free);
+ g_clear_pointer (&host_pkey, EVP_PKEY_free);
+ g_clear_pointer (&device_pkey, EVP_PKEY_free);
+ return NULL;
+}
+
+/******************************************************************************/
+
+void
+fpi_sdcp_generate_host_key (GBytes **private_key,
+ GBytes **public_key,
+ GError **error)
+{
+ EVP_PKEY *key = EVP_EC_gen ("P-256");
+
+ if (*private_key)
+ g_bytes_unref (*private_key);
+ *private_key = fpi_sdcp_get_private_key (key, error);
+ if (*error)
+ goto out;
+
+ if (*public_key)
+ g_bytes_unref (*public_key);
+ *public_key = fpi_sdcp_get_public_key (key, error);
+ if (*error)
+ goto out;
+
+ EVP_PKEY_free (key);
+
+ return;
+
+out:
+ print_openssl_errors ();
+ g_clear_pointer (&key, EVP_PKEY_free);
+ return;
+}
+
+GBytes *
+fpi_sdcp_generate_random (GError **error)
+{
+ g_autoptr(GBytes) res = NULL;
+ guint8 *random = g_malloc0 (SDCP_RANDOM_SIZE);
+
+ if (!RAND_bytes (random, SDCP_RANDOM_SIZE))
+ {
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "OpenSSL error generating random"));
+ print_openssl_errors ();
+ g_free (random);
+ return NULL;
+ }
+
+ res = g_bytes_new_take (random, SDCP_RANDOM_SIZE);
+
+ return g_steal_pointer (&res);
+}
+
+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)
+{
+ g_autoptr(GBytes) claim_hash = NULL;
+ g_autoptr(GBytes) claim_mac = NULL;
+ g_autoptr(GBytes) key_agreement = NULL;
+ g_autoptr(GBytes) master_secret = NULL;
+ g_autoptr(GBytes) application_keys = NULL;
+ g_autoptr(GBytes) out_app_secret = NULL;
+ guint8 *app_secret_buf = NULL;
+
+ const guint8 *cert_ptr;
+ gsize cert_len = 0;
+ X509 *cert = NULL;
+ EVP_PKEY *cert_public_pkey = NULL;
+ EVP_PKEY *device_public_pkey = NULL;
+
+ fp_dbg ("SDCP Verify ConnectResponse");
+
+ /* SDCP Connect: 5.i. Perform key agreement */
+
+ key_agreement = fpi_sdcp_key_agreement (host_private_key, claim->firmware_public_key, error);
+ if (*error)
+ goto out_error;
+
+ fp_dbg ("key_agreement:");
+ fp_dbg_hex_dump_gbytes (key_agreement);
+
+ /* SDCP Connect: 5.ii. Derive master secret */
+
+ master_secret = fpi_sdcp_kdf (key_agreement, "master secret", host_random, device_random,
+ SDCP_MASTER_SECRET_SIZE, error);
+ if (*error)
+ goto out_error;
+
+ fp_dbg ("master_secret:");
+ fp_dbg_hex_dump_gbytes (master_secret);
+
+ /* SDCP Connect: 5.iii. Derive MAC secret and symetric key */
+
+ application_keys = fpi_sdcp_kdf (master_secret, "application keys", NULL, NULL,
+ SDCP_APPLICATION_KEYS_SIZE, error);
+ if (*error)
+ goto out_error;
+
+ fp_dbg ("application_keys:");
+ fp_dbg_hex_dump_gbytes (application_keys);
+
+ /* slice first half of application_keys as application_secret */
+ g_assert (g_bytes_get_size (application_keys) >= SDCP_APPLICATION_SECRET_SIZE);
+ app_secret_buf = g_malloc0 (SDCP_APPLICATION_SECRET_SIZE);
+ memcpy (app_secret_buf,
+ g_bytes_get_data (application_keys, NULL),
+ SDCP_APPLICATION_SECRET_SIZE);
+
+ out_app_secret = g_bytes_new_take (app_secret_buf, SDCP_APPLICATION_SECRET_SIZE);
+
+ fp_dbg ("application_secret:");
+ fp_dbg_hex_dump_gbytes (out_app_secret);
+
+ /* SDCP Connect: 5.iv. Validate the MAC over H(claim) */
+
+ claim_hash = fpi_sdcp_hash_claim (claim, error);
+ if (*error)
+ goto out_error;
+
+ claim_mac = fpi_sdcp_mac (out_app_secret, "connect", claim_hash, NULL, error);
+ if (*error)
+ goto out_error;
+
+ fp_dbg ("Device MAC:");
+ fp_dbg_hex_dump_gbytes (mac);
+
+ fp_dbg ("Host MAC:");
+ fp_dbg_hex_dump_gbytes (claim_mac);
+
+ if (g_bytes_equal (mac, claim_mac))
+ {
+ fp_dbg ("SDCP ConnectResponse claim validated successfully");
+ }
+ else
+ {
+ fp_warn ("SDCP ConnectResponse claim validation failed");
+ goto out;
+ }
+
+
+ /* SDCP Connect: 5.v. Unpack the claim (SKIP; already done) */
+ /* SDCP Connect: 5.vi. Verify the claim */
+
+ if (validate_certificate)
+ {
+ /* Validate the certificate */
+
+ cert_ptr = g_bytes_get_data (claim->model_certificate, &cert_len);
+ cert = d2i_X509 (NULL, &cert_ptr, cert_len);
+ if (!cert)
+ {
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "Error parsing model certificate"));
+ goto out;
+ }
+
+ print_certificate (cert);
+
+ if (fpi_sdcp_verify_certificate (cert, error))
+ {
+ fp_dbg ("SDCP model ceritifcate verified successfully");
+ }
+ else
+ {
+ fp_dbg ("SDCP model certificate verification failed");
+ goto out;
+ }
+
+ if (verify_signatures)
+ {
+ /* Get the certificate's public key */
+
+ cert_public_pkey = X509_get_pubkey (cert);
+ if (!cert_public_pkey)
+ {
+ g_propagate_error (error,
+ fpi_device_error_new_msg (FP_DEVICE_ERROR_GENERAL,
+ "Error getting public key from model certificate"));
+ goto out;
+ }
+
+ /* Verify(pk_m, H(pk_d), s_m) */
+
+ fp_dbg ("model_signature:");
+ fp_dbg_hex_dump_gbytes (claim->model_signature);
+
+ if (!fpi_sdcp_verify_signature (cert_public_pkey,
+ NULL,
+ claim->device_public_key,
+ NULL,
+ claim->model_signature,
+ error))
+ {
+ fp_warn ("SDCP model signature verification failed");
+ goto out_error;
+ }
+
+ fp_dbg ("SDCP model signature verified successfully");
+ EVP_PKEY_free (cert_public_pkey);
+
+ /* Verify(pk_d, H(C001||h_f||pk_f), s_d) */
+
+ fp_dbg ("device_public_key:");
+ fp_dbg_hex_dump_gbytes (claim->device_public_key);
+
+ fp_dbg ("firmware_hash:");
+ fp_dbg_hex_dump_gbytes (claim->firmware_hash);
+
+ fp_dbg ("firmware_public_key:");
+ fp_dbg_hex_dump_gbytes (claim->firmware_public_key);
+
+ device_public_pkey = fpi_sdcp_get_public_pkey (claim->device_public_key, error);
+ if (*error)
+ goto out_error;
+
+ if (!fpi_sdcp_verify_signature (device_public_pkey,
+ "C001",
+ claim->firmware_hash,
+ claim->firmware_public_key,
+ claim->model_signature,
+ error))
+ {
+ fp_warn ("SDCP device signature verification failed");
+ goto out_error;
+ }
+
+ fp_dbg ("SDCP device signature verified successfully");
+ EVP_PKEY_free (device_public_pkey);
+ }
+ X509_free (cert);
+ }
+
+ /* output application_secret */
+
+ if (*application_secret)
+ g_bytes_unref (*application_secret);
+
+ *application_secret = g_steal_pointer (&out_app_secret);
+
+ return TRUE;
+
+out_error:
+ print_openssl_errors ();
+out:
+ if (!out_app_secret)
+ g_clear_pointer (&app_secret_buf, g_free);
+ g_clear_pointer (&device_public_pkey, EVP_PKEY_free);
+ g_clear_pointer (&cert_public_pkey, EVP_PKEY_free);
+ g_clear_pointer (&cert, X509_free);
+ return FALSE;
+}
+
+gboolean
+fpi_sdcp_verify_reconnect (GBytes *application_secret,
+ GBytes *random,
+ GBytes *mac,
+ GError **error)
+{
+ g_autoptr(GBytes) host_mac = NULL;
+
+ fp_dbg ("SDCP Verify ReconnectResponse");
+
+ host_mac = fpi_sdcp_mac (application_secret, "reconnect", random, NULL, error);
+ if (*error)
+ return FALSE;
+
+ fp_dbg ("Device MAC:");
+ fp_dbg_hex_dump_gbytes (mac);
+
+ fp_dbg ("Host MAC:");
+ fp_dbg_hex_dump_gbytes (host_mac);
+
+ if (g_bytes_equal (mac, host_mac))
+ {
+ fp_dbg ("SDCP ReconnectResponse verified successfully");
+ return TRUE;
+ }
+ else
+ {
+ fp_warn ("SDCP ReconnectResponse verification failed");
+ return FALSE;
+ }
+}
+
+gboolean
+fpi_sdcp_verify_identify (GBytes *application_secret,
+ GBytes *nonce,
+ GBytes *id,
+ GBytes *mac,
+ GError **error)
+{
+ g_autoptr(GBytes) host_mac = NULL;
+
+ fp_dbg ("SDCP Verify AuthorizedIdentity");
+
+ host_mac = fpi_sdcp_mac (application_secret, "identify", nonce, id, error);
+ if (*error)
+ return FALSE;
+
+ fp_dbg ("Device MAC:");
+ fp_dbg_hex_dump_gbytes (mac);
+
+ fp_dbg ("Host MAC:");
+ fp_dbg_hex_dump_gbytes (host_mac);
+
+ if (g_bytes_equal (mac, host_mac))
+ {
+ fp_dbg ("SDCP AuthorizedIdentity verified successfully");
+ return TRUE;
+ }
+ else
+ {
+ fp_warn ("SDCP AuthorizedIdentity verification failed");
+ return FALSE;
+ }
+}
+
+GBytes *
+fpi_sdcp_generate_enrollment_id (GBytes *application_secret,
+ GBytes *nonce,
+ GError **error)
+{
+ g_autoptr(GBytes) id = NULL;
+
+ id = fpi_sdcp_mac (application_secret, "enroll", nonce, NULL, error);
+ if (*error)
+ return NULL;
+
+ fp_dbg ("Generated SDCP Enrollment ID:");
+ fp_dbg_hex_dump_gbytes (id);
+
+ return g_steal_pointer (&id);
+}
diff --git a/libfprint/fpi-sdcp.h b/libfprint/fpi-sdcp.h
new file mode 100644
index 00000000..a32dc05e
--- /dev/null
+++ b/libfprint/fpi-sdcp.h
@@ -0,0 +1,132 @@
+/*
+ * Secure Device Connection Protocol (SDCP) support implementation
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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);
diff --git a/libfprint/meson.build b/libfprint/meson.build
index f83dd71b..f132fe60 100644
--- a/libfprint/meson.build
+++ b/libfprint/meson.build
@@ -21,6 +21,7 @@ libfprint_private_sources = [
'fpi-device.c',
'fpi-image-device.c',
'fpi-image.c',
+ 'fpi-log.c',
'fpi-print.c',
'fpi-ssm.c',
'fpi-usb-transfer.c',
@@ -47,8 +48,8 @@ libfprint_private_headers = [
'fpi-log.h',
'fpi-minutiae.h',
'fpi-print.h',
- 'fpi-usb-transfer.h',
'fpi-ssm.h',
+ 'fpi-usb-transfer.h',
] + spi_headers
nbis_sources = [
@@ -143,8 +144,6 @@ driver_sources = {
[ 'drivers/virtual-device.c' ],
'virtual_device_storage' :
[ 'drivers/virtual-device-storage.c' ],
- 'virtual_sdcp' :
- [ 'drivers/virtual-sdcp.c' ],
'synaptics' :
[ 'drivers/synaptics/synaptics.c', 'drivers/synaptics/bmkt_message.c' ],
'goodixmoc' :
@@ -158,8 +157,6 @@ driver_sources = {
}
helper_sources = {
- 'sdcp' :
- [ 'fp-sdcp-device.c', 'fpi-sdcp-device.c' ],
'aeslib' :
[ 'drivers/aeslib.c' ],
'aesx660' :
@@ -168,6 +165,8 @@ helper_sources = {
[ 'drivers/aes3k.c' ],
'openssl' :
[ ],
+ 'sdcp' :
+ [ ],
'udev' :
[ ],
'virtual' :
@@ -183,6 +182,24 @@ foreach helper : driver_helpers
drivers_sources += helper_sources[helper]
endforeach
+subdir('sdcp')
+if 'sdcp' in driver_helpers
+ libfprint_sources += [
+ 'fp-sdcp-device.c',
+ ]
+ libfprint_private_sources += [
+ 'fpi-sdcp.c',
+ 'fpi-sdcp-device.c',
+ sdcp_truststore_resource_c,
+ ]
+ libfprint_public_headers += [
+ 'fp-sdcp-device.h',
+ ]
+ libfprint_private_headers += [
+ 'fpi-sdcp.h',
+ 'fpi-sdcp-device.h',
+ ]
+endif
fp_enums = gnome.mkenums_simple('fp-enums',
sources: libfprint_public_headers,
diff --git a/libfprint/sdcp/generate-gresource.py b/libfprint/sdcp/generate-gresource.py
new file mode 100644
index 00000000..8acb7149
--- /dev/null
+++ b/libfprint/sdcp/generate-gresource.py
@@ -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 ")
+ 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('\n')
+ f.write('\n')
+ f.write(f' \n')
+ for file in files:
+ f.write(f' {relative_folder}/{file}\n')
+ f.write(' \n')
+ f.write('\n')
diff --git a/libfprint/sdcp/meson.build b/libfprint/sdcp/meson.build
new file mode 100644
index 00000000..79920c82
--- /dev/null
+++ b/libfprint/sdcp/meson.build
@@ -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@']
+)
diff --git a/libfprint/sdcp/truststore/Microsoft ECC Devices Root Certificate Authority 2017.pem b/libfprint/sdcp/truststore/Microsoft ECC Devices Root Certificate Authority 2017.pem
new file mode 100644
index 00000000..50e05f3f
--- /dev/null
+++ b/libfprint/sdcp/truststore/Microsoft ECC Devices Root Certificate Authority 2017.pem
@@ -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-----
diff --git a/libfprint/sdcp/truststore/Windows Hello 2096ADCC CA 2021.pem b/libfprint/sdcp/truststore/Windows Hello 2096ADCC CA 2021.pem
new file mode 100644
index 00000000..72ec3efb
--- /dev/null
+++ b/libfprint/sdcp/truststore/Windows Hello 2096ADCC CA 2021.pem
@@ -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-----
diff --git a/libfprint/sdcp/truststore/Windows Hello Secure Devices PCA 2018.pem b/libfprint/sdcp/truststore/Windows Hello Secure Devices PCA 2018.pem
new file mode 100644
index 00000000..690ee90f
--- /dev/null
+++ b/libfprint/sdcp/truststore/Windows Hello Secure Devices PCA 2018.pem
@@ -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-----
diff --git a/meson.build b/meson.build
index 8008cdd5..a8846ae0 100644
--- a/meson.build
+++ b/meson.build
@@ -112,7 +112,6 @@ virtual_drivers = [
'virtual_image',
'virtual_device',
'virtual_device_storage',
- 'virtual_sdcp',
]
default_drivers = [
'upektc_img',
@@ -209,12 +208,12 @@ driver_helper_mapping = {
'aes2660' : [ 'aeslib', 'aesx660' ],
'aes3500' : [ 'aeslib', 'aes3k' ],
'aes4000' : [ 'aeslib', 'aes3k' ],
- 'uru4000' : [ 'openssl' ],
+ 'egismoc' : [ 'sdcp' ],
'elanspi' : [ 'udev' ],
+ 'uru4000' : [ 'openssl' ],
'virtual_image' : [ 'virtual' ],
'virtual_device' : [ 'virtual' ],
'virtual_device_storage' : [ 'virtual' ],
- 'virtual_sdcp' : [ 'sdcp' ],
}
driver_helpers = []
diff --git a/tests/egismoc/custom.pcapng b/tests/egismoc/custom.pcapng
index fcd8119e..c435cb84 100644
Binary files a/tests/egismoc/custom.pcapng and b/tests/egismoc/custom.pcapng differ
diff --git a/tests/egismoc/device b/tests/egismoc/device
index 6bd912a0..28544ba3 100644
--- a/tests/egismoc/device
+++ b/tests/egismoc/device
@@ -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
diff --git a/tests/meson.build b/tests/meson.build
index dd8da975..3c3af4e8 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -14,9 +14,6 @@ envs.prepend('LD_LIBRARY_PATH', meson.project_build_root() / 'libfprint')
# random numbers rather than proper ones)
envs.set('FP_DEVICE_EMULATION', '1')
-# Path to SDCP virtual device binary, only used for virtual-sdcp test
-envs.set('SDCP_VIRT_BINARY', get_option('sdcp_virt_binary'))
-
# Set a colon-separated list of native drivers we enable in tests
envs.set('FP_DRIVERS_ALLOWLIST', ':'.join([
'virtual_image',
@@ -55,9 +52,9 @@ drivers_tests = [
'nb1010',
'egis0570',
'egismoc',
- 'egismoc-05a1',
- 'egismoc-0586',
- 'egismoc-0587',
+# 'egismoc-05a1', # commented out until new capture with SDCP support can be provided
+# 'egismoc-0586', # commented out until new capture with SDCP support can be provided
+# 'egismoc-0587', # commented out until new capture with SDCP support can be provided
'fpcmoc',
'realtek',
'realtek-5816',
@@ -249,13 +246,10 @@ else
endforeach
endif
-test_utils = static_library('fprint-test-utils',
- sources: [
- 'test-utils.c',
- 'test-device-fake.c',
- ],
- dependencies: libfprint_private_dep,
- install: false)
+test_util_sources = [
+ 'test-utils.c',
+ 'test-device-fake.c',
+]
unit_tests = [
'fpi-device',
@@ -270,6 +264,22 @@ if 'virtual_image' in drivers
]
endif
+if 'sdcp' in driver_helpers
+ test_util_sources += [
+ 'test-sdcp-device-fake.c',
+ 'test-sdcp-utils.c',
+ ]
+ unit_tests += [
+ 'fp-sdcp-device',
+ 'fpi-sdcp',
+ ]
+endif
+
+test_utils = static_library('fprint-test-utils',
+ sources: test_util_sources,
+ dependencies: libfprint_private_dep,
+ install: false)
+
unit_tests_deps = { 'fpi-assembling' : [cairo_dep] }
foreach test_name: unit_tests
diff --git a/tests/sdcp-key-db/cert9.db b/tests/sdcp-key-db/cert9.db
deleted file mode 100644
index 221ac06e..00000000
Binary files a/tests/sdcp-key-db/cert9.db and /dev/null differ
diff --git a/tests/sdcp-key-db/key4.db b/tests/sdcp-key-db/key4.db
deleted file mode 100644
index b27f777b..00000000
Binary files a/tests/sdcp-key-db/key4.db and /dev/null differ
diff --git a/tests/test-fp-sdcp-device.c b/tests/test-fp-sdcp-device.c
new file mode 100644
index 00000000..baa2e323
--- /dev/null
+++ b/tests/test-fp-sdcp-device.c
@@ -0,0 +1,143 @@
+/*
+ * FpSdcpDevice Unit tests
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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
+
+#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 ();
+}
diff --git a/tests/test-fpi-sdcp.c b/tests/test-fpi-sdcp.c
new file mode 100644
index 00000000..e7a99d7a
--- /dev/null
+++ b/tests/test-fpi-sdcp.c
@@ -0,0 +1,162 @@
+/*
+ * Secure Device Connection Protocol (SDCP) support unit tests
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#define FP_COMPONENT "test_fpi_sdcp"
+#include "fpi-log.h"
+
+#include "test-sdcp-utils.h"
+
+static void
+test_generate_enrollment_id (void)
+{
+ g_autoptr(GBytes) id = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
+ g_autoptr(GBytes) nonce = g_bytes_from_hex (enrollment_nonce_hex);
+ g_autoptr(GBytes) expected_id = g_bytes_from_hex (enrollment_enrollment_id_hex);
+
+ id = fpi_sdcp_generate_enrollment_id (application_secret, nonce, &error);
+
+ fp_dbg ("id:");
+ fp_dbg_hex_dump_gbytes (id);
+
+ fp_dbg ("expected:");
+ fp_dbg_hex_dump_gbytes (expected_id);
+
+ g_assert (g_bytes_equal (expected_id, id));
+ g_assert_null (error);
+}
+
+static void
+test_verify_identify (void)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
+ g_autoptr(GBytes) nonce = g_bytes_from_hex (identify_nonce_hex);
+ g_autoptr(GBytes) id = g_bytes_from_hex (identify_enrollment_id_hex);
+ g_autoptr(GBytes) mac = g_bytes_from_hex (identify_mac_hex);
+
+ g_assert_true (fpi_sdcp_verify_identify (application_secret, nonce, id, mac, &error));
+ g_assert_null (error);
+}
+
+static void
+test_verify_reconnect (void)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GBytes) application_secret = g_bytes_from_hex (application_secret_hex);
+ g_autoptr(GBytes) random = g_bytes_from_hex (reconnect_random_hex);
+ g_autoptr(GBytes) mac = g_bytes_from_hex (reconnect_mac_hex);
+
+ g_assert_true (fpi_sdcp_verify_reconnect (application_secret, random, mac, &error));
+ g_assert_null (error);
+}
+
+static void
+test_verify_connect (void)
+{
+ g_autoptr(GBytes) application_secret = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_autoptr(GBytes) host_private_key = g_bytes_from_hex (host_private_key_hex);
+ g_autoptr(GBytes) host_random = g_bytes_from_hex (host_random_hex);
+ g_autoptr(GBytes) device_random = g_bytes_from_hex (device_random_hex);
+ g_autoptr(GBytes) connect_mac = g_bytes_from_hex (connect_mac_hex);
+ FpiSdcpClaim *claim = sdcp_test_claim ();
+
+ g_autoptr(GBytes) expected_application_secret = g_bytes_from_hex (application_secret_hex);
+
+ g_assert_true (fpi_sdcp_verify_connect (host_private_key,
+ host_random,
+ device_random,
+ claim,
+ connect_mac,
+ TRUE,
+ TRUE,
+ &application_secret,
+ &error));
+
+ g_assert_null (error);
+ g_assert (g_bytes_get_size (application_secret) == SDCP_APPLICATION_SECRET_SIZE);
+
+ fp_dbg ("application_secret:");
+ fp_dbg_hex_dump_gbytes (application_secret);
+
+ fp_dbg ("expected:");
+ fp_dbg_hex_dump_gbytes (expected_application_secret);
+
+ g_assert_true (g_bytes_equal (expected_application_secret, application_secret));
+
+ fpi_sdcp_claim_free (claim);
+}
+
+static void
+test_generate_random (void)
+{
+ g_autoptr(GBytes) random = NULL;
+ g_autoptr(GError) error = NULL;
+
+ random = fpi_sdcp_generate_random (&error);
+
+ g_assert_null (error);
+ g_assert (g_bytes_get_size (random) == SDCP_RANDOM_SIZE);
+
+ fp_dbg ("random:");
+ fp_dbg_hex_dump_gbytes (random);
+}
+
+static void
+test_generate_host_key (void)
+{
+ g_autoptr(GBytes) private_key = NULL;
+ g_autoptr(GBytes) public_key = NULL;
+ g_autoptr(GError) error = NULL;
+ gsize len = 0;
+
+ fpi_sdcp_generate_host_key (&private_key, &public_key, &error);
+
+ g_assert_null (error);
+
+ g_bytes_get_data (private_key, &len);
+ g_assert (len == 32);
+
+ fp_dbg ("private_key:");
+ fp_dbg_hex_dump_gbytes (private_key);
+
+ g_bytes_get_data (public_key, &len);
+ g_assert (len == SDCP_PUBLIC_KEY_SIZE);
+
+ fp_dbg ("public_key:");
+ fp_dbg_hex_dump_gbytes (public_key);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/sdcp/generate_host_key", test_generate_host_key);
+ g_test_add_func ("/sdcp/generate_random", test_generate_random);
+ g_test_add_func ("/sdcp/verify_connect", test_verify_connect);
+ g_test_add_func ("/sdcp/verify_reconnect", test_verify_reconnect);
+ g_test_add_func ("/sdcp/verify_identify", test_verify_identify);
+ g_test_add_func ("/sdcp/generate_enrollment_id", test_generate_enrollment_id);
+
+ return g_test_run ();
+}
diff --git a/tests/test-sdcp-device-fake.c b/tests/test-sdcp-device-fake.c
new file mode 100644
index 00000000..408650aa
--- /dev/null
+++ b/tests/test-sdcp-device-fake.c
@@ -0,0 +1,196 @@
+/*
+ * Virtual driver for SDCP unit testing
+ * Completes actions using example values from Microsoft's SDCP documentation
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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;
+}
diff --git a/tests/test-sdcp-device-fake.h b/tests/test-sdcp-device-fake.h
new file mode 100644
index 00000000..5ad8f0eb
--- /dev/null
+++ b/tests/test-sdcp-device-fake.h
@@ -0,0 +1,33 @@
+/*
+ * Virtual driver for SDCP unit testing
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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;
+};
diff --git a/tests/test-sdcp-utils.c b/tests/test-sdcp-utils.c
new file mode 100644
index 00000000..5b2682c0
--- /dev/null
+++ b/tests/test-sdcp-utils.c
@@ -0,0 +1,60 @@
+/*
+ * Secure Device Connection Protocol (SDCP) support test utils
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * 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);
+}
diff --git a/tests/test-sdcp-utils.h b/tests/test-sdcp-utils.h
new file mode 100644
index 00000000..f9b2a3c9
--- /dev/null
+++ b/tests/test-sdcp-utils.h
@@ -0,0 +1,102 @@
+/*
+ * Secure Device Connection Protocol (SDCP) support test utils
+ * Copyright (C) 2025 Joshua Grisham
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include "fpi-log.h"
+#include "fpi-sdcp.h"
+#include "fpi-sdcp-device.h"
+
+/******************************************************************************/
+
+/* host keys */
+
+static const gchar host_private_key_hex[] = "8400ed14579cdf11586477e836e8cb52708441c1c2a447c218c5bbc2d118fbc7";
+static const gchar host_public_key_hex[] = "0452f056ffb9c6728654771a3629b770767b19a2106a4916fb81ba06ef6797c4"
+ "a3df672ade0e9116d1abe278a8223abde4958d62d4ff6882159f0617c6f8ce10"
+ "bf";
+static const gchar host_random_hex[] = "d877403abe82f4d97e1448c5052d83a532a45e56ef049cbbf981137520e713bf";
+
+/* device keys */
+
+static const gchar device_random_hex[] = "6e2f6c1abef2a1973fb1315a17e209fdb0c78520f1fd6a85d294d7aeb40a04a7";
+static const gchar model_certificate_hex[] = "30820323308202caa00302010202133300000004c45d661d6eed040d00000000"
+ "0004300a06082a8648ce3d0403023056310b3009060355040613025553311e30"
+ "1c060355040a13154d6963726f736f667420436f72706f726174696f6e312730"
+ "250603550403131e57696e646f77732048656c6c6f2032303936414443432043"
+ "412032303231301e170d3232303130343231303033355a170d32333034303432"
+ "31303033355a301c311a3018060355040313115365637572652042494f205365"
+ "6e736f723059301306072a8648ce3d020106082a8648ce3d0301070342000414"
+ "cfc287f872a2b7d3339e0b31390e3ca688e61165eaa6687c959270e07666b1fa"
+ "19e3efaf1750d134a886d494424fe471970c4b06838408a18d1f5d57735dd7a3"
+ "8201af308201ab30750603551d11046e306ca46a30683132303006082b060104"
+ "82376402132436423045413344382d383339372d343444332d383043392d3930"
+ "324130414346344343303132303006082b060104823764011324413944423730"
+ "32362d433646462d344341462d423134382d393133454641333730423933301d"
+ "0603551d0e04160414db6d66d642b7236d5e6bb2ec2186decda98067b6301f06"
+ "03551d23041830168014bf3748e34a632de953a3ba890298c069472a99b9305f"
+ "0603551d1f045830563054a052a050864e687474703a2f2f7777772e6d696372"
+ "6f736f66742e636f6d2f706b696f70732f63726c2f57696e646f777325323048"
+ "656c6c6f25323032303936414443432532304341253230323032312e63726c30"
+ "6c06082b060105050701010460305e305c06082b060105050730028650687474"
+ "703a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f636572"
+ "74732f57696e646f777325323048656c6c6f2532303230393641444343253230"
+ "4341253230323032312e637274300c0603551d130101ff040230003015060355"
+ "1d25040e300c060a2b0601040182374c2b01300a06082a8648ce3d0403020347"
+ "003044022077553ef520f732e03cd740c8cf807e6366e12918bc581f75bfe0f1"
+ "95b7b1fd4f0220324e25b93b9da7538b797a624272b21b7cc0e96ea487924250"
+ "4677600450f283";
+static const gchar device_public_key_hex[] = "04e2787890a684f95b96b9a2316ca8d3d33d4d79ff4c89dc6f9e888e973990d1"
+ "d3154133dcc8bd33b99af9dbf0673390d404d092498a3f214cd93f9b9f28fb5f"
+ "66";
+static const gchar firmware_public_key_hex[] = "04f06a84ab51a3a6e8ff46868f91dd720e4cdad21f2e090d11e8f9bfc2ea19ee"
+ "1b5eac850b4532968a9399f76cd779e7723e8c2ca73b597c0df5f73b94a36f2b"
+ "6c";
+static const gchar firmware_hash_hex[] = "c3bf47ea1f4a4a605470313cacb3a44f4a461f68c6faeab07e737610cb5ac835";
+static const gchar model_signature_hex[] = "febe6ba3107813e185f05189e69ae79d9f7a40802582d94324459844c8b97ec6"
+ "c5daed5462276cb8a193c33e350424b0305d63d79a93a9188dcfc0cb5595f6c1";
+static const gchar device_signature_hex[] = "10cc57dd8dafb463510a7327a5fca49b698e999b36448e2023eaf0dff0b0d4a3"
+ "4f1caf4e872b77364a0a00d7476554d0324c4cc931937e232a0315837d696c06";
+static const gchar connect_mac_hex[] = "422bc475a78f972bae842a28e5ad721207457fcbd9a1a3aaf71587c07b84d247";
+
+/* expected application_secret based on above */
+
+static const gchar application_secret_hex[] = "13330ba3135ecf5dc71cede01a886540771efab35c8ba053902b2c1ee7de6efe";
+
+/* test verify_reconnect values */
+
+static const gchar reconnect_random_hex[] = "8a7451c1d3a8dca1c1330ca50d73454b351a49f46c8e9dcee15c964d295c31c9";
+static const gchar reconnect_mac_hex[] = "bf3f3bb3bd6ecb2784c160f526f7bc3b3ca8faf5557194c48e0024a0493903c7";
+
+/* test verify_identify values */
+
+static const gchar identify_nonce_hex[] = "3a1b506f5bec089059acefb9b44dfbdea7a599ee9aa267e5252664d60b798053";
+static const gchar identify_enrollment_id_hex[] = "ef2055244e49c39beabdac49fdf4ee418605d195da23b202ba219a13831ae621";
+static const gchar identify_mac_hex[] = "f0a5c5f261c2fe937d8b113857bc629cd07ca88edf991f69ca6fae5c332390f6";
+
+/* test enrollment_id values */
+
+static const gchar enrollment_nonce_hex[] = "c2101c44c9a667bba397e81f48b143398603e2c9335a68b409e1dbe71e005ca2";
+static const gchar enrollment_enrollment_id_hex[] = "67109dc70a216331f1580ddac601915929c1ff6c9bcba6544ba572c660c3d91e";
+
+/******************************************************************************/
+
+GBytes *g_bytes_from_hex (const gchar *hex);
+
+FpiSdcpClaim *sdcp_test_claim (void);
diff --git a/tests/virtual-sdcp.py b/tests/virtual-sdcp.py
deleted file mode 100644
index 6efb24d7..00000000
--- a/tests/virtual-sdcp.py
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-try:
- import gi
- import os
-
- from gi.repository import GLib, Gio
-
- import unittest
- import subprocess
- import shutil
- import tempfile
-except Exception as e:
- print("Missing dependencies: %s" % str(e))
- sys.exit(77)
-
-FPrint = None
-
-# Re-run the test with the passed wrapper if set
-wrapper = os.getenv('LIBFPRINT_TEST_WRAPPER')
-if wrapper:
- wrap_cmd = wrapper.split(' ') + [sys.executable, os.path.abspath(__file__)] + \
- sys.argv[1:]
- os.unsetenv('LIBFPRINT_TEST_WRAPPER')
- sys.exit(subprocess.check_call(wrap_cmd))
-
-# Only permit loading virtual_sdcp driver for tests in this file
-os.environ['FP_DRIVERS_WHITELIST'] = 'virtual_sdcp'
-
-if hasattr(os.environ, 'MESON_SOURCE_ROOT'):
- root = os.environ['MESON_SOURCE_ROOT']
-else:
- root = os.path.join(os.path.dirname(__file__), '..')
-
-ctx = GLib.main_context_default()
-
-class VirtualSDCP(unittest.TestCase):
-
- @classmethod
- def setUpClass(cls):
- os.environ['FP_VIRTUAL_SDCP'] = os.environ['SDCP_VIRT_BINARY']
-
- cls.ctx = FPrint.Context()
-
- cls.dev = None
- for dev in cls.ctx.get_devices():
- cls.dev = dev
- break
-
- assert cls.dev is not None, "You need to compile with virtual_sdcp for testing"
-
- @classmethod
- def tearDownClass(cls):
- del cls.dev
- del cls.ctx
-
- def setUp(self):
- pass
-
- def tearDown(self):
- pass
-
- def enroll(self, progress_cb=None):
- # Enroll another print
- template = FPrint.Print.new(self.dev)
- template.props.finger = FPrint.Finger.LEFT_THUMB
- template.props.username = "testuser"
- template.props.description = "test print"
- datetime = GLib.DateTime.new_now_local()
- date = GLib.Date()
- date.set_dmy(*datetime.get_ymd()[::-1])
- template.props.enroll_date = date
- return self.dev.enroll_sync(template, None, progress_cb, None)
-
- def test_connect(self):
- self.dev.open_sync()
- self.dev.close_sync()
-
- def test_reconnect(self):
- # Ensure device was opened once before, this may be a reconnect if
- # it is the same process as another test.
- self.dev.open_sync()
- self.dev.close_sync()
-
- # Check that a reconnect happens on next open. To know about this, we
- # need to parse check log messages for that.
- success = [False]
- def log_func(domain, level, msg):
- print("log: '%s', '%s', '%s'" % (str(domain), str(level), msg))
- if msg == 'Reconnect succeeded':
- success[0] = True
-
- # Call default handler
- GLib.log_default_handler(domain, level, msg)
-
- handler_id = GLib.log_set_handler('libfprint-sdcp_device', GLib.LogLevelFlags.LEVEL_DEBUG, log_func)
- self.dev.open_sync()
- self.dev.close_sync()
- GLib.log_remove_handler('libfprint-sdcp_device', handler_id)
- assert success[0]
-
- def test_enroll(self):
- self.dev.open_sync()
-
- # Must return a print
- assert isinstance(self.enroll(), FPrint.Print)
-
- self.dev.close_sync()
-
-
- def test_verify(self):
- self.dev.open_sync()
-
- # Enroll a new print (will be the last), check that it verifies
- p = self.enroll()
- match, dev_print = self.dev.verify_sync(p)
- assert match
-
- # The returned "device" print is identical
- assert p.equal(dev_print)
-
- # We can do the same with it
- match, dev_print2 = self.dev.verify_sync(dev_print)
- assert match
-
- # Now, enroll a new print, causing the old one to not match anymore
- # (the device always claims to see the last enrolled print).
- self.enroll()
- match, dev_print = self.dev.verify_sync(p)
- assert match is False
-
- self.dev.close_sync()
-
-if __name__ == '__main__':
- try:
- gi.require_version('FPrint', '2.0')
- from gi.repository import FPrint
- except Exception as e:
- print("Missing dependencies: %s" % str(e))
- sys.exit(77)
-
- # avoid writing to stderr
- unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2))