git: 4a04e0a6c703 - main - u2f(4): a HID driver for FIDO/U2F security keys

From: Vladimir Kondratyev <wulf_at_FreeBSD.org>
Date: Sun, 17 Aug 2025 21:02:31 UTC
The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=4a04e0a6c703db9d2d9e6a0ef2b000644143b705

commit 4a04e0a6c703db9d2d9e6a0ef2b000644143b705
Author:     Vladimir Kondratyev <wulf@FreeBSD.org>
AuthorDate: 2025-08-17 21:00:45 +0000
Commit:     Vladimir Kondratyev <wulf@FreeBSD.org>
CommitDate: 2025-08-17 21:00:45 +0000

    u2f(4): a HID driver for FIDO/U2F security keys
    
    While FIDO/U2F keys were already supported by the generic uhid(4) and
    hidraw(4) drivers, this driver adds some additional features an does
    steps to tighten the security of FIDO/U2F access.
    
    - It automatically loads through devd.
    - Automatically enables HQ_NO_READAHEAD for FIDO/U2F devices.
    - Implements only miminum set of features.
    - Do not requires external devfs configuration to set character device
      permissions.
    - Names character device as u2f/# to make possible capsicum or any
      other pledge()-style sandboxing.
    
    PR:             265528
    Differential Revision:  https://reviews.freebsd.org/D51612
---
 share/man/man4/Makefile      |   1 +
 share/man/man4/u2f.4         |  93 +++++++
 sys/amd64/conf/GENERIC       |   1 +
 sys/arm/conf/GENERIC         |   1 +
 sys/arm64/conf/std.dev       |   1 +
 sys/conf/NOTES               |   2 +
 sys/conf/files               |   1 +
 sys/conf/options             |   1 +
 sys/dev/hid/u2f.c            | 590 +++++++++++++++++++++++++++++++++++++++++++
 sys/i386/conf/GENERIC        |   1 +
 sys/modules/hid/Makefile     |   1 +
 sys/modules/hid/u2f/Makefile |   8 +
 sys/powerpc/conf/GENERIC64   |   1 +
 sys/powerpc/conf/GENERIC64LE |   1 +
 sys/riscv/conf/GENERIC       |   1 +
 15 files changed, 704 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index f93610631bb3..519b113b0a2e 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -594,6 +594,7 @@ MAN=	aac.4 \
 	tty.4 \
 	tun.4 \
 	tws.4 \
+	u2f.4 \
 	udp.4 \
 	udplite.4 \
 	${_ufshci.4} \
