Skip to content

feat(wire): E2E PAKE handshake with X25519 ECDH + AES-256-GCM - #103

Merged
blaspat merged 2 commits into
mainfrom
feat/e2e-pake-handshake
Aug 4, 2026
Merged

feat(wire): E2E PAKE handshake with X25519 ECDH + AES-256-GCM#103
blaspat merged 2 commits into
mainfrom
feat/e2e-pake-handshake

Conversation

@blaspat

@blaspat blaspat commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Summary

Adds forward-secret end-to-end encryption between the Go node client and the Python plugin server. The pairing token is never transmitted on the wire after initial pairing — only HMAC proofs are exchanged during the auth handshake.

Changes

  • internal/wire/e2e.go (new) — X25519 keygen, ECDH, HKDF-SHA256 key derivation, HMAC mutual auth proofs, AES-256-GCM encrypt/decrypt
  • internal/wire/messages.go — Add E2E, ECDHPub, Proof, Salt fields to handshake message structs; add Enc wire type
  • internal/wire/client.go — E2E handshake in Dial: ECDH exchange in hello/hello_ack, proof verification in auth/auth_ok, session key derivation. WriteE2E/ReadE2E wrappers for all post-handshake messages
  • internal/wire/dispatch.go — Delegate to WriteE2E/ReadE2E after handshake; legacy plaintext fallback for servers without E2E
  • README.md — Document E2E encryption section with handshake flow, security properties, and backward compatibility
  • .gitignore — Ignore built binary

Handshake

Client                                    Server
  │  hello { e2e: true, ecdh_pub }        │
  │  hello_ack { ecdh_pub, salt }         │
  │  auth { proof: HMAC(handshake_key) }  │
  │  auth_ok { proof: HMAC(handshake_key) }│
  │  ─── AES-256-GCM ACTIVE ───

Security properties

Property How
Token never on wire Token mixed into HKDF; auth carries only HMAC proof
Forward secrecy Ephemeral X25519 keys per session, discarded on disconnect
MITM resistance Without token, attacker can't forge HMAC proof → 4001
Replay immunity New ephemeral keys per session

Backward compatibility

Client sends e2e: true in hello. Server without E2E support omits ecdh_pub → client falls back to legacy plaintext auth.

Closes #58

blaspat added 2 commits August 3, 2026 23:55
Adds forward-secret encrypted messaging between node and plugin:

- New e2e.go: X25519 keygen, ECDH, HKDF-SHA256 key derivation,
  HMAC-SHA256 mutual auth proofs, AES-256-GCM encrypt/decrypt
- Handshake: hello(ecdh_pub) → hello_ack(ecdh_pub+salt) →
  auth(proof, no token on wire) → auth_ok(server proof)
- Session key derived from ECDH shared secret + pairing token
  (token never transmitted after initial pairing)
- Encrypted envelop: {type: enc, data: base64url(IV||ct||tag)}
- Dispatcher delegates to Client.WriteE2E/ReadE2E
- Backward compatible: legacy plaintext auth when server lacks ECDH

Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
Add a full End-to-End Encryption section covering the handshake flow,
security properties (forward secrecy, MITM resistance, token never on
wire), encrypted vs plaintext message types, and backward compatibility.

Update Architecture and Security sections to reference E2E encryption.

Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
@blaspat
blaspat merged commit df18832 into main Aug 4, 2026
14 checks passed
@blaspat
blaspat deleted the feat/e2e-pake-handshake branch August 4, 2026 05:58
blaspat added a commit that referenced this pull request Aug 4, 2026
The E2E refactor in #103 replaced dispatch.ReadOne's 90s deadline
(DefaultPongTimeout + 30s) with readE2E's hardcoded 10s ReadMessage
deadline.  The server sends pings every ~30s, so connections died
before the first ping.

Add readTimeout/writeTimeout fields to Client (90s/10s defaults,
matching the pre-E2E dispatch values) and wire them into readE2E
and writeE2E.

Signed-off-by: Blasius Patrick <blasius.patrick@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant