Fix: Harden installer provenance boundary

This commit is contained in:
Gabriel Brown
2026-08-27 05:56:46 -04:00
parent 27acbe7a95
commit 94a4314730
8 changed files with 245 additions and 19 deletions
+62 -2
View File
@@ -47,6 +47,14 @@ snapshot() {
fi
}
snapshot_gpg_state() {
local path="$1" file
[[ -d "$path" ]] || return 0
while IFS= read -r file; do
sha256sum "$file"
done < <(find "$path" -maxdepth 1 -type f \( -name pubring.kbx -o -name trustdb.gpg \) | sort)
}
# This must be the only production file sourced by the contract.
# shellcheck source=../../setup/lib/artifact-provenance
source "$repo_dir/setup/lib/artifact-provenance"
@@ -55,30 +63,46 @@ fixture_fingerprint='6016FF18CAE298CE3648EE2325E01F765E1EF9FA'
tiny_sha256='291bd319ae85488101e908e37fc0fa1b0da1429ba27e10d2b391cb3f60dd44ea'
base64 --decode "$fixtures/signed-fixture.rpm.base64" > "$test_tmp/signed-fixture.rpm"
base64 --decode "$fixtures/unsigned-fixture.rpm.base64" > "$test_tmp/unsigned-fixture.rpm"
base64 --decode "$fixtures/wrong-signer-fixture.rpm.base64" > "$test_tmp/wrong-signer-fixture.rpm"
[[ "$(wc -l < "$fixtures/SHASUMS256.txt")" -eq 4 ]] || fail 'signed checksum fixture is not four lines'
[[ "$(cmp -l "$fixtures/tiny-artifact" "$fixtures/tiny-artifact-tampered" | wc -l)" -eq 1 ]] \
|| fail 'tampered artifact does not differ by exactly one byte'
expect_success key_fingerprint_matches "$fixtures/fixture-key.asc" "$fixture_fingerprint"
expect_failure key_fingerprint_matches "$fixtures/fixture-key.asc" '0000000000000000000000000000000000000000'
cat "$fixtures/fixture-key.asc" "$fixtures/wrong-signer-key.asc" > "$test_tmp/combined-key.asc"
expect_failure key_fingerprint_matches "$test_tmp/combined-key.asc" "$fixture_fingerprint"
expect_success verify_detached_signature \
"$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt"
expect_failure verify_detached_signature \
"$fixtures/fixture-key.asc" "$fixtures/SHASUMS256.txt.asc" "$fixtures/tiny-artifact"
cp "$fixtures/SHASUMS256.txt.asc" "$test_tmp/bad-signature.asc"
sed -i 's/^=nTYb$/=nTYa/' "$test_tmp/bad-signature.asc"
sed -i 's/^=MJqv$/=MJqa/' "$test_tmp/bad-signature.asc"
expect_failure verify_detached_signature \
"$fixtures/fixture-key.asc" "$test_tmp/bad-signature.asc" "$fixtures/SHASUMS256.txt"
expect_failure verify_detached_signature \
"$test_tmp/combined-key.asc" "$fixtures/wrong-signer-SHASUMS256.txt.asc" "$fixtures/SHASUMS256.txt"
mkdir "$test_tmp/bin"
cat > "$test_tmp/bin/curl" <<'STUB'
#!/usr/bin/env bash
set -euo pipefail
output=''
connect_timeout=''
max_time=''
max_filesize=''
while (($#)); do
case "$1" in
--output) output="$2"; shift 2 ;;
--connect-timeout) connect_timeout="$2"; shift 2 ;;
--max-time) max_time="$2"; shift 2 ;;
--max-filesize) max_filesize="$2"; shift 2 ;;
*) shift ;;
esac
done
[[ "$connect_timeout" == 10 && "$max_time" == 600 && "$max_filesize" == "${CURL_EXPECTED_MAX_BYTES:?}" ]] || exit 65
case "${CURL_FIXTURE:?}" in
good) cp "$CURL_FIXTURE_ROOT/tiny-artifact" "$output" ;;
oversized) head -c 1025 /dev/zero > "$output" ;;
@@ -88,7 +112,7 @@ esac
STUB
chmod +x "$test_tmp/bin/curl"
PATH="$test_tmp/bin:$PATH"
export PATH CURL_FIXTURE_ROOT="$fixtures"
export PATH CURL_FIXTURE_ROOT="$fixtures" CURL_EXPECTED_MAX_BYTES=1024
destination="$test_tmp/destination"
export CURL_FIXTURE=good
@@ -108,6 +132,23 @@ expect_failure download_sha256 'https://fixture.invalid/bad-digest' \
assert_file_bytes "$destination" 'known-good'
[[ ! -e "$destination.part" ]] || fail 'bad digest left a part file'
protected="$test_tmp/protected-known-good"
printf 'known-good\n' > "$protected"
ln -s "$protected" "$destination.part"
expect_failure download_sha256 'https://fixture.invalid/symlink-part' \
'0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination"
assert_file_bytes "$protected" 'known-good'
[[ -L "$destination.part" ]] || fail 'symlinked destination part was not preserved'
rm -f -- "$destination.part"
ln "$protected" "$destination.part"
expect_failure download_sha256 'https://fixture.invalid/hard-link-part' \
'0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination"
assert_file_bytes "$protected" 'known-good'
[[ "$(stat -c %i "$protected")" == "$(stat -c %i "$destination.part")" ]] \
|| fail 'hard-linked destination part was not preserved'
rm -f -- "$destination.part"
export CURL_FIXTURE=oversized
expect_failure download_sha256 'https://fixture.invalid/oversized' "$tiny_sha256" 1024 "$destination"
assert_file_bytes "$destination" 'known-good'
@@ -119,6 +160,7 @@ assert_file_bytes "$destination" 'known-good'
[[ ! -e "$destination.part" ]] || fail 'interrupted download left a part file'
before_gnupg="$(snapshot "$host_gnupg")"
before_gpg_files="$(snapshot_gpg_state "$host_gnupg")"
before_rpmdb="$(snapshot "$host_rpmdb")"
expect_success rpm_signature_matches \
"$test_tmp/signed-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint"
@@ -126,11 +168,29 @@ expect_failure rpm_signature_matches \
"$test_tmp/unsigned-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint"
expect_failure rpm_signature_matches \
"$test_tmp/signed-fixture.rpm" "$fixtures/fixture-key.asc" '0000000000000000000000000000000000000000'
expect_failure rpm_signature_matches \
"$test_tmp/wrong-signer-fixture.rpm" "$fixtures/fixture-key.asc" "$fixture_fingerprint"
expect_failure rpm_signature_matches \
"$test_tmp/wrong-signer-fixture.rpm" "$test_tmp/combined-key.asc" "$fixture_fingerprint"
[[ "$before_gnupg" == "$(snapshot "$host_gnupg")" ]] || fail 'host GPG state changed'
[[ "$before_gpg_files" == "$(snapshot_gpg_state "$host_gnupg")" ]] || fail 'host GPG keybox or trust database changed'
[[ "$before_rpmdb" == "$(snapshot "$host_rpmdb")" ]] || fail 'host RPM database changed'
expect_success load_installer_provenance "$config"
[[ "${INSTALLER_PROVENANCE[BUN_VERSION]:-}" == '1.4.0' ]] || fail 'valid provenance was not loaded'
for key_spec in \
'terra44 TERRA_FINGERPRINT' \
'claude-code CLAUDE_CODE_FINGERPRINT' \
'bun BUN_FINGERPRINT' \
'rpmfusion-free RPMFUSION_FREE_FINGERPRINT' \
'rpmfusion-nonfree RPMFUSION_NONFREE_FINGERPRINT' \
'hyprland-copr HYPRLAND_COPR_FINGERPRINT' \
'flathub FLATHUB_FINGERPRINT' \
'claude-desktop CLAUDE_DESKTOP_FINGERPRINT'; do
read -r key_file fingerprint_name <<<"$key_spec"
expect_success key_fingerprint_matches "$repo_dir/setup/provenance/keys/$key_file.asc" \
"${INSTALLER_PROVENANCE[$fingerprint_name]}"
done
parser_fixture="$test_tmp/installers.conf"
cp "$config" "$parser_fixture"