aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndy Lutomirski <luto@amacapital.net>2014-12-09 21:28:04 -0800
committerAndy Lutomirski <luto@amacapital.net>2014-12-14 09:58:11 -0800
commitcd8d7c881272ad4e8312eb156baa18d9d651c6a7 (patch)
treead831d0a73f0e9e49aba2fac6388cd87d362db94
parentcad9d87d306615a0f527ce25833c2f886a348b3e (diff)
downloaddevel-u2f.tar.gz
hid-u2f: New driveru2f
Signed-off-by: Andy Lutomirski <luto@amacapital.net>
-rw-r--r--drivers/hid/Kconfig11
-rw-r--r--drivers/hid/Makefile1
-rw-r--r--drivers/hid/hid-u2f.c388
3 files changed, 400 insertions, 0 deletions
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f42df4dd58d28..cfc87e1ef8980 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -774,6 +774,17 @@ config THRUSTMASTER_FF
a THRUSTMASTER Dual Trigger 3-in-1 or a THRUSTMASTER Ferrari GT
Rumble Force or Force Feedback Wheel.
+config HID_U2F
+ tristate "FIDO U2F HID device support"
+ depends on HID
+ ---help---
+ This driver supports "Universal 2nd Factor" devices compliant
+ with the FIDO Alliance specification.
+
+ In contrast to accessing these devices using hidraw, the U2F
+ driver provides real enumeration, a character device, and proper
+ isolation between client applications.
+
config HID_WACOM
tristate "Wacom Intuos/Graphire tablet support (USB)"
depends on HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e2850d8af9ca3..72a869dff5331 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -113,6 +113,7 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o
obj-$(CONFIG_HID_TIVO) += hid-tivo.o
obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o
obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o
+obj-$(CONFIG_HID_U2F) += hid-u2f.o
obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o
obj-$(CONFIG_HID_XINMO) += hid-xinmo.o
obj-$(CONFIG_HID_ZEROPLUS) += hid-zpff.o
diff --git a/drivers/hid/hid-u2f.c b/drivers/hid/hid-u2f.c
new file mode 100644
index 0000000000000..62897530c5728
--- /dev/null
+++ b/drivers/hid/hid-u2f.c
@@ -0,0 +1,388 @@
+/*
+ * FIDO Alliance U2F driver
+ *
+ * Copyright (c) 2014 Andy Lutomirski
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include "hid-ids.h"
+
+#define U2FHID_FRAME_TYPE_MASK 0x80
+#define U2FHID_FRAME_TYPE_INIT 0x80
+#define U2FHID_FRAME_TYPE_CONT 0x00
+
+#define U2FHID_CMD_INIT 0x06
+
+#define U2FHID_REPORT_SIZE 64
+
+struct u2fhid_header {
+ u8 channel_id[4];
+ u8 type;
+} __packed;
+
+struct u2fhid_frame {
+ u8 channel_id[4];
+
+ /*
+ * If the high bit is set, this is an INIT (initial) frame. If not,
+ * then this is a CONT (continuation) frame.
+ */
+ u8 type;
+ union {
+ struct {
+ __be16 len;
+ u8 data[U2FHID_REPORT_SIZE - 7];
+ } __packed init;
+
+ struct {
+ u8 data[U2FHID_REPORT_SIZE - 5];
+ } __packed cont;
+ } __packed;
+} __packed;
+
+struct u2fhid_init_req {
+ u8 nonce[8];
+} __packed;
+
+struct u2fhid_init_resp {
+ u8 nonce[8];
+ u8 channel_id[4];
+ u8 u2fhid_version;
+ u8 major_device_version;
+ u8 minor_device_version;
+ u8 build_device_version;
+ u8 capflags;
+};
+
+struct u2f {
+ u8 channel_id[4];
+
+ spinlock_t lock;
+
+ wait_queue_head_t wq;
+
+ /* Any change due to data from the device will wake wq. */
+ int is_receiving;
+
+ /* Valid iff is_receiving. */
+ struct u2f_recv {
+ void *buffer; /* NULL if the receive has been abandoned. */
+ size_t buffer_len;
+
+ bool got_first;
+ size_t total_len;
+ size_t len_received;
+ u8 response_type; /* only if got_first */
+
+ bool check_nonce;
+ u8 nonce[8];
+ } recv;
+};
+
+/*
+static struct class *u2f_class;
+
+static const struct attribute_group *u2f_groups[] = {
+ NULL,
+};
+*/
+
+static int u2f_sendrecv(struct hid_device *hdev, struct u2f *u2f,
+ u8 cmd, const void *out, int outlen,
+ void *in, int inlen)
+{
+ int ret;
+ struct {
+ u8 reportnum;
+ struct u2fhid_frame frame;
+ } __packed frame;
+
+ BUILD_BUG_ON(sizeof(struct u2fhid_frame) != U2FHID_REPORT_SIZE);
+ BUILD_BUG_ON(sizeof(frame) != U2FHID_REPORT_SIZE + 1);
+
+ memset(&frame, 0, sizeof(frame));
+ memcpy(frame.frame.channel_id, u2f->channel_id, 4);
+ frame.frame.type = cmd | U2FHID_FRAME_TYPE_INIT;
+ frame.frame.init.len = cpu_to_be16(outlen);
+ memcpy(frame.frame.init.data, out, outlen);
+
+ // TODO: Handle send fragmentation
+
+ //print_hex_dump(KERN_ERR, "u2f: ", DUMP_PREFIX_OFFSET,
+ // 16, 1, &frame, sizeof(frame), 0);
+
+ spin_lock_irq(&u2f->lock);
+
+ ACCESS_ONCE(u2f->is_receiving) = 1;
+ memset(&u2f->recv, 0, sizeof(u2f->recv));
+ u2f->recv.buffer = in;
+ u2f->recv.buffer_len = inlen;
+
+ BUG_ON(!ACCESS_ONCE(u2f->is_receiving));
+
+ spin_unlock_irq(&u2f->lock);
+
+ ret = hid_hw_output_report(hdev, (u8 *)&frame, sizeof(frame));
+ if (ret < 0) {
+ dev_err(&hdev->dev, "send failed");
+ return ret;
+ }
+
+ wait_event_interruptible(u2f->wq, !ACCESS_ONCE(u2f->is_receiving));
+
+ spin_lock_irq(&u2f->lock);
+ u2f->recv.buffer = NULL;
+ ret = u2f->recv.total_len;
+ spin_unlock_irq(&u2f->lock);
+
+ return ret;
+}
+
+static int u2f_init_channel(struct hid_device *hdev, struct u2f *u2f)
+{
+ int ret;
+ struct u2fhid_init_req initreq;
+ struct u2fhid_init_resp initresp;
+
+ /* Until we are assigned a channel, use 0xffffffff. */
+ memset(u2f->channel_id, 0xff, sizeof(u2f->channel_id));
+
+ get_random_bytes(initreq.nonce, sizeof(initreq.nonce));
+
+ ret = u2f_sendrecv(hdev, u2f, U2FHID_CMD_INIT,
+ &initreq, sizeof(initreq),
+ &initresp, sizeof(initresp));
+ if (ret < 0)
+ return ret;
+ if (ret < sizeof(struct u2fhid_init_resp)) {
+ dev_err(&hdev->dev, "U2FHID_INIT response was too short\n");
+ return -EIO;
+ }
+ if (memcmp(initresp.nonce, initreq.nonce, sizeof(initreq.nonce))) {
+ /*
+ * This indicates a race against a hidraw user. We could
+ * add code to survive the race, but the race is unlikely,
+ * so just bail if it happens.
+ */
+ dev_err(&hdev->dev, "U2FHID_INIT nonce mismatch\n");
+ return -EIO;
+ }
+
+ memcpy(u2f->channel_id, &initresp.channel_id, sizeof(u2f->channel_id));
+
+ dev_info(&hdev->dev, "U2FHID v%d, device v%d.%d.%d, caps 0x%x\n",
+ (int)initresp.u2fhid_version,
+ (int)initresp.major_device_version,
+ (int)initresp.minor_device_version,
+ (int)initresp.build_device_version,
+ (int)initresp.capflags);
+
+ return 0;
+}
+
+static int u2f_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int retval;
+ struct u2f *u2f;
+
+ retval = hid_parse(hdev);
+ if (retval) {
+ hid_err(hdev, "parse failed\n");
+ goto exit;
+ }
+
+ /*
+ * U2F isn't an input device, and it uses raw reports, so trying
+ * to access it with hiddev makes no sense.
+ */
+ retval = hid_hw_start(hdev, HID_CONNECT_DRIVER | HID_CONNECT_HIDRAW);
+ if (retval) {
+ hid_err(hdev, "hw start failed\n");
+ goto exit;
+ }
+
+ u2f = devm_kzalloc(&hdev->dev, sizeof(*u2f), GFP_KERNEL);
+ if (!u2f) {
+ retval = -ENOMEM;
+ goto exit_stop;
+ }
+
+ spin_lock_init(&u2f->lock);
+ init_waitqueue_head(&u2f->wq);
+
+ hid_set_drvdata(hdev, u2f);
+
+ hid_device_io_start(hdev);
+ retval = hid_hw_open(hdev);
+ if (retval) {
+ dev_err(&hdev->dev, "failed to open device");
+ goto exit_stop;
+ }
+
+ retval = u2f_init_channel(hdev, u2f);
+ if (retval != 0)
+ goto exit_close;
+
+ return 0;
+
+exit_close:
+ hid_hw_close(hdev);
+ hid_device_io_stop(hdev);
+exit_stop:
+ hid_hw_stop(hdev);
+exit:
+ return retval;
+}
+
+static void u2f_remove(struct hid_device *hdev)
+{
+ hid_hw_close(hdev);
+ hid_hw_stop(hdev);
+}
+
+static int u2f_raw_event(struct hid_device *hdev,
+ struct hid_report *report, u8 *data, int size)
+{
+ struct u2f *u2f = hid_get_drvdata(hdev);
+ struct u2fhid_header *header;
+ unsigned long flags;
+ void *payload;
+ size_t payload_len;
+
+ //dev_info(&hdev->dev, "Got %d bytes\n", size);
+
+ //print_hex_dump(KERN_ERR, "resp: ", DUMP_PREFIX_OFFSET,
+ // 16, 1, data, size, 0);
+
+ if (size < sizeof(struct u2fhid_header))
+ return 0;
+
+ spin_lock_irqsave(&u2f->lock, flags);
+
+ if (!u2f->is_receiving)
+ goto out;
+
+ header = (struct u2fhid_header *)data;
+ if (memcmp(header->channel_id, u2f->channel_id, 4) != 0)
+ goto out;
+
+ /* Handle the frame header. */
+ if (!u2f->recv.got_first) {
+ if ((header->type & U2FHID_FRAME_TYPE_MASK) !=
+ U2FHID_FRAME_TYPE_INIT) {
+ dev_err(&hdev->dev, "got continuation instead of init\n");
+ /* XXX: error out? */
+ goto out;
+ }
+
+ if (size < sizeof(struct u2fhid_header) + 2) {
+ dev_err(&hdev->dev, "got short frame\n");
+ goto out;
+ }
+
+ u2f->recv.response_type =
+ header->type & ~U2FHID_FRAME_TYPE_MASK;
+ u2f->recv.total_len =
+ be16_to_cpu(*(__be16*)(header + 1));
+ u2f->recv.got_first = true;
+
+ payload = data + (sizeof(struct u2fhid_header) + 2);
+ payload_len = size - (sizeof(struct u2fhid_header) + 2);
+ } else {
+ if (header->type !=
+ (u2f->recv.response_type | U2FHID_FRAME_TYPE_CONT)) {
+ dev_err(&hdev->dev, "bad continuation\n");
+ goto out;
+ }
+
+ payload = header + 1;
+ payload_len = size - sizeof(struct u2fhid_header);
+ }
+
+ /* Truncate excess data. */
+ if (payload_len > u2f->recv.total_len - u2f->recv.len_received)
+ payload_len = u2f->recv.total_len - u2f->recv.len_received;
+
+ /* Store the payload, if appropriate. */
+ if (u2f->recv.buffer && u2f->recv.len_received < u2f->recv.buffer_len) {
+ memcpy(u2f->recv.buffer + u2f->recv.len_received,
+ payload,
+ min(payload_len,
+ u2f->recv.buffer_len - u2f->recv.len_received));
+ }
+ u2f->recv.len_received += payload_len;
+
+ if (u2f->recv.len_received == u2f->recv.total_len) {
+ ACCESS_ONCE(u2f->is_receiving) = 0;
+ wake_up_all(&u2f->wq);
+ }
+
+out:
+ spin_unlock_irqrestore(&u2f->lock, flags);
+
+ return 0;
+}
+
+static const struct hid_device_id u2f_devices[] = {
+ { HID_DEVICE(HID_BUS_ANY, HID_GROUP_FIDO_U2F, HID_ANY_ID, HID_ANY_ID) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(hid, u2f_devices);
+
+static struct hid_driver u2f_driver = {
+ .name = "u2f",
+ .id_table = u2f_devices,
+ .probe = u2f_probe,
+ .remove = u2f_remove,
+ .raw_event = u2f_raw_event
+};
+
+static int __init u2f_init(void)
+{
+ int retval;
+
+ /*
+ u2f_class = class_create(THIS_MODULE, "u2f");
+ if (IS_ERR(u2f_class))
+ return PTR_ERR(u2f_class);
+ u2f_class->dev_groups = u2f_groups;
+ */
+
+ retval = hid_register_driver(&u2f_driver);
+ /*
+ if (retval)
+ class_destroy(u2f_class);
+ */
+ return retval;
+}
+
+static void __exit u2f_exit(void)
+{
+ hid_unregister_driver(&u2f_driver);
+ //class_destroy(u2f_class);
+}
+
+module_init(u2f_init);
+module_exit(u2f_exit);
+
+MODULE_AUTHOR("Andy Lutomirski");
+MODULE_DESCRIPTION("FIDO U2F HID Driver");
+MODULE_LICENSE("GPL v2");