diff --git a/share/man/man4/u2f.4 b/share/man/man4/u2f.4
new file mode 100644
index 000000000000..7f0e076028db
--- /dev/null
+++ b/share/man/man4/u2f.4
@@ -0,0 +1,93 @@
+.\" $OpenBSD: fido.4,v 1.4 2020/08/21 19:02:46 mglocker Exp $
+.\"
+.\" Copyright (c) 2019 Reyk Floeter <reyk@openbsd.org>
+.\" Copyright (c) 2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd August 21, 2023
+.Dt U2F 4
+.Os
+.Sh NAME
+.Nm u2f
+.Nd FIDO/U2F security keys
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device u2f"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+u2f_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for FIDO/U2F-compatible USB security keys.
+They are Human Interface Devices (HID) which can be accessed via the
+.Pa /dev/u2f/N
+interface.
+.Pp
+The driver is compatible with the
+.Xr read 2 ,
+.Xr write 2 ,
+and
+.Xr ioctl 2
+operations of the generic
+.Xr uhid 4
+device but only accepts the optional HID
+.Xr ioctl 2
+calls from u2f group users.
+.Sh SYSCTL VARIABLES
+The following variables are available as both
+.Xr sysctl 8
+variables and
+.Xr loader 8
+tunables:
+.Bl -tag -width indent
+.It Va hw.hid.u2f.debug
+Debug output level, where 0 is debugging disabled and larger values increase
+debug message verbosity.
+Default is 0.
+.El
+.Sh FILES
+.Bl -tag -width /dev/u2f/* -compact
+.It Pa /dev/u2f/*
+.El
+.Sh SEE ALSO
+.Xr uhid 4 ,
+.Xr usbhid 4 ,
+.Xr usb 4
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 15.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .
+.Pp
+This manual page was written by
+.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org
+based on the
+.Ox
+.Xr fido 4
+manual page.
diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
index 33f1630d4baa..81427b5b18b6 100644
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -386,6 +386,7 @@ options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 device		hidbus			# Generic HID Bus
 options 	IICHID_SAMPLING		# Workaround missing GPIO INTR support
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
 
 # EFI devices
 device		efidev			# EFI pseudo-device
diff --git a/sys/arm/conf/GENERIC b/sys/arm/conf/GENERIC
index 7394f3842d43..26b0c7bf0294 100644
--- a/sys/arm/conf/GENERIC
+++ b/sys/arm/conf/GENERIC
@@ -261,6 +261,7 @@ device		aw_thermal	# Allwinner Thermal Sensor Controller
 # HID support
 device		hid		# Generic HID support
 device		hidbus		# Generic HID Bus
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
 
 # Flattened Device Tree
 options 	FDT			# Configure using FDT/DTB data
diff --git a/sys/arm64/conf/std.dev b/sys/arm64/conf/std.dev
index c5c364ffda04..719f272426dd 100644
--- a/sys/arm64/conf/std.dev
+++ b/sys/arm64/conf/std.dev
@@ -115,6 +115,7 @@ device		mmcsd			# mmc/sd flash cards
 options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 device		hidbus			# Generic HID Bus
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
 
 # Firmware
 device		mmio_sram		# Generic on-chip SRAM
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 2458756ae350..1338f47199a9 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2446,6 +2446,8 @@ device		hmt		# HID multitouch (MS-compatible)
 device		hpen		# Generic pen driver
 device		hsctrl		# System controls
 device		ps4dshock	# Sony PS4 DualShock 4 gamepad driver
+device		u2f		# FIDO/U2F authenticator
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
 device		xb360gp		# XBox 360 gamepad driver
 
 #####################################################################
diff --git a/sys/conf/files b/sys/conf/files
index be65ed20d6aa..35318dbc8367 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1750,6 +1750,7 @@ dev/hid/hpen.c			optional hpen
 dev/hid/hsctrl.c		optional hsctrl
 dev/hid/ietp.c			optional ietp
 dev/hid/ps4dshock.c		optional ps4dshock
+dev/hid/u2f.c			optional u2f
 dev/hid/xb360gp.c		optional xb360gp
 dev/hifn/hifn7751.c		optional hifn
 dev/hptiop/hptiop.c		optional hptiop scbus
diff --git a/sys/conf/options b/sys/conf/options
index a637b0b74a77..bb3d08e88e21 100644
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -1009,6 +1009,7 @@ IICHID_DEBUG	opt_hid.h
 IICHID_SAMPLING	opt_hid.h
 HKBD_DFLT_KEYMAP	opt_hkbd.h
 HIDRAW_MAKE_UHID_ALIAS	opt_hid.h
+U2F_MAKE_UHID_ALIAS	opt_hid.h
 
 # kenv options
 # The early kernel environment (loader environment, config(8)-provided static)
diff --git a/sys/dev/hid/u2f.c b/sys/dev/hid/u2f.c
new file mode 100644
index 000000000000..ac2eba7a499d
--- /dev/null
+++ b/sys/dev/hid/u2f.c
@@ -0,0 +1,590 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022-2023 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#ifdef COMPAT_FREEBSD32
+#include <sys/abi_compat.h>
+#endif
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/filio.h>
+#include <sys/ioccom.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/poll.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/selinfo.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/uio.h>
+
+#include <dev/evdev/input.h>
+
+#define HID_DEBUG_VAR	u2f_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/usb/usb_ioctl.h>
+
+#ifdef HID_DEBUG
+static int u2f_debug = 0;
+static SYSCTL_NODE(_hw_hid, OID_AUTO, u2f, CTLFLAG_RW, 0,
+    "FIDO/U2F authenticator");
+SYSCTL_INT(_hw_hid_u2f, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &u2f_debug, 0, "Debug level");
+#endif
+
+#define	U2F_MAX_REPORT_SIZE	64
+
+/* A match on these entries will load u2f */
+static const struct hid_device_id u2f_devs[] = {
+	{ HID_BUS(BUS_USB), HID_TLC(HUP_FIDO, HUF_U2FHID) },
+};
+
+struct u2f_softc {
+	device_t sc_dev;		/* base device */
+	struct cdev *dev;
+
+	struct mtx sc_mtx;		/* hidbus private mutex */
+	void *sc_rdesc;
+	hid_size_t sc_rdesc_size;
+	hid_size_t sc_isize;
+	hid_size_t sc_osize;
+	struct selinfo sc_rsel;
+	struct {			/* driver state */
+		bool	open:1;		/* device is open */
+		bool	aslp:1;		/* waiting for device data in read() */
+		bool	sel:1;		/* waiting for device data in poll() */
+		bool	data:1;		/* input report is stored in sc_buf */
+		int	reserved:28;
+	} sc_state;
+	int sc_fflags;			/* access mode for open lifetime */
+
+	uint8_t sc_buf[U2F_MAX_REPORT_SIZE];
+};
+
+static d_open_t		u2f_open;
+static d_read_t		u2f_read;
+static d_write_t	u2f_write;
+static d_ioctl_t	u2f_ioctl;
+static d_poll_t		u2f_poll;
+static d_kqfilter_t	u2f_kqfilter;
+
+static d_priv_dtor_t	u2f_dtor;
+
+static struct cdevsw u2f_cdevsw = {
+	.d_version =	D_VERSION,
+	.d_open =	u2f_open,
+	.d_read =	u2f_read,
+	.d_write =	u2f_write,
+	.d_ioctl =	u2f_ioctl,
+	.d_poll =	u2f_poll,
+	.d_kqfilter =	u2f_kqfilter,
+	.d_name =	"u2f",
+};
+
+static hid_intr_t	u2f_intr;
+
+static device_probe_t	u2f_probe;
+static device_attach_t	u2f_attach;
+static device_detach_t	u2f_detach;
+
+static int		u2f_kqread(struct knote *, long);
+static void		u2f_kqdetach(struct knote *);
+static void		u2f_notify(struct u2f_softc *);
+
+static struct filterops u2f_filterops_read = {
+	.f_isfd =	1,
+	.f_detach =	u2f_kqdetach,
+	.f_event =	u2f_kqread,
+};
+
+static int
+u2f_probe(device_t dev)
+{
+	int error;
+
+	error = HIDBUS_LOOKUP_DRIVER_INFO(dev, u2f_devs);
+	if (error != 0)
+                return (error);
+
+	hidbus_set_desc(dev, "Authenticator");
+
+	return (BUS_PROBE_GENERIC);
+}
+
+static int
+u2f_attach(device_t dev)
+{
+	struct u2f_softc *sc = device_get_softc(dev);
+	struct hid_device_info *hw = __DECONST(struct hid_device_info *,
+	    hid_get_device_info(dev));
+	struct make_dev_args mda;
+	int error;
+
+	sc->sc_dev = dev;
+
+	error = hid_get_report_descr(dev, &sc->sc_rdesc, &sc->sc_rdesc_size);
+	if (error != 0)
+		return (ENXIO);
+	sc->sc_isize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size,
+	    hid_input, NULL);
+	if (sc->sc_isize > U2F_MAX_REPORT_SIZE) {
+		device_printf(dev, "Input report size too large. Truncate.\n");
+		sc->sc_isize = U2F_MAX_REPORT_SIZE;
+	}
+	sc->sc_osize = hid_report_size_max(sc->sc_rdesc, sc->sc_rdesc_size,
+	    hid_output, NULL);
+	if (sc->sc_osize > U2F_MAX_REPORT_SIZE) {
+		device_printf(dev, "Output report size too large. Truncate.\n");
+		sc->sc_osize = U2F_MAX_REPORT_SIZE;
+	}
+
+	mtx_init(&sc->sc_mtx, "u2f lock", NULL, MTX_DEF);
+	knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx);
+
+	make_dev_args_init(&mda);
+	mda.mda_flags = MAKEDEV_WAITOK;
+	mda.mda_devsw = &u2f_cdevsw;
+	mda.mda_uid = UID_ROOT;
+	mda.mda_gid = GID_U2F;
+	mda.mda_mode = 0660;
+	mda.mda_si_drv1 = sc;
+
+	error = make_dev_s(&mda, &sc->dev, "u2f/%d", device_get_unit(dev));
+	if (error) {
+		device_printf(dev, "Can not create character device\n");
+		u2f_detach(dev);
+		return (error);
+	}
+#ifdef U2F_MAKE_UHID_ALIAS
+	(void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(dev));
+#endif
+
+	hid_add_dynamic_quirk(hw, HQ_NO_READAHEAD);
+
+	hidbus_set_lock(dev, &sc->sc_mtx);
+	hidbus_set_intr(dev, u2f_intr, sc);
+
+	return (0);
+}
+
+static int
+u2f_detach(device_t dev)
+{
+	struct u2f_softc *sc = device_get_softc(dev);
+
+	DPRINTF("sc=%p\n", sc);
+
+	if (sc->dev != NULL) {
+		mtx_lock(&sc->sc_mtx);
+		sc->dev->si_drv1 = NULL;
+		/* Wake everyone */
+		u2f_notify(sc);
+		mtx_unlock(&sc->sc_mtx);
+		destroy_dev(sc->dev);
+	}
+
+	hid_intr_stop(sc->sc_dev);
+
+	knlist_clear(&sc->sc_rsel.si_note, 0);
+	knlist_destroy(&sc->sc_rsel.si_note);
+	seldrain(&sc->sc_rsel);
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+void
+u2f_intr(void *context, void *buf, hid_size_t len)
+{
+	struct u2f_softc *sc = context;
+
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	DPRINTFN(5, "len=%d\n", len);
+	DPRINTFN(5, "data = %*D\n", len, buf, " ");
+
+	if (sc->sc_state.data)
+		return;
+
+	if (len > sc->sc_isize)
+		len = sc->sc_isize;
+
+	bcopy(buf, sc->sc_buf, len);
+
+	/* Make sure we don't process old data */
+	if (len < sc->sc_isize)
+		bzero(sc->sc_buf + len, sc->sc_isize - len);
+
+	sc->sc_state.data = true;
+
+	u2f_notify(sc);
+}
+
+static int
+u2f_open(struct cdev *dev, int flag, int mode, struct thread *td)
+{
+	struct u2f_softc *sc = dev->si_drv1;
+	int error;
+
+	if (sc == NULL)
+		return (ENXIO);
+
+	DPRINTF("sc=%p\n", sc);
+
+	mtx_lock(&sc->sc_mtx);
+	if (sc->sc_state.open) {
+		mtx_unlock(&sc->sc_mtx);
+		return (EBUSY);
+	}
+	sc->sc_state.open = true;
+	mtx_unlock(&sc->sc_mtx);
+
+	error = devfs_set_cdevpriv(sc, u2f_dtor);
+	if (error != 0) {
+		mtx_lock(&sc->sc_mtx);
+		sc->sc_state.open = false;
+		mtx_unlock(&sc->sc_mtx);
+		return (error);
+	}
+
+	/* Set up interrupt pipe. */
+	sc->sc_state.data = false;
+	sc->sc_fflags = flag;
+
+	return (0);
+}
+
+
+static void
+u2f_dtor(void *data)
+{
+	struct u2f_softc *sc = data;
+
+#ifdef NOT_YET
+	/* Disable interrupts. */
+	hid_intr_stop(sc->sc_dev);
+#endif
+
+	mtx_lock(&sc->sc_mtx);
+	sc->sc_state.open = false;
+	mtx_unlock(&sc->sc_mtx);
+}
+
+static int
+u2f_read(struct cdev *dev, struct uio *uio, int flag)
+{
+	uint8_t buf[U2F_MAX_REPORT_SIZE];
+	struct u2f_softc *sc = dev->si_drv1;
+	size_t length = 0;
+	int error;
+
+	DPRINTFN(1, "\n");
+
+	if (sc == NULL)
+		return (EIO);
+
+	if (!sc->sc_state.data)
+		hid_intr_start(sc->sc_dev);
+
+	mtx_lock(&sc->sc_mtx);
+	if (dev->si_drv1 == NULL) {
+		error = EIO;
+		goto exit;
+	}
+
+	while (!sc->sc_state.data) {
+		if (flag & O_NONBLOCK) {
+			error = EWOULDBLOCK;
+			goto exit;
+		}
+		sc->sc_state.aslp = true;
+		DPRINTFN(5, "sleep on %p\n", &sc->sc_buf);
+		error = mtx_sleep(&sc->sc_buf, &sc->sc_mtx, PZERO | PCATCH,
+		    "u2frd", 0);
+		DPRINTFN(5, "woke, error=%d\n", error);
+		if (dev->si_drv1 == NULL)
+			error = EIO;
+		if (error) {
+			sc->sc_state.aslp = false;
+			goto exit;
+		}
+	}
+
+	if (sc->sc_state.data && uio->uio_resid > 0) {
+		length = min(uio->uio_resid, sc->sc_isize);
+		memcpy(buf, sc->sc_buf, length);
+		sc->sc_state.data = false;
+	}
+exit:
+	mtx_unlock(&sc->sc_mtx);
+	if (length != 0) {
+		/* Copy the data to the user process. */
+		DPRINTFN(5, "got %lu chars\n", (u_long)length);
+		error = uiomove(buf, length, uio);
+	}
+
+	return (error);
+}
+
+static int
+u2f_write(struct cdev *dev, struct uio *uio, int flag)
+{
+	uint8_t buf[U2F_MAX_REPORT_SIZE];
+	struct u2f_softc *sc = dev->si_drv1;
+	int error;
+
+	DPRINTFN(1, "\n");
+
+	if (sc == NULL)
+		return (EIO);
+
+	if (uio->uio_resid != sc->sc_osize)
+		return (EINVAL);
+	error = uiomove(buf, uio->uio_resid, uio);
+	if (error == 0)
+		error = hid_write(sc->sc_dev, buf, sc->sc_osize);
+
+	return (error);
+}
+
+#ifdef COMPAT_FREEBSD32
+static void
+update_ugd32(const struct usb_gen_descriptor *ugd,
+    struct usb_gen_descriptor32 *ugd32)
+{
+	/* Don't update hgd_data pointer */
+	CP(*ugd, *ugd32, ugd_lang_id);
+	CP(*ugd, *ugd32, ugd_maxlen);
+	CP(*ugd, *ugd32, ugd_actlen);
+	CP(*ugd, *ugd32, ugd_offset);
+	CP(*ugd, *ugd32, ugd_config_index);
+	CP(*ugd, *ugd32, ugd_string_index);
+	CP(*ugd, *ugd32, ugd_iface_index);
+	CP(*ugd, *ugd32, ugd_altif_index);
+	CP(*ugd, *ugd32, ugd_endpt_index);
+	CP(*ugd, *ugd32, ugd_report_type);
+	/* Don't update reserved */
+}
+#endif
+
+static int
+u2f_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag,
+    struct thread *td)
+{
+#ifdef COMPAT_FREEBSD32
+	struct usb_gen_descriptor local_ugd;
+	struct usb_gen_descriptor32 *ugd32 = NULL;
+#endif
+	struct u2f_softc *sc = dev->si_drv1;
+	struct usb_gen_descriptor *ugd = (struct usb_gen_descriptor *)addr;
+	uint32_t size;
+
+	DPRINTFN(2, "cmd=%lx\n", cmd);
+
+	if (sc == NULL)
+		return (EIO);
+
+#ifdef COMPAT_FREEBSD32
+	switch (cmd) {
+	case USB_GET_REPORT_DESC32:
+		cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor);
+		ugd32 = (struct usb_gen_descriptor32 *)addr;
+		ugd = &local_ugd;
+		PTRIN_CP(*ugd32, *ugd, ugd_data);
+		CP(*ugd32, *ugd, ugd_lang_id);
+		CP(*ugd32, *ugd, ugd_maxlen);
+		CP(*ugd32, *ugd, ugd_actlen);
+		CP(*ugd32, *ugd, ugd_offset);
+		CP(*ugd32, *ugd, ugd_config_index);
+		CP(*ugd32, *ugd, ugd_string_index);
+		CP(*ugd32, *ugd, ugd_iface_index);
+		CP(*ugd32, *ugd, ugd_altif_index);
+		CP(*ugd32, *ugd, ugd_endpt_index);
+		CP(*ugd32, *ugd, ugd_report_type);
+		/* Don't copy reserved */
+		break;
+	}
+#endif
+
+	/* fixed-length ioctls handling */
+	switch (cmd) {
+	case FIONBIO:
+		/* All handled in the upper FS layer. */
+		return (0);
+
+	case USB_GET_REPORT_DESC:
+		size = MIN(sc->sc_rdesc_size, ugd->ugd_maxlen);
+		ugd->ugd_actlen = size;
+#ifdef COMPAT_FREEBSD32
+		if (ugd32 != NULL)
+			update_ugd32(ugd, ugd32);
+#endif
+		if (ugd->ugd_data == NULL)
+			return (0);		/* descriptor length only */
+
+		return (copyout(sc->sc_rdesc, ugd->ugd_data, size));
+
+	case USB_GET_DEVICEINFO:
+		return(hid_ioctl(
+		    sc->sc_dev, USB_GET_DEVICEINFO, (uintptr_t)addr));
+	}
+
+	return (EINVAL);
+}
+
+static int
+u2f_poll(struct cdev *dev, int events, struct thread *td)
+{
+	struct u2f_softc *sc = dev->si_drv1;
+	int revents = 0;
+	bool start_intr = false;
+
+	if (sc == NULL)
+                return (POLLHUP);
+
+	if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE))
+		revents |= events & (POLLOUT | POLLWRNORM);
+	if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) {
+		mtx_lock(&sc->sc_mtx);
+		if (sc->sc_state.data)
+			revents |= events & (POLLIN | POLLRDNORM);
+		else {
+			sc->sc_state.sel = true;
+			start_intr = true;
+			selrecord(td, &sc->sc_rsel);
+		}
+		mtx_unlock(&sc->sc_mtx);
+		if (start_intr)
+			hid_intr_start(sc->sc_dev);
+	}
+
+	return (revents);
+}
+
+static int
+u2f_kqfilter(struct cdev *dev, struct knote *kn)
+{
+	struct u2f_softc *sc = dev->si_drv1;
+
+	if (sc == NULL)
+		return (ENXIO);
+
+	switch(kn->kn_filter) {
+	case EVFILT_READ:
+		if (sc->sc_fflags & FREAD) {
+			kn->kn_fop = &u2f_filterops_read;
+			break;
+		}
+		/* FALLTHROUGH */
+	default:
+		return(EINVAL);
+	}
+	kn->kn_hook = sc;
+
+	knlist_add(&sc->sc_rsel.si_note, kn, 0);
+	return (0);
+}
+
+static int
+u2f_kqread(struct knote *kn, long hint)
+{
+	struct u2f_softc *sc = kn->kn_hook;
+	int ret;
+
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	if (sc->dev->si_drv1 == NULL) {
+		kn->kn_flags |= EV_EOF;
+		ret = 1;
+	} else {
+		ret = sc->sc_state.data ? 1 : 0;
+		if (!sc->sc_state.data)
+			hid_intr_start(sc->sc_dev);
+	}
+
+	return (ret);
+}
+
+static void
+u2f_kqdetach(struct knote *kn)
+{
+	struct u2f_softc *sc = kn->kn_hook;
+
+	knlist_remove(&sc->sc_rsel.si_note, kn, 0);
+}
+
+static void
+u2f_notify(struct u2f_softc *sc)
+{
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	if (sc->sc_state.aslp) {
+		sc->sc_state.aslp = false;
+		DPRINTFN(5, "waking %p\n", &sc->sc_buf);
+		wakeup(&sc->sc_buf);
+	}
+	if (sc->sc_state.sel) {
+		sc->sc_state.sel = false;
+		selwakeuppri(&sc->sc_rsel, PZERO);
+	}
+	KNOTE_LOCKED(&sc->sc_rsel.si_note, 0);
+}
+
+static device_method_t u2f_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		u2f_probe),
+	DEVMETHOD(device_attach,	u2f_attach),
+	DEVMETHOD(device_detach,	u2f_detach),
+
+	DEVMETHOD_END
+};
+
+static driver_t u2f_driver = {
+#ifdef U2F_MAKE_UHID_ALIAS
+	"uhid",
+#else
+	"u2f",
+#endif
+	u2f_methods,
+	sizeof(struct u2f_softc)
+};
+
+DRIVER_MODULE(u2f, hidbus, u2f_driver, NULL, NULL);
+MODULE_DEPEND(u2f, hidbus, 1, 1, 1);
+MODULE_DEPEND(u2f, hid, 1, 1, 1);
+MODULE_VERSION(u2f, 1);
+HID_PNP_INFO(u2f_devs);
diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC
index f577cd07ac7c..88b8967cd693 100644
--- a/sys/i386/conf/GENERIC
+++ b/sys/i386/conf/GENERIC
@@ -343,3 +343,4 @@ options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 device		hidbus			# Generic HID Bus
 options 	IICHID_SAMPLING		# Workaround missing GPIO INTR support
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
index 56c3267d8684..10720570deb7 100644
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -17,6 +17,7 @@ SUBDIR += \
 	hsctrl \
 	ietp \
 	ps4dshock \
