Add FIDO2/WebAuthn passkey authentication

Phase 14: Full WebAuthn support for passwordless passkey login and
hardware security key 2FA.

- go-webauthn/webauthn v0.16.1 dependency
- WebAuthnConfig with RPID/RPOrigin/DisplayName validation
- Migration 000009: webauthn_credentials table
- DB CRUD with ownership checks and admin operations
- internal/webauthn adapter: encrypt/decrypt at rest with AES-256-GCM
- REST: register begin/finish, login begin/finish, list, delete
- Web UI: profile enrollment, login passkey button, admin management
- gRPC: ListWebAuthnCredentials, RemoveWebAuthnCredential RPCs
- mciasdb: webauthn list/delete/reset subcommands
- OpenAPI: 6 new endpoints, WebAuthnCredentialInfo schema
- Policy: self-service enrollment rule, admin remove via wildcard
- Tests: DB CRUD, adapter round-trip, interface compliance
- Docs: ARCHITECTURE.md §22, PROJECT_PLAN.md Phase 14

Security: Credential IDs and public keys encrypted at rest with
AES-256-GCM via vault master key. Challenge ceremonies use 128-bit
nonces with 120s TTL in sync.Map. Sign counter validated on each
assertion to detect cloned authenticators. Password re-auth required
for registration (SEC-01 pattern). No credential material in API
responses or logs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 16:12:59 -07:00
parent 19fa0c9a8e
commit 25417b24f4
42 changed files with 4214 additions and 84 deletions

View File

@@ -744,6 +744,79 @@ See ARCHITECTURE.md §21 (Token Issuance Delegation) for design details.
---
## Phase 14 — FIDO2/WebAuthn and Passkey Authentication
**Goal:** Add FIDO2/WebAuthn support for passwordless passkey login and hardware
security key 2FA. Discoverable credentials enable passwordless login;
non-discoverable credentials serve as 2FA. Either WebAuthn or TOTP satisfies
the 2FA requirement.
### Step 14.1: Dependency, config, and model types
**Acceptance criteria:**
- `github.com/go-webauthn/webauthn` dependency added
- `WebAuthnConfig` struct in config with RPID, RPOrigin, DisplayName
- Validation: if any field set, RPID+RPOrigin required; RPOrigin must be HTTPS
- `WebAuthnCredential` model type with encrypted-at-rest fields
- Audit events: `webauthn_enrolled`, `webauthn_removed`, `webauthn_login_ok`, `webauthn_login_fail`
- Policy actions: `ActionEnrollWebAuthn`, `ActionRemoveWebAuthn`
### Step 14.2: Database migration and CRUD
**Acceptance criteria:**
- Migration 000009: `webauthn_credentials` table with encrypted credential fields
- Full CRUD: Create, Get (by ID, by account), Delete (ownership-checked and admin),
DeleteAll, UpdateSignCount, UpdateLastUsed, Has, Count
- DB tests for all operations including ownership checks and cascade behavior
### Step 14.3: WebAuthn adapter package
**Acceptance criteria:**
- `internal/webauthn/` package with adapter, user, and converter
- `NewWebAuthn(cfg)` factory wrapping library initialization
- `AccountUser` implementing `webauthn.User` interface
- `EncryptCredential`/`DecryptCredential`/`DecryptCredentials` round-trip encryption
- Tests for encrypt/decrypt, interface compliance, wrong-key rejection
### Step 14.4: REST endpoints
**Acceptance criteria:**
- `POST /v1/auth/webauthn/register/begin` — password re-auth, returns creation options
- `POST /v1/auth/webauthn/register/finish` — completes registration, encrypts credential
- `POST /v1/auth/webauthn/login/begin` — discoverable and username-scoped flows
- `POST /v1/auth/webauthn/login/finish` — validates assertion, issues JWT
- `GET /v1/accounts/{id}/webauthn` — admin, returns metadata only
- `DELETE /v1/accounts/{id}/webauthn/{credentialId}` — admin remove
- Challenge store: `sync.Map` with 120s TTL, background cleanup
### Step 14.5: Web UI
**Acceptance criteria:**
- Profile page: passkey enrollment form, credential list with delete
- Login page: "Sign in with passkey" button with discoverable flow
- Account detail page: passkey section with admin remove
- CSP-compliant `webauthn.js` (external script, base64url helpers)
- Empty state handling for zero credentials
### Step 14.6: gRPC handlers
**Acceptance criteria:**
- Proto messages and RPCs: `ListWebAuthnCredentials`, `RemoveWebAuthnCredential`
- gRPC handler implementation delegating to shared packages
- Regenerated protobuf stubs
### Step 14.7: mciasdb offline management
**Acceptance criteria:**
- `mciasdb webauthn list --id UUID`
- `mciasdb webauthn delete --id UUID --credential-id N`
- `mciasdb webauthn reset --id UUID` (deletes all)
- `mciasdb account reset-webauthn --id UUID` alias
- All operations write audit events
### Step 14.8: OpenAPI and documentation
**Acceptance criteria:**
- All 6 REST endpoints documented in openapi.yaml
- `WebAuthnCredentialInfo` schema, `webauthn_enabled`/`webauthn_count` on Account
- ARCHITECTURE.md §22 with design details
- PROJECT_PLAN.md Phase 14
- PROGRESS.md updated
---
## Implementation Order
```
@@ -760,7 +833,9 @@ Phase 0 → Phase 1 (1.1, 1.2, 1.3, 1.4 in parallel or sequence)
→ Phase 11 (interleaved with Phase 34)
→ Phase 12 (post Phase 3)
→ Phase 13 (post Phase 3 and 11)
→ Phase 14 (post v1.0.0)
```
Each step must have passing tests before the next step begins.
All phases complete as of v1.0.0 (2026-03-15).
Phases 013 complete as of v1.0.0 (2026-03-15).
Phase 14 complete as of 2026-03-16.