GUI rework for source-only builds + CLI security fixes + packaging + cross-platform scaffolding
Fixes the "GUI functions don't work on GNU Guix" report and a batch of
adversarial-audit findings across the CLI, decoder, crypto and packaging.
GUI (gui/src/zupt_gui.py)
- Root cause of the broken GUI: every encryption path defaulted to the
libzuptsdk "SDK v2" modes, which are absent from the source-only build and
exit 1. Reworked Keys/Compress/Extract around the native modes with a
build-aware PQ-mode selector: Hybrid (--pq, default), Full PQ (--pq-only),
and SDK v2 only when the binary reports WITH_SDK support. Capability is
detected from `version` ("Build:"/"KDF:" lines) with a `help` fallback.
- Extract/Verify gain a PQ private-key input with auto-detect (reads the
archive via `info` to pick --pq vs --pq-only). Verify could not verify any
PQ archive before (password field only).
- run_async now holds a LIST of in-flight (thread, worker) refs; DiskTab's two
buttons previously shared one slot, so a second op GC'd the first QThread
mid-run. About tab corrected (codec 2.60.4, PBKDF2 default, --pq-only, URL).
CLI (src/zupt_main.c)
- compress -p <archive> <files> swallowed the archive name as the password and
truncated the first input file (silent data loss, exit 0). Added a
data-loss guard: refuse to overwrite an existing non-.zupt file as the output
archive unless -y/--force; plus a self-overwrite guard.
- compress <archive> <src> -p <pw> wrote an UNENCRYPTED archive (exit 0)
because options after the first positional were treated as files. Now errors
on a misplaced option (with a `--` escape for real dashed filenames).
- Removed duplicated dead --pq-box/--pq-sdk branches. version/banner/usage now
state the build's real KDF (PBKDF2 on source-only) and repo URL; examples
lead with native --pq / --pq-only.
Security (audit findings)
- vv_decoder.c: the two AVX2 fast-path token decoders read a 2-/3-byte match
offset after only checking 1 byte remained -> heap over-read on a crafted
archive. Added the `ip + off_bytes > ip_end` guard the tail path already had.
- zupt_crypto.c: wipe ML-KEM/X25519 secret-key buffers when hybrid decrypt
init fails on key read (matches the pq-only path).
- zupt_format.c: bound attacker-controlled encryption_header_off before the
(off_t)+7 arithmetic in the info enc_type reader (avoid signed-overflow UB).
Packaging (would fail source-only)
- debian/rules (staged into debian/zupt, installed vendored .so), aur/PKGBUILD,
nix/flake.nix, homebrew (vendored .so + nonexistent AUDIT.md, stale /zupt
URLs) now build source-only. opensuse spec %files ships the shell completions
make install writes (rpmbuild no longer fails on unpackaged files);
_service + debian/control point at the vaptvupt repo; KDF claim corrected.
Cross-platform GUI packaging (new)
- packaging/portable/: OS-agnostic GUI package (zupt_gui.py + .bat/.command/.sh
launchers + README) that runs on Windows/macOS/Linux/BSD with Python+PySide6.
- .github/workflows/cross-platform.yml: builds real native binaries on
windows-latest + macos-latest runners (CLI, PyInstaller GUI, Inno Setup
installer, .dmg) and the portable zip, attaching them to the release on tag.
- packaging/windows/vaptvupt-gui.iss: Inno Setup installer script.
Validation: make check 16/16 (all distro-safe checks), GUI imports + command
contracts verified against the fixed CLI. test_help_consistency updated to
assert the truthful (build-aware) default KDF.
This commit is contained in:
parent
e8f7b3adb2
commit
43d6306a06
19 changed files with 750 additions and 169 deletions
180
.github/workflows/cross-platform.yml
vendored
Normal file
180
.github/workflows/cross-platform.yml
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||||
|
#
|
||||||
|
# Cross-platform GUI + CLI binaries, built on REAL Windows and macOS runners.
|
||||||
|
#
|
||||||
|
# Why a dedicated workflow: the GUI is a PySide6/PyQt6 app and the CLI is
|
||||||
|
# portable C11, but self-contained native installers (Windows .exe/.msi,
|
||||||
|
# macOS .app/.dmg) can only be produced on the target OS. This workflow builds
|
||||||
|
# them on GitHub's windows-latest and macos-latest runners and attaches them to
|
||||||
|
# the GitHub release on a `v*` tag. Run it manually with "Run workflow"
|
||||||
|
# (workflow_dispatch) to smoke-test the build before tagging.
|
||||||
|
#
|
||||||
|
# Artifacts produced:
|
||||||
|
# Windows: vaptvupt.exe (CLI, mingw), vaptvupt-gui.exe (PyInstaller onefile),
|
||||||
|
# VaptVupt-Setup-<ver>.exe (Inno Setup installer)
|
||||||
|
# macOS: vaptvupt (CLI, universal where possible), VaptVupt-<ver>.dmg
|
||||||
|
# All: vaptvupt-gui-<ver>-portable.zip (Python GUI + launchers)
|
||||||
|
|
||||||
|
name: cross-platform
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ─────────────────────────── Windows ───────────────────────────
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
defaults:
|
||||||
|
run: { shell: msys2 {0} }
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up MSYS2 (mingw gcc + make)
|
||||||
|
uses: msys2/setup-msys2@v2
|
||||||
|
with:
|
||||||
|
msystem: UCRT64
|
||||||
|
update: true
|
||||||
|
install: >-
|
||||||
|
mingw-w64-ucrt-x86_64-gcc
|
||||||
|
make
|
||||||
|
coreutils
|
||||||
|
- name: Build CLI (vaptvupt.exe, source-only, C fallback crypto)
|
||||||
|
run: |
|
||||||
|
make CC=gcc WITH_SDK=0 -j2
|
||||||
|
./vaptvupt.exe version || ./vaptvupt version
|
||||||
|
cp "$(ls vaptvupt.exe vaptvupt 2>/dev/null | head -1)" vaptvupt.exe 2>/dev/null || true
|
||||||
|
- name: Set up Python
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# Use the runner's native Python (not MSYS) for PyInstaller so the
|
||||||
|
# produced .exe targets the standard Windows Python ABI.
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install PySide6 pyinstaller
|
||||||
|
- name: Get version
|
||||||
|
id: ver
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$ver = (Select-String -Path include/zupt.h -Pattern '^#define ZUPT_VERSION_STRING "([^"]+)"').Matches.Groups[1].Value
|
||||||
|
"version=$ver" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
|
||||||
|
- name: Bundle GUI with PyInstaller (vaptvupt-gui.exe)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
# onefile GUI that carries the CLI beside it via --add-binary.
|
||||||
|
pyinstaller --noconfirm --onefile --windowed `
|
||||||
|
--name vaptvupt-gui `
|
||||||
|
--icon gui/assets/zupt-icon.png `
|
||||||
|
--add-binary "vaptvupt.exe;." `
|
||||||
|
--add-data "gui/assets/zupt-icon.png;assets" `
|
||||||
|
gui/src/zupt_gui.py
|
||||||
|
- name: Build Inno Setup installer
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
choco install innosetup --no-progress -y
|
||||||
|
& "$env:ChocolateyInstall\bin\ISCC.exe" `
|
||||||
|
"/DAppVersion=${{ steps.ver.outputs.version }}" `
|
||||||
|
packaging/windows/vaptvupt-gui.iss
|
||||||
|
- name: Collect artifacts
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$v = "${{ steps.ver.outputs.version }}"
|
||||||
|
New-Item -ItemType Directory -Force out | Out-Null
|
||||||
|
Copy-Item vaptvupt.exe "out/vaptvupt-$v-windows-x86_64.exe"
|
||||||
|
Copy-Item dist/vaptvupt-gui.exe "out/vaptvupt-gui-$v-windows-x86_64.exe"
|
||||||
|
if (Test-Path "packaging/windows/Output") {
|
||||||
|
Copy-Item packaging/windows/Output/*.exe "out/" -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: out/*
|
||||||
|
- name: Attach to release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: out/*
|
||||||
|
|
||||||
|
# ─────────────────────────── macOS ───────────────────────────
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build CLI (vaptvupt, clang)
|
||||||
|
run: |
|
||||||
|
make CC=clang WITH_SDK=0 -j3
|
||||||
|
./vaptvupt version
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with: { python-version: '3.12' }
|
||||||
|
- name: Install GUI build deps
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install PySide6 pyinstaller
|
||||||
|
brew install create-dmg || true
|
||||||
|
- name: Get version
|
||||||
|
id: ver
|
||||||
|
run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Bundle GUI (.app) with PyInstaller
|
||||||
|
run: |
|
||||||
|
pyinstaller --noconfirm --windowed \
|
||||||
|
--name "VaptVupt" \
|
||||||
|
--add-binary "vaptvupt:." \
|
||||||
|
--add-data "gui/assets/zupt-icon.png:assets" \
|
||||||
|
gui/src/zupt_gui.py
|
||||||
|
- name: Build .dmg
|
||||||
|
run: |
|
||||||
|
V="${{ steps.ver.outputs.version }}"
|
||||||
|
create-dmg --volname "VaptVupt $V" --window-size 500 300 \
|
||||||
|
--app-drop-link 350 120 --icon "VaptVupt.app" 150 120 \
|
||||||
|
"VaptVupt-$V.dmg" "dist/VaptVupt.app" || \
|
||||||
|
{ mkdir -p dmgroot && cp -R dist/VaptVupt.app dmgroot/ && \
|
||||||
|
hdiutil create -volname "VaptVupt $V" -srcfolder dmgroot -ov -format UDZO "VaptVupt-$V.dmg"; }
|
||||||
|
- name: Collect artifacts
|
||||||
|
run: |
|
||||||
|
V="${{ steps.ver.outputs.version }}"
|
||||||
|
mkdir -p out
|
||||||
|
cp vaptvupt "out/vaptvupt-$V-macos"
|
||||||
|
cp "VaptVupt-$V.dmg" out/
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: out/*
|
||||||
|
- name: Attach to release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: out/*
|
||||||
|
|
||||||
|
# ─────────────── Portable GUI (works on every OS) ───────────────
|
||||||
|
portable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Get version
|
||||||
|
id: ver
|
||||||
|
run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Assemble portable package
|
||||||
|
run: |
|
||||||
|
V="${{ steps.ver.outputs.version }}"
|
||||||
|
D="vaptvupt-gui-$V-portable"
|
||||||
|
mkdir -p "$D/assets"
|
||||||
|
cp gui/src/zupt_gui.py "$D/"
|
||||||
|
cp gui/assets/zupt-icon.png "$D/assets/"
|
||||||
|
cp packaging/portable/vaptvupt-gui.bat "$D/"
|
||||||
|
cp packaging/portable/vaptvupt-gui.command "$D/"
|
||||||
|
cp packaging/portable/vaptvupt-gui.sh "$D/"
|
||||||
|
cp packaging/portable/README.txt "$D/"
|
||||||
|
chmod +x "$D/vaptvupt-gui.command" "$D/vaptvupt-gui.sh"
|
||||||
|
zip -r "$D.zip" "$D"
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: portable
|
||||||
|
path: vaptvupt-gui-*-portable.zip
|
||||||
|
- name: Attach to release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: vaptvupt-gui-*-portable.zip
|
||||||
|
|
@ -190,6 +190,81 @@ def _get_version():
|
||||||
|
|
||||||
ZUPT_VER_SHORT, ZUPT_VER_NUMBER, ZUPT_VER_FULL = _get_version()
|
ZUPT_VER_SHORT, ZUPT_VER_NUMBER, ZUPT_VER_FULL = _get_version()
|
||||||
|
|
||||||
|
# ── Detect build capabilities from `version` (and `help` as fallback) ──
|
||||||
|
#
|
||||||
|
# The default build is SOURCE-ONLY: the libzuptsdk-backed modes (Argon2id
|
||||||
|
# KDF, --pq-sdk, --pq-box) are absent and fail with exit 1. Offering them in
|
||||||
|
# the UI is the #1 reason "functions don't work". We detect what THIS binary
|
||||||
|
# actually supports and build the encryption UI around it:
|
||||||
|
# - SDK_AVAILABLE : --pq-sdk / --pq-box / Argon2id compiled in (WITH_SDK=1)
|
||||||
|
# - PQONLY_AVAILABLE: native --pq-only (full post-quantum, v4.2.0+)
|
||||||
|
# - DEFAULT_KDF : the password KDF this build actually uses
|
||||||
|
# The `version` banner carries a machine-readable "Build:" line (v4.2.1+);
|
||||||
|
# for older binaries we fall back to `help` text and default SDK off (safe:
|
||||||
|
# the native --pq / --pq-only / password modes work on every build).
|
||||||
|
def _get_caps():
|
||||||
|
sdk = False
|
||||||
|
pqonly = False
|
||||||
|
default_kdf = "PBKDF2-SHA256"
|
||||||
|
blob = ZUPT_VER_FULL or ""
|
||||||
|
for line in blob.splitlines():
|
||||||
|
low = line.lower()
|
||||||
|
if low.startswith("build:"):
|
||||||
|
sdk = ("full" in low) and ("libzuptsdk" in low)
|
||||||
|
elif low.startswith("kdf:"):
|
||||||
|
default_kdf = "Argon2id" if "argon2id (default)" in low else "PBKDF2-SHA256"
|
||||||
|
if "--pq-only" in line:
|
||||||
|
pqonly = True
|
||||||
|
if not pqonly:
|
||||||
|
try:
|
||||||
|
h = subprocess.run([VAPTVUPT, "help"], capture_output=True, text=True, timeout=5)
|
||||||
|
txt = (h.stdout or "") + (h.stderr or "")
|
||||||
|
if "--pq-only" in txt:
|
||||||
|
pqonly = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return sdk, pqonly, default_kdf
|
||||||
|
|
||||||
|
SDK_AVAILABLE, PQONLY_AVAILABLE, DEFAULT_KDF = _get_caps()
|
||||||
|
|
||||||
|
# Post-quantum recipient modes offered in the UI, keyed to CLI flags.
|
||||||
|
# token -> (label, keygen-flag-list, compress/extract-flag)
|
||||||
|
# keygen-flags are extra flags added to `keygen` (private) and `keygen --pub`.
|
||||||
|
def pq_mode_options(include_auto=False):
|
||||||
|
"""Return [(label, token)] for a PQ-mode dropdown given this build."""
|
||||||
|
opts = []
|
||||||
|
if include_auto:
|
||||||
|
opts.append(("Auto-detect from archive", "auto"))
|
||||||
|
opts.append(("Hybrid — ML-KEM-768 + X25519 (recommended)", "pq"))
|
||||||
|
if PQONLY_AVAILABLE:
|
||||||
|
opts.append(("Full PQ — ML-KEM-768 only", "pqonly"))
|
||||||
|
if SDK_AVAILABLE:
|
||||||
|
opts.append(("SDK v2 — HKDF + commitment + HPKE", "sdk"))
|
||||||
|
return opts
|
||||||
|
|
||||||
|
# token -> (extra keygen flags, encrypt/decrypt flag)
|
||||||
|
_PQ_FLAG = {
|
||||||
|
"pq": ([], "--pq"),
|
||||||
|
"pqonly": (["--pq-only"], "--pq-only"),
|
||||||
|
"sdk": (["--sdk"], "--pq-sdk"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _detect_archive_pq(archive):
|
||||||
|
"""Inspect an archive's `info` and return the matching PQ token, or None."""
|
||||||
|
try:
|
||||||
|
r = subprocess.run([VAPTVUPT, "info", archive], capture_output=True, text=True, timeout=15)
|
||||||
|
txt = (r.stdout or "") + (r.stderr or "")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
low = txt.lower()
|
||||||
|
if "ml-kem-768 only" in low or "no classical" in low:
|
||||||
|
return "pqonly"
|
||||||
|
if "sdk v2" in low or "hpke" in low:
|
||||||
|
return "sdk"
|
||||||
|
if "ml-kem-768" in low or "hybrid" in low or "x25519" in low:
|
||||||
|
return "pq"
|
||||||
|
return None
|
||||||
|
|
||||||
# ── Find icon file ──
|
# ── Find icon file ──
|
||||||
def _find_icon():
|
def _find_icon():
|
||||||
here = Path(getattr(sys, '_MEIPASS', Path(__file__).parent))
|
here = Path(getattr(sys, '_MEIPASS', Path(__file__).parent))
|
||||||
|
|
@ -328,14 +403,22 @@ def run_async(parent, cmd, btn, log, progress=None):
|
||||||
if progress: progress.show()
|
if progress: progress.show()
|
||||||
t = QThread(); w = Worker(cmd); w.moveToThread(t)
|
t = QThread(); w = Worker(cmd); w.moveToThread(t)
|
||||||
w.log.connect(log.append)
|
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
|
||||||
|
# single _thread/_worker slot, so starting a second op dropped the only
|
||||||
|
# Python reference to the first still-running QThread — Python GC'd it
|
||||||
|
# mid-run and aborted the operation. A list holds every in-flight thread.
|
||||||
|
if not hasattr(parent, "_jobs"):
|
||||||
|
parent._jobs = []
|
||||||
|
parent._jobs.append((t, w))
|
||||||
def finish(code, out, err):
|
def finish(code, out, err):
|
||||||
btn.setEnabled(True)
|
btn.setEnabled(True)
|
||||||
if progress: progress.hide()
|
if progress: progress.hide()
|
||||||
log.append("\nDone." if code == 0 else f"\nFailed (exit {code}).")
|
log.append("\nDone." if code == 0 else f"\nFailed (exit {code}).")
|
||||||
t.quit()
|
t.quit()
|
||||||
|
parent._jobs = [(th, wk) for (th, wk) in parent._jobs if th is not t]
|
||||||
w.done.connect(finish)
|
w.done.connect(finish)
|
||||||
t.started.connect(w.run); t.start()
|
t.started.connect(w.run); t.start()
|
||||||
parent._thread, parent._worker = t, w
|
|
||||||
|
|
||||||
# ── Tabs ──
|
# ── Tabs ──
|
||||||
|
|
||||||
|
|
@ -345,21 +428,31 @@ class KeysTab(QWidget):
|
||||||
inner = QWidget()
|
inner = QWidget()
|
||||||
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(10)
|
v = QVBoxLayout(inner); v.setContentsMargins(24,24,24,24); v.setSpacing(10)
|
||||||
|
|
||||||
v.addWidget(QLabel("Generate or export ML-KEM-768 + X25519 hybrid keys."))
|
v.addWidget(QLabel("Generate or export post-quantum keys (ML-KEM-768)."))
|
||||||
|
v.addWidget(Sep())
|
||||||
|
|
||||||
|
# Key type governs both generate and export so the two stay consistent.
|
||||||
|
v.addWidget(H("Key type"))
|
||||||
|
self.mode = QComboBox()
|
||||||
|
self._modes = pq_mode_options()
|
||||||
|
for label, _tok in self._modes:
|
||||||
|
self.mode.addItem(label)
|
||||||
|
self.mode.setToolTip("Hybrid (--pq) is recommended. Full PQ (--pq-only) drops the\n"
|
||||||
|
"classical X25519 layer for PQ-only compliance postures. Both use\n"
|
||||||
|
"in-tree crypto and work on every build.")
|
||||||
|
v.addWidget(self.mode)
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
|
|
||||||
# Section 1: Generate new keypair
|
# Section 1: Generate new keypair
|
||||||
v.addWidget(H("Generate new keypair"))
|
v.addWidget(H("Generate new keypair"))
|
||||||
v.addWidget(QLabel("Creates both private and public key files."))
|
v.addWidget(QLabel("Writes a private key and its matching public key."))
|
||||||
|
|
||||||
v.addWidget(H("Private key output"))
|
v.addWidget(H("Private key output"))
|
||||||
self.gen_priv = PathField("e.g. ~/zupt_private.key", "save", "Key (*.key);;All (*)")
|
self.gen_priv = PathField("e.g. ~/vaptvupt_private.key", "save", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.gen_priv)
|
v.addWidget(self.gen_priv)
|
||||||
|
v.addWidget(H("Public key output"))
|
||||||
self.gen_sdk = QCheckBox("SDK v2 format (HKDF combiner + commitment + HPKE — recommended)")
|
self.gen_pub = PathField("e.g. ~/vaptvupt_public.key", "save", "Key (*.key);;All (*)")
|
||||||
self.gen_sdk.setChecked(True)
|
v.addWidget(self.gen_pub)
|
||||||
self.gen_sdk.setToolTip("Generates a libzuptsdk-format keypair. Use --pq-sdk in CLI or 'SDK v2' checkbox in compress to use these keys. Disable for legacy --pq compatibility.")
|
|
||||||
v.addWidget(self.gen_sdk)
|
|
||||||
|
|
||||||
self.gen_btn = QPushButton("Generate Keypair")
|
self.gen_btn = QPushButton("Generate Keypair")
|
||||||
self.gen_btn.clicked.connect(self._generate)
|
self.gen_btn.clicked.connect(self._generate)
|
||||||
|
|
@ -370,14 +463,15 @@ class KeysTab(QWidget):
|
||||||
|
|
||||||
# Section 2: Export public key from existing private key
|
# Section 2: Export public key from existing private key
|
||||||
v.addWidget(H("Export public key from private key"))
|
v.addWidget(H("Export public key from private key"))
|
||||||
v.addWidget(QLabel("Extract the public key from an existing private key file."))
|
v.addWidget(QLabel("Extract the public key from an existing private key file "
|
||||||
|
"(uses the key type selected above)."))
|
||||||
|
|
||||||
v.addWidget(H("Existing private key"))
|
v.addWidget(H("Existing private key"))
|
||||||
self.exp_priv = PathField("Select private key", "open", "Key (*.key);;All (*)")
|
self.exp_priv = PathField("Select private key", "open", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.exp_priv)
|
v.addWidget(self.exp_priv)
|
||||||
|
|
||||||
v.addWidget(H("Public key output"))
|
v.addWidget(H("Public key output"))
|
||||||
self.exp_pub = PathField("e.g. ~/zupt_public.key", "save", "Key (*.key);;All (*)")
|
self.exp_pub = PathField("e.g. ~/vaptvupt_public.key", "save", "Key (*.key);;All (*)")
|
||||||
v.addWidget(self.exp_pub)
|
v.addWidget(self.exp_pub)
|
||||||
|
|
||||||
self.exp_btn = QPushButton("Export Public Key")
|
self.exp_btn = QPushButton("Export Public Key")
|
||||||
|
|
@ -389,27 +483,32 @@ class KeysTab(QWidget):
|
||||||
v.addStretch()
|
v.addStretch()
|
||||||
lay = QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.addWidget(scrollable(inner))
|
lay = QVBoxLayout(self); lay.setContentsMargins(0,0,0,0); lay.addWidget(scrollable(inner))
|
||||||
|
|
||||||
|
def _token(self):
|
||||||
|
return self._modes[self.mode.currentIndex()][1]
|
||||||
|
|
||||||
|
def _default_pub(self, priv):
|
||||||
|
return (priv.rsplit(".", 1)[0] + "_public.key") if "." in priv else priv + ".pub"
|
||||||
|
|
||||||
def _generate(self):
|
def _generate(self):
|
||||||
p = self.gen_priv.path() or str(Path.home() / "zupt_private.key")
|
p = self.gen_priv.path() or str(Path.home() / "vaptvupt_private.key")
|
||||||
self.gen_priv.edit.setText(p)
|
self.gen_priv.edit.setText(p)
|
||||||
|
pub = self.gen_pub.path() or self._default_pub(p)
|
||||||
|
self.gen_pub.edit.setText(pub)
|
||||||
|
tok = self._token()
|
||||||
|
kflags, _ = _PQ_FLAG[tok]
|
||||||
self.gen_log.clear(); self.gen_btn.setEnabled(False)
|
self.gen_log.clear(); self.gen_btn.setEnabled(False)
|
||||||
if self.gen_sdk.isChecked():
|
if tok == "sdk":
|
||||||
# SDK keygen creates both files in one step.
|
# SDK keygen writes the private key and <priv>.pub in one step.
|
||||||
code, _, err = run_zupt(["keygen", "--sdk", "-o", p])
|
code, _, err = run_zupt(["keygen", "--sdk", "-o", p])
|
||||||
self.gen_log.append(err.strip())
|
self.gen_log.append(err.strip())
|
||||||
if code == 0:
|
self.gen_log.append(f"\nPrivate key: {p}\nPublic key: {p}.pub" if code == 0 else "\nFailed.")
|
||||||
self.gen_log.append(f"\nPrivate key: {p}\nPublic key: {p}.pub")
|
|
||||||
else:
|
|
||||||
self.gen_log.append("\nFailed.")
|
|
||||||
else:
|
else:
|
||||||
code, _, err = run_zupt(["keygen", "-o", p])
|
code, _, err = run_zupt(["keygen"] + kflags + ["-o", p])
|
||||||
self.gen_log.append(err.strip())
|
self.gen_log.append(err.strip())
|
||||||
if code == 0:
|
if code == 0:
|
||||||
pub = p.rsplit(".", 1)[0] + "_public.key" if "." in p else p + ".pub"
|
c2, _, e2 = run_zupt(["keygen", "--pub"] + kflags + ["-o", pub, "-k", p])
|
||||||
c2, _, e2 = run_zupt(["keygen", "--pub", "-o", pub, "-k", p])
|
|
||||||
self.gen_log.append(e2.strip())
|
self.gen_log.append(e2.strip())
|
||||||
if c2 == 0:
|
self.gen_log.append(f"\nPrivate key: {p}\nPublic key: {pub}" if c2 == 0 else "\nFailed to export public key.")
|
||||||
self.gen_log.append(f"\nPrivate key: {p}\nPublic key: {pub}")
|
|
||||||
else:
|
else:
|
||||||
self.gen_log.append("\nFailed.")
|
self.gen_log.append("\nFailed.")
|
||||||
self.gen_btn.setEnabled(True)
|
self.gen_btn.setEnabled(True)
|
||||||
|
|
@ -419,15 +518,13 @@ class KeysTab(QWidget):
|
||||||
pub = self.exp_pub.path()
|
pub = self.exp_pub.path()
|
||||||
if not priv: QMessageBox.warning(self, "VaptVupt", "Select the private key file."); return
|
if not priv: QMessageBox.warning(self, "VaptVupt", "Select the private key file."); return
|
||||||
if not pub:
|
if not pub:
|
||||||
pub = priv.rsplit(".", 1)[0] + "_public.key" if "." in priv else priv + ".pub"
|
pub = self._default_pub(priv); self.exp_pub.edit.setText(pub)
|
||||||
self.exp_pub.edit.setText(pub)
|
tok = self._token()
|
||||||
|
kflags, _ = _PQ_FLAG[tok]
|
||||||
self.exp_log.clear(); self.exp_btn.setEnabled(False)
|
self.exp_log.clear(); self.exp_btn.setEnabled(False)
|
||||||
code, _, err = run_zupt(["keygen", "--pub", "-o", pub, "-k", priv])
|
code, _, err = run_zupt(["keygen", "--pub"] + kflags + ["-o", pub, "-k", priv])
|
||||||
self.exp_log.append(err.strip())
|
self.exp_log.append(err.strip())
|
||||||
if code == 0:
|
self.exp_log.append(f"\nPublic key: {pub}" if code == 0 else "\nFailed.")
|
||||||
self.exp_log.append(f"\nPublic key: {pub}")
|
|
||||||
else:
|
|
||||||
self.exp_log.append("\nFailed.")
|
|
||||||
self.exp_btn.setEnabled(True)
|
self.exp_btn.setEnabled(True)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -454,10 +551,14 @@ class CompressTab(QWidget):
|
||||||
enc = QHBoxLayout(); enc.setSpacing(16)
|
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||||
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField("AES-256"); pw.addWidget(self.pw); enc.addLayout(pw)
|
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField("AES-256"); pw.addWidget(self.pw); enc.addLayout(pw)
|
||||||
pq = QVBoxLayout(); pq.addWidget(H("PQ public key")); self.pq = PathField("Optional .key", filters="Key (*.key *.pub);;All (*)"); pq.addWidget(self.pq); enc.addLayout(pq)
|
pq = QVBoxLayout(); pq.addWidget(H("PQ public key")); self.pq = PathField("Optional .key", filters="Key (*.key *.pub);;All (*)"); pq.addWidget(self.pq); enc.addLayout(pq)
|
||||||
sdk_box = QVBoxLayout(); sdk_box.addWidget(H("Mode"))
|
mode_box = QVBoxLayout(); mode_box.addWidget(H("PQ mode"))
|
||||||
self.sdk = QCheckBox("Use SDK v2 (HKDF + commitment + HPKE)"); self.sdk.setChecked(True)
|
self.pqmode = QComboBox()
|
||||||
self.sdk.setToolTip("v2.2+ uses libzuptsdk: HKDF-SHA3 combiner, key commitment, HPKE binding, Argon2id. Disable for legacy --pq compatibility.")
|
self._pqmodes = pq_mode_options()
|
||||||
sdk_box.addWidget(self.sdk); sdk_box.addStretch(); enc.addLayout(sdk_box)
|
for label, _tok in self._pqmodes:
|
||||||
|
self.pqmode.addItem(label)
|
||||||
|
self.pqmode.setToolTip("Applies when a PQ public key is set. Must match the key type\n"
|
||||||
|
"you generated. Hybrid (--pq) is recommended.")
|
||||||
|
mode_box.addWidget(self.pqmode); mode_box.addStretch(); enc.addLayout(mode_box)
|
||||||
v.addLayout(enc)
|
v.addLayout(enc)
|
||||||
self.btn = QPushButton("Compress"); self.btn.clicked.connect(self._run); v.addWidget(self.btn)
|
self.btn = QPushButton("Compress"); self.btn.clicked.connect(self._run); v.addWidget(self.btn)
|
||||||
self.progress = QProgressBar(); self.progress.setRange(0,0); self.progress.hide(); v.addWidget(self.progress)
|
self.progress = QProgressBar(); self.progress.setRange(0,0); self.progress.hide(); v.addWidget(self.progress)
|
||||||
|
|
@ -481,7 +582,8 @@ class CompressTab(QWidget):
|
||||||
if self.solid.isChecked(): cmd.append("--solid")
|
if self.solid.isChecked(): cmd.append("--solid")
|
||||||
if self.pw.text(): cmd += ["-p", self.pw.text()]
|
if self.pw.text(): cmd += ["-p", self.pw.text()]
|
||||||
if self.pq.path():
|
if self.pq.path():
|
||||||
flag = "--pq-sdk" if self.sdk.isChecked() else "--pq"
|
tok = self._pqmodes[self.pqmode.currentIndex()][1]
|
||||||
|
_, flag = _PQ_FLAG[tok]
|
||||||
cmd += [flag, self.pq.path()]
|
cmd += [flag, self.pq.path()]
|
||||||
cmd.append(dst); cmd.extend(srcs)
|
cmd.append(dst); cmd.extend(srcs)
|
||||||
run_async(self, cmd, self.btn, self.log, self.progress)
|
run_async(self, cmd, self.btn, self.log, self.progress)
|
||||||
|
|
@ -500,10 +602,14 @@ class ExtractTab(QWidget):
|
||||||
enc = QHBoxLayout(); enc.setSpacing(16)
|
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||||
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField(); pw.addWidget(self.pw); enc.addLayout(pw)
|
pw = QVBoxLayout(); pw.addWidget(H("Password")); self.pw = PwField(); pw.addWidget(self.pw); enc.addLayout(pw)
|
||||||
pq = QVBoxLayout(); pq.addWidget(H("PQ private key")); self.pq = PathField("Optional .key", filters="Key (*.key);;All (*)"); pq.addWidget(self.pq); enc.addLayout(pq)
|
pq = QVBoxLayout(); pq.addWidget(H("PQ private key")); self.pq = PathField("Optional .key", filters="Key (*.key);;All (*)"); pq.addWidget(self.pq); enc.addLayout(pq)
|
||||||
sdk_box = QVBoxLayout(); sdk_box.addWidget(H("Mode"))
|
mode_box = QVBoxLayout(); mode_box.addWidget(H("PQ mode"))
|
||||||
self.sdk = QCheckBox("Auto-detect (SDK or legacy)"); self.sdk.setChecked(True)
|
self.pqmode = QComboBox()
|
||||||
self.sdk.setToolTip("Tries --pq-sdk first, falls back to --pq for legacy archives.")
|
self._pqmodes = pq_mode_options(include_auto=True)
|
||||||
sdk_box.addWidget(self.sdk); sdk_box.addStretch(); enc.addLayout(sdk_box)
|
for label, _tok in self._pqmodes:
|
||||||
|
self.pqmode.addItem(label)
|
||||||
|
self.pqmode.setToolTip("Auto-detect reads the archive header (vaptvupt info) to pick the\n"
|
||||||
|
"right mode. Or choose it explicitly to match your private key.")
|
||||||
|
mode_box.addWidget(self.pqmode); mode_box.addStretch(); enc.addLayout(mode_box)
|
||||||
v.addLayout(enc)
|
v.addLayout(enc)
|
||||||
self.btn = QPushButton("Extract"); self.btn.setObjectName("green"); self.btn.clicked.connect(self._run); v.addWidget(self.btn)
|
self.btn = QPushButton("Extract"); self.btn.setObjectName("green"); self.btn.clicked.connect(self._run); v.addWidget(self.btn)
|
||||||
self.progress = QProgressBar(); self.progress.setRange(0,0); self.progress.hide(); v.addWidget(self.progress)
|
self.progress = QProgressBar(); self.progress.setRange(0,0); self.progress.hide(); v.addWidget(self.progress)
|
||||||
|
|
@ -518,10 +624,13 @@ class ExtractTab(QWidget):
|
||||||
if self.out.path(): cmd += ["-o", self.out.path()]
|
if self.out.path(): cmd += ["-o", self.out.path()]
|
||||||
if self.pw.text(): cmd += ["-p", self.pw.text()]
|
if self.pw.text(): cmd += ["-p", self.pw.text()]
|
||||||
if self.pq.path():
|
if self.pq.path():
|
||||||
# Auto-detect: zupt's extract auto-discovers enc type from header,
|
tok = self._pqmodes[self.pqmode.currentIndex()][1]
|
||||||
# so passing --pq-sdk works for both SDK and legacy keyfiles when
|
if tok == "auto":
|
||||||
# the archive is SDK-encoded; --pq is needed for legacy archives.
|
# The private-key format must match how the archive was encrypted;
|
||||||
flag = "--pq-sdk" if self.sdk.isChecked() else "--pq"
|
# 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]}")
|
||||||
|
_, flag = _PQ_FLAG[tok]
|
||||||
cmd += [flag, self.pq.path()]
|
cmd += [flag, self.pq.path()]
|
||||||
cmd.append(arc)
|
cmd.append(arc)
|
||||||
run_async(self, cmd, self.btn, self.log, self.progress)
|
run_async(self, cmd, self.btn, self.log, self.progress)
|
||||||
|
|
@ -536,8 +645,16 @@ class VerifyTab(QWidget):
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
v.addWidget(H("Verify integrity"))
|
v.addWidget(H("Verify integrity"))
|
||||||
self.varc = PathField("Archive to verify", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.varc)
|
self.varc = PathField("Archive to verify", filters="VaptVupt archive (*.zupt);;All (*)"); v.addWidget(self.varc)
|
||||||
v.addWidget(H("Password (if encrypted)"))
|
enc = QHBoxLayout(); enc.setSpacing(16)
|
||||||
self.vpw = PwField("Leave empty if not encrypted"); v.addWidget(self.vpw)
|
pw = QVBoxLayout(); pw.addWidget(H("Password (if encrypted)")); self.vpw = PwField("Leave empty if not encrypted"); pw.addWidget(self.vpw); enc.addLayout(pw)
|
||||||
|
pq = QVBoxLayout(); pq.addWidget(H("PQ private key")); self.vpq = PathField("For --pq / --pq-only archives", filters="Key (*.key);;All (*)"); pq.addWidget(self.vpq); enc.addLayout(pq)
|
||||||
|
mode_box = QVBoxLayout(); mode_box.addWidget(H("PQ mode"))
|
||||||
|
self.vpqmode = QComboBox()
|
||||||
|
self._vpqmodes = pq_mode_options(include_auto=True)
|
||||||
|
for label, _tok in self._vpqmodes:
|
||||||
|
self.vpqmode.addItem(label)
|
||||||
|
mode_box.addWidget(self.vpqmode); mode_box.addStretch(); enc.addLayout(mode_box)
|
||||||
|
v.addLayout(enc)
|
||||||
self.vbtn = QPushButton("Verify"); self.vbtn.setObjectName("amber"); self.vbtn.clicked.connect(self._verify); v.addWidget(self.vbtn)
|
self.vbtn = QPushButton("Verify"); self.vbtn.setObjectName("amber"); self.vbtn.clicked.connect(self._verify); v.addWidget(self.vbtn)
|
||||||
self.vlog = Log(120); v.addWidget(self.vlog)
|
self.vlog = Log(120); v.addWidget(self.vlog)
|
||||||
v.addWidget(Sep())
|
v.addWidget(Sep())
|
||||||
|
|
@ -552,7 +669,15 @@ class VerifyTab(QWidget):
|
||||||
if not arc: return
|
if not arc: return
|
||||||
cmd = ["test"]
|
cmd = ["test"]
|
||||||
if self.vpw.text(): cmd += ["-p", self.vpw.text()]
|
if self.vpw.text(): cmd += ["-p", self.vpw.text()]
|
||||||
cmd.append(arc); self.vlog.clear()
|
self.vlog.clear()
|
||||||
|
if self.vpq.path():
|
||||||
|
tok = self._vpqmodes[self.vpqmode.currentIndex()][1]
|
||||||
|
if tok == "auto":
|
||||||
|
tok = _detect_archive_pq(arc) or "pq"
|
||||||
|
self.vlog.append(f"[auto-detect] using {_PQ_FLAG[tok][1]}")
|
||||||
|
_, flag = _PQ_FLAG[tok]
|
||||||
|
cmd += [flag, self.vpq.path()]
|
||||||
|
cmd.append(arc)
|
||||||
code, out, err = run_zupt(cmd, timeout=600)
|
code, out, err = run_zupt(cmd, timeout=600)
|
||||||
self.vlog.append((err + "\n" + out).strip())
|
self.vlog.append((err + "\n" + out).strip())
|
||||||
self.vlog.append("\nAll checksums passed." if code == 0 else "\nVerification failed.")
|
self.vlog.append("\nAll checksums passed." if code == 0 else "\nVerification failed.")
|
||||||
|
|
@ -621,30 +746,32 @@ class AboutTab(QWidget):
|
||||||
("VAPTVUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("VAPTVUPT", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
(ZUPT_VER_NUMBER, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
(ZUPT_VER_NUMBER, "color:white;font-size:28px;font-weight:800;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("Post-quantum backup compression with ML-KEM-768 + X25519", "color:#6a8898;font-size:13px;"),
|
("Post-quantum backup compression with ML-KEM-768: --pq hybrid", "color:#6a8898;font-size:13px;"),
|
||||||
("hybrid encryption, Argon2id KDF, and block deduplication.", "color:#6a8898;font-size:13px;"),
|
(f"(+ X25519) or --pq-only (pure). {DEFAULT_KDF} password KDF,", "color:#6a8898;font-size:13px;"),
|
||||||
|
("block deduplication, and full-disk backup.", "color:#6a8898;font-size:13px;"),
|
||||||
("Renamed from Zupt in v3.0.0 (INPI Brasil trademark); .zupt", "color:#6a8898;font-size:13px;"),
|
("Renamed from Zupt in v3.0.0 (INPI Brasil trademark); .zupt", "color:#6a8898;font-size:13px;"),
|
||||||
("archive extension and v1.6 wire format are unchanged.", "color:#6a8898;font-size:13px;"),
|
("archive extension and v1.6 wire format are unchanged.", "color:#6a8898;font-size:13px;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("CRYPTOGRAPHIC STACK", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("CRYPTOGRAPHIC STACK", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("ML-KEM-768 FIPS 203 Post-Quantum KEM", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("ML-KEM-768 FIPS 203 Post-Quantum KEM", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("X25519 RFC 7748 Elliptic Curve DH (hybrid w/ ML-KEM)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("X25519 RFC 7748 Elliptic Curve DH (hybrid w/ ML-KEM)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("AES-256-CTR FIPS 197 Symmetric Cipher", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("AES-256-CTR FIPS 197 Symmetric Cipher (fresh per-block nonce)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("HMAC-SHA256 RFC 2104 Authentication (Encrypt-then-MAC)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("HMAC-SHA256 RFC 2104 Authentication (Encrypt-then-MAC)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("Argon2id RFC 9106 Password KDF (default since 2.4.1)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("PBKDF2-SHA256 RFC 8018 Password KDF (default, 600k iterations)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("PBKDF2 RFC 8018 Password KDF (legacy; --kdf pbkdf2)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("Argon2id RFC 9106 Password KDF (WITH_SDK=1 builds only)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
|
("SHA3-512 FIPS 202 PQ key derivation (--pq / --pq-only)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("HKDF RFC 5869 Key Derivation Function", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("HKDF RFC 5869 Key Derivation Function", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("SHA3/SHAKE FIPS 202 Hash / XOF", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("SHA3/SHAKE FIPS 202 Hash / XOF", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("XXH64 (non-crypto) Per-block checksum (inside AEAD)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("XXH64 (non-crypto) Per-block checksum (inside AEAD)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("COMPRESSION CODEC", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("COMPRESSION CODEC", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("VaptVupt LZ + ANS 2.48.5 LZ77 + tabled ANS entropy", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("VaptVupt LZ + ANS 2.60.4 LZ77 + tabled ANS entropy", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("AVX2 / NEON SIMD acceleration; 1.27x zstd-3 decode aggregate", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("AVX2 / NEON SIMD acceleration; CBMC-verified BCJ filters", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("CREDITS", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
("CREDITS", "color:#00dde0;font-size:10px;font-weight:700;letter-spacing:2px;font-family:monospace;"),
|
||||||
("VaptVupt application Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("VaptVupt application Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" License: AGPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
(" License: AGPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" git.securityops.co/cristiancmoises/zupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
(" git.securityops.co/cristiancmoises/vaptvupt", "color:#3a5868;font-size:11px;font-family:monospace;"),
|
||||||
("", ""),
|
("", ""),
|
||||||
("VaptVupt LZ + ANS codec Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
("VaptVupt LZ + ANS codec Cristian Cezar Moisés", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
(" License: GPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
(" License: GPL-3.0-or-later (commercial license available)", "color:#5a7a88;font-size:12px;font-family:monospace;"),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ replaces=('zupt')
|
||||||
conflicts=('zupt')
|
conflicts=('zupt')
|
||||||
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
pkgdesc='Pure-C11 post-quantum backup compression utility (AES-256-CTR + HMAC-SHA256 + ML-KEM-768 + X25519)'
|
||||||
arch=('x86_64' 'aarch64')
|
arch=('x86_64' 'aarch64')
|
||||||
url='https://git.securityops.co/cristiancmoises/zupt'
|
url='https://git.securityops.co/cristiancmoises/vaptvupt'
|
||||||
license=('AGPL-3.0-or-later')
|
license=('AGPL-3.0-or-later')
|
||||||
depends=('glibc')
|
depends=('glibc')
|
||||||
makedepends=('gcc')
|
makedepends=('gcc')
|
||||||
|
|
@ -26,39 +26,31 @@ checkdepends=('python')
|
||||||
|
|
||||||
# Replace SHA256 placeholder with output of:
|
# Replace SHA256 placeholder with output of:
|
||||||
# sha256sum /tmp/zupt-2.4.4.tar.gz
|
# sha256sum /tmp/zupt-2.4.4.tar.gz
|
||||||
source=("${pkgname}-${pkgver}.tar.gz::https://git.securityops.co/cristiancmoises/zupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
source=("${pkgname}-${pkgver}.tar.gz::https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
|
||||||
sha256sums=('SKIP')
|
sha256sums=('SKIP')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
# Strict-warning build that the project's own §6 verification matrix uses.
|
# Source-only build (WITH_SDK=0) with the project's strict warning set.
|
||||||
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
|
CFLAGS="${CFLAGS:--O2 -std=c11} -Wall -Wextra -Wpedantic" \
|
||||||
make -j"$(nproc)"
|
make WITH_SDK=0 -j"$(nproc)"
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
# Project regression suite — F-06 HMAC, F-08 top-MAC, F-09 byte sweep, etc.
|
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
||||||
make test
|
make WITH_SDK=0 check
|
||||||
}
|
}
|
||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd "${pkgname}-${pkgver}"
|
cd "${pkgname}-${pkgver}"
|
||||||
make DESTDIR="${pkgdir}" PREFIX=/usr install
|
# Source-only build (no vendored libraries); `make install` places the
|
||||||
|
# binary, the zupt symlink, the man pages and the shell completions.
|
||||||
|
make DESTDIR="${pkgdir}" PREFIX=/usr WITH_SDK=0 install
|
||||||
|
|
||||||
# Docs that aren't part of `make install`
|
# Docs that aren't part of `make install`
|
||||||
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
|
install -Dm644 README.md "${pkgdir}/usr/share/doc/${pkgname}/README.md"
|
||||||
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
|
install -Dm644 SECURITY.md "${pkgdir}/usr/share/doc/${pkgname}/SECURITY.md"
|
||||||
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
|
install -Dm644 CHANGELOG.md "${pkgdir}/usr/share/doc/${pkgname}/CHANGELOG.md"
|
||||||
install -Dm644 AUDIT.md "${pkgdir}/usr/share/doc/${pkgname}/AUDIT.md"
|
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
|
||||||
|
|
||||||
# Vendored libzuptsdk shipped alongside the binary because the binary
|
|
||||||
# is linked with -Wl,-rpath,$ORIGIN/vendor/zuptsdk. For system install
|
|
||||||
# we move it to /usr/lib/zupt/ and the binary's rpath remains relative.
|
|
||||||
install -d "${pkgdir}/usr/lib/${pkgname}"
|
|
||||||
install -Dm755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
|
|
||||||
"${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so.2.0.0"
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 "${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so.2"
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 "${pkgdir}/usr/lib/${pkgname}/libzuptsdk.so"
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ Build-Depends:
|
||||||
libc6-dev,
|
libc6-dev,
|
||||||
python3 (>= 3.8)
|
python3 (>= 3.8)
|
||||||
Standards-Version: 4.6.2
|
Standards-Version: 4.6.2
|
||||||
Homepage: https://git.securityops.co/cristiancmoises/zupt
|
Homepage: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||||
Vcs-Browser: https://git.securityops.co/cristiancmoises/zupt
|
Vcs-Browser: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||||
Vcs-Git: https://git.securityops.co/cristiancmoises/zupt.git
|
Vcs-Git: https://git.securityops.co/cristiancmoises/vaptvupt.git
|
||||||
Rules-Requires-Root: no
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
Package: vaptvupt
|
Package: vaptvupt
|
||||||
|
|
@ -24,7 +24,7 @@ Description: Post-quantum backup compression utility (formerly Zupt)
|
||||||
a pure-C11 backup compression utility featuring:
|
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)
|
||||||
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
* AES-256-CTR + HMAC-SHA256 authenticated encryption (Encrypt-then-MAC)
|
||||||
* Argon2id password-based key derivation (default since 2.4.1)
|
* 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.48.5
|
||||||
* Full-disk backup and restore with sparse-region detection
|
* Full-disk backup and restore with sparse-region detection
|
||||||
* End-to-end byte-level tamper detection on encrypted archives
|
* End-to-end byte-level tamper detection on encrypted archives
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,18 @@ export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed
|
||||||
dh $@
|
dh $@
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
$(MAKE) -j$$(nproc)
|
# Source-only build: no vendored libraries, native crypto only.
|
||||||
|
$(MAKE) WITH_SDK=0 -j$$(nproc)
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
# Project's own regression suite covers F-06..F-12.
|
# Distro-safe regression subset (F-06..F-12, dedup-nonce, NIST/RFC vectors).
|
||||||
$(MAKE) test
|
$(MAKE) WITH_SDK=0 check
|
||||||
|
|
||||||
override_dh_auto_install:
|
override_dh_auto_install:
|
||||||
$(MAKE) DESTDIR=$(CURDIR)/debian/zupt PREFIX=/usr install
|
# Binary package is `vaptvupt` -> stage into debian/vaptvupt (dh derives the
|
||||||
# Vendored libzuptsdk goes alongside the binary at a relative rpath.
|
# staging dir from the Package: name in debian/control). Source-only: nothing
|
||||||
install -d $(CURDIR)/debian/zupt/usr/lib/zupt
|
# to install beyond `make install` (no .so).
|
||||||
install -m 0755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
|
$(MAKE) DESTDIR=$(CURDIR)/debian/vaptvupt PREFIX=/usr WITH_SDK=0 install
|
||||||
$(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so.2.0.0
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 $(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so.2
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 $(CURDIR)/debian/zupt/usr/lib/zupt/libzuptsdk.so
|
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
$(MAKE) clean
|
$(MAKE) clean
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
|
|
||||||
class Vaptvupt < Formula
|
class Vaptvupt < Formula
|
||||||
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
desc "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256)"
|
||||||
homepage "https://git.securityops.co/cristiancmoises/zupt"
|
homepage "https://git.securityops.co/cristiancmoises/vaptvupt"
|
||||||
url "https://git.securityops.co/cristiancmoises/zupt/releases/download/v4.2.1/vaptvupt-4.2.1.tar.gz"
|
url "https://git.securityops.co/cristiancmoises/vaptvupt/releases/download/v4.2.1/vaptvupt-4.2.1.tar.gz"
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL"
|
sha256 "REPLACE_WITH_SHA256_OF_RELEASE_TARBALL"
|
||||||
license "AGPL-3.0-or-later"
|
license "AGPL-3.0-or-later"
|
||||||
|
|
@ -31,30 +31,16 @@ class Vaptvupt < Formula
|
||||||
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
depends_on "python@3.12" => :test # only for test-suite tamper harness
|
||||||
|
|
||||||
def install
|
def install
|
||||||
# macOS build: no Jasmin, C-fallback crypto paths are used.
|
# Source-only build (WITH_SDK=0): native crypto only, no vendored libraries.
|
||||||
# The Makefile auto-detects Jasmin availability and falls back cleanly.
|
# macOS uses the C-fallback crypto paths (no Jasmin); the Makefile detects
|
||||||
|
# this and falls back cleanly.
|
||||||
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
|
ENV["CFLAGS"] = "#{ENV.cflags} -O2 -std=c11 -Wall -Wextra"
|
||||||
|
|
||||||
system "make", "-j#{ENV.make_jobs}"
|
system "make", "WITH_SDK=0", "-j#{ENV.make_jobs}"
|
||||||
system "make", "DESTDIR=#{prefix}", "PREFIX=", "install"
|
system "make", "DESTDIR=#{prefix}", "PREFIX=", "WITH_SDK=0", "install"
|
||||||
|
|
||||||
# Vendored libzuptsdk goes into lib/zupt/ with @loader_path rpath.
|
# Docs (no vendored .so/.dylib in the source-only build).
|
||||||
# Note: Linux ships .so.2.0.0; macOS .dylib equivalent must be built
|
doc.install "README.md", "SECURITY.md", "CHANGELOG.md"
|
||||||
# separately by the vendored makefile. For the initial Homebrew
|
|
||||||
# submission this assumes the upstream tarball includes a .dylib build;
|
|
||||||
# if not, build it here.
|
|
||||||
lib_zupt = lib/"zupt"
|
|
||||||
lib_zupt.mkpath
|
|
||||||
if File.exist?("vendor/zuptsdk/libzuptsdk.dylib")
|
|
||||||
cp "vendor/zuptsdk/libzuptsdk.dylib", lib_zupt
|
|
||||||
elsif File.exist?("vendor/zuptsdk/libzuptsdk.so.2.0.0")
|
|
||||||
# Fallback: link Linux-style .so on macOS (works for direct loads but
|
|
||||||
# not for dlopen-on-Darwin scenarios). Upstream is tracking this.
|
|
||||||
cp "vendor/zuptsdk/libzuptsdk.so.2.0.0", lib_zupt
|
|
||||||
end
|
|
||||||
|
|
||||||
# Docs
|
|
||||||
doc.install "README.md", "SECURITY.md", "CHANGELOG.md", "AUDIT.md"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test do
|
test do
|
||||||
|
|
|
||||||
|
|
@ -52,48 +52,38 @@
|
||||||
# hardening flags. Don't override -O2 from stdenv.
|
# hardening flags. Don't override -O2 from stdenv.
|
||||||
NIX_CFLAGS_COMPILE = "-Wall -Wextra -Wpedantic -std=c11";
|
NIX_CFLAGS_COMPILE = "-Wall -Wextra -Wpedantic -std=c11";
|
||||||
|
|
||||||
# `make` builds the binary using vendored libzuptsdk via rpath.
|
# Source-only build (WITH_SDK=0): native crypto, no vendored libraries.
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
runHook preBuild
|
runHook preBuild
|
||||||
make -j$NIX_BUILD_CORES
|
make WITH_SDK=0 -j$NIX_BUILD_CORES
|
||||||
runHook postBuild
|
runHook postBuild
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Run the full upstream regression suite. Disable per-package by
|
# Distro-safe regression subset. Disable with doCheck = false;.
|
||||||
# setting doCheck = false; on by default.
|
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
runHook preCheck
|
runHook preCheck
|
||||||
make test
|
make WITH_SDK=0 check
|
||||||
runHook postCheck
|
runHook postCheck
|
||||||
'';
|
'';
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
make DESTDIR=$out PREFIX= install
|
make DESTDIR=$out PREFIX= WITH_SDK=0 install
|
||||||
|
|
||||||
# Move libzuptsdk into $out/lib/zupt/. The binary's rpath is
|
|
||||||
# $ORIGIN/../lib/zupt after autopatchelf rewrites it during
|
|
||||||
# the fixup phase.
|
|
||||||
mkdir -p $out/lib/zupt
|
|
||||||
install -m 0755 vendor/zuptsdk/libzuptsdk.so.2.0.0 \
|
|
||||||
$out/lib/zupt/libzuptsdk.so.2.0.0
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 $out/lib/zupt/libzuptsdk.so.2
|
|
||||||
ln -sf libzuptsdk.so.2.0.0 $out/lib/zupt/libzuptsdk.so
|
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
mkdir -p $out/share/doc/zupt
|
mkdir -p $out/share/doc/vaptvupt
|
||||||
cp README.md SECURITY.md CHANGELOG.md AUDIT.md $out/share/doc/zupt/
|
cp README.md SECURITY.md CHANGELOG.md $out/share/doc/vaptvupt/
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
meta = with pkgs.lib; {
|
||||||
description = "Post-quantum backup compression utility (ML-KEM-768 + AES-256-CTR + HMAC-SHA256 + Argon2id)";
|
description = "Post-quantum backup compression utility (ML-KEM-768 + X25519 + AES-256-CTR + HMAC-SHA256)";
|
||||||
homepage = "https://git.securityops.co/cristiancmoises/zupt";
|
homepage = "https://git.securityops.co/cristiancmoises/vaptvupt";
|
||||||
license = with licenses; [ agpl3Plus gpl3Plus ];
|
license = with licenses; [ agpl3Plus gpl3Plus ];
|
||||||
maintainers = [ ];
|
maintainers = [ ];
|
||||||
platforms = [ "x86_64-linux" "aarch64-linux" ];
|
platforms = [ "x86_64-linux" "aarch64-linux" ];
|
||||||
mainProgram = "zupt";
|
mainProgram = "vaptvupt";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<services>
|
<services>
|
||||||
<service name="tar_scm" mode="manual">
|
<service name="tar_scm" mode="manual">
|
||||||
<param name="url">https://github.com/cristiancmoises/zupt</param>
|
<param name="url">https://github.com/cristiancmoises/vaptvupt</param>
|
||||||
<param name="scm">git</param>
|
<param name="scm">git</param>
|
||||||
<param name="revision">v4.2.1</param>
|
<param name="revision">v4.2.1</param>
|
||||||
<param name="versionformat">@PARENT_TAG@</param>
|
<param name="versionformat">@PARENT_TAG@</param>
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,11 @@ chmod +x tests/*.sh
|
||||||
%doc README.md SECURITY.md CHANGELOG.md
|
%doc README.md SECURITY.md CHANGELOG.md
|
||||||
%{_bindir}/vaptvupt
|
%{_bindir}/vaptvupt
|
||||||
%{_bindir}/zupt
|
%{_bindir}/zupt
|
||||||
|
%{_datadir}/bash-completion/completions/vaptvupt
|
||||||
|
%{_datadir}/bash-completion/completions/zupt
|
||||||
|
%{_datadir}/zsh/site-functions/_vaptvupt
|
||||||
|
%{_datadir}/zsh/site-functions/_zupt
|
||||||
|
%{_datadir}/fish/vendor_completions.d/vaptvupt.fish
|
||||||
%{_mandir}/man1/vaptvupt.1%{?ext_man}
|
%{_mandir}/man1/vaptvupt.1%{?ext_man}
|
||||||
%{_mandir}/man1/zupt.1%{?ext_man}
|
%{_mandir}/man1/zupt.1%{?ext_man}
|
||||||
|
|
||||||
|
|
|
||||||
57
packaging/portable/README.txt
Normal file
57
packaging/portable/README.txt
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
VaptVupt GUI — portable cross-platform package
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
The VaptVupt GUI is a single Python file (zupt_gui.py) built on Qt for Python
|
||||||
|
(PySide6, or PyQt6 as a fallback). It runs on Windows, macOS, Linux and the
|
||||||
|
BSDs — anywhere Python 3 and a Qt binding are installed. This portable package
|
||||||
|
contains the GUI plus a launcher for each platform; it drives the `vaptvupt`
|
||||||
|
command-line tool under the hood.
|
||||||
|
|
||||||
|
Contents
|
||||||
|
--------
|
||||||
|
zupt_gui.py The GUI (PySide6 / PyQt6).
|
||||||
|
vaptvupt-gui.bat Windows launcher.
|
||||||
|
vaptvupt-gui.command macOS launcher (double-clickable in Finder).
|
||||||
|
vaptvupt-gui.sh Linux / BSD launcher.
|
||||||
|
assets/zupt-icon.png Application icon.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
1. Python 3.8 or newer.
|
||||||
|
Windows: https://python.org (tick "Add python.exe to PATH")
|
||||||
|
macOS: python.org, or `brew install python`
|
||||||
|
Linux: your distro's python3 package
|
||||||
|
FreeBSD: pkg install python311
|
||||||
|
OpenBSD: pkg_add python%3
|
||||||
|
2. A Qt binding:
|
||||||
|
pip (any OS): python3 -m pip install PySide6
|
||||||
|
Debian/Ubuntu: sudo apt install python3-pyqt6
|
||||||
|
Fedora/RHEL: sudo dnf install python3-pyqt6
|
||||||
|
FreeBSD: pkg install py311-pyside6
|
||||||
|
OpenBSD: pkg_add py3-pyside6
|
||||||
|
3. The vaptvupt CLI, either:
|
||||||
|
* placed next to the launcher (vaptvupt.exe on Windows, vaptvupt
|
||||||
|
elsewhere) — the launcher auto-detects it via VAPTVUPT_BIN, or
|
||||||
|
* installed on PATH (deb/rpm/AppImage/Homebrew/pkg — see the project
|
||||||
|
release page).
|
||||||
|
|
||||||
|
Running
|
||||||
|
-------
|
||||||
|
Windows: double-click vaptvupt-gui.bat
|
||||||
|
macOS: double-click vaptvupt-gui.command
|
||||||
|
(first run: right-click > Open to bypass Gatekeeper for an
|
||||||
|
unsigned script, or `xattr -dr com.apple.quarantine .`)
|
||||||
|
Linux/BSD: ./vaptvupt-gui.sh
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
* "requires PySide6 or PyQt6" -> install a Qt binding (requirement 2).
|
||||||
|
* "vaptvupt not found" -> put the CLI next to the launcher or on PATH.
|
||||||
|
* Set VAPTVUPT_DEBUG=1 to print the binary-discovery log to stderr.
|
||||||
|
|
||||||
|
Fully self-contained native installers (Windows .exe/.msi, macOS .dmg) that
|
||||||
|
bundle Python + Qt + the CLI are produced by the project's CI on real Windows
|
||||||
|
and macOS runners — see the release page. This portable package is the
|
||||||
|
dependency-light option that works identically on every platform.
|
||||||
|
|
||||||
|
License: AGPL-3.0-or-later. Project: https://git.securityops.co/cristiancmoises/vaptvupt
|
||||||
31
packaging/portable/vaptvupt-gui.bat
Normal file
31
packaging/portable/vaptvupt-gui.bat
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
@echo off
|
||||||
|
rem SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
rem VaptVupt GUI launcher for Windows (portable package).
|
||||||
|
rem
|
||||||
|
rem Requirements on the target machine:
|
||||||
|
rem * Python 3.8+ (https://python.org — tick "Add python.exe to PATH")
|
||||||
|
rem * PySide6 or PyQt6: py -m pip install PySide6
|
||||||
|
rem * The vaptvupt CLI: vaptvupt.exe next to this file, or on PATH.
|
||||||
|
rem
|
||||||
|
rem If vaptvupt.exe sits beside this launcher we pin it via VAPTVUPT_BIN so the
|
||||||
|
rem GUI drives the bundled CLI rather than any other copy on PATH.
|
||||||
|
setlocal
|
||||||
|
set "HERE=%~dp0"
|
||||||
|
if exist "%HERE%vaptvupt.exe" set "VAPTVUPT_BIN=%HERE%vaptvupt.exe"
|
||||||
|
|
||||||
|
rem Prefer the py launcher, fall back to python on PATH.
|
||||||
|
where py >nul 2>nul
|
||||||
|
if %ERRORLEVEL%==0 (
|
||||||
|
py -3 "%HERE%zupt_gui.py" %*
|
||||||
|
) else (
|
||||||
|
python "%HERE%zupt_gui.py" %*
|
||||||
|
)
|
||||||
|
set "RC=%ERRORLEVEL%"
|
||||||
|
if not "%RC%"=="0" (
|
||||||
|
echo.
|
||||||
|
echo vaptvupt-gui exited with code %RC%.
|
||||||
|
echo If you saw an import error, install the Qt binding: py -m pip install PySide6
|
||||||
|
echo If the CLI was not found, put vaptvupt.exe next to this launcher or on PATH.
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
endlocal
|
||||||
17
packaging/portable/vaptvupt-gui.command
Normal file
17
packaging/portable/vaptvupt-gui.command
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# VaptVupt GUI launcher for macOS (portable package).
|
||||||
|
# Double-clickable in Finder (.command). Requirements on the target Mac:
|
||||||
|
# * Python 3.8+ (python.org, Homebrew `brew install python`, or Xcode CLT)
|
||||||
|
# * PySide6 or PyQt6: python3 -m pip install PySide6
|
||||||
|
# * The vaptvupt CLI: `vaptvupt` next to this file, or on PATH
|
||||||
|
# (Homebrew: `brew install cristiancmoises/tap/vaptvupt`).
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
[ -x "$HERE/vaptvupt" ] && export VAPTVUPT_BIN="$HERE/vaptvupt"
|
||||||
|
|
||||||
|
PY="$(command -v python3 || command -v python || true)"
|
||||||
|
if [ -z "$PY" ]; then
|
||||||
|
osascript -e 'display alert "VaptVupt GUI" message "Python 3 not found. Install it from python.org or `brew install python`, then run: python3 -m pip install PySide6"' 2>/dev/null
|
||||||
|
echo "Python 3 not found." >&2; exit 1
|
||||||
|
fi
|
||||||
|
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
||||||
21
packaging/portable/vaptvupt-gui.sh
Normal file
21
packaging/portable/vaptvupt-gui.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
# VaptVupt GUI launcher for Linux and the BSDs (portable package).
|
||||||
|
# Requirements on the target system:
|
||||||
|
# * Python 3.8+
|
||||||
|
# * PySide6 or PyQt6
|
||||||
|
# Debian/Ubuntu: sudo apt install python3-pyqt6
|
||||||
|
# Fedora/RHEL: sudo dnf install python3-pyqt6
|
||||||
|
# FreeBSD: pkg install py311-pyside6 (or py311-qt6-pyqt)
|
||||||
|
# OpenBSD: pkg_add py3-pyside6
|
||||||
|
# any OS via pip: python3 -m pip install PySide6
|
||||||
|
# * The vaptvupt CLI: `vaptvupt` next to this file, or on PATH.
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
[ -x "$HERE/vaptvupt" ] && export VAPTVUPT_BIN="$HERE/vaptvupt"
|
||||||
|
|
||||||
|
PY="$(command -v python3 || command -v python || true)"
|
||||||
|
if [ -z "$PY" ]; then
|
||||||
|
echo "vaptvupt-gui: Python 3 not found on PATH." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exec "$PY" "$HERE/zupt_gui.py" "$@"
|
||||||
73
packaging/windows/vaptvupt-gui.iss
Normal file
73
packaging/windows/vaptvupt-gui.iss
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
; SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
; Inno Setup script for the VaptVupt GUI Windows installer.
|
||||||
|
;
|
||||||
|
; Compiled by the cross-platform CI (.github/workflows/cross-platform.yml) with:
|
||||||
|
; ISCC.exe /DAppVersion=<version> packaging/windows/vaptvupt-gui.iss
|
||||||
|
; after PyInstaller has produced dist\vaptvupt-gui.exe (a onefile bundle that
|
||||||
|
; already contains Python, PySide6 and vaptvupt.exe). Requires Inno Setup 6+.
|
||||||
|
;
|
||||||
|
; To build locally on Windows: install Inno Setup, then run the same ISCC line
|
||||||
|
; from the repo root (with dist\vaptvupt-gui.exe present).
|
||||||
|
|
||||||
|
#ifndef AppVersion
|
||||||
|
#define AppVersion "0.0.0"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppName=VaptVupt
|
||||||
|
AppVersion={#AppVersion}
|
||||||
|
AppPublisher=Cristian Cezar Moises
|
||||||
|
AppPublisherURL=https://git.securityops.co/cristiancmoises/vaptvupt
|
||||||
|
DefaultDirName={autopf}\VaptVupt
|
||||||
|
DefaultGroupName=VaptVupt
|
||||||
|
UninstallDisplayIcon={app}\vaptvupt-gui.exe
|
||||||
|
OutputDir=packaging\windows\Output
|
||||||
|
OutputBaseFilename=VaptVupt-Setup-{#AppVersion}
|
||||||
|
Compression=lzma2
|
||||||
|
SolidCompression=yes
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
WizardStyle=modern
|
||||||
|
LicenseFile=LICENSE
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
; PyInstaller onefile bundle (Python + PySide6 + the GUI + vaptvupt.exe).
|
||||||
|
Source: "dist\vaptvupt-gui.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
; Ship the raw CLI too so it can be added to PATH and used from a terminal.
|
||||||
|
Source: "vaptvupt.exe"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist
|
||||||
|
Source: "README.md"; DestDir: "{app}"; Flags: ignoreversion isreadme
|
||||||
|
Source: "CHANGELOG.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\VaptVupt"; Filename: "{app}\vaptvupt-gui.exe"
|
||||||
|
Name: "{group}\Uninstall VaptVupt"; Filename: "{uninstallexe}"
|
||||||
|
Name: "{autodesktop}\VaptVupt"; Filename: "{app}\vaptvupt-gui.exe"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional icons:"
|
||||||
|
Name: "addtopath"; Description: "Add the vaptvupt CLI to PATH (current user)"; GroupDescription: "Command line:"
|
||||||
|
|
||||||
|
[Registry]
|
||||||
|
; Optionally add the install dir to the user PATH (for the vaptvupt.exe CLI).
|
||||||
|
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; \
|
||||||
|
ValueData: "{olddata};{app}"; Tasks: addtopath; Check: NeedsAddPath('{app}')
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\vaptvupt-gui.exe"; Description: "Launch VaptVupt"; \
|
||||||
|
Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
function NeedsAddPath(Param: string): Boolean;
|
||||||
|
var
|
||||||
|
OrigPath: string;
|
||||||
|
begin
|
||||||
|
if not RegQueryStringValue(HKEY_CURRENT_USER, 'Environment', 'Path', OrigPath) then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
Result := Pos(';' + ExpandConstant(Param) + ';', ';' + OrigPath + ';') = 0;
|
||||||
|
end;
|
||||||
|
|
@ -202,6 +202,12 @@ decode_block_tokens_impl(
|
||||||
|
|
||||||
if (VV_UNLIKELY(ip >= ip_end)) break;
|
if (VV_UNLIKELY(ip >= ip_end)) break;
|
||||||
|
|
||||||
|
/* Bound the 2-/3-byte offset read against the compressed-block end.
|
||||||
|
* Without this a crafted block whose last literal advances ip to
|
||||||
|
* ip_end-1 (or ip_end-2 for 3-byte offsets) makes vv_read16 / the
|
||||||
|
* 3-byte load read past the heap buffer. The general/tail decode path
|
||||||
|
* already carries this guard; the AVX2 fast paths were missing it. */
|
||||||
|
if (VV_UNLIKELY(ip + off_bytes > ip_end)) return VV_ERR_CORRUPT;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
if (off_bytes == 2) {
|
if (off_bytes == 2) {
|
||||||
offset = vv_read16(ip);
|
offset = vv_read16(ip);
|
||||||
|
|
@ -285,6 +291,12 @@ decode_block_tokens_impl(
|
||||||
|
|
||||||
if (VV_UNLIKELY(ip >= ip_end)) break;
|
if (VV_UNLIKELY(ip >= ip_end)) break;
|
||||||
|
|
||||||
|
/* Bound the 2-/3-byte offset read against the compressed-block end.
|
||||||
|
* Without this a crafted block whose last literal advances ip to
|
||||||
|
* ip_end-1 (or ip_end-2 for 3-byte offsets) makes vv_read16 / the
|
||||||
|
* 3-byte load read past the heap buffer. The general/tail decode path
|
||||||
|
* already carries this guard; the AVX2 fast paths were missing it. */
|
||||||
|
if (VV_UNLIKELY(ip + off_bytes > ip_end)) return VV_ERR_CORRUPT;
|
||||||
uint32_t offset;
|
uint32_t offset;
|
||||||
if (off_bytes == 2) {
|
if (off_bytes == 2) {
|
||||||
offset = vv_read16(ip);
|
offset = vv_read16(ip);
|
||||||
|
|
|
||||||
|
|
@ -817,7 +817,14 @@ int zupt_hybrid_decrypt_init(zupt_keyring_t *kr, const char *privkeyfile,
|
||||||
const uint8_t *nonce = enc_hdr + 1 + 1088 + 32;
|
const uint8_t *nonce = enc_hdr + 1 + 1088 + 32;
|
||||||
|
|
||||||
uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32];
|
uint8_t ml_pk[1184], x_pk[32], ml_sk[2400], x_sk[32];
|
||||||
if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) return -1;
|
if (read_privkey(privkeyfile, ml_pk, x_pk, ml_sk, x_sk) != 0) {
|
||||||
|
/* Wipe any partially-read secret-key material on error, matching the
|
||||||
|
* pq-only decrypt path (a bad/truncated key file must not leave secret
|
||||||
|
* bytes on the stack). */
|
||||||
|
zupt_secure_wipe(ml_sk, sizeof(ml_sk));
|
||||||
|
zupt_secure_wipe(x_sk, sizeof(x_sk));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ML-KEM-768 decapsulation */
|
/* ML-KEM-768 decapsulation */
|
||||||
uint8_t ml_ss[32];
|
uint8_t ml_ss[32];
|
||||||
|
|
|
||||||
|
|
@ -2911,7 +2911,11 @@ zupt_error_t zupt_archive_info(const char *path) {
|
||||||
* write_enc_header: 7-byte prefix (magic0,magic1,block_type,codec u16,
|
* write_enc_header: 7-byte prefix (magic0,magic1,block_type,codec u16,
|
||||||
* flags u16) + varint(len) + varint(len) + u64 xxh64 + enc_hdr[0]=enc_type. */
|
* flags u16) + varint(len) + varint(len) + u64 xxh64 + enc_hdr[0]=enc_type. */
|
||||||
uint8_t enc_type = 0;
|
uint8_t enc_type = 0;
|
||||||
|
/* encryption_header_off is attacker-controlled; bound it inside the file
|
||||||
|
* before the (off_t)+7 arithmetic so the signed addition cannot overflow
|
||||||
|
* (UB) and the seek stays in-range. All subsequent reads are EOF-checked. */
|
||||||
if ((hdr.global_flags & ZUPT_FLAG_ENCRYPTED) && hdr.encryption_header_off != 0 &&
|
if ((hdr.global_flags & ZUPT_FLAG_ENCRYPTED) && hdr.encryption_header_off != 0 &&
|
||||||
|
hdr.encryption_header_off < file_size && (file_size - hdr.encryption_header_off) > 7 &&
|
||||||
fseeko(f, (off_t)hdr.encryption_header_off + 7, SEEK_SET) == 0) {
|
fseeko(f, (off_t)hdr.encryption_header_off + 7, SEEK_SET) == 0) {
|
||||||
uint64_t l1 = 0, l2 = 0;
|
uint64_t l1 = 0, l2 = 0;
|
||||||
if (zupt_read_varint(f, &l1) > 0 && zupt_read_varint(f, &l2) > 0 &&
|
if (zupt_read_varint(f, &l1) > 0 && zupt_read_varint(f, &l2) > 0 &&
|
||||||
|
|
|
||||||
135
src/zupt_main.c
135
src/zupt_main.c
|
|
@ -12,6 +12,12 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <sys/stat.h> /* stat()/S_ISREG for the compress output-overwrite guard */
|
||||||
|
|
||||||
|
/* MSVC's <sys/stat.h> defines _S_IFREG/S_IFREG but not the S_ISREG macro. */
|
||||||
|
#ifndef S_ISREG
|
||||||
|
# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <conio.h>
|
#include <conio.h>
|
||||||
|
|
@ -23,7 +29,12 @@ static void banner(void) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"%s %s - %s\n"
|
"%s %s - %s\n"
|
||||||
"Format v%d.%d | Codec: VaptVupt + Zupt-LZ | Checksum: XXH64\n"
|
"Format v%d.%d | Codec: VaptVupt + Zupt-LZ | Checksum: XXH64\n"
|
||||||
"Encryption: AES-256-CTR + HMAC-SHA256 | KDF: Argon2id (default) / PBKDF2 (--kdf pbkdf2)\n\n",
|
"Encryption: AES-256-CTR + HMAC-SHA256 | KDF: "
|
||||||
|
#ifdef ZUPT_WITH_SDK
|
||||||
|
"Argon2id (default) / PBKDF2 (--kdf pbkdf2)\n\n",
|
||||||
|
#else
|
||||||
|
"PBKDF2-SHA256 (Argon2id needs a WITH_SDK=1 build)\n\n",
|
||||||
|
#endif
|
||||||
ZUPT_PRODUCT_NAME, ZUPT_VERSION_STRING, ZUPT_PRODUCT_TAGLINE,
|
ZUPT_PRODUCT_NAME, ZUPT_VERSION_STRING, ZUPT_PRODUCT_TAGLINE,
|
||||||
ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR);
|
ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR);
|
||||||
}
|
}
|
||||||
|
|
@ -67,19 +78,25 @@ static void usage(void) {
|
||||||
" -f, --fast Use fast LZ codec (less compression)\n"
|
" -f, --fast Use fast LZ codec (less compression)\n"
|
||||||
" --vv, --vaptvupt Use VaptVupt codec (LZ + ANS entropy, default)\n"
|
" --vv, --vaptvupt Use VaptVupt codec (LZ + ANS entropy, default)\n"
|
||||||
" --lzhp Use Zupt-LZHP codec (LZ77+Huffman, no SIMD needed)\n"
|
" --lzhp Use Zupt-LZHP codec (LZ77+Huffman, no SIMD needed)\n"
|
||||||
" -p, --password <PW> Encrypt with AES-256 (prompted if empty)\n"
|
" -p, --password <PW> Encrypt with AES-256 (bare -p prompts). Options must\n"
|
||||||
" --kdf <argon2id|pbkdf2> KDF for password mode. Default: argon2id (v2.4.1+).\n"
|
" precede <output.zupt>; a value ending in .zupt is\n"
|
||||||
|
" taken as the password, so put -p before the archive.\n"
|
||||||
|
#ifdef ZUPT_WITH_SDK
|
||||||
|
" --kdf <argon2id|pbkdf2> KDF for password mode. Default: argon2id.\n"
|
||||||
" Use 'pbkdf2' for v2.4.0-and-older reader compatibility.\n"
|
" Use 'pbkdf2' for v2.4.0-and-older reader compatibility.\n"
|
||||||
|
#else
|
||||||
|
" --kdf <pbkdf2> KDF for password mode. Default (and only, this build):\n"
|
||||||
|
" PBKDF2-SHA256 600k. Argon2id needs a WITH_SDK=1 build.\n"
|
||||||
|
#endif
|
||||||
" -c, --comment <TEXT> Embed a free-form archive comment (v2.4.3+).\n"
|
" -c, --comment <TEXT> Embed a free-form archive comment (v2.4.3+).\n"
|
||||||
" --comment-file <FILE> Read comment from file (max 4096 bytes).\n"
|
" --comment-file <FILE> Read comment from file (max 4096 bytes).\n"
|
||||||
" --pq <pubkey> Post-quantum HYBRID encryption (ML-KEM-768 + X25519) [recommended]\n"
|
" --pq <pubkey> Post-quantum HYBRID encryption (ML-KEM-768 + X25519) [recommended]\n"
|
||||||
" --pq-only <pubkey> FULL post-quantum encryption (ML-KEM-768 only, no classical layer)\n"
|
" --pq-only <pubkey> FULL post-quantum encryption (ML-KEM-768 only, no classical layer)\n"
|
||||||
" --pq-sdk <pubkey> Post-quantum encryption via libzuptsdk (WITH_SDK=1 builds only)\n"
|
" --pq-sdk <pubkey> Post-quantum encryption via libzuptsdk (WITH_SDK=1 builds only)\n"
|
||||||
" --pq-box <pubkey> Post-quantum sealed box via libpqvaptvupt (HKDF combiner)\n"
|
" --pq-box <pubkey> Post-quantum sealed box via libpqvaptvupt (WITH_SDK=1 builds only)\n"
|
||||||
" (HKDF combiner + key commitment + HPKE binding\n"
|
|
||||||
" + Argon2id; recommended for new archives)\n"
|
|
||||||
" --dedup, -D Block-level deduplication\n"
|
" --dedup, -D Block-level deduplication\n"
|
||||||
" --solid Solid mode (single stream)\n"
|
" --solid Solid mode (single stream)\n"
|
||||||
|
" -y, --force Overwrite an existing non-.zupt file as the output archive\n"
|
||||||
" -v, --verbose Verbose per-file output\n"
|
" -v, --verbose Verbose per-file output\n"
|
||||||
" -t, --threads <N> Thread count (0=auto, 1=single, 2-64=explicit)\n"
|
" -t, --threads <N> Thread count (0=auto, 1=single, 2-64=explicit)\n"
|
||||||
"\n");
|
"\n");
|
||||||
|
|
@ -112,35 +129,44 @@ static void usage(void) {
|
||||||
/* ── Section 4: examples ── */
|
/* ── Section 4: examples ── */
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Examples:\n"
|
"Examples:\n"
|
||||||
" # Legacy PQ workflow\n"
|
" # Post-quantum HYBRID workflow (--pq, recommended)\n"
|
||||||
" vaptvupt keygen -o mykey.key # Generate keypair\n"
|
" vaptvupt keygen -o mykey.key # Generate hybrid private key\n"
|
||||||
" vaptvupt keygen --pub -o pub.key -k mykey.key # Export public key\n"
|
" vaptvupt keygen --pub -o pub.key -k mykey.key # Export public key\n"
|
||||||
" vaptvupt compress --pq pub.key backup.zupt ~/Documents/ # Encrypt\n"
|
" vaptvupt compress --pq pub.key backup.zupt ~/Documents/ # Encrypt\n"
|
||||||
" vaptvupt extract --pq mykey.key backup.zupt -o ~/restored/ # Decrypt\n"
|
" vaptvupt extract --pq mykey.key backup.zupt -o ~/restored/ # Decrypt\n"
|
||||||
"\n"
|
"\n"
|
||||||
" # SDK v2 PQ workflow (recommended for new archives)\n"
|
" # Full (pure) post-quantum workflow (--pq-only, ML-KEM-768 only)\n"
|
||||||
" vaptvupt keygen --sdk -o mykey.priv # Writes mykey.priv + .pub\n"
|
" vaptvupt keygen --pq-only -o pqkey # Generate pq-only private key\n"
|
||||||
" vaptvupt compress --pq-sdk mykey.priv.pub backup.zupt files/ # Encrypt (HKDF+commit+HPKE)\n"
|
" vaptvupt keygen --pub --pq-only -o pqkey.pub -k pqkey # Export public key\n"
|
||||||
" vaptvupt extract --pq-sdk mykey.priv backup.zupt # Decrypt\n"
|
" vaptvupt compress --pq-only pqkey.pub backup.zupt files/ # Encrypt (no classical layer)\n"
|
||||||
|
" vaptvupt extract --pq-only pqkey backup.zupt -o out/ # Decrypt\n"
|
||||||
"\n"
|
"\n"
|
||||||
" # Conventional / password\n"
|
" # Conventional / password (PBKDF2-SHA256)\n"
|
||||||
" vaptvupt compress backup.zupt ~/Documents/ # No encryption\n"
|
" vaptvupt compress backup.zupt ~/Documents/ # No encryption\n"
|
||||||
" vaptvupt compress -l 9 -p mysecret secure.zupt data/ # Password + max compression\n"
|
" vaptvupt compress -l 9 -p mysecret secure.zupt data/ # Password + max compression\n"
|
||||||
" vaptvupt list secure.zupt -p mysecret # List with password\n"
|
" vaptvupt list secure.zupt -p mysecret # List with password\n"
|
||||||
" vaptvupt extract -o restored/ -p mysecret secure.zupt # Extract with password\n"
|
" vaptvupt extract -o restored/ -p mysecret secure.zupt # Extract with password\n"
|
||||||
" vaptvupt bench ~/Documents/ # Benchmark\n"
|
" vaptvupt bench ~/Documents/ # Benchmark\n"
|
||||||
|
"\n"
|
||||||
|
" # SDK v2 / sealed-box modes require an upstream 'make WITH_SDK=1' build:\n"
|
||||||
|
" # keygen --sdk / --box, compress/extract --pq-sdk / --pq-box\n"
|
||||||
"\n");
|
"\n");
|
||||||
|
|
||||||
/* ── Section 5: footer ── */
|
/* ── Section 5: footer ── */
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Default codec: VaptVupt LZ + ANS " ZUPT_CODEC_RELEASE " (AVX2/NEON SIMD)\n"
|
"Default codec: VaptVupt LZ + ANS " ZUPT_CODEC_RELEASE " (AVX2/NEON SIMD)\n"
|
||||||
"Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n"
|
"Encryption: AES-256-CTR + HMAC-SHA256 (Encrypt-then-MAC)\n"
|
||||||
"KDF: Argon2id (default, v2.4.1+); PBKDF2-SHA256 600k iter via --kdf pbkdf2\n"
|
#ifdef ZUPT_WITH_SDK
|
||||||
|
"KDF: Argon2id (default); PBKDF2-SHA256 600k iter via --kdf pbkdf2\n"
|
||||||
|
#else
|
||||||
|
"KDF: PBKDF2-SHA256 600k iter (default; Argon2id needs WITH_SDK=1)\n"
|
||||||
|
#endif
|
||||||
|
"Post-quantum: --pq (hybrid ML-KEM-768 + X25519, recommended); --pq-only (ML-KEM-768 only)\n"
|
||||||
"Format: v1.6 (since v2.3.1); archives byte-compatible with v2.3.1+\n"
|
"Format: v1.6 (since v2.3.1); archives byte-compatible with v2.3.1+\n"
|
||||||
"\n"
|
"\n"
|
||||||
"License: AGPL-3.0-or-later (VaptVupt) + GPL-3.0-or-later (VaptVupt codec)\n"
|
"License: AGPL-3.0-or-later (VaptVupt) + GPL-3.0-or-later (VaptVupt codec)\n"
|
||||||
" Dual-licensed: commercial license available: sac@securityops.co\n"
|
" Dual-licensed: commercial license available: sac@securityops.co\n"
|
||||||
"Project: https://git.securityops.co/cristiancmoises/zupt\n"
|
"Project: https://git.securityops.co/cristiancmoises/vaptvupt\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,11 +217,18 @@ int main(int argc, char **argv) {
|
||||||
"Format: v%d.%d | Archive extension: .zupt (unchanged)\n"
|
"Format: v%d.%d | Archive extension: .zupt (unchanged)\n"
|
||||||
"Codec: VaptVupt " ZUPT_CODEC_RELEASE " (0x%04X) — LZ + ANS, optimal parser + large-window extreme\n"
|
"Codec: VaptVupt " ZUPT_CODEC_RELEASE " (0x%04X) — LZ + ANS, optimal parser + large-window extreme\n"
|
||||||
"Encryption: AES-256-CTR + HMAC-SHA256\n"
|
"Encryption: AES-256-CTR + HMAC-SHA256\n"
|
||||||
|
#ifdef ZUPT_WITH_SDK
|
||||||
"KDF: Argon2id (default) / PBKDF2-SHA256 %d iter (--kdf pbkdf2)\n"
|
"KDF: Argon2id (default) / PBKDF2-SHA256 %d iter (--kdf pbkdf2)\n"
|
||||||
"Post-quantum: ML-KEM-768 + X25519 hybrid (FIPS 203 + RFC 7748)\n"
|
"Post-quantum: --pq hybrid (ML-KEM-768 + X25519), --pq-only (ML-KEM-768), --pq-sdk/--pq-box (libzuptsdk)\n"
|
||||||
|
"Build: full (libzuptsdk: Argon2id, --pq-sdk, --pq-box available)\n"
|
||||||
|
#else
|
||||||
|
"KDF: PBKDF2-SHA256 %d iter (default; Argon2id needs WITH_SDK=1)\n"
|
||||||
|
"Post-quantum: --pq hybrid (ML-KEM-768 + X25519), --pq-only (ML-KEM-768 only) — FIPS 203 + RFC 7748\n"
|
||||||
|
"Build: source-only (native crypto; --pq-sdk/--pq-box/Argon2id need WITH_SDK=1)\n"
|
||||||
|
#endif
|
||||||
"License: AGPL-3.0-or-later (VaptVupt) + GPL-3.0-or-later (codec)\n"
|
"License: AGPL-3.0-or-later (VaptVupt) + GPL-3.0-or-later (codec)\n"
|
||||||
" Dual-licensed: commercial license available\n"
|
" Dual-licensed: commercial license available\n"
|
||||||
"Project: https://git.securityops.co/cristiancmoises/zupt\n"
|
"Project: https://git.securityops.co/cristiancmoises/vaptvupt\n"
|
||||||
"Commercial: sac@securityops.co\n",
|
"Commercial: sac@securityops.co\n",
|
||||||
ZUPT_VERSION_STRING, ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR,
|
ZUPT_VERSION_STRING, ZUPT_FORMAT_MAJOR, ZUPT_FORMAT_MINOR,
|
||||||
ZUPT_CODEC_VAPTVUPT, ZUPT_KDF_ITERATIONS);
|
ZUPT_CODEC_VAPTVUPT, ZUPT_KDF_ITERATIONS);
|
||||||
|
|
@ -219,6 +252,7 @@ int main(int argc, char **argv) {
|
||||||
if (streq(cmd,"compress")||streq(cmd,"c")) {
|
if (streq(cmd,"compress")||streq(cmd,"c")) {
|
||||||
zupt_options_t opts; zupt_default_options(&opts);
|
zupt_options_t opts; zupt_default_options(&opts);
|
||||||
int ai = 2;
|
int ai = 2;
|
||||||
|
int force = 0; /* -y/--force: allow overwriting a non-.zupt output */
|
||||||
while (ai<argc && isopt(argv[ai])) {
|
while (ai<argc && isopt(argv[ai])) {
|
||||||
if ((streq(argv[ai],"-l")||streq(argv[ai],"--level"))&&ai+1<argc) {
|
if ((streq(argv[ai],"-l")||streq(argv[ai],"--level"))&&ai+1<argc) {
|
||||||
opts.level=atoi(argv[++ai]); if(opts.level<1)opts.level=1; if(opts.level>9)opts.level=9;
|
opts.level=atoi(argv[++ai]); if(opts.level<1)opts.level=1; if(opts.level>9)opts.level=9;
|
||||||
|
|
@ -260,18 +294,6 @@ int main(int argc, char **argv) {
|
||||||
} else if (streq(argv[ai],"--pq-sdk")&&ai+1<argc) {
|
} else if (streq(argv[ai],"--pq-sdk")&&ai+1<argc) {
|
||||||
opts.pq_mode=1; opts.sdk_mode=1; opts.encrypt=1;
|
opts.pq_mode=1; opts.sdk_mode=1; opts.encrypt=1;
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
||||||
} else if (streq(argv[ai],"--pq-box")&&ai+1<argc) {
|
|
||||||
opts.pq_mode=1; opts.box_mode=1; opts.encrypt=1;
|
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
|
||||||
} else if (streq(argv[ai],"--pq-sdk")&&ai+1<argc) {
|
|
||||||
opts.pq_mode=1; opts.sdk_mode=1; opts.encrypt=1;
|
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
|
||||||
} else if (streq(argv[ai],"--pq-box")&&ai+1<argc) {
|
|
||||||
opts.pq_mode=1; opts.box_mode=1; opts.encrypt=1;
|
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
|
||||||
} else if (streq(argv[ai],"--pq-sdk")&&ai+1<argc) {
|
|
||||||
opts.pq_mode=1; opts.sdk_mode=1; opts.encrypt=1;
|
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
|
||||||
} else if (streq(argv[ai],"--pq-only")&&ai+1<argc) {
|
} else if (streq(argv[ai],"--pq-only")&&ai+1<argc) {
|
||||||
opts.pqonly_mode=1; opts.encrypt=1;
|
opts.pqonly_mode=1; opts.encrypt=1;
|
||||||
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
strncpy(opts.keyfile, argv[++ai], sizeof(opts.keyfile)-1);
|
||||||
|
|
@ -314,6 +336,8 @@ int main(int argc, char **argv) {
|
||||||
fprintf(stderr, "Error: --kdf must be 'argon2id' or 'pbkdf2', got '%s'\n", argv[ai]);
|
fprintf(stderr, "Error: --kdf must be 'argon2id' or 'pbkdf2', got '%s'\n", argv[ai]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
} else if (streq(argv[ai],"-y")||streq(argv[ai],"--force")) {
|
||||||
|
force = 1;
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr,"Error: Unknown option '%s'\n",argv[ai]); return 1;
|
fprintf(stderr,"Error: Unknown option '%s'\n",argv[ai]); return 1;
|
||||||
}
|
}
|
||||||
|
|
@ -324,10 +348,59 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
const char *output = argv[ai++];
|
const char *output = argv[ai++];
|
||||||
|
|
||||||
/* Collect files (expand directories recursively) */
|
/* Reject a misplaced option among the file positionals. Without this,
|
||||||
|
* `compress out.zupt dir -p secret` silently treats "-p"/"secret" as
|
||||||
|
* (skipped) input files and writes an UNENCRYPTED archive with exit 0
|
||||||
|
* — the user believes it is encrypted. Options must precede the
|
||||||
|
* output archive (use `--` before a real filename that starts with '-'). */
|
||||||
|
int seen_dashdash = 0;
|
||||||
|
for (int i=ai; i<argc; i++) {
|
||||||
|
if (!seen_dashdash && streq(argv[i],"--")) { seen_dashdash = 1; continue; }
|
||||||
|
if (!seen_dashdash && isopt(argv[i])) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: option '%s' appears after the output archive.\n"
|
||||||
|
" In compress, all options (including -p/--pq) must come BEFORE\n"
|
||||||
|
" the output archive name. Example:\n"
|
||||||
|
" vaptvupt compress -p PASSWORD %s %s ...\n",
|
||||||
|
argv[i], output, (i>ai ? argv[ai] : "<files>"));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Data-loss guard. `compress -p out.zupt a.txt b.txt` makes -p swallow
|
||||||
|
* "out.zupt" as the PASSWORD, shifts positionals so the output archive
|
||||||
|
* becomes "a.txt", and truncates a.txt (a user data file) with archive
|
||||||
|
* bytes — silently, exit 0. Refuse to overwrite an existing regular file
|
||||||
|
* that is not a .zupt archive unless -y/--force is given. Archives the
|
||||||
|
* tool writes end in .zupt, so this never blocks normal use. */
|
||||||
|
{
|
||||||
|
struct stat ost;
|
||||||
|
size_t olen = strlen(output);
|
||||||
|
int is_zupt = (olen >= 5 && strcmp(output + olen - 5, ".zupt") == 0);
|
||||||
|
if (!force && !is_zupt && stat(output, &ost) == 0 && S_ISREG(ost.st_mode)) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Error: refusing to overwrite existing file '%s' as the output archive\n"
|
||||||
|
" (it does not end in .zupt). If you meant to set a password, use\n"
|
||||||
|
" '-p<password>' or put '-p PASSWORD' BEFORE the archive name.\n"
|
||||||
|
" Pass -y/--force to overwrite '%s' anyway.\n",
|
||||||
|
output, output);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip a leading `--` separator before the file list. */
|
||||||
|
if (ai < argc && streq(argv[ai], "--")) ai++;
|
||||||
|
|
||||||
|
/* Collect files (expand directories recursively). Guard against the
|
||||||
|
* output archive also being one of the inputs (self-overwrite). */
|
||||||
zupt_filelist_t fl; zupt_filelist_init(&fl);
|
zupt_filelist_t fl; zupt_filelist_init(&fl);
|
||||||
for (int i=ai; i<argc; i++)
|
for (int i=ai; i<argc; i++) {
|
||||||
|
if (streq(argv[i], output)) {
|
||||||
|
fprintf(stderr, "Error: input '%s' is the same as the output archive.\n", argv[i]);
|
||||||
|
zupt_filelist_free(&fl); return 1;
|
||||||
|
}
|
||||||
zupt_collect_files(&fl, argv[i], argv[i]);
|
zupt_collect_files(&fl, argv[i], argv[i]);
|
||||||
|
}
|
||||||
|
|
||||||
if (fl.count == 0) {
|
if (fl.count == 0) {
|
||||||
fprintf(stderr, "Error: No files found.\n");
|
fprintf(stderr, "Error: No files found.\n");
|
||||||
|
|
|
||||||
|
|
@ -109,11 +109,19 @@ else
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── KDF consistency ───
|
# ─── KDF consistency ───
|
||||||
# Argon2id is the default since v2.4.1; the help must say so.
|
# The help must state the ACTUAL default KDF for this build: PBKDF2-SHA256 on
|
||||||
if echo "$HELP" | grep -qE "Argon2id.*default"; then
|
# the source-only build (WITH_SDK=0), Argon2id only when built with WITH_SDK=1.
|
||||||
P "help correctly identifies Argon2id as the default KDF"
|
# A build that advertises Argon2id-by-default but derives PBKDF2 keys overstates
|
||||||
|
# its GPU/ASIC resistance (regression from v4.2.1).
|
||||||
|
if echo "$HELP" | grep -qiE "argon2id.*WITH_SDK=1"; then
|
||||||
|
P "help correctly scopes Argon2id to WITH_SDK=1 (source-only build)"
|
||||||
|
elif echo "$HELP" | grep -qE "PBKDF2.*[Dd]efault|[Dd]efault.*PBKDF2"; then
|
||||||
|
P "help correctly identifies PBKDF2-SHA256 as the default KDF"
|
||||||
|
elif echo "$HELP" | grep -qE "Argon2id.*[Dd]efault"; then
|
||||||
|
# A WITH_SDK=1 build legitimately defaults to Argon2id.
|
||||||
|
P "help identifies Argon2id as the default KDF (WITH_SDK=1 build)"
|
||||||
else
|
else
|
||||||
F "help doesn't identify Argon2id as the default KDF"
|
F "help does not state the default password KDF"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─── Format consistency ───
|
# ─── Format consistency ───
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue