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.
This commit is contained in:
parent
136a96ed20
commit
4874010d0e
39 changed files with 1097 additions and 2770 deletions
188
THREAT_MODEL.md
188
THREAT_MODEL.md
|
|
@ -10,21 +10,37 @@ trusting VaptVupt with anything you can't afford to lose.
|
|||
|
||||
## TL;DR
|
||||
|
||||
VaptVupt is designed for **at-rest backup encryption** by someone who
|
||||
VaptVupt is designed for at-rest backup encryption by someone who
|
||||
controls the machine doing the encryption and the machine doing the
|
||||
extraction. It is **not** a network protocol, a multi-party scheme, or
|
||||
extraction. It is not a network protocol, a multi-party scheme, or
|
||||
a substitute for full-disk encryption.
|
||||
|
||||
| Use case | VaptVupt is appropriate? |
|
||||
|---|---|
|
||||
| Backing up files to an untrusted cloud (S3, Backblaze, Google Drive) | **Yes** |
|
||||
| Backing up a disk image to external media you might lose | **Yes** |
|
||||
| Long-term archival of personal/business data | **Yes** |
|
||||
| Sharing an encrypted archive with someone you trust to handle the key | **Yes, with care** (see "Key distribution" below) |
|
||||
| Real-time encrypted communication | **No** (use Signal, age, or TLS) |
|
||||
| Multi-party access (n-of-m) | **No** (no threshold scheme) |
|
||||
| Hiding the existence of an archive (steganography) | **No** (archive header has fixed magic bytes) |
|
||||
| Protecting against a hostile machine you're encrypting on | **No** (a compromised host can read plaintext before encryption) |
|
||||
| Backing up files to an untrusted cloud (S3, Backblaze, Google Drive) | Yes |
|
||||
| Backing up a disk image to external media you might lose | Yes |
|
||||
| Long-term archival of personal/business data | Yes |
|
||||
| Sharing an encrypted archive with someone you trust to handle the key | Yes, with care (see "Key distribution" below) |
|
||||
| Real-time encrypted communication | No (use Signal, age, or TLS) |
|
||||
| Multi-party access (n-of-m) | No (no threshold scheme) |
|
||||
| Hiding the existence of an archive (steganography) | No (archive header has fixed magic bytes) |
|
||||
| Protecting against a hostile machine you're encrypting on | No (a compromised host can read plaintext before encryption) |
|
||||
|
||||
---
|
||||
|
||||
## Modes referenced in this document
|
||||
|
||||
- `-p` / password mode: symmetric encryption with a key derived from a
|
||||
password. The default build derives the key with PBKDF2-SHA256
|
||||
(600k iterations). Argon2id is available only in an upstream
|
||||
`make WITH_SDK=1` build against the separately distributed
|
||||
libraries.
|
||||
- `--pq`: native post-quantum mode (ML-KEM-768 + X25519), the PQ mode
|
||||
in the default build. The ML-KEM-768 implementation is in-tree.
|
||||
- `--pq-sdk` / `--pq-box`: optional post-quantum modes backed by the
|
||||
separately distributed `libzuptsdk` / `libpqvaptvupt` libraries.
|
||||
Available only in a `make WITH_SDK=1` build. Key files for these
|
||||
modes are produced by `vaptvupt keygen --sdk`, also SDK-only.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -34,31 +50,27 @@ a substitute for full-disk encryption.
|
|||
|
||||
An attacker with read access to the archive bytes cannot recover
|
||||
plaintext file contents, file names, file sizes, file modes, or
|
||||
embedded comments **without the key/password**, assuming:
|
||||
embedded comments without the key/password, assuming:
|
||||
|
||||
- The chosen mode is one of the encrypted modes (`-p`, `--pq`, `--pq-sdk`, or `--pq-box`)
|
||||
- The chosen mode is one of the encrypted modes (`-p`, `--pq`, or the
|
||||
optional `--pq-sdk` / `--pq-box`)
|
||||
- The password is strong enough to resist offline brute-force
|
||||
(Argon2id default with m=64 MB, t=3, p=4 makes this very expensive
|
||||
but not infinite — see "Password strength" below)
|
||||
- The key file (for `--pq-sdk` / `--pq-box`) was not compromised at generation time
|
||||
(see "Password strength" below)
|
||||
- The key file (for `--pq-sdk` / `--pq-box`) was not compromised at
|
||||
generation time
|
||||
|
||||
### 2. Integrity of every byte of an encrypted archive
|
||||
|
||||
If any single bit of the on-disk archive bytes is flipped, the
|
||||
extraction **must fail** with an authentication error. This has been
|
||||
verified by the v1.6 exhaustive byte sweep:
|
||||
extraction fails with an authentication error. Coverage layers:
|
||||
|
||||
- 0 silent-accept positions out of 1827 (encrypted, no comment)
|
||||
- 0 silent-accept positions out of 1878 (encrypted, with comment)
|
||||
|
||||
Coverage layers:
|
||||
|
||||
- **Per-block HMAC-SHA256** with frame-preface AAD (F-09): every data
|
||||
- Per-block HMAC-SHA256 with frame-preface AAD (F-09): every data
|
||||
block carries an HMAC over its ciphertext and over the canonical
|
||||
29-byte preface (block_type, codec_id, block_flags, sizes, plaintext-XXH64)
|
||||
- **Archive Integrity Trailer (F-08)**: HMAC-SHA256 over the
|
||||
64-byte header and 24 bytes of footer, appended after the footer
|
||||
- **Strict structural validation of the encryption-header block (F-09)**:
|
||||
29-byte preface (block_type, codec_id, block_flags, sizes,
|
||||
plaintext-XXH64)
|
||||
- Archive Integrity Trailer (F-08): HMAC-SHA256 over the 64-byte
|
||||
header and 24 bytes of footer, appended after the footer
|
||||
- Strict structural validation of the encryption-header block (F-09):
|
||||
codec must be `STORE`, flags must be 0, csz must equal usz, the
|
||||
plaintext XXH64 must match
|
||||
|
||||
|
|
@ -66,10 +78,10 @@ Coverage layers:
|
|||
|
||||
Plaintext archives (no `-p`, no `--pq*`) are protected by XXH64
|
||||
plaintext checksums per block plus structural validation. This is
|
||||
**not cryptographic integrity** — a determined attacker with
|
||||
write access can produce a tampered plaintext archive that passes
|
||||
the checksum (XXH64 is not collision-resistant). It does catch
|
||||
accidental corruption and naive tampering.
|
||||
not cryptographic integrity — a determined attacker with write access
|
||||
can produce a tampered plaintext archive that passes the checksum
|
||||
(XXH64 is not collision-resistant). It does catch accidental
|
||||
corruption and naive tampering.
|
||||
|
||||
Use an encrypted mode if you need cryptographic integrity.
|
||||
|
||||
|
|
@ -87,33 +99,34 @@ also constant (HMAC is always run, branchless return).
|
|||
The detailed cause is available via `--verbose` for debugging on
|
||||
machines under the user's own control.
|
||||
|
||||
### 5. Post-quantum forward secrecy (in `--pq-sdk` mode)
|
||||
### 5. Post-quantum forward secrecy (`--pq` and optional `--pq-sdk`)
|
||||
|
||||
`--pq-sdk` 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) 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)
|
||||
- X25519 hybridization protects against an unforeseen ML-KEM break
|
||||
- The recipient's private key is not later compromised
|
||||
|
||||
The optional `--pq-sdk` mode provides the same hybrid guarantee via
|
||||
the separately distributed SDK libraries.
|
||||
|
||||
### 6. Side-channel resistance for cryptographic primitives
|
||||
|
||||
The hot crypto paths (AES-256-CTR, HMAC-SHA256 comparison, X25519
|
||||
field operations, ML-KEM polynomial arithmetic) are implemented in
|
||||
Jasmin and proved constant-time at the assembly level on x86_64.
|
||||
Non-Jasmin platforms (aarch64, fallback x86_64) use careful C
|
||||
implementations that avoid secret-dependent branches and memory
|
||||
accesses where feasible — but **without formal proof**.
|
||||
Non-Jasmin platforms (aarch64, fallback x86_64) use C implementations
|
||||
that avoid secret-dependent branches and memory accesses where
|
||||
feasible — but without formal proof.
|
||||
|
||||
---
|
||||
|
||||
## What VaptVupt does NOT protect against
|
||||
|
||||
This list is **exhaustive of the major omissions** — if you have a
|
||||
concern that doesn't appear here, please file an issue.
|
||||
|
||||
### 1. Compromised endpoints
|
||||
|
||||
VaptVupt cannot protect against:
|
||||
|
|
@ -134,7 +147,7 @@ If you don't trust the machine, VaptVupt cannot help.
|
|||
If the password or `~/.zupt-key` is leaked:
|
||||
|
||||
- All archives encrypted with that key are decryptable
|
||||
- VaptVupt has **no forward secrecy across archives** — each archive
|
||||
- VaptVupt has no forward secrecy across archives — each archive
|
||||
is encrypted under a single static key derived from the password
|
||||
or stored in the key file
|
||||
- There is no key-rotation feature; rotate by re-encrypting
|
||||
|
|
@ -147,37 +160,33 @@ another layer (e.g. on an encrypted USB), and rotate periodically.
|
|||
|
||||
### 3. Password strength
|
||||
|
||||
Argon2id with m=64 MB, t=3, p=4 makes a single guess cost roughly
|
||||
~200 ms on commodity hardware. That's **not enough** to protect a
|
||||
short, common password against a determined attacker with GPU
|
||||
clusters or cloud compute.
|
||||
Password mode derives the key with PBKDF2-SHA256 (600k iterations)
|
||||
in the default build, or Argon2id in a `make WITH_SDK=1` build. A
|
||||
key derivation function slows offline guessing but does not make a
|
||||
short, common password safe: a determined attacker with GPU clusters
|
||||
or cloud compute can still exhaust a weak password.
|
||||
|
||||
| Password type | Approximate brute-force resistance with Argon2id |
|
||||
|---|---|
|
||||
| 6-char common word | Hours to days |
|
||||
| 10-char mixed alphanumeric | Years on a single GPU; days on a cluster |
|
||||
| 6-word diceware passphrase | Centuries to millennia even with cloud-scale resources |
|
||||
| Random 16-char with full alphabet | Infeasible without quantum breakthrough |
|
||||
|
||||
For critical data, use `--pq-sdk` mode with a random key file
|
||||
generated by `vaptvupt keygen --sdk` — the key is 64 bytes of CSPRNG
|
||||
output, not derived from human-typed text.
|
||||
Use a long, high-entropy password — a multi-word diceware passphrase
|
||||
or a random 16+ character string with a full alphabet. For critical
|
||||
data, use a key-file mode (native `--pq`, or the optional `--pq-sdk`
|
||||
with a random key file from `vaptvupt keygen --sdk`) so the key is
|
||||
CSPRNG output, not derived from human-typed text.
|
||||
|
||||
### 4. Metadata leakage from archive structure
|
||||
|
||||
Even with encryption, an attacker who can see the archive bytes
|
||||
can infer:
|
||||
|
||||
- **Approximate file count** (from `total_blocks` in the footer)
|
||||
- **Total archive size** (file size on disk)
|
||||
- **Whether the archive is encrypted at all** (`ZUPT_FLAG_ENCRYPTED`
|
||||
- Approximate file count (from `total_blocks` in the footer)
|
||||
- Total archive size (file size on disk)
|
||||
- Whether the archive is encrypted at all (`ZUPT_FLAG_ENCRYPTED`
|
||||
in the global flags is visible)
|
||||
- **Whether the archive is solid or per-file mode** (visible flag)
|
||||
- **Whether post-quantum mode is in use** (visible flag)
|
||||
- **Approximate file size distribution** (block sizes are visible
|
||||
- Whether the archive is solid or per-file mode (visible flag)
|
||||
- Whether post-quantum mode is in use (visible flag)
|
||||
- Approximate file size distribution (block sizes are visible
|
||||
even when block payloads are encrypted)
|
||||
- **Archive creation time** (a 64-bit timestamp in the header)
|
||||
- **A random 16-byte UUID per archive** (no information leak, but
|
||||
- Archive creation time (a 64-bit timestamp in the header)
|
||||
- A random 16-byte UUID per archive (no information leak, but
|
||||
globally identifies the archive across copies)
|
||||
|
||||
If metadata privacy matters, layer VaptVupt under another tool that
|
||||
|
|
@ -197,8 +206,8 @@ VaptVupt is not a network protocol. There is no:
|
|||
|
||||
### 6. Multi-party schemes
|
||||
|
||||
There is **no threshold cryptography, no n-of-m sharing, no
|
||||
multi-party computation, no proxy re-encryption**. Each archive
|
||||
There is no threshold cryptography, no n-of-m sharing, no
|
||||
multi-party computation, no proxy re-encryption. Each archive
|
||||
has exactly one decryption credential (one password OR one
|
||||
recipient key). To give two people access to the same archive,
|
||||
they must share the password or the key file.
|
||||
|
|
@ -207,7 +216,7 @@ they must share the password or the key file.
|
|||
|
||||
VaptVupt archives have a fixed 6-byte magic `\x90\x5a\x55\x50\x54\x01`
|
||||
at offset 0. Anyone scanning the bytes can see it's a VaptVupt
|
||||
archive. VaptVupt has **no hidden-volume or duress-password feature**.
|
||||
archive. VaptVupt has no hidden-volume or duress-password feature.
|
||||
|
||||
### 8. Side channels we don't claim to address
|
||||
|
||||
|
|
@ -219,29 +228,27 @@ archive. VaptVupt has **no hidden-volume or duress-password feature**.
|
|||
|
||||
### 9. Trusted setup of post-quantum primitives
|
||||
|
||||
The ML-KEM-768 implementation lives in `libzuptsdk` and was not
|
||||
independently audited at the time of writing. We use NIST KAT
|
||||
vectors for correctness verification but have not formally proven
|
||||
constant-time properties for every PQ code path.
|
||||
The in-tree ML-KEM-768 implementation was not independently audited
|
||||
at the time of writing. We use NIST KAT vectors for correctness
|
||||
verification but have not formally proven constant-time properties
|
||||
for every PQ code path.
|
||||
|
||||
For maximum assurance, treat `--pq-sdk` as the post-quantum
|
||||
**hedge** — it does not replace the X25519 layer; both must be
|
||||
broken for an attacker to recover plaintext.
|
||||
For maximum assurance, treat the post-quantum layer as a hedge — it
|
||||
does not replace the X25519 layer; both must be broken for an
|
||||
attacker to recover plaintext.
|
||||
|
||||
### 10. Format extension attacks
|
||||
|
||||
The format is versioned (v1.6). Older readers may accept newer
|
||||
archives in unexpected ways. We try to maintain forward
|
||||
compatibility (v2.4.5 readers correctly handle v1.6 archives
|
||||
including encrypted comments and the Argon2id KDF path), but a
|
||||
careful attacker who can produce malformed-but-just-valid
|
||||
archives may find parser-state issues that don't rise to the
|
||||
level of a CVE. The fuzzing harness (`make fuzz-format`) is the
|
||||
primary mitigation; report bugs.
|
||||
compatibility, but a careful attacker who can produce
|
||||
malformed-but-just-valid archives may find parser-state issues that
|
||||
don't rise to the level of a CVE. The fuzzing harness
|
||||
(`make fuzz-format`) is the primary mitigation; report bugs.
|
||||
|
||||
### 11. Compression-side-channel attacks (CRIME / BREACH style)
|
||||
|
||||
VaptVupt compresses **before** encryption. If an attacker can:
|
||||
VaptVupt compresses before encryption. If an attacker can:
|
||||
|
||||
- Influence part of the plaintext (e.g. inject a known prefix)
|
||||
- Observe the resulting archive size precisely
|
||||
|
|
@ -251,9 +258,9 @@ the rest of the plaintext — this is the classic CRIME/BREACH attack
|
|||
against TLS compression.
|
||||
|
||||
VaptVupt is designed for offline backup, where attacker-controlled
|
||||
plaintext injection is rare. **If your threat model includes
|
||||
plaintext injection is rare. If your threat model includes
|
||||
attacker-chosen plaintext mixed with secret plaintext in the same
|
||||
archive**, use `--no-compress` (codec 0 = STORE) to disable the
|
||||
archive, use `--no-compress` (codec 0 = STORE) to disable the
|
||||
LZ codec and eliminate this side channel.
|
||||
|
||||
---
|
||||
|
|
@ -266,9 +273,9 @@ VaptVupt's security rests on the following standard assumptions:
|
|||
|---|---|
|
||||
| AES-256-CTR is a secure stream cipher | All encrypted archives become readable |
|
||||
| HMAC-SHA256 is a secure PRF / MAC | Tamper detection fails; integrity can be forged |
|
||||
| Argon2id is a secure password KDF | Password-mode archives become brute-forceable faster |
|
||||
| ML-KEM-768 retains NIST Category 3 security | `--pq-sdk` mode reduces to the X25519 layer |
|
||||
| X25519 retains 128-bit security (no quantum) | `--pq-sdk` mode reduces to the ML-KEM layer; legacy `--pq` mode broken |
|
||||
| PBKDF2-SHA256 (or Argon2id, WITH_SDK) is a secure password KDF | Password-mode archives become brute-forceable faster |
|
||||
| ML-KEM-768 retains NIST Category 3 security | `--pq` / `--pq-sdk` reduce to the X25519 layer |
|
||||
| X25519 retains 128-bit security (no quantum) | PQ modes reduce to the ML-KEM layer; classical password mode unaffected |
|
||||
| HKDF-SHA256 is a secure key-derivation construction | Combined PQ + classical keys may be predictable |
|
||||
| SHA3 / SHAKE retain pre-image and collision resistance | Auxiliary protocol bindings may be forged |
|
||||
|
||||
|
|
@ -298,8 +305,7 @@ normally.
|
|||
|
||||
## Document version
|
||||
|
||||
- **v1.0** (sprint 2.4.6): initial threat model. Covers archive
|
||||
format v1.6.
|
||||
- Document is part of the source tree (`THREAT_MODEL.md`) and
|
||||
versioned with the project; this section will be updated as
|
||||
the format evolves.
|
||||
This threat model covers archive format v1.6 as shipped in VaptVupt
|
||||
4.1.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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue