Skey: a FIDO2 security key on the Raspberry Pi Pico

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>
This commit is contained in:
ckochandClaude Opus 5.5 committed 2026-10-14 15:07:38 -04:00
commit 6c7e6ab5f5
41 files changed
+4218

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
#include <string.h>
#include "crypto.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
#include "pico/rand.h"
#include "pico/stdlib.h"
#include "platform.h"
// The storage region is the last 64 KB of flash, far past the firmware image.
#define STORAGE_FLASH_OFFSET (PICO_FLASH_SIZE_BYTES - STORAGE_SIZE)
_Static_assert(STORAGE_SECTOR_SIZE == FLASH_SECTOR_SIZE, "sector size mismatch");
_Static_assert(STORAGE_PAGE_SIZE == FLASH_PAGE_SIZE, "page size mismatch");
extern char __flash_binary_end;
void platform_led(bool on) {
#ifdef PICO_DEFAULT_LED_PIN
gpio_put(PICO_DEFAULT_LED_PIN, on);
#else
(void)on;
#endif
}
void platform_init(void) {
if ((uintptr_t)&__flash_binary_end > XIP_BASE + STORAGE_FLASH_OFFSET)
platform_panic("firmware overlaps the storage region");
#ifdef PICO_DEFAULT_LED_PIN
gpio_init(PICO_DEFAULT_LED_PIN);
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
#endif
}
uint32_t platform_millis(void) {
return to_ms_since_boot(get_absolute_time());
}
// pico_rand mixes fresh hardware entropy (ROSC jitter, timers, and the TRNG
// on RP2350) into every call. Each output block hashes many of those calls.
void platform_random(uint8_t *buf, size_t len) {
uint64_t samples[32];
uint8_t block[32];
while (len > 0) {
for (size_t i = 0; i < 32; i++) samples[i] = get_rand_64();
crypto_part_t part = {samples, sizeof(samples)};
crypto_sha256(&part, 1, block);
size_t n = len < sizeof(block) ? len : sizeof(block);
memcpy(buf, block, n);
buf += n;
len -= n;
}
crypto_wipe(samples, sizeof(samples));
crypto_wipe(block, sizeof(block));
}
void platform_panic(const char *msg) {
panic("%s", msg);
}
void platform_wink(void) {
for (int i = 0; i < 3; i++) {
platform_led(true);
sleep_ms(100);
platform_led(false);
sleep_ms(100);
}
}
void platform_flash_read(uint32_t offset, void *buf, size_t len) {
memcpy(buf, (const void *)(XIP_BASE + STORAGE_FLASH_OFFSET + offset), len);
}
// Flash is unavailable for execution while it is being written, so interrupts
// (whose handlers live in flash) stay off for the duration.
void platform_flash_erase(uint32_t offset) {
uint32_t irq = save_and_disable_interrupts();
flash_range_erase(STORAGE_FLASH_OFFSET + offset, FLASH_SECTOR_SIZE);
restore_interrupts(irq);
}
void platform_flash_program(uint32_t offset, const void *buf, size_t len) {
uint32_t irq = save_and_disable_interrupts();
flash_range_program(STORAGE_FLASH_OFFSET + offset, buf, len);
restore_interrupts(irq);
}