Passkeys: WebAuthn enrolment under a fresh sign-in, 48 h cooling-off, assertion for unlink via Heimdall step-up #85

Merged
heimdall merged 7 commits from heimdall/passkey-strong-proof into main 2026-11-14 00:37:57 +00:00
Member

First native strong proof (scoping /opt/commons/handoff/strong-proof-scoping-20261113.md; Heimdall step-up route d957e5a).

  • Library: Fido2 4.3.3 (Fido2NetLib), MIT; no hand-rolled CBOR/COSE; none attestation. Decision and checks in docs/passkeys.md. Cost: Microsoft.Extensions.* 8.x -> 9.0.0 in both lockfiles, plus NSec.Cryptography/libsodium.
  • Enrolment only right after a fresh ordinary Heimdall sign-in for the session's account (single-use ticket); counts as strong 48 h later; listed, cooling-off shown, revocable; cap per account; ceremonies rate-limited.
  • Unlink: WebAuthn assertion verified server-side -> single-use <=5 min fact bound to account+identity -> fresh sign-in -> in that one request POST /v1/apps/bifrost/step-up {bifrost-passkey, fresh nonce} with app secret + bearer -> unlink. Bearer never stored or in a cookie. Google step-up route closes once a strong passkey exists.
  • Migration 20261113120000_AddPasskeyCredentials (additive, applies at boot). Ceremony state in process memory.
  • Tests: 1532 pass / 18 fail (same 18 as baseline); 49 new; mutations red except account-binding alone (covered by allowCredentials, red when both removed). No real browser/authenticator exercised.
  • Not deployed. Root: needs Host:PublicBaseUrl https and Host:ExpectedHost matching.

🤖 Generated with Claude Code

First native strong proof (scoping /opt/commons/handoff/strong-proof-scoping-20261113.md; Heimdall step-up route d957e5a). - Library: Fido2 4.3.3 (Fido2NetLib), MIT; no hand-rolled CBOR/COSE; none attestation. Decision and checks in docs/passkeys.md. Cost: Microsoft.Extensions.* 8.x -> 9.0.0 in both lockfiles, plus NSec.Cryptography/libsodium. - Enrolment only right after a fresh ordinary Heimdall sign-in for the session's account (single-use ticket); counts as strong 48 h later; listed, cooling-off shown, revocable; cap per account; ceremonies rate-limited. - Unlink: WebAuthn assertion verified server-side -> single-use <=5 min fact bound to account+identity -> fresh sign-in -> in that one request POST /v1/apps/bifrost/step-up {bifrost-passkey, fresh nonce} with app secret + bearer -> unlink. Bearer never stored or in a cookie. Google step-up route closes once a strong passkey exists. - Migration 20261113120000_AddPasskeyCredentials (additive, applies at boot). Ceremony state in process memory. - Tests: 1532 pass / 18 fail (same 18 as baseline); 49 new; mutations red except account-binding alone (covered by allowCredentials, red when both removed). No real browser/authenticator exercised. - Not deployed. Root: needs Host:PublicBaseUrl https and Host:ExpectedHost matching. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Heimdall-backed members enrol WebAuthn passkeys on the Linked accounts page (ticket from a fresh ordinary Heimdall sign-in, 48 h cooling-off, cap 5,
FixedWindowBudget on ceremonies) and unlink with a strong passkey: the assertion is verified at Bifrost, then inside the completion that holds the bearer
Bifrost calls Heimdall's POST /v1/apps/bifrost/step-up and the unlink with the returned token. The Google step-up closes once a strong passkey exists.
Migration AddPasskeyCredentials is additive. Tests use a software authenticator and a fake Heimdall step-up route modeled on the contract.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Soul fixes on PR #85: junk from a session thief no longer burns the member's ticket, challenge or verification permits; a passkey revoked between
assertion and completion does not unlink; PasskeyRelyingParty is resolved at startup and rejects an ExpectedHost with a port; with a strong passkey and an
unavailable RP the page shows a message instead of a dead form; Label() cuts by text element and drops format characters. Tests for each, the user-handle
owner callback, the account-scoped lookup (allow list stripped through the ceremony store) and the ticket cookie SameSite.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
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
GameCult/Bifrost!85
No description provided.