60 lines
3.4 KiB
Markdown
60 lines
3.4 KiB
Markdown
# Security Audit — Zupt v1.0.0
|
|
|
|
## Cryptographic Correctness
|
|
|
|
| Check | Status | Evidence |
|
|
|-------|--------|----------|
|
|
| ML-KEM-768 keygen+encaps+decaps roundtrip | ✅ | 10/10 trials pass (`test_pq.sh`) |
|
|
| ML-KEM-768 constant-time basemul | ✅ | No secret-dependent branches; Montgomery reduction is branchless |
|
|
| ML-KEM-768 FO implicit rejection | ✅ | cmov selects rejection key on invalid ct; both paths always execute |
|
|
| X25519 Montgomery ladder | ✅ | Constant-time by construction (cswap on every iteration) |
|
|
| AES-256-CTR | ✅ | Verified against NIST SP 800-38A via regression tests |
|
|
| HMAC-SHA256 | ✅ | Verified via password-mode archive integrity tests |
|
|
| PBKDF2-SHA256 | ✅ | 600,000 iterations, 32-byte random salt per archive |
|
|
| SHA-256 | ✅ | Used by HMAC/PBKDF2, verified transitively |
|
|
| SHA3-256/512 | ✅ | Used by ML-KEM; Keccak-f[1600] per FIPS 202 |
|
|
| SHAKE-128/256 | ✅ | Used by ML-KEM sampling; verified via KEM roundtrip |
|
|
|
|
## Constant-Time Verification
|
|
|
|
| Operation | Constant-Time | Method |
|
|
|-----------|---------------|--------|
|
|
| HMAC comparison | Yes | XOR accumulation (`diff \|= a[i] ^ b[i]`) |
|
|
| ML-KEM decaps implicit rejection | Yes | cmov with branchless fail detection |
|
|
| ML-KEM NTT/basemul | Yes | No secret-dependent branches; Barrett/Montgomery reduction branchless |
|
|
| ML-KEM CBD sampling | Yes | Bitwise operations only |
|
|
| X25519 ladder | Yes | fe_cswap with masked XOR on every bit |
|
|
| AES-256 | **No** | Table-based (T-tables). Vulnerable to cache-timing on shared hardware. |
|
|
| SHA-256 | **No** | Standard implementation. Not constant-time w.r.t. message length. |
|
|
|
|
**Documented limitation:** AES-256 and SHA-256 use lookup tables susceptible to cache-timing side channels. Do not use on shared multi-tenant hardware where an attacker can measure cache access patterns.
|
|
|
|
## Memory Safety
|
|
|
|
| Check | Status |
|
|
|-------|--------|
|
|
| `make test-asan`: zero errors | ✅ All modes: normal, solid, encrypted, PQ, MT |
|
|
| All `malloc()` return values checked | ✅ Propagated via `ZUPT_ERR_NOMEM` |
|
|
| All ML-KEM polynomial buffers wiped | ✅ `zupt_secure_wipe()` in keygen/encaps/decaps |
|
|
| All X25519 scalars wiped | ✅ `memset(e, 0, 32)` after ladder |
|
|
| All intermediate key material wiped | ✅ In `zupt_crypto.c` hybrid encrypt/decrypt init |
|
|
| Keyring copy wiped in parallel pool destructor | ✅ `zupt_secure_wipe(&ctx->keyring, ...)` |
|
|
|
|
## Format Stability
|
|
|
|
| Check | Status |
|
|
|-------|--------|
|
|
| v1.0 reads v0.3+ archives | ✅ Regression test covers password-encrypted v0.5 format |
|
|
| v0.6 rejects v1.4 PQ archives cleanly | ✅ Version check returns `ZUPT_ERR_BAD_VERSION` |
|
|
| FORMAT.md documents all fields | ✅ See FORMAT.md |
|
|
| FORMAT_STABLE flag set in v1.0 archives | ✅ Bit 4 of global_flags |
|
|
|
|
## Known Bugs Fixed (v0.7.0)
|
|
|
|
| Bug | Impact | Fix |
|
|
|-----|--------|-----|
|
|
| ML-KEM basemul OOB (`zetas[64+i]`, i up to 127) | Buffer overread → undefined behavior | Fixed to 64 iterations, 4-coeff groups |
|
|
| ML-KEM missing `poly_tomont` in keygen | Public key in wrong domain → K-PKE roundtrip fails | Added `poly_tomont()` after basemul in keygen |
|
|
| ML-KEM inverted cmov in FO decaps | Always selected rejection key → KEM roundtrip fails | Fixed fail detection: `(-(int64_t)diff) >> 63` |
|
|
| ML-KEM `inv_ntt` used wrong zetas table | NTT/invNTT roundtrip failed | Uses same `zetas[]` table, k counts 127→0 |
|
|
| PQ hybrid nonce mismatch | Encrypt/decrypt used different random nonces | Store base_nonce in enc_hdr; decrypt reads it back |
|