zupt/doc/vaptvupt.1
Cristian Cezar Moisés 4874010d0e v4.1.0: source-only build, multithreaded-encryption fix, security hardening
Build from source with no vendored binaries:
- Remove prebuilt libzuptsdk.so / libpqvaptvupt.so (and a stray .pyc). The
  default build needs only a C compiler + make; it links no external library
  and installs no .so. The libzuptsdk-backed modes (Argon2id KDF, --pq-sdk,
  --pq-box) are gated behind an opt-in `make WITH_SDK=1`. The default password
  KDF is PBKDF2-SHA256 and --pq (native ML-KEM-768 + X25519) is the built-in PQ
  mode. openSUSE/RPM/deb/AUR/Homebrew/Nix recipes bumped to 4.1.0; the openSUSE
  spec now builds source-only (%files ships no .so, %build/%install WITH_SDK=0).

Fix: multithreaded encrypted archives were unextractable on the native AEAD
path. The parallel compress/decompress workers skipped the F-09 frame-preface
AAD that the serial path and the archive's AAD_PREFACE flag bind into every
block MAC, so each multithreaded block failed authentication. The workers now
bind the preface via a shared serializer; output is byte-identical across
thread counts and interoperates with single-threaded archives (also fixes
`--kdf pbkdf2 -t N` in any build).

Security hardening (crafted-archive memory safety + crypto):
- LZH raw code-length stack overflow + huff_lut OOB write
- overflow-safe bounds in parse_index and solid-mode extract (heap OOB read)
- SEQ decoder safe-zone heap overflow (litlen+matchlen reserve)
- require the per-block ENCRYPTED flag on encrypted archives (plaintext forgery)
- cap archive-supplied PBKDF2 iteration count (KDF-amplification DoS)
- non-elidable secret wipe in the SDK path; restored disk images created 0600

Docs: remove AUDIT.md / BENCHMARKS.md / ROADMAP.md; trim marketing/AI-styled
text and correct KDF/PQ facts across README, SECURITY, INSTALL, DISTRIBUTION,
THREAT_MODEL, THIRD-PARTY-NOTICES, the man page, and packaging READMEs. Wire
format v1.6 unchanged.
2026-07-07 19:45:37 -03:00

618 lines
17 KiB
Groff

