feat: add export and post-quantum signing (ML-DSA-65)#11
Open
akeemjenkins wants to merge 6 commits into
Open
Conversation
- export: deterministic tar.gz archive of entire bundle with per-file SHA-256 manifest and archive hash - sign: post-quantum authentication via ML-KEM-768 (FIPS 203) and HPKE (RFC 9180) using only the Go standard library (crypto/hpke + crypto/mlkem) — no external dependencies - sign keygen: generate ML-KEM-768 key pair - sign sign: seal archive hash with HPKE using public key - sign verify: open HPKE ciphertext with private key, confirm archive hash matches (detects tampering) - Schema entries for both commands (agentic-first discovery) - 9 new tests (5 export, 4 sign) covering happy path, determinism, tampered archive, wrong key, hidden dir skipping - Updated README with export + sign documentation End-to-end verified: export → keygen → sign → verify cycle works.
The previous scheme sealed the archive hash with HPKE using the recipient's PUBLIC key. Since anyone with the public key can seal, signatures were forgeable by design: an attacker could tamper with the archive and mint a fresh valid "signature" with the very key published in the signature file. Verification also required the private key, so third parties could not verify at all. Replace it with ML-DSA-65 (FIPS 204, formerly CRYSTALS-Dilithium) via cloudflare/circl: the private key (stored as the 32-byte FIPS 204 seed) signs the archive's SHA-256 hash, and anyone with the public key verifies. Verification uses only the key passed on the command line, never the one embedded in the signature file. CLI flags swap accordingly: sign --priv, verify --pub. Also fix export correctness issues: - take tar entry size from the bytes actually read, not a separate Stat - fix tar mode to 0644 so archives are byte-reproducible across machines - do not skip the bundle root itself when it is a hidden directory - use bytes.Buffer instead of strings.Builder for binary data Adds a forgery regression test proving the public key cannot produce a valid signature, plus wrong-algorithm and tampered-signature tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #10 merged a second identical copy of the testBundle helper into internal/validate/validate_test.go, so main currently fails go vet and golangci-lint with a redeclaration error. Merge main and keep a single copy of the helper. Also update the README test count. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Exclude G703 alongside the existing G304 exclusion: reading and writing user-supplied paths from argv is the point of a CLI. Suppress G117 on KeyPairToJSON, where emitting the private key is the purpose of keygen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
okf exportexports an entire OKF bundle as a deterministic.okftar.gz archive. The archive is byte-reproducible (sorted paths, zeroed timestamps, fixed file modes) so the same bundle contents always produce the same bytes, on any machine. Output includes a manifest with per-file SHA-256 hashes, file count, total bytes, and archive hash.okf signprovides post-quantum digital signatures for exported archives using ML-DSA-65 (FIPS 204, formerly CRYSTALS-Dilithium) via cloudflare/circl.keygengenerates an ML-DSA-65 key pair (private key stored as the 32-byte FIPS 204 seed)signsigns the archive's SHA-256 hash with the private keyverifychecks the signature with the signer's public key and confirms the archive is untamperedBoth commands are registered in the schema (
okf schema) for agentic-first discovery.Design note
An earlier revision of this branch used ML-KEM-768 + HPKE. ML-KEM is a key-encapsulation mechanism, not a signature scheme: anyone holding the public key could seal a fresh "signature" over a tampered archive, and verification required the private key. That revision was replaced with ML-DSA-65, the NIST post-quantum signature standard, with the conventional key roles: private key signs, public key verifies. Verification always uses the public key supplied on the command line, never the one embedded in the signature file.
Test plan
go vetandgolangci-lintclean on PR packages🤖 Generated with Claude Code