市販の安い ESP32 ボードを pico-fido ファームウェアで焼いて作る、USB 接続の
FIDO2/WebAuthn パスキーデバイス(自作 YubiKey)。
出典: https://www.makeuseof.com/made-own-yubikey-less-than-8/
- ESP32-S3 ボードで USB FIDO2 キーとして動作。
GetInfo(CTAP 2.3 / 1024 bytes)、登録(packed)、認証(署名 71B)、再起動後の資格情報の永続化を実機で確認 - 同じボードで Flash Encryption (release) + Secure Boot v2 署名 (RSA-3072) + SDM を有効化済み。以後の書き込みは 署名+事前暗号化イメージ+
--forceが必要(セキュリティプロファイル) - LCD にキャラクター画像と状態(READY / PRESS / WORKING)を表示
- ESP32-C3 の OLED / BLE(Zephyr) 版は別系統の実験として docs/c3-ble.md に分離した(USB HID は不可)
- ESP32-S2 または ESP32-S3(USB-OTG デバイスモードが必須)
- ⚠ ESP32-C3・無印 ESP32・C6・H2 は不可(USB-OTG ペリフェラルが無い)
- ブラウザ(
picokeys.com/esp32-flasher/の Web 書き込みツール用)
pico-fido の機能一式(CTAP 2.3 / CTAP 1 / WebAuthn / U2F / ECDSA・EdDSA 複数曲線 /
YubiKey 風 OTP エミュレーション)を、PC から見て本物の USB セキュリティキーとして使う。
1. ESP32-S2/S3 ボードを用意($6〜8 程度)
2. https://picokeys.com/esp32-flasher/ を開く
3. BOOT+RESET でブートローダに入れてブラウザから接続
4. ファームウェア「Pico Fido」を選んで書き込み
5. PC に挿すと HID オーセンティケータとして認識される
6. PicoKey App で VID/PID をパッチ(OpenSC/ykman/GnuPG との互換用)
このリポジトリでは Web ツールを使わず、CLI でビルドして書き込む構成も扱う(LCD 表示・ Flash Encryption を含む)。手順は「ESP32-S3 + ST7735 LCD」と「セキュリティプロファイル」を参照。 既に暗号化を有効にした個体では Web ツールでの書き込みはできない。
- FIDO 認証なし・セキュアエレメントなし。 個人用途・実験用・バックアップ鍵に限定
- ファームウェアは AGPLv3
USB-OTG を持つ実機として ESP32-S3 ボード(0.96inch ST7735 80×160 IPS 付き) を追加。
- SoC: ESP32-S3 (QFN56) rev v0.2 / Flash 16MB / PSRAM 8MB / native USB
- USB シリアルは MAC 由来
- LCD: ST7735S 80×160、
SPI2_HOST、SCLK=10 / MOSI=11 / CS=12 / DC=13 / RST=14 - BOOT ボタン = GPIO0(基板裏)。起動時に押したままだと ROM ダウンロードモードに固定される
- 元の 16MB フラッシュは
backups/esp32s3-original-20261022.aesgcm(鍵は~/.config/diy-fido-key/backup-keys/、Git 対象外)
ESP-IDF v5.5.1(upstream CI と同じ)でビルドする。vendor/pico-fido は Git 対象外。
git clone https://github.com/polhenarejos/pico-fido vendor/pico-fido
git -C vendor/pico-fido submodule update --init --recursive
. ~/esp/esp-idf-v5.5.1/export.sh
sh tools/build-pico-fido-esp32s3.sh # LCD パッチ適用 + build + merge
esptool --chip esp32s3 --port /dev/cu.usbmodemXXXX write-flash 0x0 build/pico-fido-esp32s3/pico_fido_full.binダウンロードモードは BOOT(GPIO0) を押したまま USB を挿す。書き込み後は BOOT を離して USB を挿し直す(押したままだと起動しない)。
注意:
tools/install-esp32-lcd.shは LCD に加えてpatches/pico-fido-psram-shadow.patch(part0を PSRAM のシャドウに載せる)も適用する。ビルドスクリプトは PSRAM(OCT / 40MHz /IGNORE_NOTFOUND)を有効にする- 上の
write-flashは 暗号化していない個体向け。SDM(暗号化有効)の個体では失敗するので、セキュリティプロファイルの手順を使う
MADCTL 0x08(BGR)/ オフセットcol=26, row=1/ INVON- 16bit カラーは 送信前にバイトスワップ必須(ESP32 はリトルエンディアン、ST7735 は MSB first)。忘れると画像がモザイク状に崩れる
- SPI クロックは 12MHz で動作確認(ST7735 の書き込み上限は概ね 15MHz。40MHz は未検証)
- ベンダーデモは TFT_eSPI の
TFT_eSPI(80,160)+setRotation(1)。160x80 用のGREENTAB160x80(26,1 + INVON)に相当 - キャラ画像は
tools/make-lcd-image.pyで白背景を輪郭からフラッドフィルして透過し、esp32-lcd/mascot_80x80.hに埋め込む - 校正用に
-DLCD_CALIBRATION=ON(向き/オフセット/反転)と-DLCD_COLOR_CAL=ON(色順/バイト順)のビルドがある。BOOT 押下で候補を切り替える
Pico Key(VID:PID2E8A:10FE)として macOS に HID 列挙。CTAP 2.3 / max msg 1024python-fido2で登録(attestationpacked、UP+AT)と認証(署名検証、counter 2、UP)に成功- Chrome からの WebAuthn も成功(
tools/webauthn_local_test.py、RP=localhost:8787):webauthn.create→webauthn.get(署名 72 bytes、BOOT 承認)。サーバー側に challenge/credential を記録 - LCD にキャラ画像+状態表示(READY / PRESS BOOT / WORKING)を出す
esp32-lcd/を追加。色・向き・バイト順は実機で確定済み
UV を要求するサービス(ChatGPT など)では PIN が必要。設定は対話式で、PIN は画面に出ない:
.venv/bin/python tools/set_fido_pin.py- USB-Serial/JTAG のセカンダリコンソールを有効にしたままだと、TinyUSB(OTG) と GPIO19/20 を奪い合い、TG0WDT の起動ループになる。
CONFIG_ESP_CONSOLE_SECONDARY_NONE=yにする(ビルドスクリプトが自動適用) - フラッシュモードは DIO で動作確認(公式ビルドと同じ。QIO は未検証)
- 書き込み後に HID として動いている間は
/dev/cu.*が消えるため、再書き込みは毎回 BOOT+挿し直しが必要 - Flash Encryption 有効後は SDM に入り、
read-flashなどの読み出しができない。書き込みは--forceと事前暗号化イメージが必須で、署名のないイメージは起動しない
USB-OTG を持たない C3 での OLED 表示・Zephyr BLE FIDO2・ES256 鍵の取り込み・macOS 向け BLE→HID ブリッジの試作は docs/c3-ble.md に分離した。
このリポジトリのファームは pico-fido(AGPLv3) の派生物です。バイナリを配るなら次を満たしてください。
- 対応するソースの提供(AGPLv3 §4-6)
- 本リポジトリのコード +
tools/install-esp32-lcd.shが当てるパッチ - pico-fido
ade7702/ pico-keys-sdka2f0a51/ ESP-IDFv5.5.1 - ビルド手順(
tools/build-pico-fido-esp32s3.sh)
- 本リポジトリのコード +
- LICENSE(AGPL-3.0)と NOTICE を同梱(両方このリポジトリにあり)
- 変更したファイルへの告知(著作権表示は削除しない)
- VID/PID を自分のものに変更(下記)
2E8A:10FE は pico-fido 用に Raspberry Pi が付与した ID で、pico-fido の README は
自分が所有していない VID/PID でバイナリを配布する権利はないとしています。
-
ソースのみ公開する場合: 既定の
2E8A:10FEのままで可(利用者が自分の環境でビルドする形。個人利用の範囲) -
ビルド済みバイナリを配布する場合: Raspberry Pi の ID を同梱することになるため、自分の VID/PID に変更する
-
個人開発なら pid.codes で VID
0x1209+ 固有 PID の割当を受ける -
ビルド時に環境変数で差し替え可能(確認済み):
FIDO_USB_VID=0x1209 FIDO_USB_PID=0x0001 sh tools/build-pico-fido-esp32s3.sh- VID/PID を変えると OpenSC / ykman の互換設定が変わるので README に注記する
esp32-lcd/mascot_80x80.h は画像そのものを埋め込んでいます。著作者は NOTICE に記載しています。
「FIDO 認証・セキュアエレメントなし、個人・実験用途、無保証」を明記してください。YubiKey や Pico を製品名に使わないこと。
tools/security-profiles.sh で保護レベルを切り替えます。スクリプトは eFuse を焼きません(ビルドと手順書のみ)。実機では下記の eFuse が既に焼かれています。
| プロファイル | Secure Boot | Flash Encryption | 用途 |
|---|---|---|---|
backup |
なし | なし | 開発・検証(再書き込み自由) |
encrypted |
RSA-3072 で署名(eFuse は焼かない) | development | 暗号化の試行 |
encrypted-release |
なし | release | 暗号化の試行用。SDM では署名なしのため起動しない(実測) |
secure |
RSA-3072(署名) | release | 実機で動作確認した構成 |
- ESP32-S3 の Secure Boot v2 は RSA-3072 のみ(ECDSA は非対応)。ECDSA 鍵では起動時に署名検証で失敗する
- Flash Encryption を有効にすると Secure Download Mode (SDM) になる。SDM では
read-flash/chip-id/ eFuse 読み出しが不可で、署名済みブートローダが必要。書き込みは--forceと事前暗号化イメージで行う - pico-fido は
part0を mmap するが、暗号化有効時は mmap が復号するため平文 FS と一致しない。patches/pico-fido-psram-shadow.patchで PSRAM 上のシャドウに置き換え、esp_flash_read/write(平文)で読み書きする。part0は暗号化フラグを付けない - PSRAM は 40MHz、
CONFIG_SPIRAM_IGNORE_NOTFOUND=y(未搭載でも起動) - 署名付きブートローダは 32KB を超えるため、Secure Boot を含むプロファイルは パーティションテーブルを 0x10000 に移動する(マージ済みイメージが配置を含む)。
encrypted-releaseは 0x9000
- 署名鍵(RSA-3072):
~/.config/diy-fido-key/secure-boot/secure-boot-rsa3072.pem - フラッシュ暗号化鍵:
~/.config/diy-fido-key/secure-boot/flash-encryption-key.bin - どちらも オフラインで2箇所保管。失うと更新不可
# ビルド(事前暗号化まで)
sh tools/security-profiles.sh secure
# 書き込み(SDM では --force、スタブなし)
esptool --port /dev/cu.usbmodemXXXX write-flash --force 0x0 build/pico-fido-secure/pico_fido_secure.enc.bin書き込み後は BOOT を離して USB を挿し直す。part0 のレイアウトを変えた場合だけ
esptool --port ... erase-region 0x200000 0x100000 で保存領域を作り直す。
「もう書き換えさせない」ための操作。どれも戻せません。完成したイメージで十分に 動作確認してから、最後に実施します。
| 操作 | 効果 | 戻せる |
|---|---|---|
SECURE_BOOT_EN + digest |
署名済みイメージのみ起動(更新は署名鍵が必要) | ✗ |
DIS_DOWNLOAD_MODE |
ROM ダウンロードモードを無効化=再書き込み不可 | ✗ |
DIS_USB_JTAG / DIS_PAD_JTAG |
JTAG 経路を無効化 | ✗ |
| pico-fido Secure Lock | MKEK を OTP に固定(資格情報を端末に紐付け) | ✗ |
# 最後に実行する。事前に署名済みイメージでの起動確認と鍵の退避を済ませること
espefuse.py --port /dev/cu.usbmodemXXXX burn_efuse DIS_DOWNLOAD_MODE
espefuse.py --port /dev/cu.usbmodemXXXX burn_efuse DIS_USB_JTAG注意:
DIS_DOWNLOAD_MODEを焼くと 二度と書き込めない(失敗時は基板ごと交換)- 署名鍵・暗号化鍵の控えがない状態で焼かない
- 現行機は 未実施(
DIS_DOWNLOAD_MODEは未焼)なので、まだ更新できる - 書き込み経路を残す限り、
SECURE_BOOT_ENなしでも SDM + 暗号化で「読み出し不可・ 任意ファームの書き込み不可」までは達成できる(署名検証はされない)
GetInfo(CTAP 2.3 / max 1024)、登録(packed / UP+AT)、認証(署名 71B / counter)、再起動後の永続化、LCD 表示- eFuse 状態:
BLOCK_KEY0に XTS 鍵、SPI_BOOT_CRYPT_CNT = 0b001、SECURE_BOOT_ENは未焼(署名は SDM 要求のため付与) - 実機で実行した eFuse 操作(一方通行):
espefuse.py --port /dev/cu.usbmodemXXXX burn_key BLOCK_KEY0 flash-encryption-key.bin XTS_AES_128_KEY
espefuse.py --port /dev/cu.usbmodemXXXX burn_efuse SPI_BOOT_CRYPT_CNT 0x1