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

@ -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 }}

85
AUDIT.md Normal file
View file

@ -0,0 +1,85 @@
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
# 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).

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

View file

@ -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):

View file

@ -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)

101
README.md
View file

@ -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.

View file

@ -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]

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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"

View file

@ -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')

View file

@ -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 <archive> <files> data-loss guard (was overwriting an
input file); compress <archive> <dir> -p <pw> 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 <sac@securityops.co> 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

View file

@ -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)

View file

@ -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 <sac@securityops.co>
Source: https://git.securityops.co/cristiancmoises/zupt
Source: https://git.securityops.co/cristiancmoises/vaptvupt
Files: *
Copyright: 2025-2026 Cristian Cezar Moisés

View file

@ -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"

View file

@ -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

View file

@ -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.

View file

@ -2,7 +2,7 @@
<service name="tar_scm" mode="manual">
<param name="url">https://github.com/cristiancmoises/vaptvupt</param>
<param name="scm">git</param>
<param name="revision">v4.2.1</param>
<param name="revision">v5.0.0</param>
<param name="versionformat">@PARENT_TAG@</param>
<param name="versionrewrite-pattern">v(.*)</param>
<param name="submodules">enable</param>

View file

@ -1,3 +1,21 @@
-------------------------------------------------------------------
Fri Jul 10 18:00:00 UTC 2026 - Alessandro de Oliveira Faria <cabelo@opensuse.org>
- 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 <cabelo@opensuse.org>

View file

@ -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

View file

@ -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 <sac@securityops.co> - 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 <sac@securityops.co> - 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

View file

@ -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);