mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Harden authenticated Desktop release recovery
This commit is contained in:
1 parent
37d18c872b
commit
d1385aa47e
4 files changed
+136
-8
No files matched your search
@@ -869,7 +869,18 @@ jobs:
|
||||
"$signature_verifier" verify "$payload"
|
||||
done < <(find "$directory" -type f -name '*.minisig' -print0)
|
||||
}
|
||||
require_signature_coverage() {
|
||||
local directory="$1"
|
||||
local payload
|
||||
while IFS= read -r -d '' payload; do
|
||||
if [ ! -s "$payload.minisig" ]; then
|
||||
echo "::error::Desktop payload has no non-empty signature: $payload"
|
||||
return 1
|
||||
fi
|
||||
done < <(find "$directory" -type f ! -name '*.minisig' ! -name 'latest.json' -print0)
|
||||
}
|
||||
verify_signature_directory assets
|
||||
require_signature_coverage assets
|
||||
|
||||
# A version directory is immutable once written. Recovery may fill an
|
||||
# incomplete candidate subset, but it may never replace conflicting or
|
||||
@@ -889,22 +900,26 @@ jobs:
|
||||
verify_signature_directory "$existing_directory"
|
||||
bash release-control/scripts/verify-desktop-release-directory.sh \
|
||||
--allow-missing --allow-legacy-manifest \
|
||||
--allow-signature-differences assets "$existing_directory"
|
||||
--allow-authenticated-payload-differences assets "$existing_directory"
|
||||
if [ -f "$existing_directory/latest.json" ]; then
|
||||
existing_manifest=true
|
||||
bash release-control/scripts/validate-desktop-release-manifest.sh \
|
||||
"legacy-${validation_channel}" "$VERSION" "$asset_base" \
|
||||
"$existing_directory/latest.json" "$NOTES_VERSION"
|
||||
bash release-control/scripts/compare-desktop-release-manifests.sh \
|
||||
assets/latest.json "$existing_directory/latest.json"
|
||||
bash release-control/scripts/verify-desktop-release-manifest-assets.sh \
|
||||
"$existing_directory/latest.json" "$existing_directory"
|
||||
cp "$existing_directory/latest.json" assets/latest.json
|
||||
fi
|
||||
|
||||
# Preserve already-published valid signature bytes. Minisign
|
||||
# signatures are intentionally non-deterministic, so a recovery
|
||||
# may fill missing signatures but must not replace existing ones.
|
||||
# Preserve every already-published authenticated payload/signature
|
||||
# pair. Platform signing and packaging are non-deterministic, so a
|
||||
# recovery may fill missing pairs but must not replace valid ones.
|
||||
while IFS= read -r -d '' signature; do
|
||||
relative="${signature#"$existing_directory"/}"
|
||||
payload="${signature%.minisig}"
|
||||
payload_relative="${payload#"$existing_directory"/}"
|
||||
mkdir -p "assets/$(dirname "$relative")"
|
||||
cp "$payload" "assets/$payload_relative"
|
||||
cp "$signature" "assets/$relative"
|
||||
done < <(find "$existing_directory" -type f -name '*.minisig' -print0)
|
||||
fi
|
||||
@@ -926,6 +941,9 @@ jobs:
|
||||
bash release-control/scripts/verify-desktop-release-directory.sh \
|
||||
--allow-legacy-manifest assets "$published_directory"
|
||||
verify_signature_directory "$published_directory"
|
||||
require_signature_coverage "$published_directory"
|
||||
bash release-control/scripts/verify-desktop-release-manifest-assets.sh \
|
||||
"$published_directory/latest.json" "$published_directory"
|
||||
|
||||
aws s3 cp "s3://${R2_BUCKET}/${TAG}/latest.json" \
|
||||
/tmp/reasonix-desktop-tag-latest.json --endpoint-url "$ENDPOINT"
|
||||
|
||||
@@ -175,9 +175,11 @@ grep -Fq 'go -C release-control/desktop build -o "$signature_verifier" ./cmd/sig
|
||||
grep -Fq 'verify_signature_directory assets' "$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'verify_signature_directory "$existing_directory"' \
|
||||
"$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq -- '--allow-signature-differences' \
|
||||
grep -Fq -- '--allow-authenticated-payload-differences' \
|
||||
"$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'cp "$payload" "assets/$payload_relative"' "$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'cp "$signature" "assets/$relative"' "$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'verify-desktop-release-manifest-assets.sh' "$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'verify_signature_directory "$published_directory"' \
|
||||
"$repo_root/.github/workflows/release-desktop.yml"
|
||||
grep -Fq 'download_optional "preview/latest.json"' "$repo_root/.github/workflows/release-desktop.yml"
|
||||
@@ -614,6 +616,25 @@ if bash "$desktop_directory_verifier" "$candidate_directory" "$existing_director
|
||||
fi
|
||||
bash "$desktop_directory_verifier" --allow-signature-differences \
|
||||
"$candidate_directory" "$existing_directory"
|
||||
printf 'candidate-payload\n' >"$candidate_directory/signed"
|
||||
printf 'existing-payload\n' >"$existing_directory/signed"
|
||||
printf 'candidate-signature\n' >"$candidate_directory/signed.minisig"
|
||||
printf 'existing-signature\n' >"$existing_directory/signed.minisig"
|
||||
if bash "$desktop_directory_verifier" --allow-signature-differences \
|
||||
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
|
||||
echo "Desktop release directory verifier accepted a byte-distinct payload without authenticated-payload opt-in" >&2
|
||||
exit 1
|
||||
fi
|
||||
bash "$desktop_directory_verifier" --allow-authenticated-payload-differences \
|
||||
"$candidate_directory" "$existing_directory"
|
||||
printf '' >"$existing_directory/signed.minisig"
|
||||
if bash "$desktop_directory_verifier" --allow-authenticated-payload-differences \
|
||||
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
|
||||
echo "Desktop release directory verifier accepted a byte-distinct payload with an empty signature" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$candidate_directory/signed" "$existing_directory/signed"
|
||||
cp "$candidate_directory/signed.minisig" "$existing_directory/signed.minisig"
|
||||
printf '' >"$existing_directory/a.minisig"
|
||||
if bash "$desktop_directory_verifier" --allow-signature-differences \
|
||||
"$candidate_directory" "$existing_directory" >/dev/null 2>&1; then
|
||||
@@ -661,6 +682,38 @@ if bash "$desktop_directory_verifier" --allow-legacy-manifest \
|
||||
exit 1
|
||||
fi
|
||||
|
||||
desktop_manifest_asset_verifier="$repo_root/scripts/verify-desktop-release-manifest-assets.sh"
|
||||
test -x "$desktop_manifest_asset_verifier"
|
||||
manifest_asset_directory="$test_root/desktop-manifest-assets"
|
||||
mkdir -p "$manifest_asset_directory"
|
||||
printf 'manifest-bound-payload\n' >"$manifest_asset_directory/payload.zip"
|
||||
printf 'manifest-bound-native\n' >"$manifest_asset_directory/payload.deb"
|
||||
manifest_asset_sha="$(shasum -a 256 "$manifest_asset_directory/payload.zip" | awk '{print $1}')"
|
||||
manifest_asset_size="$(wc -c <"$manifest_asset_directory/payload.zip" | tr -d '[:space:]')"
|
||||
manifest_native_sha="$(shasum -a 256 "$manifest_asset_directory/payload.deb" | awk '{print $1}')"
|
||||
manifest_native_size="$(wc -c <"$manifest_asset_directory/payload.deb" | tr -d '[:space:]')"
|
||||
jq -n \
|
||||
--arg url "https://dl.reasonix.io/desktop-v1.2.3/payload.zip" \
|
||||
--arg sha "$manifest_asset_sha" \
|
||||
--argjson size "$manifest_asset_size" \
|
||||
--arg native_url "https://dl.reasonix.io/desktop-v1.2.3/payload.deb" \
|
||||
--arg native_sha "$manifest_native_sha" \
|
||||
--argjson native_size "$manifest_native_size" \
|
||||
'{
|
||||
platforms: {test: {url: $url, sha256: $sha, size: $size}},
|
||||
native_packages: {test: {url: $native_url, sha256: $native_sha, size: $native_size}},
|
||||
downloads: {}
|
||||
}' \
|
||||
>"$manifest_asset_directory/latest.json"
|
||||
bash "$desktop_manifest_asset_verifier" \
|
||||
"$manifest_asset_directory/latest.json" "$manifest_asset_directory"
|
||||
printf 'corrupted-payload\n' >"$manifest_asset_directory/payload.zip"
|
||||
if bash "$desktop_manifest_asset_verifier" \
|
||||
"$manifest_asset_directory/latest.json" "$manifest_asset_directory" >/dev/null 2>&1; then
|
||||
echo "Desktop release manifest asset verifier accepted a mismatched payload" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# GitHub release recovery uses the same immutable-subset rule: an interrupted
|
||||
# release may fill missing assets, while conflicting or extra assets fail closed.
|
||||
desktop_github_publisher="$repo_root/scripts/publish-desktop-github-release.sh"
|
||||
|
||||
@@ -4,6 +4,7 @@ set -euo pipefail
|
||||
allow_missing=false
|
||||
allow_legacy_manifest=false
|
||||
allow_signature_differences=false
|
||||
allow_authenticated_payload_differences=false
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--allow-missing)
|
||||
@@ -21,11 +22,20 @@ while [ "$#" -gt 0 ]; do
|
||||
allow_signature_differences=true
|
||||
shift
|
||||
;;
|
||||
--allow-authenticated-payload-differences)
|
||||
# Callers must cryptographically verify both complete payload/signature
|
||||
# sets before using this comparison mode. Signed Desktop packages can be
|
||||
# byte-distinct across rebuilds because platform signing and packaging
|
||||
# embed timestamps and other non-deterministic data.
|
||||
allow_authenticated_payload_differences=true
|
||||
allow_signature_differences=true
|
||||
shift
|
||||
;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "usage: $0 [--allow-missing] [--allow-legacy-manifest] [--allow-signature-differences] CANDIDATE_DIRECTORY EXISTING_DIRECTORY" >&2
|
||||
echo "usage: $0 [--allow-missing] [--allow-legacy-manifest] [--allow-signature-differences] [--allow-authenticated-payload-differences] CANDIDATE_DIRECTORY EXISTING_DIRECTORY" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
@@ -63,6 +73,10 @@ verify_subset() {
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ "$allow_authenticated_payload_differences" = "true" ] &&
|
||||
[ -s "$source_file.minisig" ] && [ -s "$target_file.minisig" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! cmp -s "$source_file" "$target_file"; then
|
||||
echo "Desktop release directory has conflicting content for $relative" >&2
|
||||
return 1
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
manifest="${1:-}"
|
||||
asset_dir="${2:-}"
|
||||
|
||||
if [ ! -f "$manifest" ] || [ ! -d "$asset_dir" ]; then
|
||||
echo "usage: $0 MANIFEST ASSET_DIRECTORY" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
entries="$(mktemp)"
|
||||
trap 'rm -f "$entries"' EXIT
|
||||
jq -er '
|
||||
[.platforms, .native_packages, (.downloads // {})]
|
||||
| map(to_entries)
|
||||
| add[]
|
||||
| [.value.url, .value.sha256, (.value.size | tostring)]
|
||||
| @tsv
|
||||
' "$manifest" >"$entries"
|
||||
|
||||
while IFS=$'\t' read -r url expected_sha expected_size; do
|
||||
name="${url##*/}"
|
||||
if [[ ! "$name" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
||||
echo "Desktop release manifest has an unsafe asset URL: $url" >&2
|
||||
exit 1
|
||||
fi
|
||||
asset="$asset_dir/$name"
|
||||
if [ ! -f "$asset" ]; then
|
||||
echo "Desktop release manifest asset is missing: $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_size="$(wc -c <"$asset" | tr -d '[:space:]')"
|
||||
if [ "$actual_size" != "$expected_size" ]; then
|
||||
echo "Desktop release manifest size does not match $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_sha="$(shasum -a 256 "$asset" | awk '{print $1}')"
|
||||
if [ "$actual_sha" != "$expected_sha" ]; then
|
||||
echo "Desktop release manifest digest does not match $name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done <"$entries"
|
||||
Reference in new issue
Block a user