.\" 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.1.0" "User Commands"
.SH NAME
vaptvupt \- post-quantum backup compression utility (formerly zupt)
.SH SYNOPSIS
.B vaptvupt compress
.RI [ options ]
.I out.zupt
.I files...
.br
.B vaptvupt extract
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt list
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt test
.RI [ options ]
.I archive.zupt
.br
.B vaptvupt info
.I archive.zupt
.br
.B vaptvupt bench
.I files/dirs...
.br
.B vaptvupt disk backup
.RI [ options ]
.I out.zupt
.I device
.br
.B vaptvupt disk restore
.RI [ options ]
.I archive.zupt
.I device
.br
.B vaptvupt keygen
.RI [ options ]
.br
.B vaptvupt version
.br
.B vaptvupt help
.PP
The legacy command name
.B zupt
is preserved as an alias for backward compatibility; both invocations
behave identically.
.SH DESCRIPTION
.B vaptvupt
compresses and encrypts files, directories, and whole block devices
into self-contained, authenticated archives with the
.B .zupt
extension. It targets long-lived backup storage where:
.RS
.IP \(bu 2
the archive is written once and restored under time pressure many years later;
.IP \(bu 2
the encryption envelope must remain secure against a future cryptographically-relevant quantum computer (ML-KEM-768);
.IP \(bu 2
every byte of the archive — header, footer, per-block metadata, comments — is authenticated, and a single bit-flip is rejected at restore time.
.RE
.PP
The compression layer is the
.B VaptVupt LZ + ANS
codec (version 2.53.3), 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
.B PERFORMANCE
below.
.PP
The on-disk format is v1.6 and has been wire-compatible since release
v2.3.1. The product was renamed from
.B Zupt
to
.B VaptVupt
in v3.0.0 because of a prior INPI Brasil trademark registration of the
name "Zupt" for unrelated software. The
.B .zupt
file extension and the
.B ZUPT
header magic bytes are unchanged: archives produced by any v2.x release
extract cleanly under v3.0.0 and vice versa.
.SH COMMANDS
.TP
.B compress
Create an archive. Default codec is VaptVupt (level 7). Compression
is multi-threaded; one worker per detected CPU by default.
.TP
.B extract
Decompress an archive into the current directory (or
.BR -o " " \fIdir\fR ).
Refuses to write outside the destination directory (path-traversal
defence). Files are written with their original permissions and
mtime preserved.
.TP
.B list
Print archive metadata: per-file path, size, mtime, mode, compressed
size, codec. With
.B --verbose
also prints per-block sizes and HMAC tags (first 8 bytes).
.TP
.B test
Decompress all blocks in memory and verify HMAC tags + archive
integrity trailer. Does not write any files. Use to validate an
archive without restoring it. Exit code is non-zero on any failure.
.TP
.B info
Print archive header metadata without requiring the decryption key.
Reports: format version, codec, encryption type (none / PBKDF2 /
Argon2id / ML-KEM-768+X25519), KDF iteration count, file count,
creation timestamp, archive UUID, AIT presence. Safe to run on an
untrusted archive.
.TP
.B bench
Compare compression levels 1\(en9 on the supplied files; reports
ratio and encode/decode throughput per level. Useful when picking
the right
.B -l
for a given workload.
.TP
.B disk backup
Read a block device and write a sparse-aware archive. Detects
all-zero regions and records them as runs rather than compressing
them.
.TP
.B disk restore
Inverse of
.BR "disk backup" .
Writes the archive's contents back to a block device. Verifies
target device size before writing; refuses if the target is smaller
than the archived size. With
.B --sync
issues
.BR fsync (2)
after each block.
.TP
.B keygen
Generate a key file for keyfile-mode encryption. With
.B --sdk
generates an ML-KEM-768 + X25519 hybrid keypair suitable for
.B --pq-sdk
mode. With
.B --box
generates a libpqvaptvupt sealed-box keypair (writes \fIfile\fR and
\fIfile\fR.pub) for
.B --pq-box
mode. With
.B --pub
extracts the public key from an existing private key.
.SH GLOBAL OPTIONS
.TP
.BR -v ", " --verbose
Print per-file and per-block details during compress/extract/list/test.
.TP
.BR -q ", " --quiet
Suppress non-error output.
.TP
.BR -j " " \fIN\fR ", " --jobs " " \fIN\fR
Worker thread count for parallel compression. Default: number of
online CPUs.
.SH COMPRESS OPTIONS
.TP
.BR -l " " \fI1..9\fR ", " --level " " \fI1..9\fR
Compression level. 1\(en2 = ultra-fast (~80 MB/s encode on typical
hardware, lower ratio). 3\(en7 = balanced (default 7). 8\(en9 =
extreme (optimal parsing, ~5\(en10\(mu slower encode, best ratio).
.TP
.B --codec \fIid\fR
Force a specific codec by id. Accepted values:
.BR store " (0x0000), "
.BR vaptvupt-lz " (0x0008), "
.BR vaptvupt-lzh " (0x0009), "
.BR vaptvupt-lzhp " (0x000A), "
.BR vaptvupt " (0x0010 — default), "
.BR auto " (0xFFFF — pick at runtime)."
.TP
.BR -p " " \fIpassword\fR
Enable password-based encryption (PBKDF2-SHA256 KDF; Argon2id is available
only in a WITH_SDK=1 build via \fB--kdf argon2id\fR).
Reading the password from a flag exposes it in
.BR ps (1)
output; prefer
.B --pass-file
or interactive prompt.
.TP
.B --pass-file \fIpath\fR
Read password from the first line of the file. The file's permission
bits should be 0600.
.TP
.B --pass-fd \fIN\fR
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)."
.TP
.B --keyfile \fIpath\fR
Use a 32-byte raw key file (generated with
.BR "vaptvupt keygen" ).
.TP
.B --pq-box \fIpub\fR
Enable post-quantum sealed-box encryption via the vendored libpqvaptvupt
(v4.0.0+, envelope type 0x05). ML-KEM-768 + X25519 shared secrets are
combined through HKDF-SHA256 with a domain-separating info string
("pqvv-seal-v1"); the box carries AES-256-CTR + HMAC-SHA256
Encrypt-then-MAC. The
.I pub
argument is the recipient's public-key file from
.B keygen --box
(magic-tagged; public and secret key files are not interchangeable).
On extraction, pass the secret key:
.B --pq-box
\fIpriv\fR.
.TP
.B --pq-sdk \fIpub\fR
Enable post-quantum hybrid encryption. Uses ML-KEM-768 + X25519 with
HKDF combiner, HPKE binding, and key commitment. The
.I pub
argument is the recipient's public-key file generated by
.BR "vaptvupt keygen --sdk" .
.TP
.B -c \fItext\fR ", " --comment " " \fItext\fR
Embed an encrypted UTF-8 comment in the archive (up to 4096 bytes).
The comment is bound to the archive's frame-preface AAD; tampering
is detected at extract time.
.TP
.B --comment-file \fIpath\fR
Read the comment from a file rather than the command line.
.TP
.B -b \fIsize\fR ", " --block-size " " \fIsize\fR
Compression block size. Default 4 MiB. Smaller blocks improve
random-access decode but lose some ratio.
.SH EXTRACT, LIST, TEST OPTIONS
.TP
.BR -o " " \fIdir\fR ", " --output " " \fIdir\fR
Extract into
.IR dir
(created if it doesn't exist). Default: current directory.
.TP
.B --no-mtime
Do not restore archived modification times; use current time instead.
.TP
.B --strip-components \fIN\fR
Strip
.I N
leading path components from each entry, like
.BR tar 's
flag of the same name.
.SH POST-QUANTUM ENCRYPTION
.B vaptvupt
implements a hybrid KEM as specified in FIPS 203 (ML-KEM) combined
with X25519 (RFC 7748). The session key is derived as:
.RS
.nf
ss_pq = ML-KEM-768.decaps(sk_pq, ct_pq)
ss_ec = X25519(sk_ec, pk_ec_peer)
session = HKDF-SHA256(ss_pq || ss_ec,
info = "vaptvupt-pq-sdk-v1",
salt = archive_uuid)
.fi
.RE
The hybrid combiner means the session key is at least as strong as
the strongest of {ML-KEM-768, X25519}: an attacker must break both
to recover the key.
.PP
.B Key commitment:
the ciphertext is bound to the exact public key it was encrypted to
via an HPKE-style derivation. An attacker cannot present a different
public key that decrypts to the same plaintext (this defeats the
"partitioning" attack class).
.PP
.B Implementation notes:
the ML-KEM-768 implementation is vendored from a clean reference and
verified against the FIPS 203 KAT vectors. The X25519 implementation
uses 4\(mu64-bit field arithmetic with Jasmin-verified constant-time
field operations on x86_64. On other architectures the same routines
run in pure C, also constant-time by construction.
.SH SECURITY
.SS Threat model
What
.B vaptvupt
.B protects against:
.RS
.IP \(bu 2
Confidentiality of archived data at rest (AES-256-CTR with HMAC-SHA256 EtM, or AEAD via libzuptsdk on the
.B --pq-sdk
path).
.IP \(bu 2
End-to-end byte-level tamper detection on encrypted archives. The F-09 byte-sweep regression (1827 positions on a representative archive, 2000 trials, every run) shows zero silent-accept positions.
.IP \(bu 2
Wrong-password and tampered-archive indistinguishability at the user-visible message layer (F-11). The default error wording is identical for both cases; only
.B --verbose
prints the distinguishing detail. This closes the "verbal probe-oracle" attack class where the error string leaked which check failed first.
.IP \(bu 2
Post-quantum forward secrecy on archives encrypted with
.B --pq-sdk
(assuming ML-KEM-768 holds against future quantum attack).
.IP \(bu 2
Archive-header and footer authentication via a 32-byte HMAC-SHA256 trailer (F-08). Tampering with the file count, comment offset, or timestamp is detected at archive open time.
.IP \(bu 2
Path-traversal at extract time. Entries with absolute paths or
.B ..
components are refused or stripped.
.RE
What it does
.B NOT
protect against:
.RS
.IP \(bu 2
Compromise of the endpoint that creates or restores the archive. If the host is compromised, the password, key file, or plaintext is accessible.
.IP \(bu 2
Compromise of the key file or password. Key custody is the user's responsibility.
.IP \(bu 2
A weak password. Argon2id with default parameters needs ~256 MiB and ~1 s to derive a key on commodity hardware; a 4-character password is still trivially crackable.
.IP \(bu 2
Metadata leakage. File names, sizes, and modification times are encrypted, but the archive's total size and the count of compressed blocks are visible to an observer.
.IP \(bu 2
Side channels on the compression layer (CRIME/BREACH-style). If the same archive contains both attacker-controlled and secret data and the attacker can observe the compressed size, length-based oracles may be possible.
.IP \(bu 2
Denial-of-service via malformed input on the decoder. The decoder rejects malformed input cleanly (no crashes in the fuzz harness), but a very large compressed input can still consume CPU and memory proportional to its size.
.RE
.SS Cryptographic primitives
.TS
tab(|);
l l.
SHA-256 | FIPS 180-4
SHA-3 / SHAKE | FIPS 202
ML-KEM-768 | FIPS 203
AES-256-CTR | NIST SP 800-38A
HMAC-SHA256 | RFC 2104 / FIPS 198-1
X25519 | RFC 7748
HKDF-SHA256 | RFC 5869
PBKDF2-SHA256 | RFC 8018
Argon2id | RFC 9106
XXH64 | non-cryptographic; used only inside the AEAD envelope
.TE
.SS Constant-time guarantees
All secret-dependent comparisons and table lookups in the cryptographic
core are constant-time. On x86_64 the hot paths (HMAC equality compare,
ML-KEM Fujisaki-Okamoto implicit rejection) are implemented in Jasmin
and assembled with
.BR jasminc (1).
On other architectures the same routines run in portable C; the
constant-time property is preserved by source-level construction.
.SH FILES
.TP
.I ~/.config/vaptvupt/
Per-user configuration directory (reserved; not used in v3.0.0).
.TP
.I /etc/vaptvupt/
System-wide configuration directory (reserved; not used in v3.0.0).
.TP
.I /usr/share/bash-completion/completions/vaptvupt
Bash completion (and the symlinked legacy
.IR /usr/share/bash-completion/completions/zupt ).
.TP
.I /usr/share/zsh/site-functions/_vaptvupt
zsh completion.
.TP
.I /usr/share/fish/vendor_completions.d/vaptvupt.fish
fish completion.
.SH ENVIRONMENT
.TP
.B VAPTVUPT_BIN
Override the binary path used by the GUI front-end. Legacy
.B ZUPT_BIN
is also honoured.
.TP
.B VAPTVUPT_DEBUG
If set to any non-empty value, the GUI front-end prints its binary-discovery log to stderr.
.SH EXIT STATUS
.TP
.B 0
Success.
.TP
.B 1
General error (bad arguments, file not found, etc.).
.TP
.B 2
Authentication failed. Wrong password, wrong key file, or the archive has been tampered with. Use
.B --verbose
to see the distinguishing detail (subject to F-11's threat model: detailed messages may leak which failure cause fired first).
.TP
.B 3
Archive-format error (wrong magic bytes, unsupported format version, corrupted header).
.TP
.B 4
I/O error (disk full, permission denied, network failure).
.TP
.B 5
Compressed-data integrity error (per-block HMAC mismatch detected mid-stream).
.SH PERFORMANCE
Numbers below are from the v3.0.0 release benchmark (May 2026), run
on an Intel Xeon @ 2.8 GHz with the codec compiled with the
distribution's default optimisation level.
.TS
tab(|);
l l l l l.
\fBFixture\fR | \fBTool\fR | \fBRatio\fR | \fBEnc MB/s\fR | \fBDec MB/s\fR
text 8 MB | vv-9 | 34.6% | 5.4 | 219
text 8 MB | gzip-9 | 30.9% | 6.2 | 137
text 8 MB | zstd-3 | 31.6% | 137 | 427
text 8 MB | zstd-19 | 25.5% | 1.6 | 384
source 670 KB | vv-9 | 25.1% | 11.4 | 128
source 670 KB | gzip-9 | 23.2% | 10.0 | 107
source 670 KB | zstd-3 | 24.1% | 91 | 160
binary 2.4 MB | vv-9 | 44.7% | 7.7 | 153
binary 2.4 MB | gzip-9 | 52.0% | 12.6 | 109
binary 2.4 MB | zstd-3 | 77.3% | 164 | 382
binary 2.4 MB | zstd-19 | 48.0% | 5.8 | 229
random 5 MB | vv-9 | 100.0% | 11.1 | 477
random 5 MB | zstd-3 | 100.0% | 397 | 681
.TE
.PP
Honest reading of these numbers:
.RS
.IP \(bu 2
On binary-structured data (game saves, mmap'd structures, struct arrays),
.B vaptvupt
beats zstd-3 by a wide margin on ratio (44.7% vs 77.3%) at the cost of being ~20\(mu slower to encode. For write-once / restore-often workloads this is the right trade.
.IP \(bu 2
On text and source, zstd-19 beats
.B vaptvupt
on ratio. The fundamental codec difference is that zstd's reference encoder has had years of compiler-engineering attention that
.B vaptvupt
has not.
.IP \(bu 2
Encode throughput is
.BR vaptvupt 's
weak axis. If encode latency matters more than ratio, use
.B -l 1
or
.BR -l 2 .
.IP \(bu 2
On random / already-compressed data, all codecs hit the incompressibility wall; comparing encode/decode throughput in that regime is mostly measuring memcpy speed plus framing overhead.
.RE
.SH EXAMPLES
.PP
Compress with default settings (PBKDF2-SHA256 password, VaptVupt level 7, multi-threaded):
.RS
.nf
$ vaptvupt compress -p secret backup.zupt ~/Documents
.fi
.RE
Compress with post-quantum hybrid encryption to a published public key:
.RS
.nf
$ vaptvupt keygen --sdk -o ~/.config/vaptvupt-mykey
$ vaptvupt keygen -o ~/.config/vaptvupt-mykey
$ vaptvupt keygen --pub -o mykey.pub -k ~/.config/vaptvupt-mykey
$ vaptvupt compress --pq mykey.pub backup.zupt ~/Documents
.fi
.RE
Backup a block device, sparse-aware:
.RS
.nf
$ sudo vaptvupt disk backup -p secret system.zupt /dev/nvme0n1p2
.fi
.RE
Verify an archive without restoring:
.RS
.nf
$ vaptvupt test -p secret backup.zupt
.fi
.RE
Print archive metadata without supplying a password:
.RS
.nf
$ vaptvupt info backup.zupt
.fi
.RE
Compare compression levels:
.RS
.nf
$ vaptvupt bench ~/Downloads/big-dataset.bin
.fi
.RE
Run the GUI from a desktop session where /usr/bin isn't on PATH (the bug fixed in v3.0.0):
.RS
.nf
$ VAPTVUPT_DEBUG=1 vaptvupt-gui 2> /tmp/discovery.log
.fi
.RE
.SH STANDARDS
ISO C11; POSIX.1-2017 for I/O and threading. The cryptographic
primitives implement the specifications listed in
.BR SECURITY
above. The on-disk archive format is documented in
.B FORMAT.md
in the source distribution.
.SH AUTHORS
Cristian Cezar Moisés <zupt@riseup.net> — primary author and maintainer.
.SH BUGS
Report bugs at https://git.securityops.co/cristiancmoises/vaptvupt/issues
or by email to <zupt@riseup.net>.
.SH LICENSE
AGPL-3.0-or-later for the application; GPL-3.0-or-later for the
embedded VaptVupt codec. Dual-licensed: a commercial licence is
available from <sac@securityops.co>.
.SH SEE ALSO
.BR vaptvupt-gui (1),
.BR zstd (1),
.BR gzip (1),
.BR xz (1),
.BR tar (1),
.BR cryptsetup (8),
.BR jasminc (1).
.PP
Project home: https://git.securityops.co/cristiancmoises/vaptvupt
.br
Threat model: see
.B THREAT_MODEL.md
in the source distribution.
.br
Archive format spec: see
.B FORMAT.md
in the source distribution.