From 50670389ba3bee6a3786ec9da1a5c4514c2a76d7 Mon Sep 17 00:00:00 2001 From: Dave Thompson Date: Wed, 22 Jul 2026 14:13:59 -0400 Subject: [PATCH] release: pin exact GPG signing subkey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - require an exact signing-subkey selector while retaining an independent authorized primary fingerprint - verify subkey capability, lifecycle state, parent relationship, and the resulting tag signature - document generic primary and signing-subkey discovery for the maintainer ceremony - expand hermetic release controls for alternate-primary substitution and disabled-key states Validation - DECERNOR_BIN=/absolute/path/to/decernor make check-all - make release-control-test — 66 hermetic controls - macOS /bin/bash 3.2 syntax and runtime - ShellCheck - live version-tag ruleset validation - real committed-public-key selector resolution - git diff --check Generated by GPT-5 via Codex under supervision of @3leapsdave Role: devlead Co-authored-by: GPT-5 --- RELEASE_CHECKLIST.md | 33 +++++- scripts/release-common.sh | 182 ++++++++++++++++++++++++------- scripts/release-tag.sh | 2 +- scripts/test-release-controls.sh | 124 ++++++++++++++++++--- 4 files changed, 284 insertions(+), 57 deletions(-) diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md index ddac16e..d97dde9 100644 --- a/RELEASE_CHECKLIST.md +++ b/RELEASE_CHECKLIST.md @@ -22,12 +22,39 @@ dedicated keyring must remain outside every Git worktree. on which the release gates and hosted checks passed; - `THREELEAPS_SYNTHCORPUS_GPG_HOMEDIR`: the dedicated release-signing keyring; - `THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT`: the full uppercase 40-hex - primary GPG fingerprint authorized to sign the Git tag; + primary fingerprint that independently authorizes the release identity; +- `THREELEAPS_SYNTHCORPUS_PGP_KEY_ID`: an exact signing-subkey selector: either + its uppercase 16-hex key ID or full uppercase 40-hex fingerprint, followed by + `!` so GnuPG cannot silently select a different signing subkey; - `THREELEAPS_SYNTHCORPUS_TAGGER_NAME`: the tagger name associated with the signing identity; - `THREELEAPS_SYNTHCORPUS_TAGGER_EMAIL`: a tagger email present on the signing key and verified for the publishing account. +To discover the primary and signing-subkey identifiers from the dedicated +keyring without exporting key material, run: + +```sh +GNUPGHOME="$THREELEAPS_SYNTHCORPUS_GPG_HOMEDIR" \ + gpg --keyid-format long \ + --with-subkey-fingerprint \ + --list-secret-keys +``` + +Use the full fingerprint printed beneath the `sec` record as the independent +primary authorization value. Choose an unexpired `ssb` record carrying `[S]`, +then use either its long key ID from the `ssb` line or its full fingerprint from +the following line, with `!` appended as the exact selector. For example: + +```sh +export THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT='<40-hex-primary-fingerprint>' +export THREELEAPS_SYNTHCORPUS_PGP_KEY_ID='<16-or-40-hex-signing-subkey-id>!' +``` + +The guard requires the selected signing subkey to belong to the independently +authorized primary and rejects disabled, expired, revoked, invalid, or +non-signing key records. + Git release tags carry GPG signatures. This no-asset release creates no minisign signature or uploaded key; minisign material is not part of this ceremony. @@ -141,8 +168,8 @@ ceremony. environment and live ruleset, embeds the ruleset-policy fingerprint, explicitly tags the check-verified commit, and verifies the annotated object directly targets that commit with type `commit`, plus the exact - signer fingerprint, tagger identity, policy attestation, and peeled - target: + signing-subkey fingerprint and its primary-key relationship, tagger + identity, policy attestation, and peeled target: ```sh make release-tag diff --git a/scripts/release-common.sh b/scripts/release-common.sh index c2f8620..49eac79 100755 --- a/scripts/release-common.sh +++ b/scripts/release-common.sh @@ -87,10 +87,18 @@ release_configure_gpg() { export GNUPGHOME="${canonical_home}" } +release_validate_pgp_key_id() { + release_require_env THREELEAPS_SYNTHCORPUS_PGP_KEY_ID + if ! [[ "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" =~ ^([0-9A-F]{16}|[0-9A-F]{40})!$ ]]; then + echo "error: THREELEAPS_SYNTHCORPUS_PGP_KEY_ID must be an uppercase 16-hex key ID or 40-hex fingerprint followed by !" >&2 + return 1 + fi +} + release_validate_fingerprint() { release_require_env THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT if ! [[ "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" =~ ^[0-9A-F]{40}$ ]]; then - echo "error: THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT must be a full uppercase 40-hex fingerprint" >&2 + echo "error: THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT must be a full uppercase 40-hex primary fingerprint" >&2 return 1 fi } @@ -112,14 +120,108 @@ release_validate_tagger_env() { esac } -release_primary_fingerprints() { +release_primary_records() { local listing="$1" printf '%s\n' "${listing}" | awk -F: ' - $1 == "sec" || $1 == "pub" { want_fingerprint = 1; next } - want_fingerprint && $1 == "fpr" { print $10; want_fingerprint = 0 } + $1 == "sec" || $1 == "pub" { + want_fingerprint = 1 + validity = $2 + expires = $7 + capabilities = $12 + next + } + want_fingerprint && $1 == "fpr" { + print $10 "|" capabilities "|" validity "|" expires + want_fingerprint = 0 + } ' } +release_matching_subkey_records() { + local listing="$1" + local selector="${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID%!}" + printf '%s\n' "${listing}" | awk -F: -v selector="${selector}" ' + $1 == "sub" || $1 == "ssb" { + want_fingerprint = 1 + validity = $2 + key_id = $5 + expires = $7 + capabilities = $12 + next + } + want_fingerprint && $1 == "fpr" { + fingerprint = $10 + if (fingerprint == selector || key_id == selector) { + print fingerprint "|" capabilities "|" validity "|" expires + } + want_fingerprint = 0 + } + ' +} + +release_resolve_signing_subkey() { + local listing="$1" + local primary_records primary_count matching_records matching_count + primary_records="$(release_primary_records "${listing}")" + primary_count="$(printf '%s\n' "${primary_records}" | awk 'NF { count++ } END { print count + 0 }')" + if [ "${primary_count}" -ne 1 ]; then + echo "error: signing subkey selector does not resolve beneath exactly one primary key" >&2 + return 1 + fi + + local primary_fingerprint primary_capabilities primary_validity primary_expires + IFS='|' read -r primary_fingerprint primary_capabilities primary_validity primary_expires <<<"${primary_records}" + if [ "${primary_fingerprint}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ]; then + echo "error: selected signing subkey does not belong to the independently authorized primary fingerprint" >&2 + return 1 + fi + release_validate_key_state "authorized primary key" \ + "${primary_capabilities}" "${primary_validity}" "${primary_expires}" || return 1 + + matching_records="$(release_matching_subkey_records "${listing}")" + matching_count="$(printf '%s\n' "${matching_records}" | awk 'NF { count++ } END { print count + 0 }')" + if [ "${matching_count}" -ne 1 ]; then + echo "error: signing subkey selector does not identify exactly one subkey" >&2 + return 1 + fi + + local subkey_fingerprint capabilities validity expires + IFS='|' read -r subkey_fingerprint capabilities validity expires <<<"${matching_records}" + if ! [[ "${capabilities}" =~ [sS] ]]; then + echo "error: selected GPG subkey is not signing-capable" >&2 + return 1 + fi + release_validate_key_state "selected GPG signing subkey" \ + "${capabilities}" "${validity}" "${expires}" || return 1 + + printf '%s %s\n' "${subkey_fingerprint}" "${primary_fingerprint}" +} + +release_validate_key_state() { + local label="$1" + local capabilities="$2" + local validity="$3" + local expires="$4" + if [[ "${capabilities}" == *D* ]]; then + echo "error: ${label} is disabled" >&2 + return 1 + fi + case "${validity}" in + r | e | d | i) + echo "error: ${label} is revoked, expired, disabled, or invalid" >&2 + return 1 + ;; + esac + if [ -n "${expires}" ] && [ "${expires}" != "0" ]; then + local now + now="$(date +%s)" + if ! [[ "${expires}" =~ ^[0-9]+$ ]] || [ "${expires}" -le "${now}" ]; then + echo "error: ${label} is expired or has malformed expiry metadata" >&2 + return 1 + fi + fi +} + release_key_uid_emails() { local listing="$1" printf '%s\n' "${listing}" | @@ -128,24 +230,19 @@ release_key_uid_emails() { } release_validate_verification_env() { - release_assert_tag_version - release_validate_release_commit - release_configure_gpg - release_validate_fingerprint - release_validate_tagger_env - release_require_command gpg - - local public_listing primary_fingerprints primary_count + release_assert_tag_version || return 1 + release_validate_release_commit || return 1 + release_configure_gpg || return 1 + release_validate_pgp_key_id || return 1 + release_validate_fingerprint || return 1 + release_validate_tagger_env || return 1 + release_require_command gpg || return 1 + + local public_listing public_listing="$(GNUPGHOME="${GNUPGHOME}" gpg --batch --with-colons \ - --fingerprint --list-keys \ - "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" 2>/dev/null || true)" - primary_fingerprints="$(release_primary_fingerprints "${public_listing}")" - primary_count="$(printf '%s\n' "${primary_fingerprints}" | awk 'NF { count++ } END { print count + 0 }')" - if [ "${primary_count}" -ne 1 ] || - [ "${primary_fingerprints}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ]; then - echo "error: signing fingerprint does not select exactly one primary public key in the release keyring" >&2 - return 1 - fi + --fingerprint --with-subkey-fingerprint --list-keys \ + "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" 2>/dev/null || true)" + release_resolve_signing_subkey "${public_listing}" >/dev/null || return 1 local uid_emails uid_emails="$(release_key_uid_emails "${public_listing}")" @@ -156,19 +253,13 @@ release_validate_verification_env() { } release_validate_signing_env() { - release_validate_verification_env + release_validate_verification_env || return 1 - local secret_listing primary_fingerprints primary_count + local secret_listing secret_listing="$(GNUPGHOME="${GNUPGHOME}" gpg --batch --with-colons \ - --fingerprint --list-secret-keys \ - "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" 2>/dev/null || true)" - primary_fingerprints="$(release_primary_fingerprints "${secret_listing}")" - primary_count="$(printf '%s\n' "${primary_fingerprints}" | awk 'NF { count++ } END { print count + 0 }')" - if [ "${primary_count}" -ne 1 ] || - [ "${primary_fingerprints}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ]; then - echo "error: signing fingerprint does not select exactly one primary secret key in the release keyring" >&2 - return 1 - fi + --fingerprint --with-subkey-fingerprint --list-secret-keys \ + "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" 2>/dev/null || true)" + release_resolve_signing_subkey "${secret_listing}" >/dev/null || return 1 } release_setup_gpg_tty() { @@ -237,7 +328,7 @@ release_expected_policy_attestation() { "${release_common_dir}/release-guard-tag-ruleset.sh" --expected-attestation } -release_validsig_primary_fingerprint() { +release_validsig_fingerprints() { local verification="$1" local validsig count validsig="$(sed -n 's/^\[GNUPG:\] VALIDSIG //p' <<<"${verification}")" @@ -245,7 +336,7 @@ release_validsig_primary_fingerprint() { if [ "${count}" -ne 1 ]; then return 1 fi - awk '{ if (NF >= 10) print $10; else print $1 }' <<<"${validsig}" + awk '{ if (NF >= 10) print $1, $10; else print $1, $1 }' <<<"${validsig}" } release_verify_local_tag() { @@ -304,7 +395,16 @@ release_verify_local_tag() { return 1 fi - local verification primary_fingerprint + local public_listing expected_fingerprints expected_subkey_fingerprint expected_primary_fingerprint + public_listing="$(GNUPGHOME="${GNUPGHOME}" gpg --batch --with-colons \ + --fingerprint --with-subkey-fingerprint --list-keys \ + "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" 2>/dev/null || true)" + if ! expected_fingerprints="$(release_resolve_signing_subkey "${public_listing}")"; then + return 1 + fi + read -r expected_subkey_fingerprint expected_primary_fingerprint <<<"${expected_fingerprints}" + + local verification actual_fingerprints actual_subkey_fingerprint actual_primary_fingerprint if ! verification="$(GNUPGHOME="${GNUPGHOME}" git verify-tag --raw "${tag}" 2>&1)"; then printf '%s\n' "${verification}" >&2 echo "error: tag signature verification failed" >&2 @@ -314,9 +414,15 @@ release_verify_local_tag() { echo "error: tag signature or signing key is expired or revoked" >&2 return 1 fi - primary_fingerprint="$(release_validsig_primary_fingerprint "${verification}" || true)" - if [ "${primary_fingerprint}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ]; then - echo "error: tag signature was not made by the required primary signing fingerprint" >&2 + actual_fingerprints="$(release_validsig_fingerprints "${verification}" || true)" + read -r actual_subkey_fingerprint actual_primary_fingerprint <<<"${actual_fingerprints}" + if [ "${actual_subkey_fingerprint}" != "${expected_subkey_fingerprint}" ]; then + echo "error: tag signature was not made by the selected exact signing subkey" >&2 + return 1 + fi + if [ "${actual_primary_fingerprint}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ] || + [ "${expected_primary_fingerprint}" != "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" ]; then + echo "error: tag signature does not chain to the independently authorized primary fingerprint" >&2 return 1 fi diff --git a/scripts/release-tag.sh b/scripts/release-tag.sh index 254c280..be69ed7 100755 --- a/scripts/release-tag.sh +++ b/scripts/release-tag.sh @@ -26,7 +26,7 @@ export GIT_COMMITTER_EMAIL="${THREELEAPS_SYNTHCORPUS_TAGGER_EMAIL}" echo "Creating GPG-signed tag ${THREELEAPS_SYNTHCORPUS_RELEASE_TAG} at ${THREELEAPS_SYNTHCORPUS_RELEASE_COMMIT}" GNUPGHOME="${GNUPGHOME}" git tag -s -a \ - -u "${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" \ + -u "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" \ -m "synthcorpus ${THREELEAPS_SYNTHCORPUS_RELEASE_TAG}" \ -m "${policy_attestation}" \ "${THREELEAPS_SYNTHCORPUS_RELEASE_TAG}" \ diff --git a/scripts/test-release-controls.sh b/scripts/test-release-controls.sh index ed75b32..3f92e3a 100755 --- a/scripts/test-release-controls.sh +++ b/scripts/test-release-controls.sh @@ -56,34 +56,73 @@ first_commit="$(git -C "${repo}" rev-parse HEAD)" git -C "${repo}" remote add origin "${remote}" git -C "${repo}" push -q -u origin main -readonly expected_fingerprint="0123456789ABCDEF0123456789ABCDEF01234567" +readonly expected_primary_fingerprint="0123456789ABCDEF0123456789ABCDEF01234567" +readonly expected_subkey_id="2F9F89F7673ECF7F" +readonly expected_subkey_fingerprint="111111111111111111111111${expected_subkey_id}" +readonly alternate_subkey_id="77BF08F29CE81D9B" +readonly alternate_subkey_fingerprint="222222222222222222222222${alternate_subkey_id}" +readonly encryption_subkey_id="280A9D47CB54F85D" +readonly encryption_subkey_fingerprint="333333333333333333333333${encryption_subkey_id}" +readonly expired_subkey_id="AAAAAAAAAAAAAAAA" +readonly expired_subkey_fingerprint="444444444444444444444444${expired_subkey_id}" +readonly other_primary_fingerprint="FEDCBA9876543210FEDCBA9876543210FEDCBA98" +readonly other_subkey_id="BBBBBBBBBBBBBBBB" +readonly other_subkey_fingerprint="555555555555555555555555${other_subkey_id}" readonly expected_name="3 Leaps Release" readonly expected_email="release@example.invalid" export THREELEAPS_SYNTHCORPUS_RELEASE_TAG="v0.1.0" export THREELEAPS_SYNTHCORPUS_RELEASE_COMMIT="${first_commit}" export THREELEAPS_SYNTHCORPUS_GPG_HOMEDIR="${gpg_home}" -export THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT="${expected_fingerprint}" +export THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT="${expected_primary_fingerprint}" +export THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${expected_subkey_id}!" export THREELEAPS_SYNTHCORPUS_TAGGER_NAME="${expected_name}" export THREELEAPS_SYNTHCORPUS_TAGGER_EMAIL="${expected_email}" real_git="$(command -v git)" verify_mode="good" +listing_mode="good" gpg() { + local primary_type subkey_type + primary_type="pub" + subkey_type="sub" case " $* " in *' --list-secret-keys '*) - printf 'sec:u:255:22:0123456789ABCDEF:0:0:::::scESC:::\n' - printf 'fpr:::::::::%s:\n' "${expected_fingerprint}" - printf 'uid:u::::0::HASH::3 Leaps Release <%s>::::::::::0:\n' "${expected_email}" - ;; - *' --list-keys '*) - printf 'pub:u:255:22:0123456789ABCDEF:0:0:::::scESC:::\n' - printf 'fpr:::::::::%s:\n' "${expected_fingerprint}" - printf 'uid:u::::0::HASH::3 Leaps Release <%s>::::::::::0:\n' "${expected_email}" + primary_type="sec" + subkey_type="ssb" ;; + *' --list-keys '*) ;; *) return 1 ;; esac + local primary_fingerprint primary_capabilities selected_subkey_capabilities + primary_fingerprint="${expected_primary_fingerprint}" + primary_capabilities="scESC" + selected_subkey_capabilities="s" + case "${listing_mode}" in + other-primary) + primary_fingerprint="${other_primary_fingerprint}" + ;; + disabled-primary) primary_capabilities="scESCD" ;; + disabled-subkey) selected_subkey_capabilities="sD" ;; + esac + printf '%s:u:255:22:0123456789ABCDEF:0:0:::::%s:::\n' "${primary_type}" "${primary_capabilities}" + printf 'fpr:::::::::%s:\n' "${primary_fingerprint}" + printf 'uid:u::::0::HASH::3 Leaps Release <%s>::::::::::0:\n' "${expected_email}" + if [ "${listing_mode}" = "other-primary" ]; then + printf '%s:u:255:22:%s:0:0:::::s:::\n' "${subkey_type}" "${other_subkey_id}" + printf 'fpr:::::::::%s:\n' "${other_subkey_fingerprint}" + return 0 + fi + printf '%s:u:255:22:%s:0:0:::::%s:::\n' \ + "${subkey_type}" "${expected_subkey_id}" "${selected_subkey_capabilities}" + printf 'fpr:::::::::%s:\n' "${expected_subkey_fingerprint}" + printf '%s:u:255:22:%s:0:0:::::s:::\n' "${subkey_type}" "${alternate_subkey_id}" + printf 'fpr:::::::::%s:\n' "${alternate_subkey_fingerprint}" + printf '%s:u:255:22:%s:0:0:::::e:::\n' "${subkey_type}" "${encryption_subkey_id}" + printf 'fpr:::::::::%s:\n' "${encryption_subkey_fingerprint}" + printf '%s:e:255:22:%s:0:1:::::s:::\n' "${subkey_type}" "${expired_subkey_id}" + printf 'fpr:::::::::%s:\n' "${expired_subkey_fingerprint}" } git() { @@ -91,19 +130,30 @@ git() { case "${verify_mode}" in good) printf '[GNUPG:] VALIDSIG %s 2026-07-22 1784736000 0 4 0 22 10 00 %s\n' \ - "${expected_fingerprint}" "${expected_fingerprint}" + "${expected_subkey_fingerprint}" "${expected_primary_fingerprint}" return 0 ;; - wrong-signer) + wrong-subkey) printf '[GNUPG:] VALIDSIG %s 2026-07-22 1784736000 0 4 0 22 10 00 %s\n' \ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \ + "${expected_primary_fingerprint}" + return 0 + ;; + wrong-primary) + printf '[GNUPG:] VALIDSIG %s 2026-07-22 1784736000 0 4 0 22 10 00 %s\n' \ + "${expected_subkey_fingerprint}" \ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" return 0 ;; + other-primary) + printf '[GNUPG:] VALIDSIG %s 2026-07-22 1784736000 0 4 0 22 10 00 %s\n' \ + "${other_subkey_fingerprint}" "${other_primary_fingerprint}" + return 0 + ;; expired) printf '[GNUPG:] EXPKEYSIG 0123456789ABCDEF expired\n' printf '[GNUPG:] VALIDSIG %s 2026-07-22 1784736000 0 4 0 22 10 00 %s\n' \ - "${expected_fingerprint}" "${expected_fingerprint}" + "${expected_subkey_fingerprint}" "${expected_primary_fingerprint}" return 0 ;; invalid) return 1 ;; @@ -132,6 +182,40 @@ expect_failure "non-commit object rejected as release SHA" release_validate_rele THREELEAPS_SYNTHCORPUS_RELEASE_COMMIT="${saved_commit}" expect_success "valid OOB signing environment accepted" release_validate_signing_env +saved_primary_fingerprint="${THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT}" +unset THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT +expect_failure "absent authorized primary fingerprint rejected" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT="0123456789ABCDEF" +expect_failure "abbreviated authorized primary fingerprint rejected" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_GPG_SIGNING_FINGERPRINT="${saved_primary_fingerprint}" +saved_key_id="${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}" +unset THREELEAPS_SYNTHCORPUS_PGP_KEY_ID +expect_failure "absent signing subkey selector rejected" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${expected_subkey_id}" +expect_failure "signing subkey selector without exact-selection bang rejected" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${expected_primary_fingerprint}!" +expect_failure "primary key rejected as signing subkey selector" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${encryption_subkey_id}!" +expect_failure "encryption-only subkey rejected for signing" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${expired_subkey_id}!" +expect_failure "expired signing subkey rejected" release_validate_verification_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${alternate_subkey_fingerprint}!" +expect_success "alternate full signing-subkey fingerprint accepted" release_validate_signing_env +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${saved_key_id}" +listing_mode="disabled-primary" +expect_failure "current-format disabled primary marker rejected" release_validate_verification_env +listing_mode="disabled-subkey" +expect_failure "current-format disabled signing-subkey marker rejected" release_validate_verification_env +listing_mode="other-primary" +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${other_subkey_id}!" +expect_failure "same-UID subkey under non-authorized primary rejected in public validation" \ + release_validate_verification_env +other_secret_listing="$(gpg --batch --with-colons --fingerprint \ + --with-subkey-fingerprint --list-secret-keys "${THREELEAPS_SYNTHCORPUS_PGP_KEY_ID}")" +expect_failure "same-UID subkey under non-authorized primary rejected in secret resolution" \ + release_resolve_signing_subkey "${other_secret_listing}" +listing_mode="good" +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${saved_key_id}" unset THREELEAPS_SYNTHCORPUS_TAGGER_NAME expect_failure "absent tagger name rejected" release_validate_verification_env export THREELEAPS_SYNTHCORPUS_TAGGER_NAME="${expected_name}" @@ -220,13 +304,23 @@ write_tag_object() { write_tag_object "${second_commit}" good "${expected_name}" "${expected_email}" signed verify_mode=good expect_success "fully bound signed tag accepted" release_verify_local_tag +listing_mode="other-primary" +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${other_subkey_id}!" +verify_mode=other-primary +expect_failure "same-UID subkey under non-authorized primary rejected during tag verification" \ + release_verify_local_tag +listing_mode="good" +THREELEAPS_SYNTHCORPUS_PGP_KEY_ID="${saved_key_id}" +verify_mode=good write_tag_object "${second_commit}" good "${expected_name}" "${expected_email}" unsigned inner_tag="$(command git rev-parse "refs/tags/${THREELEAPS_SYNTHCORPUS_RELEASE_TAG}")" write_tag_object "${inner_tag}" good "${expected_name}" "${expected_email}" signed tag expect_failure "indirect nested annotated tag target rejected" release_verify_local_tag write_tag_object "${second_commit}" good "${expected_name}" "${expected_email}" signed -verify_mode=wrong-signer -expect_failure "wrong signer rejected" release_verify_local_tag +verify_mode=wrong-subkey +expect_failure "wrong signing subkey rejected" release_verify_local_tag +verify_mode=wrong-primary +expect_failure "wrong primary-key relationship rejected" release_verify_local_tag verify_mode=expired expect_failure "expired signer rejected" release_verify_local_tag verify_mode=invalid