1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /* Inspired by Andrew Lutomirski's 'u2f-hidraw-policy.c' */
3 
4 #include <errno.h>
5 #include <stdbool.h>
6 #include <stddef.h>
7 #include <stdint.h>
8 
9 #include "fido_id_desc.h"
10 
11 #define HID_RPTDESC_FIRST_BYTE_LONG_ITEM 0xfeu
12 #define HID_RPTDESC_TYPE_GLOBAL 0x1u
13 #define HID_RPTDESC_TYPE_LOCAL 0x2u
14 #define HID_RPTDESC_TAG_USAGE_PAGE 0x0u
15 #define HID_RPTDESC_TAG_USAGE 0x0u
16 
17 /*
18  * HID usage for FIDO CTAP1 ("U2F") and CTAP2 security tokens.
19  * https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-u2f-u2f_hid.h-v1.0-ps-20141009.txt
20  * https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#usb-discovery
21  * https://www.usb.org/sites/default/files/hutrr48.pdf
22  */
23 #define FIDO_FULL_USAGE_CTAPHID 0xf1d00001u
24 
25 /*
26  * Parses a HID report descriptor and identifies FIDO CTAP1 ("U2F")/CTAP2 security tokens based on their
27  * declared usage.
28  * A positive return value indicates that the report descriptor belongs to a FIDO security token.
29  * https://www.usb.org/sites/default/files/documents/hid1_11.pdf (Section 6.2.2)
30  */
is_fido_security_token_desc(const uint8_t * desc,size_t desc_len)31 int is_fido_security_token_desc(const uint8_t *desc, size_t desc_len) {
32         uint32_t usage = 0;
33 
34         for (size_t pos = 0; pos < desc_len; ) {
35                 uint8_t tag, type, size_code;
36                 size_t size;
37                 uint32_t value;
38 
39                 /* Report descriptors consists of short items (1-5 bytes) and long items (3-258 bytes). */
40                 if (desc[pos] == HID_RPTDESC_FIRST_BYTE_LONG_ITEM) {
41                         /* No long items are defined in the spec; skip them.
42                          * The length of the data in a long item is contained in the byte after the long
43                          * item tag. The header consists of three bytes: special long item tag, length,
44                          * actual tag. */
45                         if (pos + 1 >= desc_len)
46                                 return -EINVAL;
47                         pos += desc[pos + 1] + 3;
48                         continue;
49                 }
50 
51                 /* The first byte of a short item encodes tag, type and size. */
52                 tag = desc[pos] >> 4;          /* Bits 7 to 4 */
53                 type = (desc[pos] >> 2) & 0x3; /* Bits 3 and 2 */
54                 size_code = desc[pos] & 0x3;   /* Bits 1 and 0 */
55                 /* Size is coded as follows:
56                  * 0 -> 0 bytes, 1 -> 1 byte, 2 -> 2 bytes, 3 -> 4 bytes
57                  */
58                 size = size_code < 3 ? size_code : 4;
59                 /* Consume header byte. */
60                 pos++;
61 
62                 /* Extract the item value coded on size bytes. */
63                 if (pos + size > desc_len)
64                         return -EINVAL;
65                 value = 0;
66                 for (size_t i = 0; i < size; i++)
67                         value |= (uint32_t) desc[pos + i] << (8 * i);
68                 /* Consume value bytes. */
69                 pos += size;
70 
71                 if (type == HID_RPTDESC_TYPE_GLOBAL && tag == HID_RPTDESC_TAG_USAGE_PAGE) {
72                         /* A usage page is a 16 bit value coded on at most 16 bits. */
73                         if (size > 2)
74                                 return -EINVAL;
75                         /* A usage page sets the upper 16 bits of a following usage. */
76                         usage = (value & 0x0000ffffu) << 16;
77                 }
78 
79                 if (type == HID_RPTDESC_TYPE_LOCAL && tag == HID_RPTDESC_TAG_USAGE) {
80                         /* A usage is a 32 bit value, but is prepended with the current usage page if
81                          * coded on less than 4 bytes (that is, at most 2 bytes). */
82                         if (size == 4)
83                                 usage = value;
84                         else
85                                 usage = (usage & 0xffff0000u) | (value & 0x0000ffffu);
86                         if (usage == FIDO_FULL_USAGE_CTAPHID)
87                                 return 1;
88                 }
89         }
90 
91         return 0;
92 }
93