Use your USB security key wirelessly. KeyRelay puts a USB-A security key (tested with a YubiKey 5 NFC) in a small box next to your keyboard and relays it over Bluetooth LE to a receiver plugged into your computer. Touch the key on your desk; your computer can stay closed under the desk.
Status: a working personal build, published as is. Tested on macOS with one YubiKey 5 NFC (firmware 5.1.2). Windows and Linux hosts are untested. Secure boot and flash encryption are not set up yet, so treat a unit like the key itself and read SECURITY.md before relying on it.
| Feature | How it is relayed | Status |
|---|---|---|
| FIDO2 / U2F (passkeys, WebAuthn) | 64-byte CTAPHID reports | Works: webauthn.io register + sign-in (Chrome) |
| OpenPGP (GnuPG) | CCID smart-card messages | Works: gpg --card-status |
| OTP typed on touch | keyboard reports, sent as a fixed sequence per touch; only Yubico OTP keys by default | Works: Yubico OTP validated on demo.yubico.com |
| PIV, OATH | CCID smart-card messages | Works with ykman through tools/keyrelay_ykman.py: info, piv info, oath accounts add / code / delete (ykman 5.9.2) |
| OTP challenge-response | HID feature reports, proxied one by one | Relayed; see the note on Yubico tools below. Commands that change slots or settings are blocked (SECURITY.md) |
Not yet confirmed: PIV/OpenPGP signing, PIV through standard PC/SC software (e.g. OpenSC), and
passkey management through Chrome's chrome://settings/securityKeys.
These tools look for devices with Yubico's USB vendor ID. KeyRelay uses its own USB IDs and names and does not impersonate another vendor.
-
ykman: works through
tools/keyrelay_ykman.pyfor the commands that use the smart-card interface (info,piv,oath,openpgp,config). ykman can use a reader picked with--reader, but it works out the YubiKey model from the reader's name (such as "Yubico YubiKey OTP+FIDO+CCID"), so with KeyRelay's reader it stops with "Failed to connect to YubiKey". The script tells ykman, inside its own process only, which model to assume for a reader named "KeyRelay", then runs the normal ykman CLI. Run it with the Python that ykman is installed in:# ykman installed with Homebrew; run from the repository root ykr() { "$(brew --prefix ykman)/libexec/bin/python" tools/keyrelay_ykman.py --reader KeyRelay "$@"; } ykr info ykr piv info ykr oath accounts code
ykman otpandykman fidodo not work: they use other USB interfaces, not the smart-card reader. The script replaces a private ykman function, so an ykman update may break it (tested with ykman 5.9.2). -
Yubico Authenticator (GUI) and KeePassXC do not work today. Once KeyRelay has its own pid.codes PID, the plan is to ask KeePassXC to add it to its list of supported devices.
Browsers (FIDO), GnuPG and typed OTPs work as is.
computer ══USB══ receiver ─────── BLE ─────── desk unit ══USB══ security key
(FIDO HID + CCID reader (ESP32-S3, (XIAO ESP32S3,
+ OTP keyboard) native USB) USB host)
- The desk unit (Seeed XIAO ESP32S3) is a USB host for the key. It claims the key's FIDO, CCID and OTP keyboard interfaces and forwards traffic without interpreting it (FIDO channel IDs are virtualised so channels survive re-enumeration).
- The receiver (any ESP32-S3 board with a native USB port) shows up as the same three interfaces and forwards everything to the desk unit.
- The BLE link uses LE Secure Connections plus an application layer with a pre-shared key, ephemeral X25519 (forward secrecy) and AES-256-GCM. See SECURITY.md for what this does and does not protect.
- Both units are USB powered. The desk unit takes power (and its log console) through a CP2102N USB-UART module whose USB-C port sits at the back of the case.
See docs/BUILD.md (日本語) for the parts list, wiring, firmware build, key provisioning and case assembly. Design notes and example logs (in Japanese, with the Japanese log setting) are in docs/ja/DEV_NOTES.md. In short, from the repository root:
# ESP-IDF v5.4
(printf 'YDK1'; head -c 32 /dev/urandom) > keyrelay-key.bin # same key for both units
idf.py -C firmware/receiver set-target esp32s3 build merge-bin -o keyrelay_receiver_full.bin
idf.py -C firmware/desk_unit set-target esp32s3 build merge-bin -o keyrelay_desk_unit_full.bin
# flash each merged image at 0x0 and keyrelay-key.bin at 0x190000 (see docs/BUILD.md)firmware/
components/yd_link/ BLE link, PSK + X25519 encryption (yd_sec), fragmentation, CCID helpers
desk_unit/ USB host side: FIDO, CCID and OTP relays (XIAO ESP32S3)
receiver/ USB device side: FIDO HID, CCID, OTP keyboard drivers (TinyUSB)
tests/host/ host-side unit tests (make -C firmware/tests/host)
tests/fuzz/ libFuzzer targets for the link input (make -C firmware/tests/fuzz run)
hardware/
case/ parametric case (Python + manifold3d) and ready-to-print STLs
docs/ build guide (English / Japanese), images
- USB IDs: the default build uses the pid.codes test PID
0x1209:0x0001, which is not unique. pid.codes does not allow devices with test PIDs to be redistributed, so no prebuilt firmware is published until KeyRelay has its own PID: build it yourself for your own use. A dedicated PID will be requested from pid.codes now that the project is public. - Only the USB-A key variant has a case. A USB-C key variant and a battery-powered desk unit are planned.
- One request at a time per interface (same as the key itself). If the BLE link drops, the computer sees the smart card being removed, and PIN state is lost: before a new connection's first smart card request reaches the key, the desk unit power-cycles the card (or, if the key refuses, restarts the key's USB bus).
- Serial log messages are in English by default. Japanese can be selected in
idf.py menuconfig→ KeyRelay log language. - Secure boot, flash encryption and NVS encryption are not set up by default. The procedure in docs/HARDENING.md is verified on a receiver; the desk unit is not hardened on hardware yet.
- Firmware, tools and documentation: MIT
- Hardware (case design files in
hardware/): CERN-OHL-P-2.0
KeyRelay is an independent project and is not affiliated with, endorsed by or sponsored by Yubico AB. YubiKey and Yubico are trademarks of Yubico AB. Other product names are trademarks of their respective owners and are used only to describe compatibility.
