#!/usr/bin/env bash # The production change that breaks these cases is accepting an unverified # download, a wrong signer, or executable/malformed provenance data. set -euo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" fixtures="$repo_dir/tests/setup/fixtures/provenance" config="$repo_dir/setup/provenance/installers.conf" test_tmp="$(mktemp -d)" host_gnupg="${GNUPGHOME:-$HOME/.gnupg}" host_rpmdb="/usr/lib/sysimage/rpm/rpmdb.sqlite" cleanup() { rm -rf -- "$test_tmp" } trap cleanup EXIT trap 'exit 130' INT trap 'exit 143' TERM fail() { printf 'package provenance contract: %s\n' "$*" >&2 exit 1 } expect_success() { "$@" || fail "expected success: $*" } expect_failure() { if "$@"; then fail "expected failure: $*" fi } assert_file_bytes() { local path="$1" expected="$2" [[ "$(<"$path")" == "$expected" ]] || fail "unexpected bytes in $path" } snapshot() { local path="$1" if [[ -e "$path" ]]; then stat -c '%i:%s:%Y:%Z' "$path" else printf 'absent' 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" 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/^=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" ;; interrupted) printf 'partial' > "$output"; exit 42 ;; *) exit 64 ;; esac STUB chmod +x "$test_tmp/bin/curl" PATH="$test_tmp/bin:$PATH" export PATH CURL_FIXTURE_ROOT="$fixtures" CURL_EXPECTED_MAX_BYTES=1024 destination="$test_tmp/destination" export CURL_FIXTURE=good expect_success download_sha256 'https://fixture.invalid/good' "$tiny_sha256" 1024 "$destination" cmp -s "$fixtures/tiny-artifact" "$destination" || fail 'verified download changed artifact bytes' [[ ! -e "$destination.part" ]] || fail 'successful download left a part file' export CURL_FIXTURE=good expect_failure download_sha256 'https://fixture.invalid/uppercase-digest' \ '291BD319AE85488101E908E37FC0FA1B0DA1429BA27E10D2B391CB3F60DD44EA' 1024 \ "$test_tmp/uppercase-destination" printf 'known-good\n' > "$destination" export CURL_FIXTURE=good expect_failure download_sha256 'https://fixture.invalid/bad-digest' \ '0000000000000000000000000000000000000000000000000000000000000000' 1024 "$destination" 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' [[ ! -e "$destination.part" ]] || fail 'oversized download left a part file' export CURL_FIXTURE=interrupted expect_failure download_sha256 'https://fixture.invalid/interrupted' "$tiny_sha256" 1024 "$destination" 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" 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" printf 'UNKNOWN_KEY=value\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" printf 'BUN_VERSION=1.4.0\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" grep -v '^NODE_VERSION=' "$config" > "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" sed -i 's/^BUN_VERSION=/ BUN_VERSION=/' "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" sed -i 's/^BUN_VERSION=.*/BUN_VERSION=$(id -u)/' "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" cp "$config" "$parser_fixture" printf 'BUN_ARMV7_URL=https://fixture.invalid/bun\n' >> "$parser_fixture" expect_failure load_installer_provenance "$parser_fixture" printf 'package provenance contract: PASS\n'