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:
commit
6c7e6ab5f5
41 files changed
+4218
No files matched your search
+10
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
// Reads one packet from stdin, waiting up to timeout_ms. Exits the process
|
||||
// when stdin is closed.
|
||||
bool host_receive(uint8_t packet[HID_PACKET_SIZE], int timeout_ms);
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Simulator: speaks CTAPHID over stdin/stdout, one 64-byte packet at a time.
|
||||
#include "ctaphid.h"
|
||||
#include "host.h"
|
||||
#include "pin.h"
|
||||
#include "platform.h"
|
||||
#include "presence.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define TICK_MS 50
|
||||
|
||||
int main(void) {
|
||||
uint8_t packet[HID_PACKET_SIZE];
|
||||
|
||||
platform_init();
|
||||
storage_init();
|
||||
pin_init();
|
||||
ctaphid_init();
|
||||
for (;;) {
|
||||
if (host_receive(packet, TICK_MS)) ctaphid_handle_packet(packet);
|
||||
ctaphid_tick();
|
||||
presence_tick();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Simulator backend: flash is emulated in a file (SKEY_FLASH_FILE), randomness
|
||||
// comes from the OS, and HID packets travel over stdin/stdout.
|
||||
//
|
||||
// The button: without SKEY_BUTTON_FILE every request is approved. With it,
|
||||
// creating that file is a press (its mtime is the press time), and the
|
||||
// simulator deletes it when the press is used.
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "host.h"
|
||||
#include "platform.h"
|
||||
|
||||
static uint8_t flash[STORAGE_SIZE];
|
||||
static const char *flash_path;
|
||||
static const char *button_path;
|
||||
static struct timespec start;
|
||||
|
||||
void platform_init(void) {
|
||||
flash_path = getenv("SKEY_FLASH_FILE");
|
||||
if (!flash_path) flash_path = "skey_flash.bin";
|
||||
button_path = getenv("SKEY_BUTTON_FILE");
|
||||
memset(flash, 0xff, sizeof(flash));
|
||||
FILE *f = fopen(flash_path, "rb");
|
||||
if (f) {
|
||||
if (fread(flash, 1, sizeof(flash), f) != sizeof(flash)) memset(flash, 0xff, sizeof(flash));
|
||||
fclose(f);
|
||||
}
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
}
|
||||
|
||||
uint32_t platform_millis(void) {
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
return (uint32_t)((now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec - start.tv_nsec) / 1000000);
|
||||
}
|
||||
|
||||
void platform_random(uint8_t *buf, size_t len) {
|
||||
while (len > 0) {
|
||||
ssize_t n = getrandom(buf, len, 0);
|
||||
if (n < 0) platform_panic("getrandom failed");
|
||||
buf += n;
|
||||
len -= (size_t)n;
|
||||
}
|
||||
}
|
||||
|
||||
void platform_panic(const char *msg) {
|
||||
fprintf(stderr, "skey_sim: %s\n", msg);
|
||||
abort();
|
||||
}
|
||||
|
||||
bool platform_button_take_press(uint32_t max_age_ms) {
|
||||
struct stat st;
|
||||
struct timespec now;
|
||||
if (!button_path) return true;
|
||||
if (stat(button_path, &st) != 0) return false;
|
||||
unlink(button_path);
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
int64_t age_ms = (int64_t)(now.tv_sec - st.st_mtim.tv_sec) * 1000 + (now.tv_nsec - st.st_mtim.tv_nsec) / 1000000;
|
||||
return age_ms <= (int64_t)max_age_ms;
|
||||
}
|
||||
|
||||
void platform_led(bool on) {
|
||||
(void)on;
|
||||
}
|
||||
|
||||
void platform_wink(void) {
|
||||
fprintf(stderr, "skey_sim: wink\n");
|
||||
}
|
||||
|
||||
static void save_flash(void) {
|
||||
FILE *f = fopen(flash_path, "wb");
|
||||
if (!f || fwrite(flash, 1, sizeof(flash), f) != sizeof(flash)) platform_panic("cannot write flash file");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void platform_flash_read(uint32_t offset, void *buf, size_t len) {
|
||||
if (offset > STORAGE_SIZE || len > STORAGE_SIZE - offset) platform_panic("flash read out of range");
|
||||
memcpy(buf, flash + offset, len);
|
||||
}
|
||||
|
||||
void platform_flash_erase(uint32_t offset) {
|
||||
if (offset % STORAGE_SECTOR_SIZE || offset >= STORAGE_SIZE) platform_panic("bad flash erase");
|
||||
memset(flash + offset, 0xff, STORAGE_SECTOR_SIZE);
|
||||
save_flash();
|
||||
}
|
||||
|
||||
// Behaves like NOR flash: whole pages only, and programming only clears bits.
|
||||
void platform_flash_program(uint32_t offset, const void *buf, size_t len) {
|
||||
const uint8_t *src = buf;
|
||||
if (offset % STORAGE_PAGE_SIZE || len % STORAGE_PAGE_SIZE || offset > STORAGE_SIZE ||
|
||||
len > STORAGE_SIZE - offset)
|
||||
platform_panic("bad flash program");
|
||||
for (size_t i = 0; i < len; i++) flash[offset + i] &= src[i];
|
||||
save_flash();
|
||||
}
|
||||
|
||||
void platform_hid_send(const uint8_t packet[HID_PACKET_SIZE]) {
|
||||
fwrite(packet, 1, HID_PACKET_SIZE, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
bool host_receive(uint8_t packet[HID_PACKET_SIZE], int timeout_ms) {
|
||||
struct pollfd pfd = {.fd = STDIN_FILENO, .events = POLLIN};
|
||||
if (poll(&pfd, 1, timeout_ms) <= 0) return false;
|
||||
for (size_t got = 0; got < HID_PACKET_SIZE;) {
|
||||
ssize_t n = read(STDIN_FILENO, packet + got, HID_PACKET_SIZE - got);
|
||||
if (n <= 0) exit(0); // the host closed the pipe: unplugged
|
||||
got += (size_t)n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool platform_hid_receive(uint8_t packet[HID_PACKET_SIZE]) {
|
||||
return host_receive(packet, 0);
|
||||
}
|
||||
Reference in new issue
Block a user