summaryrefslogtreecommitdiffstats
path: root/src/udev/fido_id
diff options
context:
space:
mode:
Diffstat (limited to 'src/udev/fido_id')
-rw-r--r--src/udev/fido_id/fido_id.c95
-rw-r--r--src/udev/fido_id/fido_id_desc.c92
-rw-r--r--src/udev/fido_id/fido_id_desc.h8
-rw-r--r--src/udev/fido_id/fuzz-fido-id-desc.c24
-rw-r--r--src/udev/fido_id/test-fido-id-desc.c80
5 files changed, 299 insertions, 0 deletions
diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c
new file mode 100644
index 0000000..58a2827
--- /dev/null
+++ b/src/udev/fido_id/fido_id.c
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on the usage declared in their report
+ * descriptor and outputs suitable environment variables.
+ *
+ * Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c'
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/hid.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "device-private.h"
+#include "device-util.h"
+#include "fd-util.h"
+#include "fido_id_desc.h"
+#include "log.h"
+#include "macro.h"
+#include "main-func.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "udev-util.h"
+
+static int run(int argc, char **argv) {
+ _cleanup_(sd_device_unrefp) struct sd_device *device = NULL;
+ _cleanup_free_ char *desc_path = NULL;
+ _cleanup_close_ int fd = -1;
+
+ struct sd_device *hid_device;
+ const char *sys_path;
+ uint8_t desc[HID_MAX_DESCRIPTOR_SIZE];
+ ssize_t desc_len;
+
+ int r;
+
+ log_set_target(LOG_TARGET_AUTO);
+ udev_parse_config();
+ log_parse_environment();
+ log_open();
+
+ if (argc > 2)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Usage: %s [SYSFS_PATH]", program_invocation_short_name);
+
+ if (argc == 1) {
+ r = device_new_from_strv(&device, environ);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get current device from environment: %m");
+ } else {
+ r = sd_device_new_from_syspath(&device, argv[1]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get device from syspath: %m");
+ }
+
+ r = sd_device_get_parent(device, &hid_device);
+ if (r < 0)
+ return log_device_error_errno(device, r, "Failed to get parent HID device: %m");
+
+ r = sd_device_get_syspath(hid_device, &sys_path);
+ if (r < 0)
+ return log_device_error_errno(hid_device, r, "Failed to get syspath for HID device: %m");
+
+ desc_path = path_join(sys_path, "report_descriptor");
+ if (!desc_path)
+ return log_oom();
+
+ fd = open(desc_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NOCTTY);
+ if (fd < 0)
+ return log_device_error_errno(hid_device, errno,
+ "Failed to open report descriptor at '%s': %m", desc_path);
+
+ desc_len = read(fd, desc, sizeof(desc));
+ if (desc_len < 0)
+ return log_device_error_errno(hid_device, errno,
+ "Failed to read report descriptor at '%s': %m", desc_path);
+ if (desc_len == 0)
+ return log_device_debug_errno(hid_device, SYNTHETIC_ERRNO(EINVAL),
+ "Empty report descriptor at '%s'.", desc_path);
+
+ r = is_fido_security_token_desc(desc, desc_len);
+ if (r < 0)
+ return log_device_debug_errno(hid_device, r,
+ "Failed to parse report descriptor at '%s'.", desc_path);
+ if (r > 0) {
+ printf("ID_FIDO_TOKEN=1\n");
+ printf("ID_SECURITY_TOKEN=1\n");
+ }
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/udev/fido_id/fido_id_desc.c b/src/udev/fido_id/fido_id_desc.c
new file mode 100644
index 0000000..2dfa759
--- /dev/null
+++ b/src/udev/fido_id/fido_id_desc.c
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "fido_id_desc.h"
+
+#define HID_RPTDESC_FIRST_BYTE_LONG_ITEM 0xfeu
+#define HID_RPTDESC_TYPE_GLOBAL 0x1u
+#define HID_RPTDESC_TYPE_LOCAL 0x2u
+#define HID_RPTDESC_TAG_USAGE_PAGE 0x0u
+#define HID_RPTDESC_TAG_USAGE 0x0u
+
+/*
+ * HID usage for FIDO CTAP1 ("U2F") and CTAP2 security tokens.
+ * https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-u2f_hid.h-v1.0-ps-20141009.txt
+ * https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#usb-discovery
+ * https://www.usb.org/sites/default/files/hutrr48.pdf
+ */
+#define FIDO_FULL_USAGE_CTAPHID 0xf1d00001u
+
+/*
+ * Parses a HID report descriptor and identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on their
+ * declared usage.
+ * A positive return value indicates that the report descriptor belongs to a FIDO security token.
+ * https://www.usb.org/sites/default/files/documents/hid1_11.pdf (Section 6.2.2)
+ */
+int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len) {
+ uint32_t usage = 0;
+
+ for (size_t pos = 0; pos < desc_len; ) {
+ uint8_t tag, type, size_code;
+ size_t size;
+ uint32_t value;
+
+ /* Report descriptors consists of short items (1-5 bytes) and long items (3-258 bytes). */
+ if (desc[pos] == HID_RPTDESC_FIRST_BYTE_LONG_ITEM) {
+ /* No long items are defined in the spec; skip them.
+ * The length of the data in a long item is contained in the byte after the long
+ * item tag. The header consists of three bytes: special long item tag, length,
+ * actual tag. */
+ if (pos + 1 >= desc_len)
+ return -EINVAL;
+ pos += desc[pos + 1] + 3;
+ continue;
+ }
+
+ /* The first byte of a short item encodes tag, type and size. */
+ tag = desc[pos] >> 4; /* Bits 7 to 4 */
+ type = (desc[pos] >> 2) & 0x3; /* Bits 3 and 2 */
+ size_code = desc[pos] & 0x3; /* Bits 1 and 0 */
+ /* Size is coded as follows:
+ * 0 -> 0 bytes, 1 -> 1 byte, 2 -> 2 bytes, 3 -> 4 bytes
+ */
+ size = size_code < 3 ? size_code : 4;
+ /* Consume header byte. */
+ pos++;
+
+ /* Extract the item value coded on size bytes. */
+ if (pos + size > desc_len)
+ return -EINVAL;
+ value = 0;
+ for (size_t i = 0; i < size; i++)
+ value |= (uint32_t) desc[pos + i] << (8 * i);
+ /* Consume value bytes. */
+ pos += size;
+
+ if (type == HID_RPTDESC_TYPE_GLOBAL && tag == HID_RPTDESC_TAG_USAGE_PAGE) {
+ /* A usage page is a 16 bit value coded on at most 16 bits. */
+ if (size > 2)
+ return -EINVAL;
+ /* A usage page sets the upper 16 bits of a following usage. */
+ usage = (value & 0x0000ffffu) << 16;
+ }
+
+ if (type == HID_RPTDESC_TYPE_LOCAL && tag == HID_RPTDESC_TAG_USAGE) {
+ /* A usage is a 32 bit value, but is prepended with the current usage page if
+ * coded on less than 4 bytes (that is, at most 2 bytes). */
+ if (size == 4)
+ usage = value;
+ else
+ usage = (usage & 0xffff0000u) | (value & 0x0000ffffu);
+ if (usage == FIDO_FULL_USAGE_CTAPHID)
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/udev/fido_id/fido_id_desc.h b/src/udev/fido_id/fido_id_desc.h
new file mode 100644
index 0000000..57af57e
--- /dev/null
+++ b/src/udev/fido_id/fido_id_desc.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len);
diff --git a/src/udev/fido_id/fuzz-fido-id-desc.c b/src/udev/fido_id/fuzz-fido-id-desc.c
new file mode 100644
index 0000000..dd2ae5b
--- /dev/null
+++ b/src/udev/fido_id/fuzz-fido-id-desc.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/hid.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "fido_id_desc.h"
+#include "fuzz.h"
+#include "log.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ /* We don't want to fill the logs with messages about parse errors.
+ * Disable most logging if not running standalone */
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ if (outside_size_range(size, 0, HID_MAX_DESCRIPTOR_SIZE))
+ return 0;
+
+ (void) is_fido_security_token_desc(data, size);
+
+ return 0;
+}
diff --git a/src/udev/fido_id/test-fido-id-desc.c b/src/udev/fido_id/test-fido-id-desc.c
new file mode 100644
index 0000000..36c777a
--- /dev/null
+++ b/src/udev/fido_id/test-fido-id-desc.c
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "fido_id_desc.h"
+#include "macro.h"
+#include "tests.h"
+
+TEST(is_fido_security_token_desc__fido) {
+ static const uint8_t FIDO_HID_DESC_1[] = {
+ 0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+ 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
+ 0x40, 0x91, 0x02, 0xc0,
+ };
+ assert_se(is_fido_security_token_desc(FIDO_HID_DESC_1, sizeof(FIDO_HID_DESC_1)) > 0);
+
+ static const uint8_t FIDO_HID_DESC_2[] = {
+ 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
+ 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
+ 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
+ 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
+ 0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
+ 0x06, 0xd0, 0xf1, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+ 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
+ 0x40, 0x91, 0x02, 0xc0,
+ };
+ assert_se(is_fido_security_token_desc(FIDO_HID_DESC_2, sizeof(FIDO_HID_DESC_2)) > 0);
+}
+
+TEST(is_fido_security_token_desc__non_fido) {
+ /* Wrong usage page */
+ static const uint8_t NON_FIDO_HID_DESC_1[] = {
+ 0x06, 0xd0, 0xf0, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+ 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
+ 0x40, 0x91, 0x02, 0xc0,
+ };
+ assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_1, sizeof(NON_FIDO_HID_DESC_1)) == 0);
+
+ /* Wrong usage */
+ static const uint8_t NON_FIDO_HID_DESC_2[] = {
+ 0x06, 0xd0, 0xf1, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75,
+ 0x08, 0x95, 0x40, 0x81, 0x02, 0x09, 0x21, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
+ 0x40, 0x91, 0x02, 0xc0,
+ };
+ assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_2, sizeof(NON_FIDO_HID_DESC_2)) == 0);
+
+ static const uint8_t NON_FIDO_HID_DESC_3[] = {
+ 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25,
+ 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05,
+ 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 0x95, 0x01, 0x75, 0x03, 0x91,
+ 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65,
+ 0x81, 0x00, 0x09, 0x03, 0x75, 0x08, 0x95, 0x08, 0xb1, 0x02, 0xc0,
+ };
+ assert_se(is_fido_security_token_desc(NON_FIDO_HID_DESC_3, sizeof(NON_FIDO_HID_DESC_3)) == 0);
+}
+
+TEST(is_fido_security_token_desc__invalid) {
+ /* Size coded on 1 byte, but no byte given */
+ static const uint8_t INVALID_HID_DESC_1[] = { 0x01 };
+ assert_se(is_fido_security_token_desc(INVALID_HID_DESC_1, sizeof(INVALID_HID_DESC_1)) < 0);
+
+ /* Size coded on 2 bytes, but only 1 byte given */
+ static const uint8_t INVALID_HID_DESC_2[] = { 0x02, 0x01 };
+ assert_se(is_fido_security_token_desc(INVALID_HID_DESC_2, sizeof(INVALID_HID_DESC_2)) < 0);
+
+ /* Size coded on 4 bytes, but only 3 bytes given */
+ static const uint8_t INVALID_HID_DESC_3[] = { 0x03, 0x01, 0x02, 0x03 };
+ assert_se(is_fido_security_token_desc(INVALID_HID_DESC_3, sizeof(INVALID_HID_DESC_3)) < 0);
+
+ /* Long item without a size byte */
+ static const uint8_t INVALID_HID_DESC_4[] = { 0xfe };
+ assert_se(is_fido_security_token_desc(INVALID_HID_DESC_4, sizeof(INVALID_HID_DESC_4)) < 0);
+
+ /* Usage pages are coded on at most 2 bytes */
+ static const uint8_t INVALID_HID_DESC_5[] = { 0x07, 0x01, 0x02, 0x03, 0x04 };
+ assert_se(is_fido_security_token_desc(INVALID_HID_DESC_5, sizeof(INVALID_HID_DESC_5)) < 0);
+}
+
+DEFINE_TEST_MAIN(LOG_INFO);