P7 spec rev4: pin provider-contract unblock wording, PKCS8-prefix X25519 private-key extraction, DC-143 authorization-vs-key-expiry distinction

This commit is contained in:
Hermes committed 2026-10-03 21:31:08 -07:00
1 parent 5f47e12f18
commit a4f961d609
2 files changed
+17 -11

No files matched your search

+5 -3
View File
@@ -473,9 +473,11 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami
- **status:** todo - **status:** todo
- **details:** POST `/network/people/:id/block|unblock|remove`, - **details:** POST `/network/people/:id/block|unblock|remove`,
`/network/devices/:id/revoke`. Orchestrates: provider calls, session kill `/network/devices/:id/revoke`. Orchestrates: provider calls, session kill
(existing auth store), access-map purge/restore, audit log entries. Block must (existing auth store), access-map purge/restore, audit log entries. Block
be fully reversible (snapshots pre-block grants). Remove deletes devices + snapshots pre-block grants; unblock restores the AUTHORIZATION state (grants,
person record. Tests: orchestration order (mocked provider), honest unblock 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 semantics (Tailscale: reauthRequired true + note; WireGuard: immediate
restore), last-admin guard integration, audit entries written. restore), last-admin guard integration, audit entries written.
+12 -8
View File
@@ -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 | | `createInvite({role, email?})` | POST user-invites (email optional → returns inviteUrl) | mint one-time invite token in store |
| `revokeInvite(id)` | DELETE user-invite | delete token | | `revokeInvite(id)` | DELETE user-invite | delete token |
| `blockPerson(userId)` | expire all device keys + ACL deny | drop peer + firewall deny set | | `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 | | `removePerson(userId)` | DELETE devices, revoke invites | remove peer + person record |
| `revokeDevice(deviceId)` | expire/delete device key | remove single peer | | `revokeDevice(deviceId)` | expire/delete device key | remove single peer |
| `syncAccess(map, resources)` | compile ACLs → dry-run → push (hash-guarded) | compile nftables → apply → verify | | `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) ### 8.2 Key lifecycle (no server-side private-key storage)
- **Client keypairs are generated in the invitee's browser** at redemption time - **Client keypairs are generated in the invitee's browser** using WebCrypto
using WebCrypto **X25519** — WireGuard requires Curve25519 key material; there **X25519** — WireGuard requires Curve25519 key material; there is NO fallback
is NO fallback algorithm (a browser lacking X25519 support gets an explicit algorithm (a browser lacking X25519 support gets an explicit "browser not
"browser not supported" error, not a degraded key). Raw key serialization: supported" error, not a degraded key). Serialization (browser-export
the browser exports the 32-byte raw key, base64-encoded (standard WireGuard reality): `exportKey('raw', publicKey)` yields the raw 32-byte public key →
private-key format, `wg genkey`-compatible). The server receives the base64 base64. `exportKey('pkcs8', privateKey)` yields a PKCS#8 wrapper — the page
public key (44 chars ending '=') and validates length/charset before use. 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 - 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 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). config (Address, DNS, PublicKey of hub, AllowedIPs = 10.53.0.0/16).