docs: add domain expert guide, task queue, and research notes

CLAUDE.md: architecture guide for auth/crypt/trust with algorithm reference
TODO.md: 4-phase task queue (hardening, sessions, key mgmt, policy)
FINDINGS.md: package inventory, security review flags, integration points

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-02-20 00:58:58 +00:00
parent 8498ecf890
commit 5087f710c6
3 changed files with 248 additions and 0 deletions

142
CLAUDE.md Normal file
View file

@ -0,0 +1,142 @@
# CLAUDE.md — go-crypt Domain Expert Guide
You are a dedicated domain expert for `forge.lthn.ai/core/go-crypt`. Virgil (in core/go) orchestrates your work via TODO.md. Pick up tasks in phase order, mark `[x]` when done, commit and push.
## What This Package Does
Cryptographic primitives, authentication, and trust policy engine. ~3.7K LOC across 28 Go files. Provides:
- **Symmetric encryption** — ChaCha20-Poly1305 and AES-256-GCM with Argon2id key derivation
- **OpenPGP authentication** — Challenge-response (online + air-gapped courier mode)
- **Password hashing** — Argon2id (primary) + Bcrypt (fallback)
- **Trust policy engine** — 3-tier agent access control with capability evaluation
- **RSA** — OAEP-SHA256 key generation and encryption (2048+ bit)
- **LTHN hash** — RFC-0004 quasi-salted deterministic hash (content IDs, NOT passwords)
## Commands
```bash
go test ./... # Run all tests
go test -v -run TestName ./... # Single test
go test -race ./... # Race detector
go vet ./... # Static analysis
```
## Local Dependencies
| Module | Local Path | Notes |
|--------|-----------|-------|
| `forge.lthn.ai/core/go` | `../go` | Framework (core.E, core.Crypt, io.Medium) |
**Do NOT change the replace directive path.** Use go.work for local resolution if needed.
## Architecture
### auth/ — OpenPGP Challenge-Response + Password Auth (455 LOC)
`Authenticator` backed by `io.Medium` storage abstraction.
**Registration flow**: Generate PGP keypair → store `.pub`, `.key`, `.rev`, `.json`, `.lthn` files under `users/{userID}/`.
**Online challenge-response**:
1. `CreateChallenge(userID)` → 32-byte random nonce, encrypted with user's public key
2. Client decrypts nonce, signs it with private key
3. `ValidateResponse(userID, signedNonce)` → verifies signature, issues 24h session token
**Air-gapped (courier) mode**:
1. `WriteChallengeFile(userID, path)` → JSON with encrypted nonce
2. Client signs offline
3. `ReadResponseFile(userID, path)` → verify, issue session
**Session management**: In-memory `sync.RWMutex`-protected map. 32-byte hex tokens, 24h TTL. `ValidateSession`, `RefreshSession`, `RevokeSession`.
**Protected users**: `"server"` cannot be deleted.
### crypt/ — Symmetric Encryption & Hashing (624 LOC)
| File | LOC | Purpose |
|------|-----|---------|
| `crypt.go` | 90 | High-level `Encrypt`/`Decrypt` (ChaCha20 + Argon2id) and AES-256-GCM variant |
| `kdf.go` | 60 | `DeriveKey` (Argon2id: 64MB/3 iter/4 threads), `DeriveKeyScrypt`, `HKDF` |
| `symmetric.go` | 100 | Low-level `ChaCha20Encrypt`/`Decrypt`, `AESGCMEncrypt`/`Decrypt` |
| `hash.go` | 89 | `HashPassword`/`VerifyPassword` (Argon2id format string), Bcrypt |
| `hmac.go` | 30 | `HMACSHA256`/`512`, constant-time `VerifyHMAC` |
| `checksum.go` | 55 | `SHA256File`, `SHA512File`, `SHA256Sum`, `SHA512Sum` |
#### crypt/chachapoly/ (50 LOC)
Standalone ChaCha20-Poly1305 AEAD wrapper. 24-byte nonce prepended to ciphertext.
#### crypt/lthn/ (94 LOC)
RFC-0004 quasi-salted hash. Deterministic: reverse input → leet-speak substitution → SHA-256. For content IDs and deduplication. **NOT for passwords.**
#### crypt/pgp/ (230 LOC)
OpenPGP primitives via ProtonMail `go-crypto`:
- `CreateKeyPair(name, email, password)` → armored DSA primary + RSA subkey
- `Encrypt`/`Decrypt` → armored PGP messages
- `Sign`/`Verify` → detached signatures
#### crypt/rsa/ (91 LOC)
RSA key generation (2048+ bit), OAEP-SHA256 encrypt/decrypt, PEM encoding.
#### crypt/openpgp/ (191 LOC)
Service wrapper implementing `core.Crypt` interface. RSA-4096, SHA-256, AES-256. Registers IPC handler for `"openpgp.create_key_pair"`.
### trust/ — Agent Trust & Policy Engine (403 LOC)
| File | LOC | Purpose |
|------|-----|---------|
| `trust.go` | 165 | `Registry` (thread-safe agent store), `Agent` struct, `Tier` enum |
| `policy.go` | 238 | `PolicyEngine`, 9 capabilities, `Evaluate` → Allow/Deny/NeedsApproval |
**Trust tiers**:
| Tier | Name | Rate Limit | Example Agents |
|------|------|-----------|----------------|
| 3 | Full | Unlimited | Athena, Virgil, Charon |
| 2 | Verified | 60/min | Clotho, Hypnos (scoped repos) |
| 1 | Untrusted | 10/min | BugSETI instances |
**9 Capabilities**: `repo.push`, `pr.merge`, `pr.create`, `issue.create`, `issue.comment`, `secrets.read`, `cmd.privileged`, `workspace.access`, `flows.modify`
**Evaluation order**: Agent exists → policy exists → explicitly denied → requires approval → allowed (with repo scope check).
## Algorithm Reference
| Component | Algorithm | Parameters |
|-----------|-----------|-----------|
| KDF (primary) | Argon2id | Time=3, Memory=64MB, Parallelism=4 |
| KDF (alt) | scrypt | N=32768, r=8, p=1 |
| KDF (expand) | HKDF-SHA256 | Variable key length |
| Symmetric | ChaCha20-Poly1305 | 24-byte nonce, 32-byte key |
| Symmetric (alt) | AES-256-GCM | 12-byte nonce, 32-byte key |
| Password hash | Argon2id | Custom format string |
| Password hash (alt) | Bcrypt | Default cost |
| Deterministic hash | SHA-256 + quasi-salt | RFC-0004 |
| Asymmetric | RSA-OAEP-SHA256 | 2048+ bit |
| PGP | DSA + RSA subkey | ProtonMail go-crypto |
| HMAC | SHA-256 / SHA-512 | Constant-time verify |
## Security Considerations
1. **LTHN hash is NOT for passwords** — deterministic, no random salt. Use `HashPassword()` (Argon2id) instead.
2. **Sessions are in-memory only** — lost on restart. No Redis/DB backend yet.
3. **PGP output is armored** — ~33% Base64 overhead. Consider compression for large payloads.
4. **Policy engine returns decisions but doesn't enforce approval workflow** — higher-level layer needed.
5. **Challenge nonces are 32 bytes** — 256-bit, cryptographically random.
## Coding Standards
- **UK English**: colour, organisation, centre
- **Tests**: testify assert/require, `_Good`/`_Bad`/`_Ugly` naming convention
- **Conventional commits**: `feat(auth):`, `fix(crypt):`, `refactor(trust):`
- **Co-Author**: `Co-Authored-By: Virgil <virgil@lethean.io>`
- **Licence**: EUPL-1.2
- **Imports**: stdlib → forge.lthn.ai → third-party, each group separated by blank line
## Forge
- **Repo**: `forge.lthn.ai/core/go-crypt`
- **Push via SSH**: `git push forge main` (remote: `ssh://git@forge.lthn.ai:2223/core/go-crypt.git`)
## Task Queue
See `TODO.md` for prioritised work. See `FINDINGS.md` for research notes.

