Design: auth — per-client keypairs over IRC #6

Open
opened 2026-10-16 17:06:43 +00:00 by muse-lantern · 2 comments
Member

proj-rechat-auth

Client authentication via per-client ed25519 keypairs, exchanged entirely over IRC in the &auth channel. (Decided 2026-10-16.)

Key model

  • Server keypair: generated at setup, public key at /.well-known/res-chat. Single trust root for federation, client pinning, and capability tokens.
  • Client keypairs: per-client (per-device), not per-user. Generated by official client on first run, registered with server.

Registration (new device)

C → S: REGISTER <uid> <client-pubkey-b64> <device-name>
S → C: REGISTER-CHALLENGE <nonce-b64>
C → S: REGISTER-RESPONSE <sig-b64>
S → C: REGISTERED

Requires existing auth (password or trusted-device approval).

Login (returning device)

C → S: LOGIN <uid> <client-pubkey-b64>
S → C: CHALLENGE <server-pubkey-b64> <nonce-b64>
C → S: RESPONSE <sig-b64> <client-nonce-b64>
S → C: VERIFIED <server-sig-b64> <jwt>

Both sides verify. Client checks server sig against pinned key (or pins on first use — TOFU). Server checks client sig against registered pubkey.

Security policy

  • Failed challenge → key suspended, not retried. Re-authorization requires password or trusted-device approval.
  • Unknown pubkey → AUTH-FAILED unknown-key
  • Bad signature → AUTH-FAILED bad-sig + suspend
  • Suspended key → AUTH-FAILED suspended (no challenge issued)

Implementation notes

  • Sign "res-chat-login:" || nonce (context binding, not bare nonce)
  • Nonces: 32 bytes, single-use, unpredictable, never reused
  • All messages fit in 512-byte IRC limit (base64-encoded keys/sigs)
  • JWTs remain IP-bound (existing design)

Tiered trust

Tier Mechanism
Chat IRC + JWT (IP-bound)
Files/voice Signed capability tokens (scoped, stateless)
Server identity Pinned ed25519 key
Admin Capability tokens + separate scope

Deferred

  • Server key rotation (sign new with old, broadcast, grace period) — v1 ships with manual re-pin + loud warning
  • User keypairs as message-signing identity (federation future)
proj-rechat-auth Client authentication via per-client ed25519 keypairs, exchanged entirely over IRC in the `&auth` channel. (Decided 2026-10-16.) ## Key model - **Server keypair**: generated at setup, public key at `/.well-known/res-chat`. Single trust root for federation, client pinning, and capability tokens. - **Client keypairs**: per-client (per-device), not per-user. Generated by official client on first run, registered with server. ## Registration (new device) ``` C → S: REGISTER <uid> <client-pubkey-b64> <device-name> S → C: REGISTER-CHALLENGE <nonce-b64> C → S: REGISTER-RESPONSE <sig-b64> S → C: REGISTERED ``` Requires existing auth (password or trusted-device approval). ## Login (returning device) ``` C → S: LOGIN <uid> <client-pubkey-b64> S → C: CHALLENGE <server-pubkey-b64> <nonce-b64> C → S: RESPONSE <sig-b64> <client-nonce-b64> S → C: VERIFIED <server-sig-b64> <jwt> ``` Both sides verify. Client checks server sig against pinned key (or pins on first use — TOFU). Server checks client sig against registered pubkey. ## Security policy - **Failed challenge → key suspended**, not retried. Re-authorization requires password or trusted-device approval. - Unknown pubkey → `AUTH-FAILED unknown-key` - Bad signature → `AUTH-FAILED bad-sig` + suspend - Suspended key → `AUTH-FAILED suspended` (no challenge issued) ## Implementation notes - Sign `"res-chat-login:" || nonce` (context binding, not bare nonce) - Nonces: 32 bytes, single-use, unpredictable, never reused - All messages fit in 512-byte IRC limit (base64-encoded keys/sigs) - JWTs remain IP-bound (existing design) ## Tiered trust | Tier | Mechanism | |------|-----------| | Chat | IRC + JWT (IP-bound) | | Files/voice | Signed capability tokens (scoped, stateless) | | Server identity | Pinned ed25519 key | | Admin | Capability tokens + separate scope | ## Deferred - Server key rotation (sign new with old, broadcast, grace period) — v1 ships with manual re-pin + loud warning - User keypairs as message-signing identity (federation future)
Author
Member

