Added
Link Here
|
1 |
See https://github.com/Yubico/yubikey-manager/commit/ecd7897b3f02054 |
2 |
--- ykman/hid/freebsd.py.orig 2022-05-27 13:02:44 UTC |
3 |
+++ ykman/hid/freebsd.py |
4 |
@@ -0,0 +1,297 @@ |
5 |
+# Original work Copyright 2016 Google Inc. All Rights Reserved. |
6 |
+# |
7 |
+# Licensed under the Apache License, Version 2.0 (the "License"); |
8 |
+# you may not use this file except in compliance with the License. |
9 |
+# You may obtain a copy of the License at |
10 |
+# |
11 |
+# http://www.apache.org/licenses/LICENSE-2.0 |
12 |
+# |
13 |
+# Unless required by applicable law or agreed to in writing, software |
14 |
+# distributed under the License is distributed on an "AS IS" BASIS, |
15 |
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 |
+# See the License for the specific language governing permissions and |
17 |
+# limitations under the License. |
18 |
+# |
19 |
+# Modified work Copyright 2022 Michael Gmelin. All Rights Reserved. |
20 |
+# This file, with modifications, is licensed under the above Apache License. |
21 |
+# |
22 |
+# Modified work Copyright 2022 Yubico AB. All Rights Reserved. |
23 |
+# This file, with modifications, is licensed under the above Apache License. |
24 |
+ |
25 |
+# FreeBSD HID driver. |
26 |
+# |
27 |
+# There are two options to access UHID on FreeBSD: |
28 |
+# |
29 |
+# hidraw(4) - New method, not enabled by default |
30 |
+# on FreeBSD 13.x and earlier |
31 |
+# uhid(4) - Classic method, default option on |
32 |
+# FreeBSD 13.x and earlier |
33 |
+# |
34 |
+# To avoid attaching the Yubikey as a keyboard, do: |
35 |
+# |
36 |
+# usbconfig ugenX.Y add_quirk UQ_KBD_IGNORE |
37 |
+# usbconfig ugenX.Y reset |
38 |
+# |
39 |
+# The list of available devices is shown using `usbconfig list` |
40 |
+# You can make these changes permanent by altering loader.conf. |
41 |
+# |
42 |
+# Starting from FreeBSD 13 hidraw(4) can be enabled using: |
43 |
+# |
44 |
+# sysrc kld_list+="hidraw hkbd" |
45 |
+# cat >>/boot/loader.conf<<EOF |
46 |
+# hw.usb.usbhid.enable="1" |
47 |
+# hw.usb.quirk.0="0x1050 0x0010 0 0xffff UQ_KBD_IGNORE" # YKS_OTP |
48 |
+# hw.usb.quirk.1="0x1050 0x0110 0 0xffff UQ_KBD_IGNORE" # NEO_OTP |
49 |
+# hw.usb.quirk.2="0x1050 0x0111 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_CCID |
50 |
+# hw.usb.quirk.3="0x1050 0x0114 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO |
51 |
+# hw.usb.quirk.4="0x1050 0x0116 0 0xffff UQ_KBD_IGNORE" # NEO_OTP_FIDO_CCID |
52 |
+# hw.usb.quirk.5="0x1050 0x0401 0 0xffff UQ_KBD_IGNORE" # YK4_OTP |
53 |
+# hw.usb.quirk.6="0x1050 0x0403 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO |
54 |
+# hw.usb.quirk.7="0x1050 0x0405 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_CCID |
55 |
+# hw.usb.quirk.8="0x1050 0x0407 0 0xffff UQ_KBD_IGNORE" # YK4_OTP_FIDO_CCID |
56 |
+# hw.usb.quirk.9="0x1050 0x0410 0 0xffff UQ_KBD_IGNORE" # YKP_OTP_FIDO |
57 |
+# EOF |
58 |
+# reboot |
59 |
+# |
60 |
+from yubikit.core.otp import OtpConnection |
61 |
+from .base import OtpYubiKeyDevice, YUBICO_VID, USAGE_OTP |
62 |
+ |
63 |
+from ctypes.util import find_library |
64 |
+import ctypes |
65 |
+ |
66 |
+import glob |
67 |
+import fcntl |
68 |
+import os |
69 |
+import re |
70 |
+import struct |
71 |
+import logging |
72 |
+ |
73 |
+logger = logging.getLogger(__name__) |
74 |
+ |
75 |
+devdir = "/dev/" |
76 |
+ |
77 |
+# /usr/include/dev/ususb_ioctl.h |
78 |
+USB_GET_REPORT = 0xC0205517 |
79 |
+USB_SET_REPORT = 0x80205518 |
80 |
+USB_GET_REPORT_DESC = 0xC0205515 |
81 |
+ |
82 |
+# /usr/include/dev/hid/hidraw.h> |
83 |
+HIDIOCGRAWINFO = 0x40085520 |
84 |
+HIDIOCGRDESC = 0x2000551F |
85 |
+HIDIOCGRDESCSIZE = 0x4004551E |
86 |
+HIDIOCGFEATURE_9 = 0xC0095524 |
87 |
+HIDIOCSFEATURE_9 = 0x80095523 |
88 |
+ |
89 |
+ |
90 |
+class HidrawConnection(OtpConnection): |
91 |
+ """ |
92 |
+ hidraw(4) is FreeBSD's modern raw access driver, based on usbhid(4). |
93 |
+ It is available since FreeBSD 13 and can be activated by adding |
94 |
+ `hw.usb.usbhid.enable="1"` to `/boot/loader.conf`. The actual kernel |
95 |
+ module is loaded with `kldload hidraw`. |
96 |
+ """ |
97 |
+ |
98 |
+ def __init__(self, path): |
99 |
+ self.fd = os.open(path, os.O_RDWR) |
100 |
+ |
101 |
+ def close(self): |
102 |
+ os.close(self.fd) |
103 |
+ |
104 |
+ def receive(self): |
105 |
+ buf = bytearray(1 + 8) |
106 |
+ fcntl.ioctl(self.fd, HIDIOCGFEATURE_9, buf, True) |
107 |
+ return buf[1:] |
108 |
+ |
109 |
+ def send(self, data): |
110 |
+ buf = bytes([0]) + data |
111 |
+ fcntl.ioctl(self.fd, HIDIOCSFEATURE_9, buf) |
112 |
+ |
113 |
+ @staticmethod |
114 |
+ def get_info(dev): |
115 |
+ buf = bytearray(4 + 2 + 2) |
116 |
+ fcntl.ioctl(dev, HIDIOCGRAWINFO, buf, True) |
117 |
+ return struct.unpack("<IHH", buf) |
118 |
+ |
119 |
+ @staticmethod |
120 |
+ def get_descriptor(dev): |
121 |
+ buf = bytearray(4) |
122 |
+ fcntl.ioctl(dev, HIDIOCGRDESCSIZE, buf, True) |
123 |
+ size = struct.unpack("<I", buf)[0] |
124 |
+ buf += bytearray(size) |
125 |
+ fcntl.ioctl(dev, HIDIOCGRDESC, buf, True) |
126 |
+ return buf[4:] |
127 |
+ |
128 |
+ @staticmethod |
129 |
+ def get_usage(dev): |
130 |
+ buf = HidrawConnection.get_descriptor(dev) |
131 |
+ usage, usage_page = (None, None) |
132 |
+ while buf: |
133 |
+ head, buf = buf[0], buf[1:] |
134 |
+ typ, size = 0xFC & head, 0x03 & head |
135 |
+ value, buf = buf[:size], buf[size:] |
136 |
+ if typ == 4: # Usage page |
137 |
+ usage_page = struct.unpack("<I", value.ljust(4, b"\0"))[0] |
138 |
+ if usage is not None: |
139 |
+ return usage_page, usage |
140 |
+ elif typ == 8: # Usage |
141 |
+ usage = struct.unpack("<I", value.ljust(4, b"\0"))[0] |
142 |
+ if usage_page is not None: |
143 |
+ return usage_page, usage |
144 |
+ |
145 |
+ @staticmethod |
146 |
+ def list_devices(): |
147 |
+ devices = [] |
148 |
+ for hidraw in glob.glob(devdir + "hidraw?*"): |
149 |
+ try: |
150 |
+ with open(hidraw, "rb") as f: |
151 |
+ bustype, vid, pid = HidrawConnection.get_info(f) |
152 |
+ if vid == YUBICO_VID and HidrawConnection.get_usage(f) == USAGE_OTP: |
153 |
+ devices.append(OtpYubiKeyDevice(hidraw, pid, HidrawConnection)) |
154 |
+ except Exception as e: |
155 |
+ logger.debug("Failed opening HID device", exc_info=e) |
156 |
+ continue |
157 |
+ return devices |
158 |
+ |
159 |
+ |
160 |
+# For UhidConnection |
161 |
+libc = ctypes.CDLL(find_library("c")) |
162 |
+ |
163 |
+ |
164 |
+class usb_gen_descriptor(ctypes.Structure): |
165 |
+ _fields_ = [ |
166 |
+ ( |
167 |
+ "ugd_data", |
168 |
+ ctypes.c_void_p, |
169 |
+ ), |
170 |
+ ("ugd_lang_id", ctypes.c_uint16), |
171 |
+ ("ugd_maxlen", ctypes.c_uint16), |
172 |
+ ("ugd_actlen", ctypes.c_uint16), |
173 |
+ ("ugd_offset", ctypes.c_uint16), |
174 |
+ ("ugd_config_index", ctypes.c_uint8), |
175 |
+ ("ugd_string_index", ctypes.c_uint8), |
176 |
+ ("ugd_iface_index", ctypes.c_uint8), |
177 |
+ ("ugd_altif_index", ctypes.c_uint8), |
178 |
+ ("ugd_endpt_index", ctypes.c_uint8), |
179 |
+ ("ugd_report_type", ctypes.c_uint8), |
180 |
+ ("reserved", ctypes.c_uint8 * 8), |
181 |
+ ] |
182 |
+ |
183 |
+ |
184 |
+class UhidConnection(OtpConnection): |
185 |
+ """ |
186 |
+ uhid(4) is FreeBSD's classic USB hid access driver and enabled |
187 |
+ by default in FreeBSD 13.x and earlier. |
188 |
+ """ |
189 |
+ |
190 |
+ def __init__(self, path): |
191 |
+ self.fd = os.open(path, os.O_RDWR) |
192 |
+ |
193 |
+ def close(self): |
194 |
+ os.close(self.fd) |
195 |
+ |
196 |
+ def receive(self): |
197 |
+ buf = ctypes.create_string_buffer(9) |
198 |
+ desc = usb_gen_descriptor( |
199 |
+ ugd_data=ctypes.addressof(buf), |
200 |
+ ugd_maxlen=ctypes.sizeof(buf), |
201 |
+ ugd_report_type=3, |
202 |
+ ) |
203 |
+ ret = libc.ioctl(self.fd, USB_GET_REPORT, ctypes.pointer(desc)) |
204 |
+ if ret != 0: |
205 |
+ raise ValueError("ioctl failed: " + str(ret)) |
206 |
+ return buf[:-1] |
207 |
+ |
208 |
+ def send(self, data): |
209 |
+ buf = ctypes.create_string_buffer(8) |
210 |
+ for i in range(0, len(data)): |
211 |
+ buf[i] = data[i] |
212 |
+ |
213 |
+ desc = usb_gen_descriptor( |
214 |
+ ugd_data=ctypes.addressof(buf), |
215 |
+ ugd_maxlen=len(buf), |
216 |
+ ugd_report_type=0x3, |
217 |
+ ) |
218 |
+ ret = libc.ioctl(self.fd, USB_SET_REPORT, ctypes.pointer(desc)) |
219 |
+ if ret != 0: |
220 |
+ raise ValueError("ioctl failed: " + str(ret)) |
221 |
+ |
222 |
+ @staticmethod |
223 |
+ def get_usage(dev): |
224 |
+ c_data = ctypes.create_string_buffer(4096) |
225 |
+ desc = usb_gen_descriptor( |
226 |
+ ugd_data=ctypes.addressof(c_data), |
227 |
+ ugd_maxlen=ctypes.sizeof(c_data), |
228 |
+ ugd_report_type=3, |
229 |
+ ) |
230 |
+ ret = libc.ioctl(dev, USB_GET_REPORT_DESC, ctypes.pointer(desc)) |
231 |
+ if ret != 0: |
232 |
+ raise ValueError("ioctl failed") |
233 |
+ |
234 |
+ REPORT_DESCRIPTOR_KEY_MASK = 0xFC |
235 |
+ SIZE_MASK = ~REPORT_DESCRIPTOR_KEY_MASK |
236 |
+ USAGE_PAGE = 0x04 |
237 |
+ USAGE = 0x08 |
238 |
+ |
239 |
+ data = c_data.raw |
240 |
+ usage, usage_page = (None, None) |
241 |
+ while data and not (usage and usage_page): |
242 |
+ head, data = struct.unpack_from(">B", data)[0], data[1:] |
243 |
+ key, size = REPORT_DESCRIPTOR_KEY_MASK & head, SIZE_MASK & head |
244 |
+ value = struct.unpack_from("<I", data[:size].ljust(4, b"\0"))[0] |
245 |
+ data = data[size:] |
246 |
+ if key == USAGE_PAGE and not usage_page: |
247 |
+ usage_page = value |
248 |
+ elif key == USAGE and not usage: |
249 |
+ usage = value |
250 |
+ |
251 |
+ return (usage_page, usage) |
252 |
+ |
253 |
+ @staticmethod |
254 |
+ def get_info(index): |
255 |
+ vendor_re = re.compile("vendor=(0x[0-9a-fA-F]+)") |
256 |
+ product_re = re.compile("product=(0x[0-9a-fA-F]+)") |
257 |
+ sernum_re = re.compile('sernum="([^"]+)') |
258 |
+ |
259 |
+ pnpinfo = ("dev.uhid." + index + ".%pnpinfo").encode() |
260 |
+ |
261 |
+ ovalue = ctypes.create_string_buffer(1024) |
262 |
+ olen = ctypes.c_size_t(ctypes.sizeof(ovalue)) |
263 |
+ key = ctypes.c_char_p(pnpinfo) |
264 |
+ retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) |
265 |
+ if retval != 0: |
266 |
+ raise IOError("sysctlbyname failed") |
267 |
+ |
268 |
+ value = ovalue.value[: olen.value].decode() |
269 |
+ m = vendor_re.search(value) |
270 |
+ vid = int(m.group(1), 16) if m else None |
271 |
+ m = product_re.search(value) |
272 |
+ pid = int(m.group(1), 16) if m else None |
273 |
+ m = sernum_re.search(value) |
274 |
+ serial = m.group(1) if m else None |
275 |
+ return (vid, pid, serial) |
276 |
+ |
277 |
+ @staticmethod |
278 |
+ def list_devices(): |
279 |
+ devices = [] |
280 |
+ for uhid in glob.glob(devdir + "uhid?*"): |
281 |
+ index = uhid[len(devdir) + len("uhid") :] |
282 |
+ if not index.isdigit(): |
283 |
+ continue |
284 |
+ |
285 |
+ try: |
286 |
+ (vid, pid, serial) = UhidConnection.get_info(index) |
287 |
+ if vid == YUBICO_VID: |
288 |
+ with open(uhid, "rb") as f: |
289 |
+ if UhidConnection.get_usage(f.fileno()) == USAGE_OTP: |
290 |
+ devices.append(OtpYubiKeyDevice(uhid, pid, UhidConnection)) |
291 |
+ except Exception as e: |
292 |
+ logger.debug("Failed opening HID device", exc_info=e) |
293 |
+ continue |
294 |
+ return devices |
295 |
+ |
296 |
+ |
297 |
+def list_devices(): |
298 |
+ devices = HidrawConnection.list_devices() |
299 |
+ if not devices: |
300 |
+ devices = UhidConnection.list_devices() |
301 |
+ return devices |