64
FINDINGS.md Normal file
View file

@ -0,0 +1,64 @@
# FINDINGS.md — go-crypt Research & Discovery
## 2026-02-20: Initial Analysis (Virgil)
### Origin
Extracted from `core/go` on 16 Feb 2026 (commit `8498ecf`). Single extraction commit — fresh repo with no prior history.
### Package Inventory
| Package | Source LOC | Test LOC | Test Count | Notes |
|---------|-----------|----------|-----------|-------|
| `auth/` | 455 | 581 | 25+ | OpenPGP challenge-response + LTHN password |
| `crypt/` | 90 | 45 | 4 | High-level encrypt/decrypt convenience |
| `crypt/kdf.go` | 60 | 56 | — | Argon2id, scrypt, HKDF |
| `crypt/symmetric.go` | 100 | 55 | — | ChaCha20-Poly1305, AES-256-GCM |
| `crypt/hash.go` | 89 | 50 | — | Argon2id password hashing, Bcrypt |
| `crypt/hmac.go` | 30 | 40 | — | HMAC-SHA256/512 |
| `crypt/checksum.go` | 55 | 23 | — | SHA-256/512 file and data checksums |
| `crypt/chachapoly/` | 50 | 114 | 9 | Standalone ChaCha20-Poly1305 wrapper |
| `crypt/lthn/` | 94 | 66 | 6 | RFC-0004 quasi-salted hash |
| `crypt/pgp/` | 230 | 164 | 11 | OpenPGP via ProtonMail go-crypto |
| `crypt/rsa/` | 91 | 101 | 3 | RSA OAEP-SHA256 |
| `crypt/openpgp/` | 191 | 43 | — | Service wrapper, core.Crypt interface |
| `trust/` | 165 | 164 | — | Agent registry, tier management |
| `trust/policy.go` | 238 | 268 | 40+ | Policy engine, 9 capabilities |
**Total**: ~1,938 source LOC, ~1,770 test LOC (47.7% test ratio)
### Dependencies
- `forge.lthn.ai/core/go` — core.E error handling, core.Crypt interface, io.Medium storage
- `github.com/ProtonMail/go-crypto` v1.3.0 — OpenPGP (replaces deprecated golang.org/x/crypto/openpgp)
- `golang.org/x/crypto` v0.48.0 — Argon2, ChaCha20-Poly1305, scrypt, HKDF, bcrypt
- `github.com/cloudflare/circl` v1.6.3 — indirect (elliptic curve via ProtonMail)
### Key Observations
1. **Dual ChaCha20 wrappers**`crypt/symmetric.go` and `crypt/chachapoly/chachapoly.go` implement nearly identical ChaCha20-Poly1305. The chachapoly sub-package pre-allocates nonce+plaintext capacity (minor optimisation). Consider consolidating.
2. **LTHN hash is NOT constant-time**`lthn.Verify()` uses direct string comparison (`==`), not `subtle.ConstantTimeCompare`. This is acceptable since LTHN is for content IDs, not passwords — but should be documented clearly.
3. **OpenPGP service has IPC handler**`openpgp.Service.HandleIPCEvents()` dispatches `"openpgp.create_key_pair"`. This is the only IPC-aware component in go-crypt.
4. **Trust policy decisions are advisory**`PolicyEngine.Evaluate()` returns `NeedsApproval` but there's no approval queue or workflow. The enforcement layer is expected to live in a higher-level package (go-agentic or go-scm).
5. **Session tokens are in-memory** — No persistence. Suitable for development and single-process deployments, but not distributed systems or crash recovery.
6. **Test naming follows `_Good`/`_Bad`/`_Ugly` pattern** — Consistent with core/go conventions.
### Integration Points
- **go-p2p** → UEPS layer will need crypt/ for consent-gated encryption
- **go-scm** → AgentCI trusts agents via trust/ policy engine
- **go-agentic** → Agent session management via auth/
- **core/go** → OpenPGP service registered via core.Crypt interface
### Security Review Flags
- Argon2id parameters (64MB/3/4) are within OWASP recommended range
- RSA minimum 2048-bit enforced at key generation
- ChaCha20 nonces are 24-byte (XChaCha20-Poly1305), not 12-byte — good, avoids nonce reuse risk
- PGP uses ProtonMail fork (actively maintained, post-quantum research)
- No detected use of `math/rand` — all randomness from `crypto/rand`

