Files
DDI/scripts/sign-release.sh
T
sir_melonchenandClaude Opus 5.5 709b7a1aab feat(release): sign and verify SHA256SUMS with Ed25519
scripts/sign-release.sh signs with a key file or the PEM key from
RELEASE_SIGNING_KEY (private temporary file, never printed), verifies
with openssl pkeyutl -rawin and has a selftest with a throwaway key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:41:04 +02:00

132 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: Apache-2.0
#
# Signs and verifies release checksums with Ed25519 (ADR 0024, docs/RELEASE.md).
# The signature is the raw 64-byte output of
# `openssl pkeyutl -sign -rawin` over SHA256SUMS, stored as SHA256SUMS.sig.
#
# Usage:
# scripts/sign-release.sh sign SUMS [KEY_FILE]
# writes SUMS.sig; without KEY_FILE the PEM private key is taken from
# the environment variable RELEASE_SIGNING_KEY (it is written to a
# private temporary file and removed again, never printed)
# scripts/sign-release.sh verify SUMS SIG PUBLIC_KEY_FILE
# exits 0 only if SIG is a valid signature of SUMS
# scripts/sign-release.sh public-key KEY_FILE
# prints the PEM public key that belongs to a private key
# scripts/sign-release.sh selftest
# signs and verifies with a throwaway key (CI); a tampered file and a
# foreign key must fail
#
# Requires OpenSSL 3.
set -euo pipefail
usage() {
sed -n '4,21p' "$0" | sed 's/^# \{0,1\}//'
}
die() {
echo "$*" >&2
exit 1
}
command -v openssl >/dev/null || die "openssl is required"
# sign SUMS KEY_FILE
sign_with_file() {
local sums="$1" key="$2"
[[ -f "$sums" ]] || die "no such file: $sums"
[[ -f "$key" ]] || die "no such key file: $key"
openssl pkeyutl -sign -rawin -inkey "$key" -in "$sums" -out "${sums}.sig"
[[ "$(wc -c <"${sums}.sig")" -eq 64 ]] || die "unexpected signature size (is the key an Ed25519 key?)"
echo "signed ${sums} -> ${sums}.sig"
}
cmd_sign() {
(($# >= 1 && $# <= 2)) || {
usage >&2
exit 2
}
if (($# == 2)); then
sign_with_file "$1" "$2"
return
fi
[[ -n "${RELEASE_SIGNING_KEY:-}" ]] || die "RELEASE_SIGNING_KEY is not set and no key file was given"
local dir
dir="$(umask 077 && mktemp -d)"
# shellcheck disable=SC2064 # expand now: $dir is local
trap "rm -rf '$dir'" EXIT
(umask 077 && printf '%s\n' "$RELEASE_SIGNING_KEY" >"$dir/key.pem")
sign_with_file "$1" "$dir/key.pem"
rm -rf "$dir"
trap - EXIT
}
cmd_verify() {
(($# == 3)) || {
usage >&2
exit 2
}
local sums="$1" sig="$2" pub="$3"
for f in "$sums" "$sig" "$pub"; do
[[ -f "$f" ]] || die "no such file: $f"
done
if openssl pkeyutl -verify -pubin -inkey "$pub" -rawin -in "$sums" -sigfile "$sig" >/dev/null 2>&1; then
echo "signature of ${sums} is valid"
else
die "signature of ${sums} is NOT valid"
fi
}
cmd_public_key() {
(($# == 1)) || {
usage >&2
exit 2
}
openssl pkey -in "$1" -pubout
}
cmd_selftest() {
local dir
dir="$(umask 077 && mktemp -d)"
# shellcheck disable=SC2064 # expand now: $dir is local
trap "rm -rf '$dir'" EXIT
openssl genpkey -algorithm ed25519 -out "$dir/key.pem" 2>/dev/null
openssl pkey -in "$dir/key.pem" -pubout -out "$dir/pub.pem"
openssl genpkey -algorithm ed25519 -out "$dir/other.pem" 2>/dev/null
openssl pkey -in "$dir/other.pem" -pubout -out "$dir/other-pub.pem"
printf '%s example_v0.0.0_linux_amd64.tar.gz\n' "$(printf 'x' | sha256sum | cut -d' ' -f1)" >"$dir/SHA256SUMS"
# Signing from the environment, as the release workflow does.
RELEASE_SIGNING_KEY="$(cat "$dir/key.pem")" bash "$0" sign "$dir/SHA256SUMS" >/dev/null
cmd_verify "$dir/SHA256SUMS" "$dir/SHA256SUMS.sig" "$dir/pub.pem" >/dev/null ||
die "selftest: valid signature rejected"
if bash "$0" verify "$dir/SHA256SUMS" "$dir/SHA256SUMS.sig" "$dir/other-pub.pem" >/dev/null 2>&1; then
die "selftest: signature accepted with a foreign key"
fi
printf 'tampered\n' >>"$dir/SHA256SUMS"
if bash "$0" verify "$dir/SHA256SUMS" "$dir/SHA256SUMS.sig" "$dir/pub.pem" >/dev/null 2>&1; then
die "selftest: tampered file accepted"
fi
echo "selftest passed: sign, verify, foreign key and tampered file rejected"
}
(($# >= 1)) || {
usage >&2
exit 2
}
cmd="$1"
shift
case "$cmd" in
sign) cmd_sign "$@" ;;
verify) cmd_verify "$@" ;;
public-key) cmd_public_key "$@" ;;
selftest) cmd_selftest "$@" ;;
-h | --help | help) usage ;;
*)
usage >&2
exit 2
;;
esac