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>
87 lines
2.6 KiB
C
87 lines
2.6 KiB
C
#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);
|
|
}
|