Multi-key device model (Zach, 2026-10-16)

  • Accounts support MULTIPLE keys. Each registered keypair = a different device (laptop key, phone key, etc.).
  • client_keys table already has the shape for this (uid, pubkey, device_name, status). Device naming happens at registration.
  • Key revocation is per-key (lose your phone → revoke that key, others keep working).
### Multi-key device model (Zach, 2026-10-16) - Accounts support MULTIPLE keys. Each registered keypair = a different device (laptop key, phone key, etc.). - `client_keys` table already has the shape for this (uid, pubkey, device_name, status). Device naming happens at registration. - Key revocation is per-key (lose your phone → revoke that key, others keep working).
Author
Member

TLDR: Mutual Challenge Key Auth (2026-10-17)

Server identity (TOFU): Server generates Ed25519 keypair at setup. Public key broadcast in MOTD on connect. Client caches on first see, warns loudly on mismatch (SSH known_hosts model).

Mutual challenge on key auth:

  1. Client → &auth: KEYAUTH <fingerprint>
  2. Server → &auth: CHALLENGE <server-nonce> (UUIDv7 — timestamp baked in)
  3. Client → &auth: RESPONSE <sig-of-server-nonce> <client-nonce>
  4. Server verifies client sig → looks up pubkey in DB → finds account
  5. Server → &auth: VERIFY <sig-of-client-nonce>
  6. Client verifies server sig → both sides proven

Neither side accepts a bare public key. Signatures prove private key possession both ways. UUIDv7 nonce kills replay attacks (timestamp checked against timeout window). Response timing is a heuristic tripwire for relay attacks (not a hard reject).

Challenge on every connect: Server sends pubkey + nonce before any credentials. Client verifies server identity before sending password. Don't auth to an imposter.

Key registration: Password login first (bootstrap) → ADDKEY <name> <pubkey> in &auth → sets key_registered flag on account. LISTKEYS / REVOKEKEY <fingerprint> for management.

key_registered flag: Boolean on account. Prerequisite for joining encrypted groups. Shown in profiles.

## TLDR: Mutual Challenge Key Auth (2026-10-17) **Server identity (TOFU):** Server generates Ed25519 keypair at setup. Public key broadcast in MOTD on connect. Client caches on first see, warns loudly on mismatch (SSH `known_hosts` model). **Mutual challenge on key auth:** 1. Client → `&auth`: `KEYAUTH <fingerprint>` 2. Server → `&auth`: `CHALLENGE <server-nonce>` (UUIDv7 — timestamp baked in) 3. Client → `&auth`: `RESPONSE <sig-of-server-nonce> <client-nonce>` 4. Server verifies client sig → looks up pubkey in DB → finds account 5. Server → `&auth`: `VERIFY <sig-of-client-nonce>` 6. Client verifies server sig → both sides proven Neither side accepts a bare public key. Signatures prove private key possession both ways. UUIDv7 nonce kills replay attacks (timestamp checked against timeout window). Response timing is a heuristic tripwire for relay attacks (not a hard reject). **Challenge on every connect:** Server sends pubkey + nonce before any credentials. Client verifies server identity *before* sending password. Don't auth to an imposter. **Key registration:** Password login first (bootstrap) → `ADDKEY <name> <pubkey>` in `&auth` → sets `key_registered` flag on account. `LISTKEYS` / `REVOKEKEY <fingerprint>` for management. **`key_registered` flag:** Boolean on account. Prerequisite for joining encrypted groups. Shown in profiles.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
residual/rechat#6
No description provided.