Commit cf82ad6
Axiom flagship (3) — real hybrid Ed448+Dilithium5 certificate signing (G01) (#48)
## Summary
Closes the **authenticating‑signature half of G01** — the certificate
can now carry a *real* cryptographic signature, not just a forgeable
content digest. This is **opt‑in**: the default
`generate_certificate`/`save_certificate` path is byte‑for‑byte
unchanged (`authenticated=false`, SHA‑256 content digest), so nothing
downstream breaks.
Uses **only vetted primitives** (no hand‑rolled crypto), matching the
estate Trustfile scheme and the `pqcrypto` family `opsm` already ships.
### What's added
- **`crypto/` — new `axiom_crypto` Rust cdylib.** Ed448 via **OpenSSL
libcrypto**; Dilithium5 (ML‑DSA‑87 / FIPS 204) via
**`pqcrypto-dilithium`**. Clean C ABI (caller‑allocates + returned
length), minimal `unsafe` each with a `// SAFETY:` comment, 7 Rust
round‑trip/tamper/wrong‑key tests.
- **`src/verification/signing.jl`** — `Libdl` loader +
`generate_hybrid_keypair` / `hybrid_sign` / `hybrid_verify`.
Verification is **AND** — both Ed448 *and* Dilithium5 must verify (an
attacker must break both to forge). Includes a **runtime ABI
self‑check** that caught a real Dilithium5 signature‑length mismatch
(4627, not the 4595 first assumed) during development.
- **`certificates.jl`** — opt‑in `sign_certificate_hybrid` /
`verify_certificate_hybrid` / `save_certificate_hybrid`. Signs the
**SHA3‑512** digest (estate hashing standard) of the canonical content,
which **includes the properties list** so a claimed property can't be
added/removed without invalidating the signature.
- **`test/verification/hybrid_signing_tests.jl`** — real round‑trip +
**THE KEY ASSERTION**: a forger who knows the exact content and
recomputes the SHA3‑512 digest but lacks the private keys **cannot**
produce a signature that verifies against the victim's public keys (plus
a self‑verify sanity check proving it's authentication, not a crypto
malfunction).
- `Justfile` `build-crypto`/`test-crypto`; `ROADMAP.adoc` documents the
shipped capability and the **private‑key custody story** (HSM/offline
signer; public keys only in certificates; rotation/revocation explicitly
flagged as not‑yet‑implemented).
### Key‑management stance
**No private key material is committed anywhere** (verified).
Certificates embed only public keys; signing keys are generated
out‑of‑band.
## Verification (independent re‑run; crypto reviewed by hand)
- `cd crypto && cargo test --release` → **7/7 pass**.
- Built the cdylib, then `Pkg.test()` → **697 / 697 pass** (670 + 27
new), hybrid path actually executing (not skipped).
- Read `_hybrid_verify_digest` — confirmed **AND** semantics and real
`ccall` into the cdylib (no mock).
- Confirmed the forged‑certificate rejection assertion is genuine and
passing; confirmed no key files staged/untracked.
## Tracked follow‑ups
SPHINCS+ fallback tier; key rotation/revocation; the estate‑wide
Ed448‑vs‑Ed25519 reconciliation (`opsm` currently uses Ed25519 — flagged
for owner); wiring `build-crypto` into CI as part of the tier‑2 tooling
wave.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01UPFC9YQ7g9gc3VnRox42Q1
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UPFC9YQ7g9gc3VnRox42Q1)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9acb0ef commit cf82ad6
12 files changed
Lines changed: 1786 additions & 8 deletions
File tree
- crypto
- src
- src
- verification
- test
- verification
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
50 | 59 | | |
51 | 60 | | |
52 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
49 | 61 | | |
50 | 62 | | |
51 | 63 | | |
| |||
87 | 99 | | |
88 | 100 | | |
89 | 101 | | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
90 | 137 | | |
91 | 138 | | |
92 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
0 commit comments