+	u2f \
 	xb360gp
 
 .include <bsd.subdir.mk>
diff --git a/sys/modules/hid/u2f/Makefile b/sys/modules/hid/u2f/Makefile
new file mode 100644
index 000000000000..227e7154035b
--- /dev/null
+++ b/sys/modules/hid/u2f/Makefile
@@ -0,0 +1,8 @@
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD=	u2f
+SRCS=	u2f.c
+SRCS+=	opt_hid.h opt_usb.h
+SRCS+=	bus_if.h device_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/powerpc/conf/GENERIC64 b/sys/powerpc/conf/GENERIC64
index 85711c8fc3ff..630c88b97dd7 100644
--- a/sys/powerpc/conf/GENERIC64
+++ b/sys/powerpc/conf/GENERIC64
@@ -293,3 +293,4 @@ device          virtio_balloon          # VirtIO Memory Balloon device
 options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 device		hidbus			# Generic HID Bus
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
diff --git a/sys/powerpc/conf/GENERIC64LE b/sys/powerpc/conf/GENERIC64LE
index a56feb6574a4..eb9a9441425d 100644
--- a/sys/powerpc/conf/GENERIC64LE
+++ b/sys/powerpc/conf/GENERIC64LE
@@ -274,3 +274,4 @@ device          virtio_balloon          # VirtIO Memory Balloon device
 options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 device		hidbus			# Generic HID Bus
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
diff --git a/sys/riscv/conf/GENERIC b/sys/riscv/conf/GENERIC
index a8500fe80019..2ff711e80127 100644
--- a/sys/riscv/conf/GENERIC
+++ b/sys/riscv/conf/GENERIC
@@ -132,6 +132,7 @@ device		umass			# Disks/Mass storage - Requires scbus and da
 options 	HID_DEBUG	# enable debug msgs
 device		hid		# Generic HID support
 device		hidbus		# Generic HID Bus
+options 	U2F_MAKE_UHID_ALIAS	# install /dev/uhid alias for /dev/u2f/
 
 # Serial (COM) ports
 device		uart		# Generic UART driver