release: restore ZUPT and harden source-only 5.2.2
This commit is contained in:
parent
74e393ba3e
commit
ff99770bd0
205 changed files with 19627 additions and 13215 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# Copyright (c) 2025-2026 Cristian Cezar Moisés
|
||||
#
|
||||
# F-09 regression test (Zupt 2.3.1).
|
||||
# F-09 regression test (VaptVupt 2.3.1).
|
||||
#
|
||||
# F-09 closed the per-block frame preface tamper window by:
|
||||
# 1. Binding the canonical preface (block_type, codec_id, block_flags,
|
||||
|
|
@ -12,18 +12,20 @@
|
|||
# block's frame preface in read_enc_header (same pattern as F-07
|
||||
# for the index block in v2.2.5).
|
||||
#
|
||||
# This test does the full exhaustive byte sweep on a small v1.6 PQ-SDK
|
||||
# archive: every byte from 0 to N-1 is flipped one at a time, and we
|
||||
# assert the extract fails for ALL of them. With pre-F-09 code this
|
||||
# would show 15-18 silent acceptances; post-F-09 it must show zero.
|
||||
# This test flips every serialized block-preface byte in a small v1.6 PBKDF2
|
||||
# archive and asserts that each mutation is rejected. With pre-F-09 code this
|
||||
# would show silent acceptances; post-F-09 it must show zero. A PQ-SDK archive
|
||||
# receives the historical full-archive byte sweep when system libvuptsdk is
|
||||
# enabled.
|
||||
#
|
||||
# Why limit to PQ-SDK encrypted: plaintext archives have no HMAC at
|
||||
# all (XXH64 best-effort only), so per-byte coverage is intentionally
|
||||
# weaker and a different, separately-tracked promise.
|
||||
# Plaintext archives have no HMAC (XXH64 best-effort only), so per-byte
|
||||
# coverage is intentionally weaker and a different, separately-tracked
|
||||
# promise.
|
||||
|
||||
set -u
|
||||
set -Eeuo pipefail
|
||||
|
||||
ZUPT="${ZUPT_BIN:-./zupt}"
|
||||
repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd -P)
|
||||
ZUPT="${ZUPT_BIN:-$repo_root/zupt}"
|
||||
case "$ZUPT" in
|
||||
/*) ;;
|
||||
*) ZUPT="$PWD/$ZUPT" ;;
|
||||
|
|
@ -33,58 +35,129 @@ if [ ! -x "$ZUPT" ]; then
|
|||
echo " ✗ $ZUPT not found — run 'make' first" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo ' ✗ python3 is required for byte-level archive mutations' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
cd "$TMPDIR"
|
||||
cd "$TMPDIR" || exit 1
|
||||
|
||||
"$ZUPT" keygen --sdk -o k.priv >/dev/null 2>&1
|
||||
echo "F-09 regression test payload" > input.txt
|
||||
"$ZUPT" c --pq-sdk k.priv.pub a.zupt input.txt >/dev/null 2>&1
|
||||
printf 'F-09 regression test payload\n' > input.txt
|
||||
printf 'source-only-preface-password\n' > password.txt
|
||||
chmod 600 password.txt
|
||||
|
||||
SZ=$(wc -c < a.zupt)
|
||||
if [ "$SZ" -lt 100 ] || [ "$SZ" -gt 10000 ]; then
|
||||
echo " ✗ unexpected archive size $SZ" >&2
|
||||
exit 1
|
||||
fi
|
||||
run_sweep() {
|
||||
local label=$1
|
||||
local archive=$2
|
||||
local scope=$3
|
||||
shift 3
|
||||
local -a auth_options=("$@")
|
||||
local -a positions=()
|
||||
local size
|
||||
local position
|
||||
local positions_output
|
||||
local tested=0
|
||||
local undetected_positions=''
|
||||
local undetected_count
|
||||
local clean_dir="clean-$label"
|
||||
|
||||
# Sanity: clean archive extracts.
|
||||
mkdir -p clean
|
||||
( cd clean && "$ZUPT" x --pq-sdk ../k.priv ../a.zupt >/dev/null 2>&1 )
|
||||
if [ ! -f clean/input.txt ]; then
|
||||
echo " ✗ clean v1.6 PQ-SDK archive doesn't extract" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Exhaustive sweep.
|
||||
echo " [F-09: exhaustive byte sweep of $SZ-byte v1.6 PQ-SDK archive]"
|
||||
UNDETECTED_POSITIONS=""
|
||||
TAMPER_SAMPLED=0
|
||||
for POS in $(seq 0 $((SZ - 1))); do
|
||||
cp a.zupt t.zupt
|
||||
python3 -c "
|
||||
b=bytearray(open('t.zupt','rb').read())
|
||||
b[$POS] ^= 1
|
||||
open('t.zupt','wb').write(bytes(b))"
|
||||
rm -rf out && mkdir out
|
||||
( cd out && "$ZUPT" x --pq-sdk ../k.priv ../t.zupt >/dev/null 2>&1 )
|
||||
TAMPER_SAMPLED=$((TAMPER_SAMPLED + 1))
|
||||
if [ -f out/input.txt ]; then
|
||||
UNDETECTED_POSITIONS="$UNDETECTED_POSITIONS $POS"
|
||||
size=$(wc -c < "$archive")
|
||||
if [ "$size" -lt 100 ] || [ "$size" -gt 10000 ]; then
|
||||
echo " ✗ $label archive has unexpected size $size" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
UNDETECTED_COUNT=$(echo $UNDETECTED_POSITIONS | wc -w)
|
||||
mkdir "$clean_dir"
|
||||
if ! "$ZUPT" extract "${auth_options[@]}" -o "$clean_dir" "$archive" \
|
||||
>/dev/null 2>&1 ||
|
||||
! cmp input.txt "$clean_dir/input.txt" >/dev/null 2>&1; then
|
||||
echo " ✗ clean $label archive does not extract byte-exact" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " ───────────────────────────────────────"
|
||||
if [ "$UNDETECTED_COUNT" = 0 ]; then
|
||||
echo " F-09 regression: $TAMPER_SAMPLED tamper positions tested, 0 silent-accepted ✓"
|
||||
echo " ───────────────────────────────────────"
|
||||
exit 0
|
||||
else
|
||||
echo " F-09 regression: $UNDETECTED_COUNT silent-accepted positions (must be 0)"
|
||||
echo " positions:$UNDETECTED_POSITIONS"
|
||||
echo " ───────────────────────────────────────"
|
||||
if [ "$scope" = preface ]; then
|
||||
if ! positions_output=$(python3 \
|
||||
"$repo_root/tests/archive_surgery.py" preface-positions \
|
||||
"$archive") || [ -z "$positions_output" ]; then
|
||||
echo " ✗ could not locate $label block prefaces" >&2
|
||||
return 1
|
||||
fi
|
||||
while IFS= read -r position; do
|
||||
[ -n "$position" ] && positions+=("$position")
|
||||
done <<<"$positions_output"
|
||||
echo " [F-09: all block-preface bytes in $size-byte $label archive]"
|
||||
elif [ "$scope" = full ]; then
|
||||
for ((position = 0; position < size; position++)); do
|
||||
positions+=("$position")
|
||||
done
|
||||
echo " [F-09: exhaustive byte sweep of $size-byte $label archive]"
|
||||
else
|
||||
echo " ✗ internal error: unknown sweep scope $scope" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
for position in "${positions[@]}"; do
|
||||
if ! python3 - "$archive" t.zupt "$position" <<'PY'
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
source = pathlib.Path(sys.argv[1]).read_bytes()
|
||||
mutated = bytearray(source)
|
||||
mutated[int(sys.argv[3])] ^= 0x01
|
||||
pathlib.Path(sys.argv[2]).write_bytes(mutated)
|
||||
PY
|
||||
then
|
||||
echo " ✗ could not mutate $label archive byte $position" >&2
|
||||
return 1
|
||||
fi
|
||||
tested=$((tested + 1))
|
||||
if "$ZUPT" test "${auth_options[@]}" t.zupt >/dev/null 2>&1; then
|
||||
undetected_positions="$undetected_positions $position"
|
||||
fi
|
||||
done
|
||||
|
||||
undetected_count=$(printf '%s\n' "$undetected_positions" | wc -w)
|
||||
if [ "$undetected_count" -ne 0 ]; then
|
||||
echo " ✗ $label: $undetected_count silent-accepted positions (must be 0)"
|
||||
echo " positions:$undetected_positions"
|
||||
return 1
|
||||
fi
|
||||
echo " ✓ $label: $tested tamper positions tested, 0 accepted"
|
||||
}
|
||||
|
||||
FAIL=0
|
||||
|
||||
if ! "$ZUPT" compress --store --kdf pbkdf2 --pass-file password.txt \
|
||||
pbkdf2.zupt input.txt >/dev/null 2>&1; then
|
||||
echo ' ✗ could not create source-only PBKDF2 archive' >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! run_sweep PBKDF2 pbkdf2.zupt preface --pass-file password.txt; then
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
|
||||
version=$("$ZUPT" --version 2>&1)
|
||||
if grep -Fq 'libvuptsdk=enabled' <<<"$version"; then
|
||||
if ! "$ZUPT" keygen --sdk -o k.priv >/dev/null 2>&1 ||
|
||||
! "$ZUPT" compress --store --pq-sdk k.priv.pub pq-sdk.zupt \
|
||||
input.txt >/dev/null 2>&1; then
|
||||
echo ' ✗ could not create PQ-SDK archive' >&2
|
||||
FAIL=$((FAIL + 1))
|
||||
elif ! run_sweep PQ-SDK pq-sdk.zupt full --pq-sdk k.priv; then
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
else
|
||||
echo ' SKIP: additional PQ-SDK sweep needs WITH_SDK=1 and system libvuptsdk'
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " ───────────────────────────────────────"
|
||||
if [ "$FAIL" -eq 0 ]; then
|
||||
echo " F-09 regression: PASS"
|
||||
else
|
||||
echo " F-09 regression: FAIL ($FAIL archive variants)"
|
||||
fi
|
||||
echo " ───────────────────────────────────────"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
|
|
|||
Loading…
Reference in a new issue