# MCIAS Progress Source of truth for current development state. --- ## Current Status: Phase 5 Complete — Full Implementation All phases are complete. The system is ready for deployment. ### Completed Phases - [x] Phase 0: Repository bootstrap (go.mod, .gitignore, docs) - [x] Phase 1: Foundational packages (model, config, crypto, db) - [x] Phase 2: Auth core (auth, token, middleware) - [x] Phase 3: HTTP server (server, mciassrv binary) - [x] Phase 4: Admin CLI (mciasctl binary) - [x] Phase 5: E2E tests, security hardening, commit --- ## Implementation Log ### 2026-03-11 — Initial Full Implementation #### Phase 0: Bootstrap - Wrote ARCHITECTURE.md (security model, crypto choices, DB schema, API design) - Wrote PROJECT_PLAN.md (5 phases, 12 steps with acceptance criteria) - Created go.mod with dependencies (golang-jwt/jwt/v5, uuid, go-toml/v2, golang.org/x/crypto, modernc.org/sqlite) - Created .gitignore #### Phase 1: Foundational Packages **internal/model** - Account (human/system), Role, TokenRecord, SystemToken, PGCredential, AuditEvent structs - All credential fields tagged `json:"-"` — never serialised to responses - Audit event type constants **internal/config** - TOML config parsing with validation - Enforces OWASP 2023 Argon2id minimums (time≥2, memory≥64MiB) - Requires exactly one of passphrase_env or keyfile for master key - NewTestConfig() for test use **internal/crypto** - Ed25519 key generation, PEM marshal/parse - AES-256-GCM seal/open with random nonces - Argon2id KDF (DeriveKey) with OWASP-exceeding parameters - NewSalt(), RandomBytes() **internal/db** - SQLite with WAL mode, FK enforcement, busy timeout - Idempotent migrations (schema_version table) - Migration 1: full schema (server_config, accounts, account_roles, token_revocation, system_tokens, pg_credentials, audit_log) - Migration 2: master_key_salt column in server_config - Full CRUD: accounts, roles, tokens, PG credentials, audit log #### Phase 2: Auth Core **internal/auth** - Argon2id password hashing in PHC format - Constant-time password verification (crypto/subtle) - TOTP generation and validation (RFC 6238 ±1 window, constant-time) - HOTP per RFC 4226 **internal/token** - Ed25519/EdDSA JWT issuance with UUID JTI - alg header validated BEFORE signature verification (alg confusion defence) - alg:none explicitly rejected - ErrWrongAlgorithm, ErrExpiredToken, ErrInvalidSignature, ErrMissingClaim **internal/middleware** - RequestLogger — never logs Authorization header - RequireAuth — validates JWT, checks revocation table - RequireRole — checks claims for required role - RateLimit — per-IP token bucket #### Phase 3: HTTP Server **internal/server** - Full REST API wired to middleware - Handlers: health, public-key, login (dummy Argon2 on unknown user for timing uniformity), logout, renew, token validate/issue/revoke, account CRUD, roles, TOTP enrol/confirm/remove, PG credentials - Strict JSON decoding (DisallowUnknownFields) - Credential fields never appear in any response **cmd/mciassrv** - Config loading, master key derivation (passphrase via Argon2id KDF or key file), signing key load/generate (AES-256-GCM encrypted in DB), HTTPS listener with graceful shutdown - TLS 1.2+ minimum, X25519+P256 curves - 30s read/write timeouts, 5s header timeout #### Phase 4: Admin CLI **cmd/mciasctl** - Subcommands: account (list/create/get/update/delete), role (list/set), token (issue/revoke), pgcreds (get/set) - Auth via -token flag or MCIAS_TOKEN env var - Custom CA cert support for self-signed TLS #### Phase 5: Tests and Hardening **Test coverage:** - internal/model: 5 tests - internal/config: 8 tests - internal/crypto: 12 tests - internal/db: 13 tests - internal/auth: 13 tests - internal/token: 9 tests (including alg confusion and alg:none attacks) - internal/middleware: 12 tests - internal/server: 14 tests - test/e2e: 11 tests Total: 97 tests — all pass, zero race conditions (go test -race ./...) **Security tests (adversarial):** - JWT alg:HS256 confusion attack → 401 - JWT alg:none attack → 401 - Revoked token reuse → 401 - Non-admin calling admin endpoint → 403 - Wrong password → 401 (same response as unknown user) - Credential material absent from all API responses **Security hardening:** - go vet ./... — zero issues - gofmt applied to all files - golangci-lint v2 config updated (note: v2.6.2 built with go1.25.3 cannot analyse go1.26 source; go vet used as primary linter for now) --- ## Architecture Decisions - **SQLite driver**: `modernc.org/sqlite` (pure Go, no CGo) - **JWT**: `github.com/golang-jwt/jwt/v5`; alg validated manually before library dispatch to defeat algorithm confusion - **No ORM**: `database/sql` with parameterized statements only - **Master key salt**: stored in server_config table for stable KDF across restarts; generated on first run - **Signing key**: stored AES-256-GCM encrypted in server_config; generated on first run, decrypted each startup using master key - **Timing uniformity**: unknown user login runs dummy Argon2 to match timing of wrong-password path; all credential comparisons use `crypto/subtle.ConstantTimeCompare`