mirror of
https://gitlab.freedesktop.org/libfprint/fprintd.git
synced 2026-05-21 06:08:13 +02:00
Merge branch 'feature/sdcp' into 'master'
sdcp: Implement SDCP data storage and resetting See merge request libfprint/fprintd!232
This commit is contained in:
commit
558c7c22aa
7 changed files with 369 additions and 2 deletions
|
|
@ -55,7 +55,10 @@ stages:
|
|||
before_script:
|
||||
# Make sure we don't build or link against the system libfprint
|
||||
- dnf remove -y libfprint-devel
|
||||
- git clone https://gitlab.freedesktop.org/libfprint/libfprint.git
|
||||
- git clone https://gitlab.freedesktop.org/joshuagrisham/libfprint.git # TODO: Changed temporarily for testing
|
||||
- cd libfprint # TODO: Changed temporarily for testing
|
||||
- git switch feature/sdcp-v2 # TODO: Changed temporarily for testing
|
||||
- cd .. # TODO: Changed temporarily for testing
|
||||
- meson setup libfprint/_build libfprint --prefix=/usr -Ddrivers=virtual_image,virtual_device,virtual_device_storage -Ddoc=false
|
||||
- meson compile -C libfprint/_build
|
||||
- meson install -C libfprint/_build
|
||||
|
|
|
|||
28
src/device.c
28
src/device.c
|
|
@ -1024,6 +1024,21 @@ _fprint_device_add_client (FprintDevice *rdev, const char *sender)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reset_sdcp_if_device_untrusted (FpDevice *dev,
|
||||
GError *error)
|
||||
{
|
||||
if (g_error_matches (error, FP_DEVICE_ERROR, FP_DEVICE_ERROR_UNTRUSTED))
|
||||
{
|
||||
g_debug ("resetting SDCP connection");
|
||||
|
||||
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dev), "sdcp-data"))
|
||||
g_object_set (G_OBJECT (dev), "sdcp-data", NULL, NULL);
|
||||
|
||||
store.sdcp_data_delete (dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dev_open_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
||||
{
|
||||
|
|
@ -1048,11 +1063,14 @@ dev_open_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
|||
"Open failed with error: %s", error->message);
|
||||
g_dbus_method_invocation_return_gerror (invocation, dbus_error);
|
||||
session_data_set_new (priv, NULL, NULL);
|
||||
reset_sdcp_if_device_untrusted (dev, error);
|
||||
return;
|
||||
}
|
||||
|
||||
g_debug ("claimed device %d", priv->id);
|
||||
|
||||
store.sdcp_data_save (priv->dev);
|
||||
|
||||
fprint_dbus_device_complete_claim (FPRINT_DBUS_DEVICE (rdev),
|
||||
invocation);
|
||||
}
|
||||
|
|
@ -1120,6 +1138,8 @@ fprint_device_claim (FprintDBusDevice *dbus_dev,
|
|||
|
||||
g_debug ("user '%s' claiming the device: %d", session->username, priv->id);
|
||||
|
||||
store.sdcp_data_load (priv->dev);
|
||||
|
||||
priv->current_action = ACTION_OPEN;
|
||||
fp_device_open (priv->dev, NULL, (GAsyncReadyCallback) dev_open_cb, rdev);
|
||||
|
||||
|
|
@ -1571,6 +1591,8 @@ verify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
|||
|
||||
g_debug ("verify_cb: result %s", name);
|
||||
|
||||
reset_sdcp_if_device_untrusted (dev, error);
|
||||
|
||||
/* Automatically restart the operation for retry failures */
|
||||
if (error && error->domain == FP_DEVICE_RETRY)
|
||||
{
|
||||
|
|
@ -1616,6 +1638,8 @@ identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
|||
|
||||
g_debug ("identify_cb: result %s", name);
|
||||
|
||||
reset_sdcp_if_device_untrusted (dev, error);
|
||||
|
||||
/* Automatically restart the operation for retry failures */
|
||||
if (error && error->domain == FP_DEVICE_RETRY)
|
||||
{
|
||||
|
|
@ -2052,6 +2076,8 @@ enroll_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
|||
|
||||
g_signal_emit (rdev, signals[SIGNAL_ENROLL_STATUS], 0, name, TRUE);
|
||||
|
||||
reset_sdcp_if_device_untrusted (dev, error);
|
||||
|
||||
if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
|
||||
g_warning ("Device reported an error during enroll: %s", error->message);
|
||||
|
||||
|
|
@ -2091,6 +2117,8 @@ enroll_identify_cb (FpDevice *dev, GAsyncResult *res, void *user_data)
|
|||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
reset_sdcp_if_device_untrusted (dev, error);
|
||||
|
||||
/* We may need to retry or error out. */
|
||||
if (error)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -366,3 +366,191 @@ file_storage_deinit (void)
|
|||
g_clear_pointer (&storage_path, g_free);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *current_boot_id = NULL;
|
||||
|
||||
static const char *
|
||||
get_boot_id (void)
|
||||
{
|
||||
g_autofree gchar *kernel_boot_id = NULL;
|
||||
|
||||
/* read the boot_id exposed by the kernel */
|
||||
if (current_boot_id == NULL)
|
||||
{
|
||||
if (g_file_get_contents ("/proc/sys/kernel/random/boot_id", &kernel_boot_id, NULL, NULL))
|
||||
{
|
||||
current_boot_id = g_strdup (kernel_boot_id);
|
||||
g_strchomp (current_boot_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_warning ("get_boot_id(): could not read '/proc/sys/kernel/random/boot_id'");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: is it better to read this boot_id sysfs file or to get BootID from
|
||||
* org.freedesktop.hostname1 using D-Bus ? Or maybe we should try one and use
|
||||
* the other as a fallback ?
|
||||
*/
|
||||
|
||||
return current_boot_id;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_sdcp_data_path (FpDevice *dev)
|
||||
{
|
||||
char *path = g_build_filename (get_storage_path (),
|
||||
".sdcp",
|
||||
fp_device_get_driver (dev),
|
||||
fp_device_get_device_id (dev),
|
||||
NULL);
|
||||
|
||||
return g_steal_pointer (&path);
|
||||
}
|
||||
|
||||
static int
|
||||
delete_sdcp_data (FpDevice *dev,
|
||||
const char *boot_id)
|
||||
{
|
||||
g_autofree gchar *dirpath = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
int r;
|
||||
|
||||
dirpath = get_sdcp_data_path (dev);
|
||||
path = g_build_filename (dirpath, boot_id, NULL);
|
||||
|
||||
if (!g_file_test (path, G_FILE_TEST_EXISTS))
|
||||
return 0;
|
||||
|
||||
r = g_unlink (path);
|
||||
g_debug ("delete_sdcp_data(): unlink(\"%s\") %s",
|
||||
path, g_strerror (r));
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
file_storage_sdcp_data_save (FpDevice *dev)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autoptr(GBytes) sdcp_data = NULL;
|
||||
g_autofree gchar *dirpath = NULL;
|
||||
g_autofree gchar *path = NULL;
|
||||
int r;
|
||||
|
||||
dirpath = get_sdcp_data_path (dev);
|
||||
path = g_build_filename (dirpath, get_boot_id (), NULL);
|
||||
|
||||
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dev), "sdcp-data") == NULL)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_save(): device does not have 'scdp-data'");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
g_object_get (G_OBJECT (dev), "sdcp-data", &sdcp_data, NULL);
|
||||
|
||||
if (!sdcp_data)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_save(): device does not have 'scdp-data'");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
r = g_mkdir_with_parents (dirpath, DIR_PERMS);
|
||||
if (r < 0)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_save(): could not mkdir(\"%s\"): %s",
|
||||
dirpath, g_strerror (r));
|
||||
return r;
|
||||
}
|
||||
|
||||
g_file_set_contents (path,
|
||||
g_bytes_get_data (sdcp_data, NULL),
|
||||
g_bytes_get_size (sdcp_data),
|
||||
&err);
|
||||
if (err)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_save(): could not save '%s': %s",
|
||||
path, err->message);
|
||||
/* FIXME interpret error codes */
|
||||
return err->code;
|
||||
}
|
||||
|
||||
g_debug ("file_storage_sdcp_data_save(): SDCP data saved to %s", path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
file_storage_sdcp_data_load (FpDevice *dev)
|
||||
{
|
||||
g_autoptr(GError) err = NULL;
|
||||
g_autofree gchar *dirpath = NULL;
|
||||
GDir *dir = NULL;
|
||||
const gchar *entry;
|
||||
g_autofree gchar *path = NULL;
|
||||
gboolean found = FALSE;
|
||||
gchar *buf = NULL;
|
||||
gsize len;
|
||||
|
||||
dirpath = get_sdcp_data_path (dev);
|
||||
|
||||
if (!g_file_test (dirpath, G_FILE_TEST_EXISTS))
|
||||
return 0;
|
||||
|
||||
dir = g_dir_open (dirpath, 0, &err);
|
||||
if (!dir)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_load(): failed to open directory '%s': %s",
|
||||
dirpath, err->message);
|
||||
g_clear_error (&err);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
while ((entry = g_dir_read_name (dir)) != NULL)
|
||||
{
|
||||
/*
|
||||
* Each dir entry should be a boot_id including the "sdcp-data". If
|
||||
* an entry does not match the current boot_id, we should delete it.
|
||||
* Otherwise, we load the sdcp-data from that file.
|
||||
*/
|
||||
|
||||
if (g_strcmp0 (entry, get_boot_id ()) != 0)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_load(): deleting SDCP data from prior system boot '%s'",
|
||||
entry);
|
||||
delete_sdcp_data (dev, entry);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = g_build_filename (dirpath, entry, NULL);
|
||||
g_file_get_contents (path, &buf, &len, &err);
|
||||
if (err)
|
||||
{
|
||||
g_debug ("file_storage_sdcp_data_load(): could not read SDCP data file '%s': %s",
|
||||
path, err->message);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_object_set (G_OBJECT (dev), "sdcp-data", g_bytes_new_take (buf, len), NULL);
|
||||
|
||||
g_debug ("file_storage_sdcp_data_load(): loaded SDCP data from file '%s'", path);
|
||||
|
||||
found = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
g_dir_close (dir);
|
||||
|
||||
if (found)
|
||||
return 0;
|
||||
else
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
int
|
||||
file_storage_sdcp_data_delete (FpDevice *dev)
|
||||
{
|
||||
return delete_sdcp_data (dev, get_boot_id ());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,3 +38,9 @@ int file_storage_deinit (void);
|
|||
GSList *file_storage_discover_prints (FpDevice *dev,
|
||||
const char *username);
|
||||
GSList *file_storage_discover_users (void);
|
||||
|
||||
int file_storage_sdcp_data_save (FpDevice *dev);
|
||||
|
||||
int file_storage_sdcp_data_load (FpDevice *dev);
|
||||
|
||||
int file_storage_sdcp_data_delete (FpDevice *dev);
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ set_storage_file (void)
|
|||
store.print_data_delete = &file_storage_print_data_delete;
|
||||
store.discover_prints = &file_storage_discover_prints;
|
||||
store.discover_users = &file_storage_discover_users;
|
||||
store.sdcp_data_save = &file_storage_sdcp_data_save;
|
||||
store.sdcp_data_load = &file_storage_sdcp_data_load;
|
||||
store.sdcp_data_delete = &file_storage_sdcp_data_delete;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
|
|
@ -75,7 +78,10 @@ load_storage_module (const char *module_name)
|
|||
!g_module_symbol (module, "print_data_load", (gpointer *) &store.print_data_load) ||
|
||||
!g_module_symbol (module, "print_data_delete", (gpointer *) &store.print_data_delete) ||
|
||||
!g_module_symbol (module, "discover_prints", (gpointer *) &store.discover_prints) ||
|
||||
!g_module_symbol (module, "discover_users", (gpointer *) &store.discover_users))
|
||||
!g_module_symbol (module, "discover_users", (gpointer *) &store.discover_users) ||
|
||||
!g_module_symbol (module, "sdcp_data_save", (gpointer *) &store.sdcp_data_save) ||
|
||||
!g_module_symbol (module, "sdcp_data_load", (gpointer *) &store.sdcp_data_load) ||
|
||||
!g_module_symbol (module, "sdcp_data_delete", (gpointer *) &store.sdcp_data_delete))
|
||||
{
|
||||
g_module_close (module);
|
||||
g_debug ("Failed to load module. Please update your code.");
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ typedef int (*storage_print_data_delete)(FpDevice *dev,
|
|||
typedef GSList *(*storage_discover_prints)(FpDevice *dev,
|
||||
const char *username);
|
||||
typedef GSList *(*storage_discover_users)(void);
|
||||
typedef int (*storage_sdcp_data_load)(FpDevice *dev);
|
||||
typedef int (*storage_sdcp_data_save)(FpDevice *dev);
|
||||
typedef int (*storage_sdcp_data_delete)(FpDevice *dev);
|
||||
typedef int (*storage_init)(void);
|
||||
typedef int (*storage_deinit)(void);
|
||||
|
||||
|
|
@ -43,6 +46,9 @@ struct storage
|
|||
storage_print_data_delete print_data_delete;
|
||||
storage_discover_prints discover_prints;
|
||||
storage_discover_users discover_users;
|
||||
storage_sdcp_data_save sdcp_data_save;
|
||||
storage_sdcp_data_load sdcp_data_load;
|
||||
storage_sdcp_data_delete sdcp_data_delete;
|
||||
};
|
||||
|
||||
typedef struct storage fp_storage;
|
||||
|
|
|
|||
130
tests/fprintd.py
130
tests/fprintd.py
|
|
@ -3448,6 +3448,136 @@ class FPrintdUtilsTest(FPrintdVirtualStorageDeviceBaseTest):
|
|||
self.run_verify(finger=FPrint.Finger.UNKNOWN, match=False,
|
||||
error=FPrint.DeviceError.PROTO)
|
||||
|
||||
class FPrintdVirtualSdcpDeviceBaseTests(FPrintdVirtualDeviceBaseTest):
|
||||
socket_env = 'FP_VIRTUAL_SDCP'
|
||||
device_driver = 'virtual_sdcp'
|
||||
driver_name = 'Virtual SDCP device for debugging'
|
||||
has_identification = True
|
||||
|
||||
def get_sdcp_data_path(self):
|
||||
with open("/proc/sys/kernel/random/boot_id", "r") as f:
|
||||
boot_id = f.read().strip()
|
||||
return os.path.join(self.state_dir, ".sdcp", self.device_driver, str(self.device_id),
|
||||
boot_id)
|
||||
|
||||
def test_connect(self):
|
||||
# To test a Connect, we just need to open and close the device
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
|
||||
# Check that the SDCP data file was created
|
||||
self.assertTrue(os.path.isfile(self.get_sdcp_data_path()))
|
||||
|
||||
def test_reconnect(self):
|
||||
# To test a Reconnect, we should connect, disconnect, and connect again
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.daemon_log.check_line('SDCP Reconnect succeeded', timeout=3)
|
||||
self.device.Release()
|
||||
|
||||
def test_reconnect_from_sdcp_data(self):
|
||||
# Connecting will create the SDCP data file
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
|
||||
# Now stop and restart the daemon so that sdcp-data will be unloaded from memory and force
|
||||
# reading data from disk instead
|
||||
self.daemon_stop()
|
||||
self.daemon_start(self.driver_name)
|
||||
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.daemon_log.check_line('loaded SDCP data from file', timeout=3)
|
||||
self.daemon_log.check_line('SDCP Reconnect succeeded', timeout=3)
|
||||
self.device.Release()
|
||||
|
||||
def test_reconnect_with_invalid_data(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
self.daemon_stop()
|
||||
|
||||
# virtual_sdcp uses a hard-coded application secret; if we just write something else then it should stop working
|
||||
bad_sdcp_data = bytes.fromhex("0123456789abcdef1032547698badcfeaabbccddeeff00112233445566778899")
|
||||
with open(self.get_sdcp_data_path(), "wb") as f:
|
||||
f.write(bad_sdcp_data)
|
||||
|
||||
# Now when we try to connect, the Reconnect will fail and it should reset SDCP data and perform a normal Connect again
|
||||
self.daemon_start(self.driver_name)
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.daemon_log.check_line('SDCP Reconnect failed', timeout=3)
|
||||
self.daemon_log.check_line('SDCP ConnectResponse claim validated successfully', timeout=3)
|
||||
self.device.Release()
|
||||
|
||||
# And check that the SDCP data file has been changed from the above bogus value
|
||||
with open(self.get_sdcp_data_path(), "rb") as f:
|
||||
new_sdcp_data = f.read()
|
||||
|
||||
self.assertNotEqual(bad_sdcp_data, new_sdcp_data)
|
||||
|
||||
def test_list(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
with self.assertFprintError('NoEnrolledPrints'):
|
||||
self.device.ListEnrolledFingers('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
|
||||
def test_enroll_list_verify(self):
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
self.device.EnrollStart('(s)', 'right-thumb')
|
||||
self.device.EnrollStop()
|
||||
|
||||
enrolled = self.device.ListEnrolledFingers('(s)', 'testuser')
|
||||
self.assertEqual(enrolled, ['right-thumb'])
|
||||
|
||||
self.device.VerifyStart('(s)', 'any')
|
||||
self.wait_for_result('verify-match')
|
||||
self.device.VerifyStop()
|
||||
|
||||
self.device.Release()
|
||||
|
||||
def test_enroll_with_invalid_data(self):
|
||||
# To test this we need to ensure that a Reconnect will not be attempted
|
||||
# (fprintd will just use the sdcp-data file as-is instead of correcting is as part of Reconnect)
|
||||
os.environ['FP_VIRTUAL_SDCP_NO_RECONNECT'] = '1'
|
||||
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
self.device.Release()
|
||||
self.daemon_stop()
|
||||
|
||||
# Now we set the bad sdcp-data which will be used with the next operation
|
||||
bad_sdcp_data = bytes.fromhex("0123456789abcdef1032547698badcfeaabbccddeeff00112233445566778899")
|
||||
with open(self.get_sdcp_data_path(), "wb") as f:
|
||||
f.write(bad_sdcp_data)
|
||||
|
||||
self.daemon_start(self.driver_name)
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
self.device.EnrollStart('(s)', 'right-thumb')
|
||||
self.device.EnrollStop()
|
||||
|
||||
self.wait_for_result('enroll-unknown-error')
|
||||
self.daemon_log.check_line('resetting SDCP connection', timeout=3)
|
||||
|
||||
# When exception occurs fprintd should release the device and then
|
||||
# claim again for the next operation, so we will mimic that here
|
||||
self.device.Release()
|
||||
self.device.Claim('(s)', 'testuser')
|
||||
|
||||
# Check that a new Connect was performed
|
||||
self.daemon_log.check_line('SDCP ConnectResponse claim validated successfully', timeout=3)
|
||||
|
||||
with self.assertFprintError('NoEnrolledPrints'):
|
||||
self.device.ListEnrolledFingers('(s)', 'testuser')
|
||||
|
||||
self.device.EnrollStart('(s)', 'right-thumb')
|
||||
self.device.EnrollStop()
|
||||
|
||||
enrolled = self.device.ListEnrolledFingers('(s)', 'testuser')
|
||||
self.assertEqual(enrolled, ['right-thumb'])
|
||||
|
||||
self.device.Release()
|
||||
|
||||
|
||||
def list_tests():
|
||||
import unittest_inspector
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue