Version bumped to 5.0.0 across include/zupt.h, all packaging recipes, man page, and docs. Audit fixes (pre-5.0.0 review): - src/zupt_format.c: overflow-safe bound in the solid-mode `test` path (off+sz could wrap and drive an OOB read in zupt_xxh64 on a crafted archive; the extract path was already hardened, the test path was not). - gui: run_async now marshals the completion callback onto the GUI thread with QueuedConnection (a bare functor connected DirectConnection and touched widgets off the worker thread); Extract auto-detect note survives the log clear via a new `info` param. - .github/workflows/ci.yml: trigger on `master` (was main/develop, so CI never ran); `make dist` tarball is vaptvupt-*.tar.gz not zupt-*; the ASAN PQ round-trip uses native --pq (was --pq-sdk, which fails on the source-only build and blocked the release job). Documentation: - New AUDIT.md (methodology, FIPS 203 conformance validation, findings, repro). - CHANGELOG 5.0.0 entry covers the FIPS 203 conformance fix + BREAKING note and the GUI/CLI/security/packaging work. - README "What's new in 5.0.0", download tables (incl. Windows/macOS/BSD + portable GUI), version-history row. - SECURITY.md + THREAT_MODEL.md: ML-KEM-768 documented as FIPS 203, validated byte-for-byte against OpenSSL 3.5. - Accuracy fixes: man page (--kdf default is PBKDF2 on source-only; codec 2.60.4), rpm %description, debian control/copyright, homebrew header (no vendored library on source-only builds). make check 16/16 (FIPS 203 conformance 3/3, all distro-safe checks).
6 KiB
VaptVupt — Security Audit
This document records the security review of VaptVupt: what is checked, how, the findings and their resolutions, and how to reproduce the checks. It complements SECURITY.md (policy + primitives) and THREAT_MODEL.md (what is and isn't defended).
Scope: the pure-C11 CLI (src/, include/) and the PySide6/PyQt6 GUI
(gui/src/zupt_gui.py). Out of scope: the optional, separately distributed
libzuptsdk / libpqvaptvupt binaries (only present in a make WITH_SDK=1
build); the shipped source-only build contains no vendored binaries.
Not independently certified. This is the project's own structured review, not a third-party accredited audit. Treat it as "reviewed, with reproducible evidence" and do your own review for high-assurance use.
Methodology
| Technique | What it covers | Where |
|---|---|---|
| Cryptographic conformance vs an independent reference | ML-KEM-768 is validated byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768 — deterministic keygen ek equality plus shared-secret agreement in both cross-decapsulation directions. |
tests/test_mlkem_fips203.sh, in make check |
| NIST/RFC known-answer vectors | SHA-256 (FIPS 180-4), SHA-3/SHAKE (FIPS 202), AES-256-CTR (SP 800-38A F.5.5/F.5.6), HMAC-SHA256 (RFC 4231), X25519 (RFC 7748), ML-KEM-768, PBKDF2. | tests/test_vectors.c |
| Byte-level tamper sweep | Every byte position of a representative archive is flipped and re-opened; zero silent-accepts required (F-09). | tests/ byte-sweep |
| Authenticated-encryption fuzzing | HMAC / integrity-trailer fuzz over many trials (F-06, F-08). | tests/ |
| Constant-time measurement | dudect-style Welch t-test on the MAC-tag compare and the ML-KEM FO implicit-rejection compare (the two decapsulation-oracle-sensitive paths). | tests/test_ct_timing.* |
| Memory-safety sanitizers | ASan + UBSan builds; exact-size decode cases; crafted-input decode. | make test-asan |
| Static analysis | cppcheck (warning/style/performance) on the first-party sources; strict -Wall -Wextra -Wpedantic -Werror gcc + clang matrix. |
CI |
| Formal annotations | Frama-C/ACSL contracts on memory-safety-critical functions; 5 Jasmin-verified constant-time assembly routines (x86_64). | include/zupt_acsl.h, jasmin/ |
| Adversarial multi-agent review | Independent reviewers per dimension (crypto, parser/memory-safety, CLI, GUI↔CLI contract, packaging), each finding then adversarially refuted before it is accepted. | manual, per release |
Cryptographic conformance
- ML-KEM-768 — genuine FIPS 203 (v5.0.0). Earlier releases shipped round-3
CRYSTALS-Kyber under a "FIPS 203" label; it was self-consistent and secure as
an IND-CCA2 KEM but not interoperable with a compliant ML-KEM. Validating
against OpenSSL 3.5 revealed three deviations — a transposed matrix-
Âsampling convention (in both K-PKE.KeyGen and K-PKE.Encrypt), the round-3 final KDF, and the implicit-rejection domain. All three were fixed and the result is now byte-for-byte interoperable with OpenSSL in both directions. A permanent conformance test guards against regression. This changed the shared secret, so it is a wire-breaking change for--pq/--pq-onlyarchives (see CHANGELOG 5.0.0 BREAKING). - Hybrid is the flagship.
--pqcombines ML-KEM-768 with X25519 through a SHA3-512 combiner; the archive key is secure if either primitive holds — the strongest real-world posture and the recommended default.--pq-onlyoffers pure ML-KEM-768 for single-primitive compliance mandates. - Envelope. AES-256-CTR with a fresh random 128-bit nonce per block (the dedup keystream-reuse bug is fixed and regression-tested), HMAC-SHA256 Encrypt-then-MAC verified before any decryption, and an archive-integrity trailer over the header/footer.
Notable findings and resolutions (recent)
| Sev | Finding | Resolution |
|---|---|---|
| High | ML-KEM-768 not FIPS 203-conformant / not interoperable | Fixed (transpose + KDF); validated vs OpenSSL; permanent conformance test |
| High | compress -p out.zupt f1 f2 overwrote an input file (data loss, exit 0) |
Refuse to overwrite an existing non-.zupt output without -y/--force; self-overwrite guard |
| High | compress out.zupt dir -p pw wrote an unencrypted archive (exit 0) |
Error on a misplaced option after the archive (-- escape available) |
| Critical | AES-CTR keystream reuse across --dedup blocks (many-time-pad) |
Fresh random per-block nonce; regression test |
| Medium | Heap OOB read in the AVX2 decoder fast path on crafted input | Bound the 2-/3-byte offset read like the scalar tail path |
| Medium | GUI defaulted to SDK modes absent from the source-only build (unusable) | Reworked to native --pq/--pq-only; SDK shown only when supported |
| Low | Hybrid-decrypt did not wipe secret buffers on key-read failure | Wipe on the error path (matches the pq-only path) |
| Low | Untruthful banner (Argon2id-default / /zupt URL) on source-only builds |
Build-aware, accurate version/help output |
| Critical* | Packaging (debian/rules, aur, nix, homebrew, opensuse) would fail a source-only build |
Removed vendored-lib/AUDIT.md steps, fixed URLs, added completions |
* build-time failure, not a runtime security issue.
Known limitations / non-goals
- No protection against a compromised endpoint, a weak password, or key custody failures (see THREAT_MODEL.md).
- Metadata (total archive size, block count) is observable.
- The review is reproducible but not third-party certified.
Reproducing
make check # vectors, tamper sweep, FIPS 203 conformance, guards
make test-asan # ASan + UBSan
bash tests/test_mlkem_fips203.sh # FIPS 203 interop vs OpenSSL (needs openssl 3.5+)
FIPS 203 conformance needs an ML-KEM-capable OpenSSL (3.5+); the test skips gracefully otherwise (e.g. inside a distro package build).