From 94a4314730721492285c69e5d145909edd2d562f Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Thu, 27 Aug 2026 05:56:46 -0400 Subject: [PATCH] Fix: Harden installer provenance boundary --- setup/lib/artifact-provenance | 32 +++-- .../setup/fixtures/provenance/SHASUMS256.txt | 3 + .../fixtures/provenance/SHASUMS256.txt.asc | 16 +-- .../provenance/tiny-artifact-tampered | 2 +- .../wrong-signer-SHASUMS256.txt.asc | 11 ++ .../wrong-signer-fixture.rpm.base64 | 118 ++++++++++++++++++ .../fixtures/provenance/wrong-signer-key.asc | 18 +++ tests/setup/package-provenance-contract | 64 +++++++++- 8 files changed, 245 insertions(+), 19 deletions(-) create mode 100644 tests/setup/fixtures/provenance/wrong-signer-SHASUMS256.txt.asc create mode 100644 tests/setup/fixtures/provenance/wrong-signer-fixture.rpm.base64 create mode 100644 tests/setup/fixtures/provenance/wrong-signer-key.asc diff --git a/setup/lib/artifact-provenance b/setup/lib/artifact-provenance index c768067..5638423 100755 --- a/setup/lib/artifact-provenance +++ b/setup/lib/artifact-provenance @@ -6,15 +6,27 @@ declare -gA INSTALLER_PROVENANCE=() +_primary_key_fingerprints() { + gpg --batch --with-colons --import-options show-only --import "$1" 2>/dev/null \ + | awk -F: '$1 == "pub" { primary = 1; next } primary && $1 == "fpr" { print $10; primary = 0 }' +} + key_fingerprint_matches() { - local file="$1" expected="$2" actual - actual="$(gpg --batch --with-colons --import-options show-only --import "$file" 2>/dev/null \ - | awk -F: '$1 == "fpr" { print $10; exit }')" - [[ "$actual" == "$expected" ]] + local file="$1" expected="$2" + local -a primary_fingerprints=() + mapfile -t primary_fingerprints < <(_primary_key_fingerprints "$file") + [[ ${#primary_fingerprints[@]} -eq 1 && "${primary_fingerprints[0]}" == "$expected" ]] +} + +_key_has_one_primary() { + local -a primary_fingerprints=() + mapfile -t primary_fingerprints < <(_primary_key_fingerprints "$1") + [[ ${#primary_fingerprints[@]} -eq 1 ]] } verify_detached_signature() { local key="$1" signature="$2" content="$3" home + _key_has_one_primary "$key" || return 1 home="$(mktemp -d)" || return 1 chmod 700 "$home" GNUPGHOME="$home" gpg --batch --quiet --import "$key" >/dev/null 2>&1 \ @@ -25,18 +37,22 @@ verify_detached_signature() { } download_sha256() { - local url="$1" expected="$2" max_bytes="$3" destination="$4" actual + local url="$1" expected="$2" max_bytes="$3" destination="$4" actual directory filename [[ "$expected" =~ ^[0-9a-f]{64}$ ]] || return 1 actual="$expected" - local part="${destination}.part" + directory="$(dirname -- "$destination")" + filename="$(basename -- "$destination")" ( - trap 'rm -f -- "$part"' EXIT + local part="" + trap '[[ -z "$part" ]] || rm -f -- "$part"' EXIT trap 'exit 130' INT trap 'exit 143' TERM [[ "$max_bytes" =~ ^[1-9][0-9]*$ ]] || exit 1 - [[ -n "$destination" && -d "$(dirname "$destination")" ]] || exit 1 + [[ -n "$destination" && -d "$directory" ]] || exit 1 + umask 077 + part="$(mktemp "$directory/.${filename}.part.XXXXXX")" || exit 1 curl --fail --location --connect-timeout 10 --max-time 600 \ --max-filesize "$max_bytes" --output "$part" "$url" \ || exit 1 diff --git a/tests/setup/fixtures/provenance/SHASUMS256.txt b/tests/setup/fixtures/provenance/SHASUMS256.txt index e97525a..9472786 100644 --- a/tests/setup/fixtures/provenance/SHASUMS256.txt +++ b/tests/setup/fixtures/provenance/SHASUMS256.txt @@ -1 +1,4 @@ 291bd319ae85488101e908e37fc0fa1b0da1429ba27e10d2b391cb3f60dd44ea tiny-artifact +5043218f6d8ecdb2ba78665116f74e7f53ce29d164117a0b6cb0f946811d3455 tiny-artifact-tampered +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa metadata-only-1 +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb metadata-only-2 diff --git a/tests/setup/fixtures/provenance/SHASUMS256.txt.asc b/tests/setup/fixtures/provenance/SHASUMS256.txt.asc index e3bad90..94b43b2 100644 --- a/tests/setup/fixtures/provenance/SHASUMS256.txt.asc +++ b/tests/setup/fixtures/provenance/SHASUMS256.txt.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCgAdFiEEYBb/GMrimM42SO4jJeAfdl4e+foFAmqQBJoACgkQJeAfdl4e -+foW9Qf/QLO0N5vVdG3Fi9ql6SE3dZItZtrJy4jFVPDEVEZJK4PoT9iRE/RQwqsU -7y86B1x0JmCoxVRigLD75gz8IYb8SzchbTrUQRB/l0FBjNZaeAX1O+E8GZIsK9Kf -KljmaIiG8vrwGilCxZnJPGv80mMpW9LETgF9be2L8bZff/UDwEZfOenYIgievUJW -cuNe41jzOd0kYuOggnxPsOqxfd+ludpwjOO/z2j2TV8J4H7JHzoRzG8100J+DYoY -ud9GyH1AtgGlQ3XALb4UotE3Btf8GiZ1Mg/igxZqvjCJhrfOiaeziKi4a80fhv9z -j1KFafXj9lc69vBgayepHF8BH6bocQ== -=nTYb +iQEzBAABCgAdFiEEYBb/GMrimM42SO4jJeAfdl4e+foFAmqQCQ8ACgkQJeAfdl4e ++fqUiQgAgPaA6eTS3ag1YMLhT4r3JVpL40HgKTLBfTgRjsc/AVwG9aV9BMYMHwGT +3pCzs1ULlF1Vw9mFODt6YPBJeuq2hdI26MVJ1YWo6YkiCqzrsu3LycZCvBezC8hi +6/4ZW/j/7tMeVr2Oqw4Vv5quvLQ2DO/ZE3vMtKJ+sRvVHnczlQJrrKpzJ15pVv5l +a5OMFfTeCeOXOJ145lDlEdvmjaJUPUp6cw248azhEAwu24a0u4iqIb59nz/IhLiD +/rkUQrc2CGwBEUSwDDMLELLMTHc/Ivm6xLQOI+TbT53+1IupyUu5gquRM7pdKGw7 +SYTz3awbjyEzFLt60AnPsSWNyo5N6A== +=MJqv -----END PGP SIGNATURE----- diff --git a/tests/setup/fixtures/provenance/tiny-artifact-tampered b/tests/setup/fixtures/provenance/tiny-artifact-tampered index 2c44775..bb49e64 100644 --- a/tests/setup/fixtures/provenance/tiny-artifact-tampered +++ b/tests/setup/fixtures/provenance/tiny-artifact-tampered @@ -1 +1 @@ -tampered artifact +tiny artifacT diff --git a/tests/setup/fixtures/provenance/wrong-signer-SHASUMS256.txt.asc b/tests/setup/fixtures/provenance/wrong-signer-SHASUMS256.txt.asc new file mode 100644 index 0000000..415c67f --- /dev/null +++ b/tests/setup/fixtures/provenance/wrong-signer-SHASUMS256.txt.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- + +iQEzBAABCgAdFiEE8NsXb+IUusO1vCd8zouSo0jXorAFAmqQCQ8ACgkQzouSo0jX +orCpQAf+PB6BsCoq/Sp4LShSNTlZkDWS7p641A29OgAtr6QYUphMsI+gY6tKK0zr +zmDzs4MyZaSy/yx9jEdPyyVWLFREk0u/x6YpQwj8KGbtuTt/hBQgkshDZZeVdGLr +x11bkZgOENjzRS/NnKiziwoVVwaZm36WrsCef6Q26sbxCnYF2nWlGNtP9SKnhxSV +/FYXi/wW41p8iZqIb0SFRoMGMsazV5i9XOjTLx4dEiMaeGvnNdZoMDGIO8in7mJG +pkYro8coAGwjyC48VZmCPFG0TPqVhc0LEYKcI4M1OyEX38xQqaoRm5k9sOswfyDF +iTFTwna0Zb1nurK/28/WfUHqLEIlQw== +=PvFe +-----END PGP SIGNATURE----- diff --git a/tests/setup/fixtures/provenance/wrong-signer-fixture.rpm.base64 b/tests/setup/fixtures/provenance/wrong-signer-fixture.rpm.base64 new file mode 100644 index 0000000..4e0b7d3 --- /dev/null +++ b/tests/setup/fixtures/provenance/wrong-signer-fixture.rpm.base64 @@ -0,0 +1,118 @@ +7avu2wMAAAAAAHBhbmFtYS1wcm92ZW5hbmNlLWZpeHR1cmUtMS0xAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAjq3oAQAAAAAAAAAIAAAQpAAA +AD4AAAAHAAAQlAAAABAAAAEMAAAABwAAAAAAAAE2AAABDQAAAAYAAAE2AAAAAQAAAREAAAAGAAAB +XwAAAAEAAAPoAAAABAAAAaAAAAABAAAD7AAAAAcAAAGkAAAAEAAAA+8AAAAEAAABtAAAAAEAAAPw +AAAABwAAAbgAAA7ciQEzBAABCgAdFiEE8NsXb+IUusO1vCd8zouSo0jXorAFAmqQCOUACgkQzouS +o0jXorAkrwf/diXYa/W3A1v8HNykf8NA2gmyNHVIm/AMGGZE1TC49eiMgj1+KpoznyImuP5bpGSC +fMeG0sMrRabG4eYJ7eXDNviVNDslet6ooNRlvRE0F4zQZERub1l4afHHuJuHWHi/J/UpWotOxQa0 +akXy7/fE4Easoy8ddzdx5IYS9SLDiKMxuVhp3oqkiC1QyX6gV3cd+iF128IJMhhA2UMnA7nRX1vH +sDK1WNQub3GeS9svYVCXfSy4NTIbsP6zynLRFp3nEKfRkVWILnrGGHL7ZBNH4aaURUN57lu83Cv+ +DOB9LlS2z704feRKJRWj+wLmOZvx37426Tr7ZllLHvs5xvkH0DM1NTg5MDBmNWUwM2YwNzFhNjM1 +ZDg1ZjgxODI3NTA3YjdiMWQ1MDYAMjQ4ZTQ3YzFiNjhlYjM5NDgxMWFmNWJhOGZlNGNiZTEzYmUy +MGM5N2I0MzM2MzM1MGRhOGE2MDY0ZWQ4YTk4ZgAAAAiZ9bUc5Ib0/pUFYQ1fQtgpwwAAASQAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAB////4AAAAAQAAAA +AI6t6AEAAAAAAAAAMwAABOEAAAA/AAAABwAABNEAAAAQAAAAZAAAAAgAAAAAAAAAAQAAA+gAAAAG +AAAAAgAAAAEAAAPpAAAABgAAABwAAAABAAAD6gAAAAYAAAAeAAAAAQAAA+wAAAAJAAAAIAAAAAEA +AAPtAAAACQAAAD8AAAABAAAD7gAAAAQAAABYAAAAAQAAA+8AAAAGAAAAXAAAAAEAAAPxAAAABAAA +AGgAAAABAAAD9gAAAAYAAABsAAAAAQAAA/gAAAAJAAAAcAAAAAEAAAP9AAAABgAAAHwAAAABAAAD +/gAAAAYAAACCAAAAAQAABAQAAAAEAAAAjAAAAAEAAAQGAAAAAwAAAJAAAAABAAAECQAAAAMAAACS +AAAAAQAABAoAAAAEAAAAlAAAAAEAAAQLAAAACAAAAJgAAAABAAAEDAAAAAgAAADZAAAAAQAABA0A +AAAEAAAA3AAAAAEAAAQPAAAACAAAAOAAAAABAAAEEAAAAAgAAADlAAAAAQAABBQAAAAGAAAA6gAA +AAEAAAQVAAAABAAAARAAAAABAAAEFwAAAAgAAAEUAAAAAQAABBgAAAAEAAABMAAAAAQAAAQZAAAA +CAAAAUAAAAAEAAAEGgAAAAgAAAGlAAAABAAABCgAAAAGAAABxAAAAAEAAARHAAAABAAAAcwAAAAB +AAAESAAAAAQAAAHQAAAAAQAABEkAAAAIAAAB1AAAAAEAAARYAAAABAAAAdgAAAABAAAEWQAAAAgA +AAHcAAAAAQAABFwAAAAEAAAB4AAAAAEAAARdAAAACAAAAeQAAAABAAAEXgAAAAgAAAHwAAAAAQAA +BGIAAAAGAAACFgAAAAEAAARkAAAABgAABAUAAAABAAAEZQAAAAYAAAQKAAAAAQAABGYAAAAGAAAE +DwAAAAEAAARsAAAABgAABBIAAAABAAAEdAAAAAQAAAQsAAAAAQAABHUAAAAEAAAEMAAAAAEAAAR2 +AAAACAAABDQAAAABAAATkwAAAAQAAARAAAAAAQAAE8YAAAAGAAAERAAAAAEAABPkAAAACAAABEoA +AAABAAAT5QAAAAQAAASMAAAAAQAAE+kAAAAIAAAEkAAAAAFDAHBhbmFtYS1wcm92ZW5hbmNlLWZp +eHR1cmUAMQAxAFBhbmFtYSBwcm92ZW5hbmNlIHRlc3QgZml4dHVyZQBBIHRlc3Qtb25seSBSUE0g +Zml4dHVyZS4AapAEmmRlc2t0b3AtZ2liAAAAAAhNSVQAVW5zcGVjaWZpZWQAbGludXgAbm9hcmNo +AAAAAAAAAAiBpAAAapAEmmU4MGI3MWNkMTRkM2NiZDY1ZjQxNzNhYmNiZmNmMDFhNTQ1ZGJjYTMy +YTcyZDU3NTEwOGI1NTNhNjQ4Y2M5NmYAAAAAAAAAAHJvb3QAcm9vdABwYW5hbWEtcHJvdmVuYW5j +ZS1maXh0dXJlLTEtMS5zcmMucnBtAP////9wYW5hbWEtcHJvdmVuYW5jZS1maXh0dXJlAAAAAQAA +CgEAAAoBAAAKAQAACnJwbWxpYihDb21wcmVzc2VkRmlsZU5hbWVzKQBycG1saWIoRmlsZURpZ2Vz +dHMpAHJwbWxpYihQYXlsb2FkRmlsZXNIYXZlUHJlZml4KQBycG1saWIoUGF5bG9hZElzWnN0ZCkA +My4wLjQtMQA0LjYuMC0xADQuMC0xADUuNC4xOC0xADYuMC4yAAAAAAAAAQAAAAEAAAAAAAAACDEt +MQAAAAAAZml4dHVyZS50eHQAL3Vzci9zaGFyZS9wYW5hbWEtcHJvdmVuYW5jZS1maXh0dXJlLwAt +TzIgLWZsdG89YXV0byAtZmZhdC1sdG8tb2JqZWN0cyAtZmV4Y2VwdGlvbnMgLWcgLWdyZWNvcmQt +Z2NjLXN3aXRjaGVzIC1waXBlIC1XYWxsIC1Xbm8tY29tcGxhaW4td3JvbmctbGFuZyAtV2Vycm9y +PWZvcm1hdC1zZWN1cml0eSAtV3AsLVVfRk9SVElGWV9TT1VSQ0UsLURfRk9SVElGWV9TT1VSQ0U9 +MyAtV3AsLURfR0xJQkNYWF9BU1NFUlRJT05TIC1zcGVjcz0vdXNyL2xpYi9ycG0vcmVkaGF0L3Jl +ZGhhdC1oYXJkZW5lZC1jYzEgLWZzdGFjay1wcm90ZWN0b3Itc3Ryb25nIC1zcGVjcz0vdXNyL2xp +Yi9ycG0vcmVkaGF0L3JlZGhhdC1hbm5vYmluLWNjMSAgLW02NCAtbWFyY2g9eDg2LTY0IC1tdHVu +ZT1nZW5lcmljIC1mYXN5bmNocm9ub3VzLXVud2luZC10YWJsZXMgLWZzdGFjay1jbGFzaC1wcm90 +ZWN0aW9uIC1mY2YtcHJvdGVjdGlvbiAtbXRscy1kaWFsZWN0PWdudTIgLWZuby1vbWl0LWZyYW1l +LXBvaW50ZXIgLW1uby1vbWl0LWxlYWYtZnJhbWUtcG9pbnRlcgBjcGlvAHpzdGQAMTkAbm9hcmNo +LXJlZGhhdC1saW51eC1nbnUAAAAAAAAAAAAAAEFTQ0lJIHRleHQAAAAAAAh1dGYtOABiZjZkYzg1 +Y2I3NjI5MzhhYjM1NDY2ZDYyNWUzZWI0MzIxMzk4MjAxZWY5NDM5NDRhZDk0NzU5YjExMTY1Yjk5 +AAAAAAAINDQ4YWY1YjI2NTgyODFlZGFhYTNmMGVlNDU5ZTk2MWVhZGYzNTEzZmI5MDBiOTQ0NDVi +YWZjNDUwZmQxYWY2NwAAAAA/AAAAB////NAAAAAQKLUv/QBofQMAkgUUGHBvDmCIWNNxsOnme3c1 +JmpNprTcf+8pMqqqrTUmWRSEae6cAZ3ZfAvlNTMJcvCJHSFGycr11LGrhbIja6V17Xsg3Hgfw380 +wR+ajfOLFw8MIACTzAMyvJyYeLFKvjdrmx/Z4OEGZKVOuIQR diff --git a/tests/setup/fixtures/provenance/wrong-signer-key.asc b/tests/setup/fixtures/provenance/wrong-signer-key.asc new file mode 100644 index 0000000..a8014e5 --- /dev/null +++ b/tests/setup/fixtures/provenance/wrong-signer-key.asc @@ -0,0 +1,18 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGqQCOUBCADHP/S269aT5t0ZvePSetTa1h2KtcwiQ+LEkWMvHM8sYCrHDjer +XR53nj+e9xZPmXtEIiYsE1LpR7ax8dwcm/1XDqFLeduCRkB6DQE4Q5Ia1u9EXpjc +Of+uiw0Y2n1/d/BuRVOJiORlFMlW+0lueMaShHfH+/g2QC9oQdIMZfk+UaoRRgVf +5hpRfpIZGKOnAEatTRRbjDaoibIe0/El8Pwt93RoCO9GxF+UeJZZA152TOrB3Y/6 +uTe7FE5HZFXQEvR78GhrAzmwZENuMIaIIaHhzOrLKrxoVtgimHymLe1rODQfq6zG +jRZ8SDJ2oLB1u51MiirV2fc18gUUPTbOSfv7ABEBAAG0KlBhbmFtYSBXcm9uZyBT +aWduZXIgPHdyb25nQHBhbmFtYS5pbnZhbGlkPokBUQQTAQoAOxYhBPDbF2/iFLrD +tbwnfM6LkqNI16KwBQJqkAjlAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheA +AAoJEM6LkqNI16KwbwgH/1yMS9PB7KQHLE+kQqaJQxqOLyANx4EPn8NeXB19XByz +Zph/qzFjnGXT/Lz2PMwqo4uem6oXYSdwXlsVM6oLeQ8wDd29/oLbI77cYBO42QSL +cerjc+IbJ7BCiqGL0e5Gmn8lzWinO+wramnVtDKQn4BzDCj7UWpBzyClqVb06IfL +9Xz4KMSv3vDGVMhtLLgVmfRkdgdgS1mcgQhSaueQmjU1Z4zQ8dHbL1cefuvxPKzM +XgkKBIIaY+2X1ulz/QA7fe4JpqNzyRC0gixod72OXldFA6aNV/EfF+1Pa/nVPNYC +MQl6tgcI8JX9lnsMxK+h9mAIilFQUmdX1Qio4FjWYLI= +=l5kF +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/setup/package-provenance-contract b/tests/setup/package-provenance-contract index b573da7..813a60f 100755 --- a/tests/setup/package-provenance-contract +++ b/tests/setup/package-provenance-contract @@ -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"