Blog
Hobby/1 min read

同居人を錬成しよう

外出中にPCを起動したい?なら誰かと同棲しろ

TL;DR

  • Raspberry Pi Pico 2 WでリモートKVMをつくろう
  • Pico 2 WがUSB HIDキーボードになる&WiFiつながるので外向きに繋ぐだけでNAT越えのリモートKVMができた
  • ブラウザ → Cloudflare Worker(Durable Objectでリレー)→ Pico 2 W → USB HID という構成
  • E2EE(エフェメラルECDH + 署名 + AES-256-GCM)を入れた

背景

私は自分の死後にそなえて、PCをVeraCryptで暗号化しています。ただ、そうすると困るときがあるのです。それがリモート接続。

VeraCrypt暗号化されたデバイスは、起動時にパスワードを入力するorキーデバイスを挿入しておく必要があります。そうしないとOSが起動しません。OSが起動しないとVNCとかが繋げられない。つまり、リモート接続ができない。

電源つけっぱにしとけば良いというのはそうなんだけど、何らかの要因(Windows Updateとか)で勝手に再起動された日には目も当てられません。ましてやそれが長期帰省の初日だったら最悪です。(最悪でした。)

こういう時、いくつか選択肢があります。

  • PiKVMみたいな本格KVMを導入
  • 彼女をつくって同棲する
  • 高速バスでさくっと往復して電源入れてくる

た、高い…全部コストがかかりすぎです。

そこで私は思いつきました。電源入れるだけならWoLでもSwitchBotでも何でもできる。あとはWiFiつながるHIDデバイスさえあれば十分や。Pico 2 W(RP2350 + Wi-Fi)なら TinyUSBでHIDデバイスになれるし、2000円くらいで済む。これでいけるだろと思って作りました。

買うデバイスまちがえた

初めにLLMとブレストした時点では、Zero 2 Wを買ってCloudflaredとかを立ててうにうにする構想だったのですが、「よくわからんけど、なんとか2 W?みたいなの買えばいいんやな??」とノリでAmazonポチった結果、Pico 2 Wが届きました。PicoはOS動かすスペックがないので、全部Cで記述する必要があります。これでGPT-6-Astraにめちゃめちゃ怒られた。デバイス売れとかいわれた。

これをなだめてCのコード書いてもらいました。

構成

ブラウザ ──WSS──► Cloudflare Worker ──WSS──► Pico 2 W ──USB HID──► 対象PC                 (Durable Objectリレー)

  • デバイスは常に**外向き**に接続するので、NATの裏でもファイアウォールの穴開け不要
  • Worker は `/device/:id` を Durable Object に流して、デバイスソケットとブラウザソケットを1対1で中継するだけ
  • ブラウザ側はログイン(パスワード + TOTP)して接続ボタンを押すと、`e.code` をHID usageに変換して8バイトのレポートを流す

Worker側の認証はPBKDF2 + TOTP + HMACセッションCookie + レートリミット + セキュリティヘッダと、まあまあちゃんとした。

この辺はPicoにいろいろ書き込むのがめんどいということ、我が家はマンションでポート開放ができないということなどを勘案した構成になっております。

E2EEを入れた

ここが本題です。Workerは信頼できるとはいえ、リレーが全部平文を見えるのは気持ち悪い。せっかくVeraCryptとかいうかっこいい(?)もの使ってるのに、肝心のパスワード仕様がこれじゃあつまらん。ということで、WebSocketの中身をさらにE2E暗号化しました。

```

browser → {"type":"key-req","nonce":...} 
device  → {"type":"hello","spub":静的公開鍵,"epub":エフェメラル公開鍵,"sig":署名} 
browser → {"type":"key","pub":ブラウザのエフェメラル}
device  → 暗号化 {"type":"ready"}
あとは全部 [0x02][seq][AES-256-GCM]

ポイント:

  • デバイスの静的鍵は署名専用。セッション鍵は両側エフェメラル ECDH → HKDF-SHA256 → AES-256-GCMなので、後でデバイス鍵が漏れても過去の通信は復号できない(forward secrecy)
  • フィンガープリント(SHA-256 of 静的公開鍵)を初回に目視照合してlocalStorageにピン留め(TOFU)。ズレてたら接続前に中止
  • ペアリングコードをHKDFのsaltに混ぜることでブラウザ側も認証する。これのおかげで、Cookie盗まれてもキー注入されない

加えてデバイス→WorkerのTLSはGTS Root R4をピン留めして`VERIFY_REQUIRED`にしてあります。2重に暗号化されてるかんじ?多分。

サブエージェントにレビューさせた

実装後に、仕様レビューとセキュリティレビューを2つのサブエージェントに並行で投げてみました。これが結構仕事をして、

  • バイトスキップのmin/maxが逆(巨大フレームでストリームが壊れる)
  • デバイス再接続時にブラウザが再ハンドシェイクしない(表示上は接続中なのにキーが届かない)
  • `Sec-WebSocket-Accept`を検証してない
  • セッション失効の仕組みがない

など、自分では思いつきもしないものがたくさん出てきてこわかった。もうぼくはAIに勝てないかもしれない。

まとめ

  • Pico 2 W 1台でNAT越えのリモートキーボードができた。コスト的には Pico 本体だけ
  • E2EE + FS + フィンガープリント照合 + ペアリングコードで、リレーを信用しなくていい構成にできた
  • マウスと映像は未対応。最小構成ヨシ!

コードは [GitHub](https://

github.com/MAV3Ndev/Pico2KVM) に置いてます。通信仕様は Detailed-Spec に全部書いたので、詳しい話はそちらを参考にしてください。