v5.0.0: version bump, audit fixes, documentation overhaul

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).
This commit is contained in:
Cristian Cezar Moisés 2026-07-10 17:22:02 -03:00
commit 5050570b23
24 changed files with 356 additions and 139 deletions

View file

@ -1,33 +1,78 @@
# VaptVupt Changelog
## [5.0.0] — unreleased — genuine FIPS 203 ML-KEM-768 (breaking PQ change)
## [5.0.0] — 2026-07-10 — genuine FIPS 203 ML-KEM-768; GUI + CLI hardening
### Security / correctness — ML-KEM-768 is now FIPS 203-conformant
- The in-tree ML-KEM-768 was **round-3 CRYSTALS-Kyber, not final FIPS 203**, and
therefore not interoperable with a compliant ML-KEM despite the "FIPS 203"
label. Three deviations were found and fixed:
1. **Matrix  transpose convention** — K-PKE.KeyGen must use `SampleNTT(ρ‖j‖i)`
and K-PKE.Encrypt `SampleNTT(ρ‖i‖j)`; the implementation had both swapped.
Self-consistent (round-trips passed) but non-standard, which is exactly why
a self-consistency-only test never caught it.
2. **Encaps/decaps KDF** — FIPS 203 outputs `K` from `G(m‖H(ek))` directly; the
round-3 final `K = KDF(K̄‖H(c))` step was removed.
- The in-tree ML-KEM-768 was **round-3 CRYSTALS-Kyber, not final FIPS 203**, so
it was **not interoperable** with a compliant ML-KEM despite the "FIPS 203"
label. Found by validating against OpenSSL 3.5's ML-KEM-768. Three deviations,
all fixed in `src/zupt_mlkem.c`:
1. **Matrix  transpose convention** — FIPS 203 K-PKE.KeyGen samples
`Â[i][j] = SampleNTT(XOF(ρ, j, i))` and K-PKE.Encrypt uses `(ρ, i, j)`; both
index orders were swapped. Self-consistent (round-trips passed) but
transposed vs the standard — precisely why a self-consistency-only test
never caught it.
2. **Encaps/decaps KDF** — the shared secret is now `K` from `G(m‖H(ek))`
directly; the round-3 final `K = KDF(K̄‖H(c))` step was removed.
3. **Implicit rejection** — now `K̄ = J(z‖c)` (SHAKE256 over the full
ciphertext) instead of `KDF(z‖H(c))`.
- **Validated for genuine conformance against OpenSSL 3.5's FIPS 203 ML-KEM-768**
(`tests/test_mlkem_fips203.sh`, wired into `make check`): deterministic keygen
produces byte-identical `ek`, and the shared secret matches in **both**
cross-decapsulation directions (our encaps ↔ OpenSSL decaps, and vice-versa).
This replaces the previous self-consistency-only round-trip test.
- **Validated for genuine conformance against OpenSSL 3.5's FIPS 203
ML-KEM-768** (`tests/test_mlkem_fips203.sh`, wired into `make check`):
deterministic keygen produces a byte-identical `ek`, and the shared secret
matches in **both** cross-decapsulation directions. This permanent conformance
test replaces the previous self-consistency-only round-trip.
### Security — CLI
- **Data-loss guard.** `compress -p out.zupt file1 file2` used to let `-p`
swallow the archive name as the password, then overwrite `file1` with the
archive (silent, exit 0). Now refuses to overwrite an existing non-`.zupt`
file as the output archive (override with `-y`/`--force`), plus a
self-overwrite guard.
- **Silent-plaintext guard.** `compress out.zupt dir -p pw` used to write an
**unencrypted** archive (exit 0) because options after the first positional
were treated as files. Now errors on a misplaced option (`--` escapes a real
dashed filename).
- **Heap OOB read** in the AVX2 decoder fast path on crafted archives: the 2-/3-
byte match-offset read is now bounded like the scalar tail path.
- Wipe ML-KEM/X25519 secret buffers on hybrid-decrypt key-read failure; bound
the attacker-controlled `encryption_header_off` in the `info` reader.
- `version`/`help`/banners now state the build's real KDF (PBKDF2-SHA256 on the
source-only build) and repo URL.
### GUI — reworked for the source-only build
- The GUI defaulted every encryption path to the libzuptsdk "SDK v2" modes,
which are absent from the source-only build and fail — so key generation and
encryption failed out of the box. Reworked around the native modes: a
build-aware **PQ-mode selector** (Hybrid `--pq` default · Full-PQ `--pq-only` ·
SDK v2 only when the binary reports `WITH_SDK` support), detected from
`version`. Extract/Verify gained a PQ private-key input with **auto-detect**
(reads the archive via `info` to pick `--pq` vs `--pq-only`); Verify could not
check any PQ archive before. Fixed a DiskTab QThread-lifetime bug (two buttons
shared one slot); corrected the About tab (codec, default KDF, `--pq-only`,
repo URL). Guix packaging: put Shiboken6 on the launcher path so PySide6
actually imports.
### Packaging & cross-platform
- `debian/rules`, `aur`, `nix`, `homebrew` no longer install the removed
vendored `.so`/`AUDIT.md` or use stale `/zupt` URLs; `opensuse` `%files` now
ships the shell completions (no more "unpackaged files" rpmbuild failure).
- New **portable cross-platform GUI package** (`packaging/portable/`) that runs
on Windows/macOS/Linux/BSD with Python + PySide6, and a **GitHub Actions
workflow** (`.github/workflows/cross-platform.yml`) that builds native
Windows (`.exe` + Inno Setup installer) and macOS (`.dmg`) artifacts on real
runners and attaches them to the release.
### BREAKING
- **`--pq` and `--pq-only` keys and archives created by ≤ 4.2.1 are not
readable by this release** (the KEM math changed). Regenerate keys
(`keygen`/`keygen --pq-only`) and re-encrypt affected archives. Password mode
(`-p`) and plain compression are unaffected. Wire format stays v1.6.
(`keygen` / `keygen --pq-only`) and re-encrypt affected archives. Password
mode (`-p`) and plain compression are unaffected. Wire format stays v1.6.
## [4.2.1] — 2026-07-10 — `info` correctly reports the post-quantum mode