diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml index 761d258e..f2c2687a 100644 --- a/.github/actions/prepare/action.yml +++ b/.github/actions/prepare/action.yml @@ -45,7 +45,7 @@ runs: uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: path: /mnt/kryptik/sources - key: sources-${{ hashFiles('sources.lock') }} + key: sources-v2-${{ hashFiles('sources.lock') }} # A changed lock restores the previous set and fetches only what is # new; every file is still hashed against the lock. restore-keys: ${{ inputs.sources == 'fetch' && 'sources-' || '' }} @@ -62,4 +62,4 @@ runs: uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: path: /mnt/kryptik/sources - key: sources-${{ hashFiles('sources.lock') }} + key: sources-v2-${{ hashFiles('sources.lock') }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3347a4e5..2dc0f248 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,7 +133,7 @@ jobs: uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: path: /mnt/kryptik/sources - key: sources-${{ hashFiles('sources.lock') }} + key: sources-v2-${{ hashFiles('sources.lock') }} restore-keys: | sources- diff --git a/docs/supply-chain.md b/docs/supply-chain.md index 610d26b0..8892cd8f 100644 --- a/docs/supply-chain.md +++ b/docs/supply-chain.md @@ -148,9 +148,9 @@ checks them against kernel.org's published developer keys. ## Open problems -- The GNU keyring is fetched over the network, so a signature checked against - it means "signed by whoever the keyring says". Checking those keys out of - band is manual. +- The GNU keyring is fetched over the network and kept with the sources, so a + signature checked against it means "signed by whoever the keyring said when + it was fetched". Checking those keys out of band is manual. - The kernel.org signing keys are pinned by fingerprint in `tools/verify-signatures.sh`, and those fingerprints still need confirming against kernel.org independently. diff --git a/tools/fetch-sources.sh b/tools/fetch-sources.sh index 31b71261..28cbe888 100755 --- a/tools/fetch-sources.sh +++ b/tools/fetch-sources.sh @@ -291,6 +291,17 @@ delete it yet - work out why first. See docs/supply-chain.md." fi done < <(manifest) +# The GNU keyring, kept with the sources so a cache of them carries it: the +# signature gate checks GNU signatures against it, and ftp.gnu.org does not +# answer every runner every time. +keyring="${KRYPTIK_SOURCES}/.keys/gnu-keyring.gpg" +if [[ ! -s "$keyring" ]]; then + mkdir -p "${KRYPTIK_SOURCES}/.keys" + log "fetching the GNU keyring" + fetch_attempt "https://ftp.gnu.org/gnu/gnu-keyring.gpg" "$keyring" fresh \ + || { rm -f "${keyring}.part"; warn "could not fetch the GNU keyring; tools/verify-signatures.sh fetches it again"; } +fi + if [[ "$MODE" == "lock" ]]; then sort -k2 "${KRYPTIK_LOCK}.new" > "$KRYPTIK_LOCK" rm -f "${KRYPTIK_LOCK}.new" diff --git a/tools/verify-signatures.sh b/tools/verify-signatures.sh index 6dcb03b1..cd9968d9 100755 --- a/tools/verify-signatures.sh +++ b/tools/verify-signatures.sh @@ -18,7 +18,8 @@ have gpg || die "gpg not found. Install gnupg." KEYDIR="${KRYPTIK_ROOT}/build/work/keys" SIGDIR="${KRYPTIK_SOURCES}/.signatures" -GNU_KEYRING="${KEYDIR}/gnu-keyring.gpg" +# With the sources, so a cache of them carries it (tools/fetch-sources.sh). +GNU_KEYRING="${KRYPTIK_SOURCES}/.keys/gnu-keyring.gpg" # A private GNUPGHOME, not --keyring: GnuPG 2.4 with keyboxd silently ignores # --keyring and verifies against the user's own store. @@ -30,7 +31,7 @@ REPORT="" NOTES="$(dirname "${BASH_SOURCE[0]}")/source-notes.tsv" for a in "$@"; do case "$a" in - --refresh) rm -rf "$GNUPGHOME" "$GNU_KEYRING" "${GNU_KEYRING}.imported" ;; + --refresh) rm -rf "$GNUPGHOME" "$GNU_KEYRING" ;; --fetch-unknown-keys) FETCH_UNKNOWN=1 ;; --strict) STRICT=1 ;; --report=*) REPORT="${a#--report=}" ;; @@ -67,10 +68,13 @@ report() { # run, not only with --fetch-unknown-keys (see UNAUDITED_FPRS). KEYS_MANIFEST="${KRYPTIK_ROOT}/keys.manifest" -mkdir -p "$KEYDIR" "$SIGDIR" "$GNUPGHOME" +mkdir -p "$KEYDIR" "$SIGDIR" "$GNUPGHOME" "$(dirname "$GNU_KEYRING")" chmod 700 "$GNUPGHOME" IMPORTED_MARK="${GNUPGHOME}/.kryptik-imported" +# This GNUPGHOME has the GNU keyring in it: kept beside the keys, not beside +# the keyring file, which outlives any one checkout's keys. +GNU_IMPORTED="${GNUPGHOME}/.gnu-keyring-imported" # A host that throttles (freedesktop.org answers 418 to a busy runner, others # 429 or 503) is asked again after a pause; a 404 is an answer. @@ -157,15 +161,15 @@ import_keys() { if [[ ! -s "$GNU_KEYRING" ]]; then quiet_fetch "${CANONICAL_GNU}/gnu-keyring.gpg" "$GNU_KEYRING" || rm -f "$GNU_KEYRING" fi - if [[ -s "$GNU_KEYRING" && ! -f "${GNU_KEYRING}.imported" ]]; then + if [[ -s "$GNU_KEYRING" && ! -f "$GNU_IMPORTED" ]]; then log "importing GNU keyring (a few thousand keys, this takes a moment)" gpg --batch --quiet --import "$GNU_KEYRING" 2>/dev/null || true count="$(gpg --batch --list-keys 2>/dev/null | grep -c '^pub' || true)" - [[ "$count" =~ ^[0-9]+$ && "$count" -ge 100 ]] && : > "${GNU_KEYRING}.imported" + [[ "$count" =~ ^[0-9]+$ && "$count" -ge 100 ]] && : > "$GNU_IMPORTED" fi # Without it nothing a GNU maintainer signed can be checked: a run that # could not fetch it is not a pass. - if [[ ! -f "${GNU_KEYRING}.imported" ]]; then + if [[ ! -f "$GNU_IMPORTED" ]]; then rm -f "$IMPORTED_MARK" if [[ "$STRICT" -eq 1 ]]; then err "the GNU keyring could not be fetched from ${CANONICAL_GNU}" @@ -188,7 +192,7 @@ Run again: what was fetched is kept." count="$(gpg --batch --list-keys 2>/dev/null | grep -c '^pub' || true)" [[ "$count" =~ ^[0-9]+$ ]] || count=0 - if [[ -f "${GNU_KEYRING}.imported" && "$missing" -eq 0 ]]; then + if [[ -f "$GNU_IMPORTED" && "$missing" -eq 0 ]]; then printf '%s' "$count" > "$IMPORTED_MARK" else rm -f "$IMPORTED_MARK"