Post-quantum protection depends on address secrecy; proposal: ephemeral ML-KEM-768 in the meow bootstrap for a per-connection PSK

オープン
#133 コメント 3 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
go

調査の方向性

Start by reading ConnInfo.PresharedKey and the --psk help text, then trace onMeow, peerConfig, SetPeerConfigFunc, SyncDevicePeer, and meowWait. Review the proposal's negotiation, compatibility, and test plan before seeking decisions on the open ML-KEM and protocol questions. Done requires an agreed design, implementation and tests for the exchange, plus corrected README and help text.

索引モデルが issue の本文から書いたものです。

説明

Problem

Since v0.6.0 (#84, #85) every tailcat address carries a WireGuard pre-shared key, and the docs describe it as providing post-quantum confidentiality and keeping a DERP operator who sees the node keys out of the tunnel. Both properties hold only while the address is secret: the PSK is a symmetric secret that lives in the address.

But the README also documents, as a first-class flow, publishing the address in a DNS TXT record and gating access with tailcat serve --allow=.... In that flow the PSK is public, so harvest-now-decrypt-later protection is zero, and --psk=true vs --psk=false makes no security difference at all. Nothing says so today: ConnInfo.PresharedKey's doc comment and the --psk help text still attribute post-quantum confidentiality to the PSK unconditionally. (Filippo's suggestion in #84 was explicitly conditioned on the addresses "already [being] secret tokens".)

Everything else in tailcat is already public-key based (Noise IK / X25519 for the tunnel, client node keys for --allow, separate disco keys). The PQ layer is the one piece that is symmetric and address-secrecy-dependent.

Question

Is any asymmetric / post-quantum key agreement planned? If not, would you be open to a PR along the lines below? I have a fuller writeup (byte layouts, KDF, negotiation matrix, test plan) and can attach it or send the PR directly if the direction is acceptable.

Proposal: ephemeral ML-KEM-768 in the meow bootstrap, per-connection PSK

  • The client generates an ephemeral ML-KEM-768 keypair per Client (Go stdlib crypto/mlkem, no new dependency).
  • New meow ping type 0x03: today's fields plus a KEM id and the encapsulation key (1184 B; packet ≈ 1254 B). The server runs the usual onMeow / allow-list path, encapsulates, and answers with a new meowed type 0x04: KEM id, an 8-byte tag of the ek, and the ciphertext (1088 B; packet ≈ 1102 B). Well under derp.MaxPacketSize.
  • Both sides derive a per-peer PSK:
    HKDF-SHA256(ikm = kem_ss, salt = addr_psk, info = "tailcat pq psk v1" || kem_id || server_pub || client_pub || H(ek) || H(ct)).
    The server keeps a small per-client PSK map behind the existing SetPeerConfigFunc source (peerConfig is already per-peer), memoised by (client, H(ek)) so the 1 s ping resends and replays are idempotent, and calls SyncDevicePeer when a client shows up with a new ek. The client installs its PSK before meowWait closes, so the very first WireGuard handshake is already hybrid.
  • Address format unchanged, no new persisted key material, PQ forward secrecy per connection (the server never holds a KEM private key). The address PSK stays as the salt, so secret-address deployments keep every property they have now.
  • Compatibility: old clients send 0x01 and get classical service as today; new clients send 0x03 and, in auto mode, start alternating with 0x01 after ~2 s so an old server (which never answers 0x03) still works. --pq=auto|require|off on serve and on client modes lets either side refuse the downgrade. tailcat ping reports (post-quantum) / (classical), served processes get TAILCAT_PEER_PQ.

Why an unauthenticated exchange over DERP is fine

The KEM secret is only ever used as the WireGuard PSK, which is mixed into the Noise IK handshake. If anyone (including the DERP operator) substitutes ek or ct in flight, the two sides derive different PSKs and the handshake fails; the attacker cannot complete a handshake without the server's static private key. So an active attacker gets denial of service, which a relay already has, and a passive / harvest-now-decrypt-later attacker must break both X25519 and ML-KEM-768. This is the same trust model as TLS 1.3 X25519MLKEM768: the key share is unauthenticated in flight and authentication is bound in afterwards. Authentication itself stays classical, as in WireGuard, TLS hybrid KEX, and Tailscale.

Alternatives I considered and would not propose

  • A static ML-KEM encapsulation key in the address: addresses go from ~60 to ~1650 characters, no PQ forward secrecy, key-file and wasm-storage migration.
  • A Rosenpass-style in-tunnel upgrade after a classical handshake: same security, two extra round trips before the first byte, and more state to get the gating right.

Questions for you

  1. Are you comfortable with a ~1.25 KB meow ping (resent each second, max 10 s) on the shared tailcat.dev relays?
  2. ML-KEM-768 (the TLS default) or 1024?
  3. Should addresses gain a "server speaks PQ meow" capability bit so new clients can skip the fallback dance for new addresses? One CBOR bool that old clients ignore. I would leave it out of v1.
  4. Independently of the feature: the README "Key Management" text and the --psk help should probably stop attributing post-quantum protection to the address PSK once the address is published.

Rough size if you want it: ~350 lines of non-test Go, ~400 lines of tests, small doc changes.

主要言語
Go
スター
8.8k
フォーク
404
平均マージ
3時間 48分
マージ済み PR(30日)
1

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

tailscale/tailcat のほかの issue

tailscale/tailcat の issue をすべて見る

似ている issue

Go の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。