Firmware for the Pico / Pico 2 that acts as a USB FIDO2 authenticator: CTAP2 and U2F, passkeys, BOOTSEL as the touch button, and clientPIN with PIN/UV auth protocols 1 and 2. A host simulator runs the same core so the python-fido2 test suite exercises it without hardware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
4.9 KiB
C
148 lines
4.9 KiB
C
// USB descriptors and the HID plumbing for the FIDO interface.
|
|
#include <string.h>
|
|
|
|
#include "pico/unique_id.h"
|
|
#include "platform.h"
|
|
#include "tusb.h"
|
|
|
|
// pid.codes test VID/PID: fine for personal use, not for distribution. Hosts
|
|
// recognize FIDO devices by the HID usage page, not the IDs.
|
|
#define USB_VID 0x1209
|
|
#define USB_PID 0x0001
|
|
|
|
#define SEND_TIMEOUT_MS 1000
|
|
#define RX_QUEUE_LEN 16 // power of two
|
|
|
|
enum { STR_LANGID, STR_MANUFACTURER, STR_PRODUCT, STR_SERIAL, STR_INTERFACE };
|
|
|
|
static const tusb_desc_device_t desc_device = {
|
|
.bLength = sizeof(tusb_desc_device_t),
|
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
|
.bcdUSB = 0x0200,
|
|
.bDeviceClass = 0x00,
|
|
.bDeviceSubClass = 0x00,
|
|
.bDeviceProtocol = 0x00,
|
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
|
.idVendor = USB_VID,
|
|
.idProduct = USB_PID,
|
|
.bcdDevice = 0x0100,
|
|
.iManufacturer = STR_MANUFACTURER,
|
|
.iProduct = STR_PRODUCT,
|
|
.iSerialNumber = STR_SERIAL,
|
|
.bNumConfigurations = 1,
|
|
};
|
|
|
|
// FIDO HID report descriptor (CTAP 2.1, section 11.2.8.1): 64-byte reports both ways.
|
|
static const uint8_t desc_hid_report[] = {
|
|
0x06, 0xd0, 0xf1, // Usage Page (FIDO Alliance)
|
|
0x09, 0x01, // Usage (CTAPHID)
|
|
0xa1, 0x01, // Collection (Application)
|
|
0x09, 0x20, // Usage (Input Report Data)
|
|
0x15, 0x00, // Logical Minimum (0)
|
|
0x26, 0xff, 0x00, // Logical Maximum (255)
|
|
0x75, 0x08, // Report Size (8)
|
|
0x95, 0x40, // Report Count (64)
|
|
0x81, 0x02, // Input (Data, Variable, Absolute)
|
|
0x09, 0x21, // Usage (Output Report Data)
|
|
0x15, 0x00, // Logical Minimum (0)
|
|
0x26, 0xff, 0x00, // Logical Maximum (255)
|
|
0x75, 0x08, // Report Size (8)
|
|
0x95, 0x40, // Report Count (64)
|
|
0x91, 0x02, // Output (Data, Variable, Absolute)
|
|
0xc0, // End Collection
|
|
};
|
|
|
|
#define EP_HID_OUT 0x01
|
|
#define EP_HID_IN 0x81
|
|
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
|
|
|
|
static const uint8_t desc_configuration[] = {
|
|
TUD_CONFIG_DESCRIPTOR(1, 1, 0, CONFIG_TOTAL_LEN, 0, 100),
|
|
TUD_HID_INOUT_DESCRIPTOR(0, STR_INTERFACE, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EP_HID_OUT,
|
|
EP_HID_IN, CFG_TUD_HID_EP_BUFSIZE, 5),
|
|
};
|
|
|
|
static uint8_t rx_queue[RX_QUEUE_LEN][HID_PACKET_SIZE];
|
|
static unsigned rx_head, rx_tail;
|
|
|
|
const uint8_t *tud_descriptor_device_cb(void) {
|
|
return (const uint8_t *)&desc_device;
|
|
}
|
|
|
|
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
|
|
(void)index;
|
|
return desc_configuration;
|
|
}
|
|
|
|
const uint8_t *tud_hid_descriptor_report_cb(uint8_t instance) {
|
|
(void)instance;
|
|
return desc_hid_report;
|
|
}
|
|
|
|
const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
|
static uint16_t desc[32];
|
|
char serial[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1];
|
|
const char *str;
|
|
size_t len;
|
|
(void)langid;
|
|
|
|
switch (index) {
|
|
case STR_LANGID:
|
|
desc[1] = 0x0409; // English (US)
|
|
desc[0] = (uint16_t)(TUSB_DESC_STRING << 8 | 4);
|
|
return desc;
|
|
case STR_MANUFACTURER: str = "Skey"; break;
|
|
case STR_PRODUCT: str = "Skey FIDO2 Key"; break;
|
|
case STR_SERIAL:
|
|
pico_get_unique_board_id_string(serial, sizeof(serial));
|
|
str = serial;
|
|
break;
|
|
case STR_INTERFACE: str = "FIDO"; break;
|
|
default: return NULL;
|
|
}
|
|
len = strlen(str);
|
|
if (len > 31) len = 31;
|
|
for (size_t i = 0; i < len; i++) desc[1 + i] = (uint8_t)str[i];
|
|
desc[0] = (uint16_t)(TUSB_DESC_STRING << 8 | (2 * len + 2));
|
|
return desc;
|
|
}
|
|
|
|
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer,
|
|
uint16_t reqlen) {
|
|
(void)instance;
|
|
(void)report_id;
|
|
(void)report_type;
|
|
(void)buffer;
|
|
(void)reqlen;
|
|
return 0;
|
|
}
|
|
|
|
// Runs inside tud_task(). Packets are queued so they are handled from the main
|
|
// loop, never re-entrantly while a response is being sent.
|
|
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type,
|
|
const uint8_t *buffer, uint16_t bufsize) {
|
|
(void)instance;
|
|
(void)report_id;
|
|
(void)report_type;
|
|
if (bufsize != HID_PACKET_SIZE || rx_head - rx_tail == RX_QUEUE_LEN) return;
|
|
memcpy(rx_queue[rx_head % RX_QUEUE_LEN], buffer, HID_PACKET_SIZE);
|
|
rx_head++;
|
|
}
|
|
|
|
bool platform_hid_receive(uint8_t packet[HID_PACKET_SIZE]) {
|
|
tud_task();
|
|
if (rx_head == rx_tail) return false;
|
|
memcpy(packet, rx_queue[rx_tail % RX_QUEUE_LEN], HID_PACKET_SIZE);
|
|
rx_tail++;
|
|
return true;
|
|
}
|
|
|
|
void platform_hid_send(const uint8_t packet[HID_PACKET_SIZE]) {
|
|
uint32_t start = platform_millis();
|
|
while (!tud_hid_ready()) {
|
|
if (!tud_mounted() || platform_millis() - start > SEND_TIMEOUT_MS) return; // host went away
|
|
tud_task();
|
|
}
|
|
tud_hid_report(0, packet, HID_PACKET_SIZE);
|
|
}
|