Add security.txt
- Add scripts/security-txt.sh to generate a security.txt file - Add the current security.txt - Extend verify.sh to verify security.txt by default and cross-check that it expires together with the signing key - Describe security.txt in OPENPGP.md and README.md
This commit is contained in:
parent
ae768285b6
commit
30fee8de37
6 changed files with 116 additions and 15 deletions
60
scripts/security-txt.sh
Executable file
60
scripts/security-txt.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Generate security.txt (RFC 9116), clearsigned with the [S] subkey
|
||||
#
|
||||
# Usage: ./scripts/security-txt.sh
|
||||
#
|
||||
# Expires is read from the key, so the January ceremony only re-runs this
|
||||
# after the key's expiry was extended
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
# Internal [S] subkey; pinned with "!" below so `gpg` never picks another subkey
|
||||
SIGNING_KEY='D6D1989DF1F6B23FD776293DB46EDB4DFC26805D'
|
||||
|
||||
|
||||
REPO=$(cd "$(dirname "$0")/.." && pwd)
|
||||
OUT="$REPO/security.txt"
|
||||
|
||||
|
||||
# Notes:
|
||||
# - the 7th field of the pub line is the primary key's expiry in epoch seconds
|
||||
# - a subkey resolves to the primary key
|
||||
EPOCH=$(gpg --list-keys --with-colons "$SIGNING_KEY" \
|
||||
| awk -F: '$1 == "pub" { print $7; exit }')
|
||||
if [ -z "$EPOCH" ]; then
|
||||
echo "Error: No expiry found for key $SIGNING_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
EXPIRES=$(date -u -d "@$EPOCH" +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
|
||||
UNSIGNED=$(mktemp)
|
||||
trap 'rm -f "$UNSIGNED"' EXIT
|
||||
|
||||
|
||||
cat > "$UNSIGNED" <<EOT
|
||||
Canonical: https://www.alexander-hess.com/.well-known/security.txt
|
||||
Canonical: https://www.webartifex.de/.well-known/security.txt
|
||||
Contact: mailto:ah@alexander-hess.com
|
||||
Contact: mailto:alexander@webartifex.de
|
||||
Encryption: https://www.alexander-hess.com/public-key.asc
|
||||
Encryption: https://www.webartifex.de/public-key.asc
|
||||
Expires: $EXPIRES
|
||||
Preferred-Languages: en, de
|
||||
EOT
|
||||
|
||||
|
||||
gpg \
|
||||
--clearsign \
|
||||
--digest-algo SHA512 \
|
||||
--local-user "${SIGNING_KEY}!" \
|
||||
--yes \
|
||||
--output "$OUT" \
|
||||
"$UNSIGNED"
|
||||
|
||||
gpg --verify "$OUT"
|
||||
|
||||
|
||||
echo "Wrote security.txt (Expires: $EXPIRES)"
|
||||
|
|
@ -4,17 +4,18 @@
|
|||
#
|
||||
# Usage: ./scripts/verify.sh [FILE...]
|
||||
#
|
||||
# FILE defaults to KEYS.md OPENPGP.md ceremonies/*.md AGE.md
|
||||
# FILE defaults to KEYS.md OPENPGP.md ceremonies/*.md AGE.md security.txt
|
||||
#
|
||||
# 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
|
||||
# that age-recipient.txt matches the recipient in them, and,
|
||||
# if security.txt is among the FILEs, that it expires with the key
|
||||
#
|
||||
# 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
|
||||
# or if the key or security.txt has expired, or if a cross-check fails
|
||||
|
||||
set -eu
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ if [ $# -eq 0 ]; then
|
|||
"$REPO/OPENPGP.md" \
|
||||
"$REPO"/ceremonies/*.md \
|
||||
"$REPO/AGE.md" \
|
||||
"$REPO/security.txt" \
|
||||
# Keep this list aligned with the documentation at the top
|
||||
fi
|
||||
|
||||
|
|
@ -104,10 +106,13 @@ fi
|
|||
|
||||
|
||||
# age-recipient.txt is the only unsigned artifact,
|
||||
# so it must match the recipient in the signed documents
|
||||
# so it must match the recipient in the signed documents;
|
||||
# security.txt must expire together with the signing key,
|
||||
# but only cross-check it when it was among the verified files
|
||||
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")
|
||||
|
|
@ -120,6 +125,16 @@ for FILE; do
|
|||
FAILED=1
|
||||
fi
|
||||
;;
|
||||
|
||||
*/security.txt | security.txt)
|
||||
EXPECTED=$(date -u -d "@$EXPIRY" +%Y-%m-%dT%H:%M:%SZ)
|
||||
ACTUAL=$(sed -n 's/^Expires: //p' "$FILE")
|
||||
if [ "$ACTUAL" != "$EXPECTED" ]; then
|
||||
echo "FAIL security.txt expires $ACTUAL, key expires $EXPECTED" >&2
|
||||
FAILED=1
|
||||
fi
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
done
|
||||
|
|
|
|||
Loading…
Reference in a new issue