- Step 2.1: LTHN→Argon2id password hash migration (addresses F1) - Step 2.2: RotateKeyPair with full decrypt/re-encrypt flow - Step 2.3: Replace .rev placeholder with proper revocation - Step 2.4: HardwareKey interface contract (no implementation) Co-Authored-By: Virgil <virgil@lethean.io>
5.5 KiB
5.5 KiB
TODO.md — go-crypt
Dispatched from core/go orchestration. Pick up tasks in order.
Phase 0: Test Coverage & Hardening
- Expand auth/ tests — Added 8 new tests: concurrent session creation (10 goroutines), session token uniqueness (1000 tokens), challenge expiry boundary, empty password registration, very long username (10K chars), Unicode username/password, air-gapped round-trip, refresh already-expired session. All pass with
-race. - Expand crypt/ tests — Added 12 new tests: wrong passphrase decrypt (ChaCha20+AES), empty plaintext round-trip (ChaCha20+AES), 1MB payload round-trip (ChaCha20+AES), ciphertext-too-short rejection, key derivation determinism (Argon2id+scrypt), HKDF different info strings, HKDF nil salt, checksum of empty file (SHA-256+SHA-512), checksum of non-existent file, checksum consistency with SHA256Sum. Note: large payload test uses 1MB (not 10MB) to keep tests fast.
- Expand trust/ tests — Added 9 new tests: concurrent Register/Get/Remove (10 goroutines, race-safe), Tier 0 rejection, negative tier rejection, token expiry boundary, zero-value token expiry, concurrent List during mutations, empty ScopedRepos behaviour (documented as finding F3), capability not in any list, concurrent Evaluate.
- Security audit — Full audit documented in FINDINGS.md. 4 findings: F1 (LTHN used for passwords, medium), F2 (PGP keys not zeroed, low), F3 (empty ScopedRepos bypasses scope, medium), F4 (go vet clean). No
math/randusage. All nonces usecrypto/rand. No secrets in error messages. go vet ./...clean — No warnings.- Benchmark suite — Created
crypt/bench_test.go(7 benchmarks: Argon2Derive, ChaCha20 1KB/1MB, AESGCM 1KB/1MB, HMACSHA256 1KB, VerifyHMACSHA256) andtrust/bench_test.go(3 benchmarks: PolicyEvaluate 100 agents, RegistryGet, RegistryRegister).
Phase 1: Session Persistence
- Session storage interface — Extracted in-memory session map into
SessionStoreinterface withGet,Set,Delete,DeleteByUser,Cleanupmethods.MemorySessionStorewraps the original map+mutex pattern.ErrSessionNotFoundsentinel error. - SQLite session store —
SQLiteSessionStorebacked by go-store (SQLite KV). Sessions stored as JSON in"sessions"group. Mutex-serialised for SQLite single-writer safety. - Background cleanup —
StartCleanup(ctx, interval)goroutine purges expired sessions periodically. Stops on context cancellation. - Session migration — Backward-compatible:
MemorySessionStoreis default,WithSessionStore(store)option for persistent store. All existing tests updated and passing. Commit1aeabfd.
Phase 2: Key Management
Step 2.1: Password hash migration (addresses Finding F1)
- Migrate Login() from LTHN to Argon2id — Currently
auth.Login()verifies passwords vialthn.Verify()which uses non-constant-time comparison. Change to usecrypt.HashPassword()/crypt.VerifyPassword()(Argon2id) for new registrations. Add migration path: on login, if stored hash is LTHN format, re-hash with Argon2id and update.lthnfile. Update.lthnfile extension to.hashor keep for backward compatibility.
Step 2.2: Key rotation
-
Add
RotateKeyPair(userID, oldPassword, newPassword string) (*User, error)— Full flow:- Load current
.keyand.pubviaio.Medium - Decrypt metadata
.jsonwith old private key + oldPassword viapgp.Decrypt() - Generate new keypair via
pgp.CreateKeyPair(userID, userID+"@auth.local", newPassword) - Re-encrypt metadata with new public key via
pgp.Encrypt() - Write new
.key,.pub,.jsonfiles (overwrite existing) - Invalidate all sessions for this user via
store.DeleteByUser(userID) - Return updated User struct
- Load current
-
Test RotateKeyPair — Register user → rotate → verify old key can't decrypt → verify new key works → verify sessions invalidated.
Step 2.3: Key revocation
- Replace
.revplaceholder — Currently stores literal string"REVOCATION_PLACEHOLDER". Options:- Option A: Generate proper OpenPGP revocation cert (may need go-crypto API research)
- Option B: Store revocation timestamp + reason as JSON (simpler, sufficient for our use case)
- Implement
RevokeKey(userID, password string) errorandIsRevoked(userID string) bool
Step 2.4: Hardware key interface (contract only)
- Define HardwareKey interface — Contract for PKCS#11 / YubiKey integration:
Addtype HardwareKey interface { Sign(data []byte) ([]byte, error) Decrypt(ciphertext []byte) ([]byte, error) GetPublicKey() (string, error) IsAvailable() bool }WithHardwareKey(HardwareKey) Optionto Authenticator. No implementation yet — just the interface and integration points in auth.go.
Phase 3: Trust Policy Extensions
- Approval workflow — Implement the approval flow that
NeedsApprovaldecisions point to. Queue-based: agent requests approval → admin reviews → approve/deny. - Audit log — Record all policy evaluations (agent, capability, decision, timestamp). Append-only log for compliance.
- Dynamic policies — Load policies from YAML/JSON config. Currently hardcoded in
DefaultPolicies(). - Scope wildcards — Support
core/*scope patterns in ScopedRepos, not just exact strings.
Workflow
- Virgil in core/go writes tasks here after research
- This repo's dedicated session picks up tasks in phase order
- Mark
[x]when done, note commit hash