git: 95299192d7e9 - main - security/py-yubikey-manager: Add OTP HID support for FreeBSD
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 23 Jun 2022 15:22:09 UTC
The branch main has been updated by grembo: URL: https://cgit.FreeBSD.org/ports/commit/?id=95299192d7e94b6fcb1e345c36d85ad989f6c42d commit 95299192d7e94b6fcb1e345c36d85ad989f6c42d Author: Michael Gmelin <grembo@FreeBSD.org> AuthorDate: 2022-05-27 13:13:56 +0000 Commit: Michael Gmelin <grembo@FreeBSD.org> CommitDate: 2022-06-23 15:20:24 +0000 security/py-yubikey-manager: Add OTP HID support for FreeBSD This makes yubikey-manager usable on FreeBSD again. FreeBSD support was broken since reliance on libusb and libykpersonalize was dropped upstream in 4.0.0. This supports the classic uhid(4) driver and the more modern hidraw(4) driver. See: https://github.com/Yubico/yubikey-manager/pull/504 As I had to redo the patch after the update to 4.0.9, I took the chance to add unit test support (`make test`). A future change could remove the dependency on ykpersonalize. PR: 263916 Approved by: egypcio (maintainer timeout, about 4 weeks) --- security/py-yubikey-manager/Makefile | 9 +- .../py-yubikey-manager/files/patch-README.adoc | 47 ++++ .../files/patch-ykman_hid_____init____.py | 12 + .../files/patch-ykman_hid_freebsd.py | 301 +++++++++++++++++++++ security/py-yubikey-manager/pkg-message | 34 +++ 5 files changed, 401 insertions(+), 2 deletions(-) diff --git a/security/py-yubikey-manager/Makefile b/security/py-yubikey-manager/Makefile index 535277195cbe..af756323aab3 100644 --- a/security/py-yubikey-manager/Makefile +++ b/security/py-yubikey-manager/Makefile @@ -1,5 +1,6 @@ PORTNAME= yubikey-manager PORTVERSION= 4.0.9 +PORTREVISION= 1 CATEGORIES= security python MASTER_SITES= CHEESESHOP PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} @@ -21,9 +22,13 @@ RUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}click>0:devel/py-click@${PY_FLAVOR} \ pcsc-spy:devel/pcsc-lite \ u2f-host:security/libu2f-host \ ykpersonalize:security/ykpers +TEST_DEPENDS= ${PYTHON_PKGNAMEPREFIX}makefun>0:devel/py-makefun@${PY_FLAVOR} \ + ${PYTHON_PKGNAMEPREFIX}pytest>0:devel/py-pytest@${PY_FLAVOR} -USES= python:3.6+ -USE_PYTHON= autoplist concurrent distutils +USES= dos2unix python:3.6+ +USE_PYTHON= autoplist concurrent distutils unittest + +DOS2UNIX_GLOB= *.adoc *.py NO_ARCH= yes diff --git a/security/py-yubikey-manager/files/patch-README.adoc b/security/py-yubikey-manager/files/patch-README.adoc new file mode 100644 index 000000000000..2c7ab76bfda9 --- /dev/null +++ b/security/py-yubikey-manager/files/patch-README.adoc @@ -0,0 +1,47 @@ +See https://github.com/Yubico/yubikey-manager/commit/ecd7897b3f02054 +--- README.adoc.orig 2022-05-27 13:02:44 UTC ++++ README.adoc +@@ -106,8 +106,43 @@ installed on FreeBSD. It's available via its ports tre + Should you opt to install and use YubiKey Manager on this platform, please be aware + that it's **NOT** maintained by Yubico. + ++To install the binary package, use `pkg install pyXY-yubikey-manager`, with `pyXY` ++specifying the version of Python the package was built for, so in order to install ++YubiKey Manager for Python 3.8, use: ++ ++ # pkg install py38-yubikey-manager ++ + For more information about how to install packages or ports on FreeBSD, please refer + to its official documentation: https://docs.freebsd.org/en/books/handbook/ports[FreeBSD Handbook]. ++ ++In order to use `ykman otp` commands, you need to make sure the _uhid(4)_ driver ++attaches to the USB device: ++ ++ # usbconfig ugenX.Y add_quirk UQ_KBD_IGNORE ++ # usbconfig ugenX.Y reset ++ ++The correct device to operate on _(ugenX.Y)_ can be determined using ++`usbconfig list`. ++ ++When using FreeBSD 13 or higher, you can switch to the more modern _hidraw(4)_ ++driver. This allows YubiKey Manager to access OTP HID in a non-exclusive way, ++so that the key will still function as a USB keyboard: ++ ++ # sysrc kld_list+="hidraw hkbd" ++ # cat >>/boot/loader.conf<<EOF ++ hw.usb.usbhid.enable="1" ++ hw.usb.quirk.0="0x1050 0x0010 0 0xffff UQ_KBD_IGNORE" # YKS_OTP ++ hw.usb.quirk.1="0x1050 0x0110 0 0xffff UQ_KBD_IGNORE" # NEO_OTP ++ hw.usb.quirk.2="0x1050 0x0111 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_CCID ++ hw.usb.quirk.3="0x1050 0x0114 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO ++ hw.usb.quirk.4="0x1050 0x0116 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO_CCID ++ hw.usb.quirk.5="0x1050 0x0401 0 0xffff UQ_KBD_IGNORE" # YK4_OTP ++ hw.usb.quirk.6="0x1050 0x0403 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO ++ hw.usb.quirk.7="0x1050 0x0405 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_CCID ++ hw.usb.quirk.8="0x1050 0x0407 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO_CCID ++ hw.usb.quirk.9="0x1050 0x0410 0 0xffff UQ_KBD_IGNORE" # YKP_OTP_FIDO ++ EOF ++ # reboot + + ==== Source + To install from source, see the link:doc/Development.adoc[development] diff --git a/security/py-yubikey-manager/files/patch-ykman_hid_____init____.py b/security/py-yubikey-manager/files/patch-ykman_hid_____init____.py new file mode 100644 index 000000000000..baa8cb71818f --- /dev/null +++ b/security/py-yubikey-manager/files/patch-ykman_hid_____init____.py @@ -0,0 +1,12 @@ +See https://github.com/Yubico/yubikey-manager/commit/ecd7897b3f02054 +--- ykman/hid/__init__.py.orig 2022-05-27 13:02:44 UTC ++++ ykman/hid/__init__.py +@@ -41,6 +41,8 @@ elif sys.platform.startswith("win32"): + from . import windows as backend + elif sys.platform.startswith("darwin"): + from . import macos as backend ++elif sys.platform.startswith("freebsd"): ++ from . import freebsd as backend + else: + + class backend: diff --git a/security/py-yubikey-manager/files/patch-ykman_hid_freebsd.py b/security/py-yubikey-manager/files/patch-ykman_hid_freebsd.py new file mode 100644 index 000000000000..47941a80f690 --- /dev/null +++ b/security/py-yubikey-manager/files/patch-ykman_hid_freebsd.py @@ -0,0 +1,301 @@ +See https://github.com/Yubico/yubikey-manager/commit/ecd7897b3f02054 +--- ykman/hid/freebsd.py.orig 2022-05-27 13:02:44 UTC ++++ ykman/hid/freebsd.py +@@ -0,0 +1,297 @@ ++# Original work Copyright 2016 Google Inc. All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++# ++# Modified work Copyright 2022 Michael Gmelin. All Rights Reserved. ++# This file, with modifications, is licensed under the above Apache License. ++# ++# Modified work Copyright 2022 Yubico AB. All Rights Reserved. ++# This file, with modifications, is licensed under the above Apache License. ++ ++# FreeBSD HID driver. ++# ++# There are two options to access UHID on FreeBSD: ++# ++# hidraw(4) - New method, not enabled by default ++# on FreeBSD 13.x and earlier ++# uhid(4) - Classic method, default option on ++# FreeBSD 13.x and earlier ++# ++# To avoid attaching the Yubikey as a keyboard, do: ++# ++# usbconfig ugenX.Y add_quirk UQ_KBD_IGNORE ++# usbconfig ugenX.Y reset ++# ++# The list of available devices is shown using `usbconfig list` ++# You can make these changes permanent by altering loader.conf. ++# ++# Starting from FreeBSD 13 hidraw(4) can be enabled using: ++# ++# sysrc kld_list+="hidraw hkbd" ++# cat >>/boot/loader.conf<<EOF ++# hw.usb.usbhid.enable="1" ++# hw.usb.quirk.0="0x1050 0x0010 0 0xffff UQ_KBD_IGNORE" # YKS_OTP ++# hw.usb.quirk.1="0x1050 0x0110 0 0xffff UQ_KBD_IGNORE" # NEO_OTP ++# hw.usb.quirk.2="0x1050 0x0111 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_CCID ++# hw.usb.quirk.3="0x1050 0x0114 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO ++# hw.usb.quirk.4="0x1050 0x0116 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO_CCID ++# hw.usb.quirk.5="0x1050 0x0401 0 0xffff UQ_KBD_IGNORE" # YK4_OTP ++# hw.usb.quirk.6="0x1050 0x0403 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO ++# hw.usb.quirk.7="0x1050 0x0405 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_CCID ++# hw.usb.quirk.8="0x1050 0x0407 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO_CCID ++# hw.usb.quirk.9="0x1050 0x0410 0 0xffff UQ_KBD_IGNORE" # YKP_OTP_FIDO ++# EOF ++# reboot ++# ++from yubikit.core.otp import OtpConnection ++from .base import OtpYubiKeyDevice, YUBICO_VID, USAGE_OTP ++ ++from ctypes.util import find_library ++import ctypes ++ ++import glob ++import fcntl ++import os ++import re ++import struct ++import logging ++ ++logger = logging.getLogger(__name__) ++ ++devdir = "/dev/" ++ ++# /usr/include/dev/ususb_ioctl.h ++USB_GET_REPORT = 0xC0205517 ++USB_SET_REPORT = 0x80205518 ++USB_GET_REPORT_DESC = 0xC0205515 ++ ++# /usr/include/dev/hid/hidraw.h> ++HIDIOCGRAWINFO = 0x40085520 ++HIDIOCGRDESC = 0x2000551F ++HIDIOCGRDESCSIZE = 0x4004551E ++HIDIOCGFEATURE_9 = 0xC0095524 ++HIDIOCSFEATURE_9 = 0x80095523 ++ ++ ++class HidrawConnection(OtpConnection): ++ """ ++ hidraw(4) is FreeBSD's modern raw access driver, based on usbhid(4). ++ It is available since FreeBSD 13 and can be activated by adding ++ `hw.usb.usbhid.enable="1"` to `/boot/loader.conf`. The actual kernel ++ module is loaded with `kldload hidraw`. ++ """ ++ ++ def __init__(self, path): ++ self.fd = os.open(path, os.O_RDWR) ++ ++ def close(self): ++ os.close(self.fd) ++ ++ def receive(self): ++ buf = bytearray(1 + 8) ++ fcntl.ioctl(self.fd, HIDIOCGFEATURE_9, buf, True) ++ return buf[1:] ++ ++ def send(self, data): ++ buf = bytes([0]) + data ++ fcntl.ioctl(self.fd, HIDIOCSFEATURE_9, buf) ++ ++ @staticmethod ++ def get_info(dev): ++ buf = bytearray(4 + 2 + 2) ++ fcntl.ioctl(dev, HIDIOCGRAWINFO, buf, True) ++ return struct.unpack("<IHH", buf) ++ ++ @staticmethod ++ def get_descriptor(dev): ++ buf = bytearray(4) ++ fcntl.ioctl(dev, HIDIOCGRDESCSIZE, buf, True) ++ size = struct.unpack("<I", buf)[0] ++ buf += bytearray(size) ++ fcntl.ioctl(dev, HIDIOCGRDESC, buf, True) ++ return buf[4:] ++ ++ @staticmethod ++ def get_usage(dev): ++ buf = HidrawConnection.get_descriptor(dev) ++ usage, usage_page = (None, None) ++ while buf: ++ head, buf = buf[0], buf[1:] ++ typ, size = 0xFC & head, 0x03 & head ++ value, buf = buf[:size], buf[size:] ++ if typ == 4: # Usage page ++ usage_page = struct.unpack("<I", value.ljust(4, b"\0"))[0] ++ if usage is not None: ++ return usage_page, usage ++ elif typ == 8: # Usage ++ usage = struct.unpack("<I", value.ljust(4, b"\0"))[0] ++ if usage_page is not None: ++ return usage_page, usage ++ ++ @staticmethod ++ def list_devices(): ++ devices = [] ++ for hidraw in glob.glob(devdir + "hidraw?*"): ++ try: ++ with open(hidraw, "rb") as f: ++ bustype, vid, pid = HidrawConnection.get_info(f) ++ if vid == YUBICO_VID and HidrawConnection.get_usage(f) == USAGE_OTP: ++ devices.append(OtpYubiKeyDevice(hidraw, pid, HidrawConnection)) ++ except Exception as e: ++ logger.debug("Failed opening HID device", exc_info=e) ++ continue ++ return devices ++ ++ ++# For UhidConnection ++libc = ctypes.CDLL(find_library("c")) ++ ++ ++class usb_gen_descriptor(ctypes.Structure): ++ _fields_ = [ ++ ( ++ "ugd_data", ++ ctypes.c_void_p, ++ ), ++ ("ugd_lang_id", ctypes.c_uint16), ++ ("ugd_maxlen", ctypes.c_uint16), ++ ("ugd_actlen", ctypes.c_uint16), ++ ("ugd_offset", ctypes.c_uint16), ++ ("ugd_config_index", ctypes.c_uint8), ++ ("ugd_string_index", ctypes.c_uint8), ++ ("ugd_iface_index", ctypes.c_uint8), ++ ("ugd_altif_index", ctypes.c_uint8), ++ ("ugd_endpt_index", ctypes.c_uint8), ++ ("ugd_report_type", ctypes.c_uint8), ++ ("reserved", ctypes.c_uint8 * 8), ++ ] ++ ++ ++class UhidConnection(OtpConnection): ++ """ ++ uhid(4) is FreeBSD's classic USB hid access driver and enabled ++ by default in FreeBSD 13.x and earlier. ++ """ ++ ++ def __init__(self, path): ++ self.fd = os.open(path, os.O_RDWR) ++ ++ def close(self): ++ os.close(self.fd) ++ ++ def receive(self): ++ buf = ctypes.create_string_buffer(9) ++ desc = usb_gen_descriptor( ++ ugd_data=ctypes.addressof(buf), ++ ugd_maxlen=ctypes.sizeof(buf), ++ ugd_report_type=3, ++ ) ++ ret = libc.ioctl(self.fd, USB_GET_REPORT, ctypes.pointer(desc)) ++ if ret != 0: ++ raise ValueError("ioctl failed: " + str(ret)) ++ return buf[:-1] ++ ++ def send(self, data): ++ buf = ctypes.create_string_buffer(8) ++ for i in range(0, len(data)): ++ buf[i] = data[i] ++ ++ desc = usb_gen_descriptor( ++ ugd_data=ctypes.addressof(buf), ++ ugd_maxlen=len(buf), ++ ugd_report_type=0x3, ++ ) ++ ret = libc.ioctl(self.fd, USB_SET_REPORT, ctypes.pointer(desc)) ++ if ret != 0: ++ raise ValueError("ioctl failed: " + str(ret)) ++ ++ @staticmethod ++ def get_usage(dev): ++ c_data = ctypes.create_string_buffer(4096) ++ desc = usb_gen_descriptor( ++ ugd_data=ctypes.addressof(c_data), ++ ugd_maxlen=ctypes.sizeof(c_data), ++ ugd_report_type=3, ++ ) ++ ret = libc.ioctl(dev, USB_GET_REPORT_DESC, ctypes.pointer(desc)) ++ if ret != 0: ++ raise ValueError("ioctl failed") ++ ++ REPORT_DESCRIPTOR_KEY_MASK = 0xFC ++ SIZE_MASK = ~REPORT_DESCRIPTOR_KEY_MASK ++ USAGE_PAGE = 0x04 ++ USAGE = 0x08 ++ ++ data = c_data.raw ++ usage, usage_page = (None, None) ++ while data and not (usage and usage_page): ++ head, data = struct.unpack_from(">B", data)[0], data[1:] ++ key, size = REPORT_DESCRIPTOR_KEY_MASK & head, SIZE_MASK & head ++ value = struct.unpack_from("<I", data[:size].ljust(4, b"\0"))[0] ++ data = data[size:] ++ if key == USAGE_PAGE and not usage_page: ++ usage_page = value ++ elif key == USAGE and not usage: ++ usage = value ++ ++ return (usage_page, usage) ++ ++ @staticmethod ++ def get_info(index): ++ vendor_re = re.compile("vendor=(0x[0-9a-fA-F]+)") ++ product_re = re.compile("product=(0x[0-9a-fA-F]+)") ++ sernum_re = re.compile('sernum="([^"]+)') ++ ++ pnpinfo = ("dev.uhid." + index + ".%pnpinfo").encode() ++ ++ ovalue = ctypes.create_string_buffer(1024) ++ olen = ctypes.c_size_t(ctypes.sizeof(ovalue)) ++ key = ctypes.c_char_p(pnpinfo) ++ retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) ++ if retval != 0: ++ raise IOError("sysctlbyname failed") ++ ++ value = ovalue.value[: olen.value].decode() ++ m = vendor_re.search(value) ++ vid = int(m.group(1), 16) if m else None ++ m = product_re.search(value) ++ pid = int(m.group(1), 16) if m else None ++ m = sernum_re.search(value) ++ serial = m.group(1) if m else None ++ return (vid, pid, serial) ++ ++ @staticmethod ++ def list_devices(): ++ devices = [] ++ for uhid in glob.glob(devdir + "uhid?*"): ++ index = uhid[len(devdir) + len("uhid") :] ++ if not index.isdigit(): ++ continue ++ ++ try: ++ (vid, pid, serial) = UhidConnection.get_info(index) ++ if vid == YUBICO_VID: ++ with open(uhid, "rb") as f: ++ if UhidConnection.get_usage(f.fileno()) == USAGE_OTP: ++ devices.append(OtpYubiKeyDevice(uhid, pid, UhidConnection)) ++ except Exception as e: ++ logger.debug("Failed opening HID device", exc_info=e) ++ continue ++ return devices ++ ++ ++def list_devices(): ++ devices = HidrawConnection.list_devices() ++ if not devices: ++ devices = UhidConnection.list_devices() ++ return devices diff --git a/security/py-yubikey-manager/pkg-message b/security/py-yubikey-manager/pkg-message new file mode 100644 index 000000000000..4ce046c0daba --- /dev/null +++ b/security/py-yubikey-manager/pkg-message @@ -0,0 +1,34 @@ +[ +{ type: install + message: <<EOM +In order to use `ykman otp` commands, you need to make sure the uhid(4) +driver attaches to the USB device: + + # usbconfig ugenX.Y add_quirk UQ_KBD_IGNORE + # usbconfig ugenX.Y reset + +The correct device to operate on (ugenX.Y) can be determined using +`usbconfig list`. + +When using FreeBSD 13 or higher, you can switch to the more modern +hidraw(4) driver. This allows YubiKey Manager to access OTP HID in a +non-exclusive way, so that the key will still function as a USB keyboard: + + # sysrc kld_list+="hidraw hkbd" + # cat >>/boot/loader.conf<<EOF + hw.usb.usbhid.enable="1" + hw.usb.quirk.0="0x1050 0x0010 0 0xffff UQ_KBD_IGNORE" # YKS_OTP + hw.usb.quirk.1="0x1050 0x0110 0 0xffff UQ_KBD_IGNORE" # NEO_OTP + hw.usb.quirk.2="0x1050 0x0111 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_CCID + hw.usb.quirk.3="0x1050 0x0114 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO + hw.usb.quirk.4="0x1050 0x0116 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO_CCID + hw.usb.quirk.5="0x1050 0x0401 0 0xffff UQ_KBD_IGNORE" # YK4_OTP + hw.usb.quirk.6="0x1050 0x0403 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO + hw.usb.quirk.7="0x1050 0x0405 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_CCID + hw.usb.quirk.8="0x1050 0x0407 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO_CCID + hw.usb.quirk.9="0x1050 0x0410 0 0xffff UQ_KBD_IGNORE" # YKP_OTP_FIDO + EOF + # reboot +EOM +} +]