#!/bin/sh # Verify the signed files in this repo against the pinned [S] subkey # # Usage: ./scripts/verify.sh [FILE...] # # FILE defaults to KEYS.md OPENPGP.md ceremonies/*.md AGE.md # # A FILE with a FILE.asc next to it is checked as a detached signature, # any other FILE as a clearsigned document # # Also checks the key's expiry, and, # if KEYS.md or AGE.md is among the FILEs, # that age-recipient.txt matches the recipient in them # # Exits non-zero if any file lacks a valid signature from the pinned key, # or if the key has expired, or if a cross-check fails set -eu # Internal [S] subkey; every file must carry a valid signature from this key SIGNING_KEY='D6D1989DF1F6B23FD776293DB46EDB4DFC26805D' # Warn when the key expires within this many days WARN_DAYS=60 REPO=$(cd "$(dirname "$0")/.." && pwd) if [ $# -eq 0 ]; then set -- \ "$REPO/KEYS.md" \ "$REPO/OPENPGP.md" \ "$REPO"/ceremonies/*.md \ "$REPO/AGE.md" \ # Keep this list aligned with the documentation at the top fi # Print the fingerprints of all keys that made a valid signature, one per line signers() { gpg \ --status-fd 1 \ --verify "$@" \ 2>/dev/null \ | awk '$2 == "VALIDSIG" { print $3 }' } FAILED=0 for FILE; do if [ ! -f "$FILE" ]; then echo "Error: $FILE not found" >&2 exit 1 fi if [ -f "$FILE.asc" ]; then SIGNERS=$(signers "$FILE.asc" "$FILE") else SIGNERS=$(signers "$FILE") fi if printf '%s\n' "$SIGNERS" | grep -qx "$SIGNING_KEY"; then echo "OK $FILE" else echo "FAIL $FILE (signed by: $(printf '%s' "${SIGNERS:-nobody}" | tr '\n' ' '))" >&2 FAILED=1 fi done # Notes: # - The 7th field of the pub line is the primary key's expiry in epoch seconds # - A subkey resolves to the primary key EXPIRY=$(gpg --list-keys --with-colons "$SIGNING_KEY" \ | awk -F: '$1 == "pub" { print $7; exit }') if [ -z "$EXPIRY" ]; then echo "Error: No expiry found for key $SIGNING_KEY" >&2 exit 1 fi NOW=$(date -u +%s) DAYS_LEFT=$(( (EXPIRY - NOW) / 86400 )) if [ "$EXPIRY" -le "$NOW" ]; then echo "FAIL Key expired $(( (NOW - EXPIRY) / 86400 )) days ago" >&2 FAILED=1 elif [ "$DAYS_LEFT" -lt "$WARN_DAYS" ]; then echo "WARN Key expires in $DAYS_LEFT days" >&2 else echo "OK Key expires in $DAYS_LEFT days" fi # age-recipient.txt is the only unsigned artifact, # so it must match the recipient in the signed documents for FILE; do case "$FILE" in */KEYS.md | KEYS.md | */AGE.md | AGE.md) # shellcheck disable=SC2016 # The backticks are literal Markdown EXPECTED=$(sed -n 's/^> `\(age1[0-9a-z]*\)`$/\1/p' "$FILE") ACTUAL=$(cat "$REPO/age-recipient.txt") if [ -z "$EXPECTED" ]; then echo "FAIL No age recipient found in $FILE" >&2 FAILED=1 elif [ "$ACTUAL" != "$EXPECTED" ]; then echo "FAIL age-recipient.txt has $ACTUAL, $FILE has $EXPECTED" >&2 FAILED=1 fi ;; esac done exit "$FAILED"