From a4f961d609dcd7874e0dabf7dd4a510f83de8f9d Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 3 Oct 2026 21:31:08 -0700 Subject: [PATCH] P7 spec rev4: pin provider-contract unblock wording, PKCS8-prefix X25519 private-key extraction, DC-143 authorization-vs-key-expiry distinction --- BACKLOG.md | 8 +++++--- specs/PRIVATE-NETWORK-SPEC.md | 20 ++++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 4af6a72..fae6315 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -473,9 +473,11 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami - **status:** todo - **details:** POST `/network/people/:id/block|unblock|remove`, `/network/devices/:id/revoke`. Orchestrates: provider calls, session kill - (existing auth store), access-map purge/restore, audit log entries. Block must - be fully reversible (snapshots pre-block grants). Remove deletes devices + - person record. Tests: orchestration order (mocked provider), honest unblock + (existing auth store), access-map purge/restore, audit log entries. Block + snapshots pre-block grants; unblock restores the AUTHORIZATION state (grants, + status, sessions) — Tailscale device keys, once expired, are irreversible and + require re-authentication (reauthRequired=true + user note); WireGuard peers + restore immediately. Remove deletes devices + person record. Tests: orchestration order (mocked provider), honest unblock semantics (Tailscale: reauthRequired true + note; WireGuard: immediate restore), last-admin guard integration, audit entries written. diff --git a/specs/PRIVATE-NETWORK-SPEC.md b/specs/PRIVATE-NETWORK-SPEC.md index 50a5610..272420d 100644 --- a/specs/PRIVATE-NETWORK-SPEC.md +++ b/specs/PRIVATE-NETWORK-SPEC.md @@ -68,7 +68,7 @@ Contract (all async, all return plain JSON, all validated): | `createInvite({role, email?})` | POST user-invites (email optional → returns inviteUrl) | mint one-time invite token in store | | `revokeInvite(id)` | DELETE user-invite | delete token | | `blockPerson(userId)` | expire all device keys + ACL deny | drop peer + firewall deny set | -| `unblockPerson(userId)` | re-enable + ACL restore | re-add peer | +| `unblockPerson(userId)` | re-allow at ACL level; expired device keys stay expired (re-auth required, §5) | re-add stored peer (immediate) | | `removePerson(userId)` | DELETE devices, revoke invites | remove peer + person record | | `revokeDevice(deviceId)` | expire/delete device key | remove single peer | | `syncAccess(map, resources)` | compile ACLs → dry-run → push (hash-guarded) | compile nftables → apply → verify | @@ -178,13 +178,17 @@ Builds on the existing DashCA pipeline (routes/ca.js, DC-076). Changes: ### 8.2 Key lifecycle (no server-side private-key storage) -- **Client keypairs are generated in the invitee's browser** at redemption time - using WebCrypto **X25519** — WireGuard requires Curve25519 key material; there - is NO fallback algorithm (a browser lacking X25519 support gets an explicit - "browser not supported" error, not a degraded key). Raw key serialization: - the browser exports the 32-byte raw key, base64-encoded (standard WireGuard - private-key format, `wg genkey`-compatible). The server receives the base64 - public key (44 chars ending '=') and validates length/charset before use. +- **Client keypairs are generated in the invitee's browser** using WebCrypto + **X25519** — WireGuard requires Curve25519 key material; there is NO fallback + algorithm (a browser lacking X25519 support gets an explicit "browser not + supported" error, not a degraded key). Serialization (browser-export + reality): `exportKey('raw', publicKey)` yields the raw 32-byte public key → + base64. `exportKey('pkcs8', privateKey)` yields a PKCS#8 wrapper — the page + strips the fixed 16-byte PKCS#8 X25519 prefix (`302e020100300506032b656e0422 + 0420`) to extract the raw 32-byte private key → base64 (`wg genkey`-format; + the extraction is pure byte-slicing of a fixed-prefix wrapper, verified by + length/constant-prefix assertions before use). The server receives the base64 + public key (44 chars) and validates length/charset before use. - The redemption page posts ONLY the public key to the server; the server registers it as a WireGuard peer (10.53.x.y/32) and returns the client config (Address, DNS, PublicKey of hub, AllowedIPs = 10.53.0.0/16).