diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f11515..84a93bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,10 +21,10 @@ name: CI on: push: - branches: [main, develop] + branches: [master] tags: ['v*'] pull_request: - branches: [main, develop] + branches: [master] jobs: # ─── Plain build + test, exactly as a user would do it ─── @@ -76,15 +76,18 @@ jobs: run: sudo apt-get update && sudo apt-get install -y build-essential python3 - name: Build with ASAN + UBSAN run: make test-asan - - name: PQ-SDK byte-exact roundtrip under ASAN + - name: Native --pq byte-exact roundtrip under ASAN env: ASAN_OPTIONS: detect_leaks=0:abort_on_error=1 UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1 run: | - ./zupt_asan keygen --sdk -o /tmp/k.priv - ./zupt_asan compress --pq-sdk /tmp/k.priv.pub /tmp/a.zupt include/ + # Native hybrid ML-KEM-768 + X25519 (the source-only default; --pq-sdk + # needs a WITH_SDK=1 build and is unavailable here). + ./zupt_asan keygen -o /tmp/k.priv + ./zupt_asan keygen --pub -o /tmp/k.pub -k /tmp/k.priv + ./zupt_asan compress --pq /tmp/k.pub /tmp/a.zupt include/ mkdir -p /tmp/extracted - ./zupt_asan extract --pq-sdk /tmp/k.priv -o /tmp/extracted /tmp/a.zupt + ./zupt_asan extract --pq /tmp/k.priv -o /tmp/extracted /tmp/a.zupt diff -qr include /tmp/extracted/include # ─── PIE hardening build — verifies no runtime breakage from -fPIE ─── @@ -147,7 +150,7 @@ jobs: id: sha1 run: | VER=$(grep '^#define ZUPT_VERSION_STRING' include/zupt.h | awk -F'"' '{print $2}') - SHA=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}') + SHA=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}') echo "sha=$SHA" >> "$GITHUB_OUTPUT" echo "ver=$VER" >> "$GITHUB_OUTPUT" echo "Run 1: $SHA" @@ -156,7 +159,7 @@ jobs: - name: Verify reproducibility run: | VER="${{ steps.sha1.outputs.ver }}" - SHA2=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}') + SHA2=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}') if [ "$SHA2" != "${{ steps.sha1.outputs.sha }}" ]; then echo "::error::make dist is NOT reproducible" echo " run 1: ${{ steps.sha1.outputs.sha }}" @@ -168,7 +171,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: zupt-source-tarball - path: /tmp/zupt-*.tar.gz + path: /tmp/vaptvupt-*.tar.gz # ─── Packaging-recipe syntax (cross-distro) ─── packaging-syntax: @@ -214,15 +217,15 @@ jobs: id: sha run: | VER="${{ steps.ver.outputs.version }}" - SHA=$(sha256sum /tmp/zupt-$VER.tar.gz | awk '{print $1}') + SHA=$(sha256sum /tmp/vaptvupt-$VER.tar.gz | awk '{print $1}') echo "sha=$SHA" >> "$GITHUB_OUTPUT" - echo "$SHA zupt-$VER.tar.gz" > /tmp/zupt-$VER.tar.gz.sha256 + echo "$SHA vaptvupt-$VER.tar.gz" > /tmp/vaptvupt-$VER.tar.gz.sha256 - name: Create GitHub release uses: softprops/action-gh-release@v2 with: files: | - /tmp/zupt-${{ steps.ver.outputs.version }}.tar.gz - /tmp/zupt-${{ steps.ver.outputs.version }}.tar.gz.sha256 + /tmp/vaptvupt-${{ steps.ver.outputs.version }}.tar.gz + /tmp/vaptvupt-${{ steps.ver.outputs.version }}.tar.gz.sha256 body: | ## Zupt v${{ steps.ver.outputs.version }} diff --git a/AUDIT.md b/AUDIT.md new file mode 100644 index 0000000..6880e1a --- /dev/null +++ b/AUDIT.md @@ -0,0 +1,85 @@ + +# 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](SECURITY.md) (policy + primitives) and +[THREAT_MODEL.md](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-only` archives (see CHANGELOG + 5.0.0 BREAKING). +- **Hybrid is the flagship.** `--pq` combines 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-only` + offers 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 + +```sh +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). diff --git a/CHANGELOG.md b/CHANGELOG.md index cedf8bf..a9de32e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/DISTRIBUTION.md b/DISTRIBUTION.md index ac7f904..43cbc9e 100644 --- a/DISTRIBUTION.md +++ b/DISTRIBUTION.md @@ -10,7 +10,7 @@ Every packaging recipe expects an upstream tarball `vaptvupt-VERSION.tar.gz` pro ```sh make dist -# → /tmp/vaptvupt-4.2.1.tar.gz +# → /tmp/vaptvupt-5.0.0.tar.gz ``` Re-running `make dist` on the same source tree produces an identical sha256 (verified by `tests/test_dist_reproducible.sh`, wired into `make test`). This lets distros pin a stable hash in their recipes. @@ -56,13 +56,13 @@ Maintainer flow: ```sh # 1. Produce the upstream tarball make dist -# → /tmp/vaptvupt-4.2.1.tar.gz +# → /tmp/vaptvupt-5.0.0.tar.gz # 2. Upload to a stable URL (e.g. git.securityops.co releases) # 3. Update packaging/aur/PKGBUILD: -# - Set pkgver=4.2.1 -# - Set sha256sums=("$(sha256sum /tmp/vaptvupt-4.2.1.tar.gz | awk '{print $1}')") +# - Set pkgver=5.0.0 +# - Set sha256sums=("$(sha256sum /tmp/vaptvupt-5.0.0.tar.gz | awk '{print $1}')") # 4. Generate .SRCINFO cd packaging/aur && makepkg --printsrcinfo > .SRCINFO @@ -73,7 +73,7 @@ makepkg -s # 6. Push to AUR git clone ssh://aur@aur.archlinux.org/vaptvupt.git aur-vaptvupt cp packaging/aur/PKGBUILD packaging/aur/.SRCINFO aur-vaptvupt/ -cd aur-vaptvupt && git add -A && git commit -m "v4.2.1" && git push +cd aur-vaptvupt && git add -A && git commit -m "v5.0.0" && git push ``` User install: @@ -117,10 +117,10 @@ The `packaging/debian/` tree is a Debian source-package layout. Maintainer flow: # 1. Produce the upstream tarball with the standard Debian # orig.tar.gz naming convention: make dist -cp /tmp/vaptvupt-4.2.1.tar.gz /tmp/vaptvupt_4.2.1.orig.tar.gz +cp /tmp/vaptvupt-5.0.0.tar.gz /tmp/vaptvupt_5.0.0.orig.tar.gz # 2. Unpack and overlay the debian/ tree: -cd /tmp && tar xzf vaptvupt_4.2.1.orig.tar.gz && cd vaptvupt-4.2.1 +cd /tmp && tar xzf vaptvupt_5.0.0.orig.tar.gz && cd vaptvupt-5.0.0 cp -a /path/to/vaptvupt/packaging/debian ./debian # 3. Build the source package: @@ -128,7 +128,7 @@ dpkg-buildpackage -S -us -uc # source-only dpkg-buildpackage -b -us -uc # binary # 4. Lint: -lintian vaptvupt_4.2.1-1_*.deb +lintian vaptvupt_5.0.0-1_*.deb # 5. Submit via the standard Debian mentors process: # https://mentors.debian.net/intro-maintainers/ @@ -145,7 +145,7 @@ sudo apt install vaptvupt ```sh # 1. Produce the tarball make dist -cp /tmp/vaptvupt-4.2.1.tar.gz ~/rpmbuild/SOURCES/ +cp /tmp/vaptvupt-5.0.0.tar.gz ~/rpmbuild/SOURCES/ # 2. Drop the .spec into the SPECS directory: cp packaging/rpm/vaptvupt.spec ~/rpmbuild/SPECS/ @@ -154,7 +154,7 @@ cp packaging/rpm/vaptvupt.spec ~/rpmbuild/SPECS/ cd ~/rpmbuild && rpmbuild -ba SPECS/vaptvupt.spec # 4. Lint: -rpmlint RPMS/x86_64/vaptvupt-4.2.1-1.fc*.rpm +rpmlint RPMS/x86_64/vaptvupt-5.0.0-1.fc*.rpm # 5. Submit via the Fedora new-package review process: # https://docs.fedoraproject.org/en-US/package-maintainers/Package_Review_Process/ @@ -177,7 +177,7 @@ The `packaging/opensuse/` tree carries an RPM `.spec` suited to the Open Build S make dist # 2. In an OBS package checkout (osc), stage the sources and spec: -cp /tmp/vaptvupt-4.2.1.tar.gz . +cp /tmp/vaptvupt-5.0.0.tar.gz . cp /path/to/vaptvupt/packaging/opensuse/vaptvupt.spec . # 3. Build locally against a target repository: @@ -230,7 +230,7 @@ nix build github:cristiancmoises/vaptvupt#vaptvupt nix run github:cristiancmoises/vaptvupt#vaptvupt -- version # 2. To consume from another flake: -# inputs.vaptvupt.url = "github:cristiancmoises/vaptvupt?ref=v4.2.1"; +# inputs.vaptvupt.url = "github:cristiancmoises/vaptvupt?ref=v5.0.0"; # packages.x86_64-linux.default = inputs.vaptvupt.packages.x86_64-linux.vaptvupt; # 3. To submit to nixpkgs (https://github.com/NixOS/nixpkgs): diff --git a/INSTALL.md b/INSTALL.md index 7c99e01..3dbb357 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -5,13 +5,13 @@ If you're seeing the error: ``` vaptvupt-gui depende de python3-pyqt6 | python3-pyside6; porém: Pacote python3-pyqt6 não está instalado. -vaptvupt-gui depende de vaptvupt (>= 4.2.1); porém: +vaptvupt-gui depende de vaptvupt (>= 5.0.0); porém: Versão de vaptvupt no sistema é 2.1.7-1. ``` This is correct behavior. The `vaptvupt-gui` deb requires: - Python 3 with **PyQt6** or **PySide6** (the GUI toolkit) -- The **vaptvupt CLI 4.2.1** or newer +- The **vaptvupt CLI 5.0.0** or newer ## The fastest fix — one command (Linux Mint, Ubuntu, Debian) @@ -33,8 +33,8 @@ the right order. sudo apt update sudo apt install -y python3-pyqt6 -# 2. Upgrade vaptvupt CLI to 4.2.1 -sudo dpkg -i vaptvupt_4.2.1_amd64.deb +# 2. Upgrade vaptvupt CLI to 5.0.0 +sudo dpkg -i vaptvupt_5.0.0_amd64.deb # 3. Install the GUI sudo dpkg -i vaptvupt-gui_1.3.0_all.deb @@ -50,7 +50,7 @@ sudo apt --fix-broken install ```bash sudo dnf install -y python3-pyqt6 -sudo dnf install -y vaptvupt-4.2.1-1.x86_64.rpm vaptvupt-gui-1.3.0-1.noarch.rpm +sudo dnf install -y vaptvupt-5.0.0-1.x86_64.rpm vaptvupt-gui-1.3.0-1.noarch.rpm ``` (Or build the RPM from the SRPM tarball with `rpmbuild -bb SPECS/vaptvupt.spec`) @@ -98,7 +98,7 @@ Qt6 inside the deb because: - Bundling would make the deb 80 MB+ instead of 35 KB - Distribution-managed Qt gets security updates automatically -## Why does the GUI need vaptvupt 4.2.1? +## Why does the GUI need vaptvupt 5.0.0? The GUI calls `vaptvupt --pq` and `vaptvupt keygen` for native post-quantum encryption (ML-KEM-768 + X25519, in-tree implementation). @@ -108,7 +108,7 @@ fail against them. ## After installing — verify ```bash -vaptvupt version # should show: 4.2.1 +vaptvupt version # should show: 5.0.0 vaptvupt-gui # should launch the GUI window ``` @@ -178,8 +178,8 @@ sudo zypper install gcc make # openSUSE ### Build VaptVupt itself ```bash -tar -xzf vaptvupt-4.2.1-source.tar.gz -cd vaptvupt-4.2.1 +tar -xzf vaptvupt-5.0.0-source.tar.gz +cd vaptvupt-5.0.0 make # build the `./vaptvupt` binary sudo make install # install to /usr/local/bin (override with PREFIX=/usr) diff --git a/README.md b/README.md index c2e94d4..8eb37d7 100644 --- a/README.md +++ b/README.md @@ -17,44 +17,40 @@ License: AGPL-3.0-or-later (dual-licensed AGPL + commercial). > command is preserved as a symlink to `vaptvupt` for one major version > cycle. -## What's new in 4.2.1 / 4.2.0 +## What's new in 5.0.0 -- **4.2.1 (patch):** `vaptvupt info` now correctly reports the post-quantum - mode — a `--pq-only` archive reads "ML-KEM-768 only, no classical layer" - instead of being mislabelled as hybrid. Reader-side only; no wire-format - change, and existing 4.2.0 archives are relabelled with no re-encryption. - -The 4.2.0 feature set (all included in 4.2.1): - -- **Full (pure) post-quantum mode — `--pq-only`.** ML-KEM-768 (FIPS 203) as - the *sole* key-establishment mechanism, with no classical X25519 component. - For compliance postures that mandate a single NIST-standardised PQ primitive - with no classical KEM in the envelope (CNSA 2.0-style "PQ-only"). Envelope - type `0x06`; archive key `SHA3-512(ml_ss ‖ ml_ct ‖ "ZUPT-PQ-ONLY-v1")`. - In-tree crypto, available in the default build. - - **Hybrid `--pq` stays the default recommendation.** `--pq-only` trades away - the hybrid safety net: a future break of ML-KEM-768 *alone* would break a - `--pq-only` archive, whereas `--pq` would still require breaking X25519 too. - Use `--pq-only` only when a policy forbids the classical component. -- **Security fix (critical): AES-CTR keystream reuse under `--dedup`.** Dedup - blocks all share sequence 0, and the old nonce (`base_nonce XOR seq`) - therefore collapsed to one value across blocks — reusing the CTR keystream - (a many-time-pad). Every block now uses a **fresh random 128-bit nonce**. - Re-encrypt any `--dedup` + encrypted archives written by ≤ 4.1.0. Regression - test: `tests/test_dedup_nonce.sh`. -- **Clearer SDK keygen guidance.** `keygen --sdk`/`--box` on the source-only - build now points you to native `--pq`/`--pq-only` (or a `WITH_SDK=1` build). -- **Wire/on-disk format is v1.6, unchanged.** All 4.x archives interoperate; - the new `0x06` envelope is additive. +- **Genuine FIPS 203 ML-KEM-768 — validated against OpenSSL.** Earlier releases + shipped round-3 CRYSTALS-Kyber under a "FIPS 203" label; it was secure but + **not interoperable** with a compliant ML-KEM. Three deviations (a transposed + matrix-`Â` sampling convention, the round-3 KDF, and the implicit-rejection + domain) are fixed, and the result is now **byte-for-byte interoperable with + OpenSSL 3.5's FIPS 203 ML-KEM-768** in both directions — checked on every + `make check` (`tests/test_mlkem_fips203.sh`). Hybrid `--pq` (ML-KEM-768 + + X25519) remains the recommended flagship; `--pq-only` is pure ML-KEM-768. +- **⚠ Breaking:** because the KEM math changed, `--pq`/`--pq-only` **keys and + archives from ≤ 4.2.1 no longer decrypt** — regenerate keys and re-encrypt. + Password mode (`-p`) and plain compression are unaffected; wire format is still v1.6. +- **CLI security fixes.** A `compress -p out.zupt file1 file2` **data-loss** bug + (the archive name was eaten as the password and overwrote `file1`) and a + `compress out.zupt dir -p pw` **silent-plaintext** bug are both guarded now; a + **heap OOB read** in the AVX2 decoder on crafted archives is bounded; banners + report the build's real KDF. +- **GUI reworked so it actually works.** It used to default every encryption + path to SDK modes absent from the source-only build (key generation failed out + of the box). Now a build-aware Hybrid/Full-PQ selector, PQ-key auto-detect on + Extract/Verify, and About/threading fixes. +- **Cross-platform.** A portable GUI package (Windows/macOS/Linux/BSD, needs + Python + PySide6) and a CI workflow that builds native Windows `.exe`/installer + and macOS `.dmg` on real runners. > **F-16 (data loss):** archives created by **≤ 3.8.0** at `-l 8`/`-l 9` > whose inputs included x86/ELF/PE executables may be **undecodable by any > version** (write-time defect in the old in-tree BCJ encoder). Re-create -> such archives with 4.2.1 and verify extraction before deleting source +> such archives with 5.0.0 and verify extraction before deleting source > data. Details in [CHANGELOG.md](CHANGELOG.md). -Binaries for the CLI (4.2.1) and GUI (1.3.0) are on the -[release page](https://git.securityops.co/cristiancmoises/vaptvupt/releases/tag/v4.2.1). +Binaries for the CLI (5.0.0) and GUI (1.3.0) are on the +[release page](https://git.securityops.co/cristiancmoises/vaptvupt/releases/tag/v5.0.0). --- @@ -129,18 +125,18 @@ Argon2id KDF. ### Pre-built packages Assets are published on the -[v4.2.1 release page](https://git.securityops.co/cristiancmoises/vaptvupt/releases/tag/v4.2.1) +[v5.0.0 release page](https://git.securityops.co/cristiancmoises/vaptvupt/releases/tag/v5.0.0) and verifiable against the published `SHA256SUMS.txt`. -**Command-line tool (`vaptvupt` 4.2.1):** +**Command-line tool (`vaptvupt` 5.0.0):** | Format | File | Distros | |---|---|---| -| Debian/Ubuntu | `vaptvupt_4.2.1_amd64.deb` | Debian 11+, Ubuntu 22.04+, Mint 21+ | -| RPM | `vaptvupt-4.2.1-1.x86_64.rpm` | Fedora 38+, RHEL 9+, openSUSE, AlmaLinux, Rocky, other RPM-based distributions | -| AppDir tarball | `vaptvupt-4.2.1-x86_64.AppDir.tar.gz` | Any glibc 2.28+ (extract & run, no FUSE) | -| Source tarball | `vaptvupt-4.2.1.tar.gz` | Build from source on any platform | -| openSUSE OBS | `vaptvupt-4.2.1-opensuse-obs.tar.gz` | Open Build Service source bundle | +| Debian/Ubuntu | `vaptvupt_5.0.0_amd64.deb` | Debian 11+, Ubuntu 22.04+, Mint 21+ | +| RPM | `vaptvupt-5.0.0-1.x86_64.rpm` | Fedora 38+, RHEL 9+, openSUSE, AlmaLinux, Rocky, other RPM-based distributions | +| AppDir tarball | `vaptvupt-5.0.0-x86_64.AppDir.tar.gz` | Any glibc 2.28+ (extract & run, no FUSE) | +| Source tarball | `vaptvupt-5.0.0.tar.gz` | Build from source on any platform | +| openSUSE OBS | `vaptvupt-5.0.0-opensuse-obs.tar.gz` | Open Build Service source bundle | **Graphical front-end (`vaptvupt-gui` 1.3.0):** @@ -151,22 +147,36 @@ and verifiable against the published `SHA256SUMS.txt`. | AppImage | `VaptVupt-GUI-1.3.0-x86_64.AppImage` | Any glibc 2.28+ (single-file, no install) | | AppDir tarball | `VaptVupt-GUI-1.3.0-x86_64.AppDir.tar.gz` | Any glibc 2.28+ (extract & run) | +**Windows / macOS / BSD:** + +| Platform | File | Notes | +|---|---|---| +| Windows | `VaptVupt-Setup-5.0.0.exe`, `vaptvupt-gui-5.0.0-windows-x86_64.exe`, `vaptvupt-5.0.0-windows-x86_64.exe` | Native installer + standalone GUI + CLI, built on a Windows runner by CI | +| macOS | `VaptVupt-5.0.0.dmg`, `vaptvupt-5.0.0-macos` | `.dmg` GUI bundle + CLI, built on a macOS runner by CI | +| Any OS (portable GUI) | `vaptvupt-gui-5.0.0-portable.zip` | Python GUI + launchers for Windows/macOS/Linux/BSD; needs Python 3.8+ and PySide6 (or PyQt6), plus the `vaptvupt` CLI on PATH | +| BSD / others | `vaptvupt-5.0.0.tar.gz` | Build the CLI from source (`make`); run the portable GUI | + +The native Windows/macOS installers are produced by the project's CI +(`.github/workflows/cross-platform.yml`) on real Windows and macOS runners — see +the GitHub release. The portable GUI package runs the same GUI everywhere Python +and Qt are available. + ```bash # Verify downloads first sha256sum -c SHA256SUMS.txt # Debian / Ubuntu / Mint -sudo dpkg -i vaptvupt_4.2.1_amd64.deb +sudo dpkg -i vaptvupt_5.0.0_amd64.deb sudo apt-get install -f # resolve any missing deps # Fedora / RHEL / openSUSE / AlmaLinux / Rocky and other RPM-based distros -sudo rpm -i vaptvupt-4.2.1-1.x86_64.rpm +sudo rpm -i vaptvupt-5.0.0-1.x86_64.rpm # or -sudo dnf install ./vaptvupt-4.2.1-1.x86_64.rpm +sudo dnf install ./vaptvupt-5.0.0-1.x86_64.rpm # AppDir tarball (no install, no FUSE required) -tar xzf vaptvupt-4.2.1-x86_64.AppDir.tar.gz -./vaptvupt-4.2.1-x86_64.AppDir/AppRun --help +tar xzf vaptvupt-5.0.0-x86_64.AppDir.tar.gz +./vaptvupt-5.0.0-x86_64.AppDir/AppRun --help # GUI AppImage (single executable) chmod +x VaptVupt-GUI-1.3.0-x86_64.AppImage @@ -176,10 +186,10 @@ chmod +x VaptVupt-GUI-1.3.0-x86_64.AppImage ### Building from SRPM (Fedora / RHEL / RPM-based distributions) ```bash -tar xzf vaptvupt-4.2.1.srpm.tar.gz +tar xzf vaptvupt-5.0.0.srpm.tar.gz cd ~/rpmbuild # or use rpmbuild --define "_topdir $(pwd)" rpmbuild -bb SPECS/vaptvupt.spec -sudo rpm -i RPMS/x86_64/vaptvupt-4.2.1-1.*.rpm +sudo rpm -i RPMS/x86_64/vaptvupt-5.0.0-1.*.rpm ``` ### Basic usage @@ -680,6 +690,7 @@ VaptVupt archives require VaptVupt v2.0+. | v4.1.0 | Source-only tree (prebuilt libzuptsdk/libpqvaptvupt removed); default build needs only a C compiler + make; native `--pq` is the default PQ mode; `--pq-sdk`/`--pq-box`/Argon2id gated behind `make WITH_SDK=1`. Wire format stays v1.6 | | v4.2.0 | Full (pure) post-quantum mode `--pq-only` (ML-KEM-768 only, envelope 0x06); critical fix for AES-CTR keystream reuse under `--dedup` (fresh random per-block nonce); clearer SDK keygen guidance. Wire format stays v1.6 | | v4.2.1 | `vaptvupt info` now reports the real post-quantum mode (`--pq-only` no longer mislabelled as hybrid); reader-side only, no wire-format change | +| v5.0.0 | Genuine FIPS 203 ML-KEM-768 (validated vs OpenSSL); CLI data-loss/plaintext guards; AVX2 decoder OOB-read fix; GUI reworked for native PQ modes; cross-platform packaging. **Breaking:** `--pq`/`--pq-only` keys+archives from ≤4.2.1 do not decrypt | See [CHANGELOG.md](CHANGELOG.md) for detailed per-version changes. diff --git a/SECURITY.md b/SECURITY.md index c104113..7e539c0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ -# Security Policy — VaptVupt 4.2.1 +# Security Policy — VaptVupt 5.0.0 ## Reporting Vulnerabilities @@ -61,7 +61,7 @@ build and are not defaults. | Authentication | HMAC-SHA256 | RFC 2104 | 256-bit | 128-bit post-quantum (Grover) | | Password KDF (default) | PBKDF2-SHA256 | RFC 8018 | 600K iterations | Password-dependent | | Password KDF (WITH_SDK=1 option) | Argon2id | RFC 9106 | OWASP minimums | Password-dependent, memory-hard | -| Post-quantum KEM | ML-KEM-768 | FIPS 203 | 1184B pk / 2400B sk | NIST Level 3 | +| Post-quantum KEM | ML-KEM-768 | FIPS 203 (validated vs OpenSSL 3.5) | 1184B ek / 2400B dk | NIST Level 3 | | Classical KEM | X25519 | RFC 7748 | 32B scalar | ~128-bit classical | | Hybrid KDF (`--pq`) | SHA3-512 | FIPS 202 | 512-bit output | Secure if either KEM holds | | PQ-only KDF (`--pq-only`) | SHA3-512 | FIPS 202 | 512-bit output | Secure if ML-KEM-768 holds (no classical fallback) | @@ -112,6 +112,14 @@ decryption. This prevents: ### Hybrid Post-Quantum KEM (`--pq`) +> **FIPS 203 conformance (v5.0.0).** The ML-KEM-768 implementation is validated +> byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768: deterministic keygen +> produces an identical `ek`, and the shared secret agrees in both +> cross-decapsulation directions (our encaps ↔ OpenSSL decaps, and vice-versa). +> This is checked on every `make check` by `tests/test_mlkem_fips203.sh`. +> Releases ≤ 4.2.1 used round-3 CRYSTALS-Kyber (secure, but not interoperable); +> 5.0.0's `--pq`/`--pq-only` archives are therefore not backward-compatible. + ``` Encapsulation: ML-KEM-768.Encaps(pk) → ml_ct[1088], ml_ss[32] diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md index 393d753..fc4bce2 100644 --- a/THREAT_MODEL.md +++ b/THREAT_MODEL.md @@ -108,10 +108,10 @@ machines under the user's own control. ### 5. Post-quantum forward secrecy (`--pq`, `--pq-only`, and optional `--pq-sdk`) -The native `--pq` mode uses ML-KEM-768 (FIPS 203) hybridized with -X25519 via an HKDF combiner. Archives encrypted today cannot be -decrypted by a future quantum adversary holding only the ciphertext, -assuming: +The native `--pq` mode uses ML-KEM-768 (FIPS 203 — validated byte-for-byte +against OpenSSL 3.5's ML-KEM-768; see AUDIT.md) hybridized with X25519 via an +HKDF combiner. Archives encrypted today cannot be decrypted by a future quantum +adversary holding only the ciphertext, assuming: - ML-KEM-768 retains its claimed security level (NIST Category 3, 192-bit classical / 96-bit quantum strength) @@ -323,6 +323,6 @@ normally. ## Document version This threat model covers archive format v1.6 as shipped in VaptVupt -4.2.1. It is part of the source tree (`THREAT_MODEL.md`) and +5.0.0. It is part of the source tree (`THREAT_MODEL.md`) and versioned with the project; this section will be updated as the format evolves. diff --git a/doc/vaptvupt.1 b/doc/vaptvupt.1 index db97dc3..4e07938 100644 --- a/doc/vaptvupt.1 +++ b/doc/vaptvupt.1 @@ -1,7 +1,7 @@ .\" Manpage for vaptvupt (formerly zupt; INPI Brasil trademark rename in v3.0.0) .\" SPDX-License-Identifier: AGPL-3.0-or-later .\" Copyright (c) 2025-2026 Cristian Cezar Moisés -.TH VAPTVUPT 1 "July 2026" "vaptvupt 4.2.1" "User Commands" +.TH VAPTVUPT 1 "July 2026" "vaptvupt 5.0.0" "User Commands" .SH NAME vaptvupt \- post-quantum backup compression utility (formerly zupt) @@ -72,7 +72,7 @@ every byte of the archive — header, footer, per-block metadata, comments — i .PP The compression layer is the .B VaptVupt LZ + ANS -codec (version 2.53.3), which prioritises decode speed and ratio over +codec (version 2.60.4), which prioritises decode speed and ratio over encode speed. Aggregate decode throughput on this build is 1.27\(mu zstd\-3; encode throughput is 0.2\(mu\(en0.5\(mu zstd\-3 depending on content. See @@ -239,9 +239,10 @@ Read password from file descriptor N. .TP .B --kdf \fIalgo\fR -Choose key-derivation function for password mode: -.BR argon2id " (default since v2.4.1; memory-hard) or " -.BR pbkdf2 " (SHA-256, 600 000 iter; needed for compatibility with v2.4.0 and earlier readers)." +Choose key-derivation function for password mode. In the default source-only +build the only KDF is +.BR pbkdf2 " (SHA-256, 600 000 iter), which is the default;" +.BR argon2id " (memory-hard) is available only in a " WITH_SDK=1 " build." .TP .B --keyfile \fIpath\fR diff --git a/gui/README.md b/gui/README.md index 7d9bdef..35ef00c 100644 --- a/gui/README.md +++ b/gui/README.md @@ -116,7 +116,7 @@ The GUI calls the vaptvupt CLI binary — all cryptography runs in native C, not ## Credits -- **vaptvupt** v4.2.1 — Cristian Cezar Moisés ([github](https://git.securityops.co/cristiancmoises/vaptvupt)) +- **vaptvupt** v5.0.0 — Cristian Cezar Moisés ([github](https://git.securityops.co/cristiancmoises/vaptvupt)) ## License diff --git a/gui/src/zupt_gui.py b/gui/src/zupt_gui.py index 20a83b9..d8efd04 100644 --- a/gui/src/zupt_gui.py +++ b/gui/src/zupt_gui.py @@ -398,10 +398,14 @@ class PathField(QWidget): def scrollable(w): sa = QScrollArea(); sa.setWidgetResizable(True); sa.setWidget(w); sa.setFrameShape(QFrame.Shape.NoFrame); return sa -def run_async(parent, cmd, btn, log, progress=None): - log.clear(); btn.setEnabled(False) +def run_async(parent, cmd, btn, log, progress=None, info=None): + log.clear() + if info: # e.g. an auto-detect note; appended AFTER the clear so it survives + log.append(info) + btn.setEnabled(False) if progress: progress.show() t = QThread(); w = Worker(cmd); w.moveToThread(t) + # log.append targets a main-thread QObject -> Qt queues it to the GUI thread. w.log.connect(log.append) # Keep a LIST of live (thread, worker) refs on the parent. Tabs with more # than one action button (Disk: backup + restore) previously shared a @@ -417,7 +421,10 @@ def run_async(parent, cmd, btn, log, progress=None): log.append("\nDone." if code == 0 else f"\nFailed (exit {code}).") t.quit() parent._jobs = [(th, wk) for (th, wk) in parent._jobs if th is not t] - w.done.connect(finish) + # `done` is emitted from the worker thread and `finish` touches GUI widgets; + # a bare functor would connect DirectConnection and run OFF the GUI thread. + # QueuedConnection marshals it onto the GUI event loop. + w.done.connect(finish, Qt.ConnectionType.QueuedConnection) t.started.connect(w.run); t.start() # ── Tabs ── @@ -621,6 +628,7 @@ class ExtractTab(QWidget): arc = self.arc.path() if not arc: QMessageBox.warning(self, "VaptVupt", "Select an archive."); return cmd = ["extract"] + info = None if self.out.path(): cmd += ["-o", self.out.path()] if self.pw.text(): cmd += ["-p", self.pw.text()] if self.pq.path(): @@ -629,11 +637,11 @@ class ExtractTab(QWidget): # The private-key format must match how the archive was encrypted; # inspect the header (vaptvupt info) to choose the right flag. tok = _detect_archive_pq(arc) or "pq" - self.log.append(f"[auto-detect] using {_PQ_FLAG[tok][1]}") + info = f"[auto-detect] using {_PQ_FLAG[tok][1]}" _, flag = _PQ_FLAG[tok] cmd += [flag, self.pq.path()] cmd.append(arc) - run_async(self, cmd, self.btn, self.log, self.progress) + run_async(self, cmd, self.btn, self.log, self.progress, info=info) class VerifyTab(QWidget): diff --git a/include/zupt.h b/include/zupt.h index 96d8ff9..817e4d8 100644 --- a/include/zupt.h +++ b/include/zupt.h @@ -50,7 +50,7 @@ #define ZUPT_PRODUCT_EXTENSION ".zupt" /* on-disk archive extension (kept stable) */ #define ZUPT_PRODUCT_TAGLINE "Post-quantum backup compression" -#define ZUPT_VERSION_STRING "4.2.1" +#define ZUPT_VERSION_STRING "5.0.0" /* Vendored codec release (upstream tag) — single source for display strings. * The codec's own VV_VERSION_* is its internal API version, not the release. */ #define ZUPT_CODEC_RELEASE "2.60.4" diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD index d6ebeb4..87a84a3 100644 --- a/packaging/aur/PKGBUILD +++ b/packaging/aur/PKGBUILD @@ -11,7 +11,7 @@ # zupt-VERSION.tar.gz alongside the PKGBUILD. pkgname=vaptvupt -pkgver=4.2.1 +pkgver=5.0.0 pkgrel=1 provides=('zupt') replaces=('zupt') diff --git a/packaging/debian/changelog b/packaging/debian/changelog index b6b2252..e917e04 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,3 +1,25 @@ +vaptvupt (5.0.0-1) UNRELEASED; urgency=high + + * ML-KEM-768 is now genuinely FIPS 203-conformant. Earlier releases shipped + round-3 CRYSTALS-Kyber under a "FIPS 203" label; it was secure but not + interoperable. Fixed a transposed matrix-A sampling convention (keygen + + encrypt), the round-3 KDF, and the implicit-rejection domain. Validated + byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768 in both + cross-decapsulation directions (tests/test_mlkem_fips203.sh, in make check). + * BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no longer decrypt + (the KEM math changed). Regenerate keys and re-encrypt. Password mode and + plain compression are unaffected; wire format stays v1.6. + * Security: compress -p data-loss guard (was overwriting an + input file); compress -p silent-plaintext guard; heap + OOB read in the AVX2 decoder fast path bounded; overflow-safe bound in the + solid-mode test path; secret-wipe on hybrid-decrypt key-read error. + * GUI reworked for the source-only build: build-aware Hybrid/Full-PQ selector + (no more SDK-mode defaults that fail), PQ-key auto-detect on Extract/Verify, + thread-safety + About fixes. + * Truthful banner/help (real default KDF); cross-platform packaging. + + -- Cristian Cezar Moisés Fri, 10 Jul 2026 18:00:00 +0000 + vaptvupt (4.2.1-1) UNRELEASED; urgency=medium * Fix: `vaptvupt info` mislabelled full post-quantum (--pq-only, enc_type diff --git a/packaging/debian/control b/packaging/debian/control index caa35b8..1c177d5 100644 --- a/packaging/debian/control +++ b/packaging/debian/control @@ -25,7 +25,7 @@ Description: Post-quantum backup compression utility (formerly Zupt) * Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203) * AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC) * PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds) - * Multi-threaded compression with the VaptVupt LZ + ANS codec 2.48.5 + * Multi-threaded compression with the VaptVupt LZ + ANS codec 2.60.4 * Full-disk backup and restore with sparse-region detection * End-to-end byte-level tamper detection on encrypted archives (0 silent-accept positions in the v1.6 exhaustive byte sweep) diff --git a/packaging/debian/copyright b/packaging/debian/copyright index 12506ba..843c6e2 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: zupt Upstream-Contact: Cristian Cezar Moisés -Source: https://git.securityops.co/cristiancmoises/zupt +Source: https://git.securityops.co/cristiancmoises/vaptvupt Files: * Copyright: 2025-2026 Cristian Cezar Moisés diff --git a/packaging/homebrew/vaptvupt.rb b/packaging/homebrew/vaptvupt.rb index e1edacf..4e0074a 100644 --- a/packaging/homebrew/vaptvupt.rb +++ b/packaging/homebrew/vaptvupt.rb @@ -17,14 +17,13 @@ # Notes for macOS: # * Jasmin assembly is disabled at build time on Darwin (no jasminc dep); # the C fallback for AES-256-CTR / HMAC compare paths is shipped. -# * libzuptsdk is vendored and installed alongside the binary; the binary -# uses @loader_path rpath so users don't have to set DYLD paths. +# * Source-only build: no vendored libraries; native crypto only. class Vaptvupt < Formula desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)" homepage "https://git.securityops.co/cristiancmoises/vaptvupt" - url "https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v4.2.1/vaptvupt-4.2.1.tar.gz" - version "4.2.1" + url "https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v5.0.0/vaptvupt-5.0.0.tar.gz" + version "5.0.0" sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL" license "AGPL-3.0-or-later" diff --git a/packaging/nix/flake.nix b/packaging/nix/flake.nix index 0e8d4cc..89e1df5 100644 --- a/packaging/nix/flake.nix +++ b/packaging/nix/flake.nix @@ -33,7 +33,7 @@ zupt = pkgs.stdenv.mkDerivation { pname = "vaptvupt"; - version = "4.2.1"; + version = "5.0.0"; # When publishing, replace this with `fetchurl` against the # release tarball. For local development the flake assumes it diff --git a/packaging/opensuse/README.md b/packaging/opensuse/README.md index d37ad13..50feedf 100644 --- a/packaging/opensuse/README.md +++ b/packaging/opensuse/README.md @@ -1,12 +1,12 @@ # openSUSE Build Service update for `home:cabelo:innovators/vaptvupt` -This directory contains the three files needed to build vaptvupt `4.2.1` +This directory contains the three files needed to build vaptvupt `5.0.0` in OBS: | File | Purpose | |---------------|-------------------------------------------------------------------------| -| `_service` | `revision` pinned to `v4.2.1`. Format unchanged (still `tar_scm`). | -| `vaptvupt.spec` | `Version: 4.2.1`. `License: AGPL-3.0-or-later`. `%check` calls `make check`. | +| `_service` | `revision` pinned to `v5.0.0`. Format unchanged (still `tar_scm`). | +| `vaptvupt.spec` | `Version: 5.0.0`. `License: AGPL-3.0-or-later`. `%check` calls `make check`. | | `vaptvupt.changes`| Changelog for the 4.x series. Older history preserved verbatim. | ## Spec notes @@ -58,22 +58,22 @@ cp /path/to/vaptvupt-source/packaging/opensuse/_service . cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.spec . cp /path/to/vaptvupt-source/packaging/opensuse/vaptvupt.changes . -# 3. Trigger the service locally to fetch v4.2.1 from GitHub +# 3. Trigger the service locally to fetch v5.0.0 from GitHub osc service runall -# Produces vaptvupt-4.2.1.tar.gz in the current directory. +# Produces vaptvupt-5.0.0.tar.gz in the current directory. # 4. (Optional) Local build to verify before committing osc build openSUSE_Tumbleweed x86_64 # 5. Commit upstream -osc status # confirm vaptvupt-4.2.1.tar.gz is staged alongside the +osc status # confirm vaptvupt-5.0.0.tar.gz is staged alongside the # three text files -osc commit -m "Update to 4.2.1" +osc commit -m "Update to 5.0.0" ``` ## Notes for future updates -* The `_service` `revision` is pinned to `v4.2.1`. To track a new +* The `_service` `revision` is pinned to `v5.0.0`. To track a new release, edit that one line and re-run `osc service runall`. * The spec's `Version:` field is hard-coded — when you bump `_service` `revision`, also bump `Version:` to match. diff --git a/packaging/opensuse/_service b/packaging/opensuse/_service index 1e294a4..82d7f90 100644 --- a/packaging/opensuse/_service +++ b/packaging/opensuse/_service @@ -2,7 +2,7 @@ https://github.com/cristiancmoises/vaptvupt git - v4.2.1 + v5.0.0 @PARENT_TAG@ v(.*) enable diff --git a/packaging/opensuse/vaptvupt.changes b/packaging/opensuse/vaptvupt.changes index eff6eeb..e0b36d6 100644 --- a/packaging/opensuse/vaptvupt.changes +++ b/packaging/opensuse/vaptvupt.changes @@ -1,3 +1,21 @@ +------------------------------------------------------------------- +Fri Jul 10 18:00:00 UTC 2026 - Alessandro de Oliveira Faria + +- Update to 5.0.0: + * ML-KEM-768 is now genuinely FIPS 203-conformant (was round-3 + CRYSTALS-Kyber): fixed a transposed matrix-A sampling convention, + the round-3 KDF, and the implicit-rejection domain. Validated + byte-for-byte against OpenSSL 3.5's FIPS 203 ML-KEM-768 + (tests/test_mlkem_fips203.sh, run in %check). + * BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no + longer decrypt (the KEM math changed). Regenerate keys and + re-encrypt. Password mode and plain compression are unaffected; + wire format stays v1.6. + * Security: compress data-loss and silent-plaintext guards; heap + OOB read in the AVX2 decoder bounded; overflow-safe solid-mode + test path; secret-wipe on hybrid-decrypt key-read error. + * GUI reworked for the source-only build; truthful banner/help. + ------------------------------------------------------------------- Fri Jul 10 12:00:00 UTC 2026 - Alessandro de Oliveira Faria diff --git a/packaging/opensuse/vaptvupt.spec b/packaging/opensuse/vaptvupt.spec index b6e3565..a19abc1 100644 --- a/packaging/opensuse/vaptvupt.spec +++ b/packaging/opensuse/vaptvupt.spec @@ -19,7 +19,7 @@ Name: vaptvupt -Version: 4.2.1 +Version: 5.0.0 Release: 0 Summary: Post-quantum backup compression with AES-256 + ML-KEM-768 hybrid encryption License: AGPL-3.0-or-later diff --git a/packaging/rpm/vaptvupt.spec b/packaging/rpm/vaptvupt.spec index 8610191..109664c 100644 --- a/packaging/rpm/vaptvupt.spec +++ b/packaging/rpm/vaptvupt.spec @@ -20,7 +20,7 @@ # in the base. Name: vaptvupt -Version: 4.2.1 +Version: 5.0.0 Release: 1%{?dist} Summary: Post-quantum backup compression utility (AES-256 + ML-KEM-768 + Argon2id, formerly Zupt) @@ -48,10 +48,12 @@ Requires: glibc %description Zupt is a pure-C11 backup compression utility featuring: - * Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203) + * Post-quantum hybrid encryption (ML-KEM-768 + X25519, FIPS 203, + validated byte-for-byte against OpenSSL's ML-KEM-768) and full + pure ML-KEM-768 (--pq-only) * AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC) - * Argon2id password-based key derivation (default since 2.4.1) - * Multi-threaded compression with the VaptVupt LZ codec + * PBKDF2-SHA256 password key derivation (Argon2id in WITH_SDK=1 builds) + * Multi-threaded compression with the VaptVupt LZ + ANS codec * Full-disk backup and restore with sparse-region detection * End-to-end byte-level tamper detection on encrypted archives (0 silent-accept positions in the v1.6 exhaustive byte sweep) @@ -107,6 +109,17 @@ and optional encrypted comments. %endif %changelog +* Fri Jul 10 2026 Cristian Cezar Moisés - 5.0.0-1 +- ML-KEM-768 is now genuinely FIPS 203-conformant (was round-3 CRYSTALS-Kyber): + fixed a transposed matrix-A sampling convention, the round-3 KDF, and the + implicit-rejection domain. Validated byte-for-byte against OpenSSL 3.5's + FIPS 203 ML-KEM-768 (tests/test_mlkem_fips203.sh, run in %%check). +- BREAKING: --pq / --pq-only keys and archives from <= 4.2.1 no longer decrypt. + Regenerate keys and re-encrypt. Password mode / plain compression unaffected. +- Security: compress data-loss + silent-plaintext guards; AVX2 decoder heap + OOB-read bound; overflow-safe solid-mode test path; secret-wipe on error. +- GUI reworked for the source-only build; truthful banner/help. + * Fri Jul 10 2026 Cristian Cezar Moisés - 4.2.1-1 - Fix: "vaptvupt info" mislabelled full post-quantum (--pq-only) archives as "PQ Hybrid (ML-KEM-768 + X25519)". info now reads the real enc_type from the diff --git a/src/zupt_format.c b/src/zupt_format.c index cc49027..d032865 100644 --- a/src/zupt_format.c +++ b/src/zupt_format.c @@ -2797,7 +2797,11 @@ zupt_error_t zupt_test_archive(const char *arc, zupt_options_t *opts) { uint64_t sz = e->uncompressed_size; int fok = 1; - if (off + sz > total_size) { + /* Overflow-safe bound: off+sz can wrap (both are attacker-controlled + * index fields), so `off + sz > total_size` could pass falsely and + * feed a wild pointer / oversized length to zupt_xxh64. Match the + * hardened extract path. */ + if (off > (uint64_t)total_size || sz > (uint64_t)total_size - off) { fok = 0; } else if (sz > 0) { uint64_t ck = zupt_xxh64(solid_buf + off, (size_t)sz, 0);