42
TODO.md Normal file
View file

@ -0,0 +1,42 @@
# TODO.md — go-crypt
Dispatched from core/go orchestration. Pick up tasks in order.
---
## Phase 0: Test Coverage & Hardening
- [ ] **Expand auth/ tests** — Add: concurrent session creation (10 goroutines), session token uniqueness (generate 1000, verify no collisions), challenge expiry edge case (5min boundary), empty password registration, very long username (10K chars), Unicode username/password, air-gapped round-trip (write challenge file → read response file), refresh already-expired session.
- [ ] **Expand crypt/ tests** — Add: wrong passphrase decrypt (should error, not corrupt), empty plaintext encrypt/decrypt round-trip, very large plaintext (10MB), key derivation determinism (same input → same output), HKDF with different info strings produce different keys, checksum of empty file, checksum of non-existent file (error handling).
- [ ] **Expand trust/ tests** — Add: concurrent Register/Get/Remove (race test), policy evaluation with empty ScopedRepos on Tier 2, capability not in any list (should deny), register agent with Tier 0 (invalid), rate limit enforcement verification, token expiry boundary tests.
- [ ] **Security audit** — Verify: constant-time comparisons used everywhere (not just HMAC), no timing side channels in session validation, nonce generation uses crypto/rand (not math/rand), PGP private keys zeroed after use, no secrets in error messages or logs.
- [ ] **`go vet ./...` clean** — Fix any warnings.
- [ ] **Benchmark suite**`BenchmarkArgon2Derive` (KDF), `BenchmarkChaCha20` (encrypt 1KB/1MB), `BenchmarkRSAEncrypt` (2048/4096 bit), `BenchmarkPGPSign`, `BenchmarkPolicyEvaluate` (100 agents).
## Phase 1: Session Persistence
- [ ] **Session storage interface** — Extract in-memory session map into `SessionStore` interface with `Get`, `Set`, `Delete`, `Cleanup` methods.
- [ ] **SQLite session store** — Implement `SessionStore` backed by go-store (SQLite KV). Migrate session tokens + expiry to persistent storage.
- [ ] **Background cleanup** — Goroutine to purge expired sessions periodically. Configurable interval.
- [ ] **Session migration** — Backward-compatible: in-memory as default, optional persistent store via config.
## Phase 2: Key Management
- [ ] **Key rotation** — Add `RotateKeyPair(userID, oldPassword, newPassword)` to auth.Authenticator. Generate new keypair, re-encrypt metadata, update stored keys.
- [ ] **Key revocation** — Implement revocation certificate flow. Currently `.rev` is a placeholder.
- [ ] **Hardware key support** — Interface for PKCS#11 / YubiKey backing. Not implemented, but define the contract.
## Phase 3: Trust Policy Extensions
- [ ] **Approval workflow** — Implement the approval flow that `NeedsApproval` decisions 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
1. Virgil in core/go writes tasks here after research
2. This repo's dedicated session picks up tasks in phase order
3. Mark `[x]` when done, note commit hash