From 5087f710c68d61102a662a1c07512ca851d66b59 Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 20 Feb 2026 00:58:58 +0000 Subject: [PATCH] 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 --- CLAUDE.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++ FINDINGS.md | 64 +++++++++++++++++++++++ TODO.md | 42 ++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 CLAUDE.md create mode 100644 FINDINGS.md create mode 100644 TODO.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..11168e6 --- /dev/null +++ b/CLAUDE.md @@ -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 ` +- **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. diff --git a/FINDINGS.md b/FINDINGS.md new file mode 100644 index 0000000..220fb5d --- /dev/null +++ b/FINDINGS.md @@ -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` diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..6b8e58e --- /dev/null +++ b/TODO.md @@ -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