upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/scripts/verify-release.sh
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/verify-release.sh')
-rwxr-xr-x[-rw-r--r--]scripts/verify-release.sh108
1 files changed, 58 insertions, 50 deletions
diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh
index c651e78..4fd4558 100644..100755
--- a/scripts/verify-release.sh
+++ b/scripts/verify-release.sh
@@ -1,23 +1,50 @@
1#!/usr/bin/env bash 1#!/usr/bin/env bash
2set -euo pipefail 2set -euo pipefail
3 3
4repo="bitflash-coin/bitflash" 4# Verifies a Bitflash release: the checksum file's signature against a pinned
5tag="${1:-latest}" 5# key, then the assets against the checksums.
6#
7# The signing key is embedded below, not fetched. That is the whole point of a
8# pinned key: the trust anchor ships with this script (in git), so it does not
9# depend on the same server that serves the release. A server that is
10# compromised can swap the binaries and the checksums, but it cannot forge a
11# signature by this key.
12
13host="https://releases.bitflash.network"
6require_signature=0 14require_signature=0
7 15
16# The Bitflash release signing key. Verification requires this exact key --
17# gpg's own "good signature" is not enough, because a good signature by *some*
18# key proves nothing. See issue #138.
19RELEASE_KEY_FP="910A2B4CCA879E81FB4B2AEA1D4A53D3B78AA4B8"
20read -r -d '' RELEASE_KEY <<'KEY' || true
21-----BEGIN PGP PUBLIC KEY BLOCK-----
22
23mDMEanSOmRYJKwYBBAHaRw8BAQdAXj24i2BYUcAmOpoXwjE4ZTynHsifCZ+niwQK
24HqO0nMa0UEJpdGZsYXNoIFJlbGVhc2VzIChiaXRmbGFzaC5uZXR3b3JrIHJlbGVh
25c2Ugc2lnbmluZykgPHJlbGVhc2VzQGJpdGZsYXNoLm5ldHdvcms+iJAEExYKADgW
26IQSRCitMyoeegftLKuodSlPTt4qkuAUCanSOmQIbAwULCQgHAgYVCgkICwIEFgID
27AQIeAQIXgAAKCRAdSlPTt4qkuIi9AP96X3OcstiCTTgKOr16Vhds3LyuZ19q3Apq
284KAcNuOUMAD+NqgJ1CqkLGPXfZLCtYBCRHXNYWxMMEk+ViLcRtdLMg4=
29=mxic
30-----END PGP PUBLIC KEY BLOCK-----
31KEY
32
8usage() { 33usage() {
9 cat <<'EOF' 34 cat <<'EOF'
10Usage: scripts/verify-release.sh [tag|latest] [--require-signature] 35Usage: scripts/verify-release.sh [tag|latest] [--require-signature]
11 36
12Downloads the release checksum file and every asset named in it, then verifies: 37Downloads the release checksum file and every asset named in it, then verifies:
13 1. SHA256SUMS.asc, if present, against SHA256SUMS with gpg. 38 1. SHA256SUMS.asc against SHA256SUMS, requiring the pinned Bitflash release
39 key (fingerprint 910A 2B4C CA87 9E81 FB4B 2AEA 1D4A 53D3 B78A A4B8).
14 2. SHA256SUMS against the downloaded assets. 40 2. SHA256SUMS against the downloaded assets.
15 41
16Use --require-signature for release audits. Without it, older releases that 42Releases from before signing was introduced ship only SHA256SUMS; without
17ship only SHA256SUMS are checked for integrity and reported as unsigned. 43--require-signature those are checked for integrity and reported as unsigned.
18EOF 44EOF
19} 45}
20 46
47tag="${1:-latest}"
21for arg in "${@:2}"; do 48for arg in "${@:2}"; do
22 case "$arg" in 49 case "$arg" in
23 --require-signature) require_signature=1 ;; 50 --require-signature) require_signature=1 ;;
@@ -25,19 +52,9 @@ for arg in "${@:2}"; do
25 *) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;; 52 *) echo "unknown option: $arg" >&2; usage >&2; exit 2 ;;
26 esac 53 esac
27done 54done
55if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then usage; exit 0; fi
28 56
29if [ "$tag" = "-h" ] || [ "$tag" = "--help" ]; then 57need() { command -v "$1" >/dev/null 2>&1 || { echo "missing required command: $1" >&2; exit 1; }; }
30 usage
31 exit 0
32fi
33
34need() {
35 if ! command -v "$1" >/dev/null 2>&1; then
36 echo "missing required command: $1" >&2
37 exit 1
38 fi
39}
40
41need sed 58need sed
42need awk 59need awk
43 60
@@ -46,39 +63,25 @@ if command -v sha256sum >/dev/null 2>&1; then
46elif command -v shasum >/dev/null 2>&1; then 63elif command -v shasum >/dev/null 2>&1; then
47 sha_check=(shasum -a 256 -c SHA256SUMS) 64 sha_check=(shasum -a 256 -c SHA256SUMS)
48else 65else
49 echo "missing required command: sha256sum or shasum" >&2 66 echo "missing required command: sha256sum or shasum" >&2; exit 1
50 exit 1
51fi 67fi
52 68
53download() { 69download() {
54 local url="$1" 70 if command -v curl >/dev/null 2>&1; then curl -fsSL "$1" -o "$2"
55 local out="$2" 71 elif command -v wget >/dev/null 2>&1; then wget -q "$1" -O "$2"
56 if command -v curl >/dev/null 2>&1; then 72 else echo "missing required command: curl or wget" >&2; exit 1; fi
57 curl -fsSL "$url" -o "$out"
58 elif command -v wget >/dev/null 2>&1; then
59 wget -q "$url" -O "$out"
60 else
61 echo "missing required command: curl or wget" >&2
62 exit 1
63 fi
64} 73}
65 74
66if [ "$tag" = "latest" ]; then 75if [ "$tag" = "latest" ]; then
67 tag="$(download "https://api.github.com/repos/$repo/releases/latest" - | 76 tag="$(download "$host/latest.txt" - 2>/dev/null | tr -d '[:space:]' || true)"
68 sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | 77 [ -n "$tag" ] || { echo "could not resolve the latest release tag from $host/latest.txt" >&2; exit 1; }
69 head -1)"
70 if [ -z "$tag" ]; then
71 echo "could not resolve latest release tag" >&2
72 exit 1
73 fi
74fi 78fi
75 79
76workdir="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-release-verify.XXXXXX")" 80workdir="$(mktemp -d "${TMPDIR:-/tmp}/bitflash-release-verify.XXXXXX")"
77cleanup() { rm -rf "$workdir"; } 81cleanup() { rm -rf "$workdir"; }
78trap cleanup EXIT 82trap cleanup EXIT
79 83
80base_url="https://github.com/$repo/releases/download/$tag" 84base_url="$host/$tag"
81
82echo "Verifying Bitflash $tag" 85echo "Verifying Bitflash $tag"
83echo "Working directory: $workdir" 86echo "Working directory: $workdir"
84cd "$workdir" 87cd "$workdir"
@@ -86,27 +89,32 @@ cd "$workdir"
86download "$base_url/SHA256SUMS" SHA256SUMS 89download "$base_url/SHA256SUMS" SHA256SUMS
87 90
88if download "$base_url/SHA256SUMS.asc" SHA256SUMS.asc 2>/dev/null; then 91if download "$base_url/SHA256SUMS.asc" SHA256SUMS.asc 2>/dev/null; then
89 if ! command -v gpg >/dev/null 2>&1; then 92 need gpg
90 echo "SHA256SUMS.asc exists, but gpg is not installed" >&2 93 # Verify in a throwaway keyring holding ONLY the pinned key, and require that
94 # the signature validates against it with the pinned fingerprint. This is the
95 # difference between "signed" and "signed by Bitflash".
96 keyring="$workdir/keyring"
97 mkdir -p "$keyring"; chmod 700 "$keyring"
98 printf '%s\n' "$RELEASE_KEY" | GNUPGHOME="$keyring" gpg --batch --quiet --import 2>/dev/null
99 status="$(GNUPGHOME="$keyring" gpg --batch --status-fd=1 --verify SHA256SUMS.asc SHA256SUMS 2>/dev/null || true)"
100 if printf '%s' "$status" | grep -q "VALIDSIG.*$RELEASE_KEY_FP"; then
101 echo "Signature OK -- signed by the pinned Bitflash release key"
102 else
103 echo "SIGNATURE REJECTED: not a valid signature by the pinned release key" >&2
104 echo " expected key: $RELEASE_KEY_FP" >&2
91 exit 1 105 exit 1
92 fi 106 fi
93 gpg --verify SHA256SUMS.asc SHA256SUMS
94 echo "Signature OK"
95else 107else
96 if [ "$require_signature" -eq 1 ]; then 108 if [ "$require_signature" -eq 1 ]; then
97 echo "release does not publish SHA256SUMS.asc" >&2 109 echo "release does not publish SHA256SUMS.asc" >&2; exit 1
98 exit 1
99 fi 110 fi
100 echo "WARNING: no SHA256SUMS.asc found; checking hashes only" 111 echo "WARNING: no SHA256SUMS.asc found; checking hashes only (unsigned release)"
101fi 112fi
102 113
103awk '{print $2}' SHA256SUMS | while IFS= read -r file; do 114awk '{print $2}' SHA256SUMS | while IFS= read -r file; do
104 file="${file#\*}" 115 file="${file#\*}"; file="${file#./}"
105 file="${file#./}"
106 [ -z "$file" ] && continue 116 [ -z "$file" ] && continue
107 case "$file" in 117 case "$file" in */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;; esac
108 */*|..*) echo "refusing unexpected checksum path: $file" >&2; exit 1 ;;
109 esac
110 echo "Downloading $file" 118 echo "Downloading $file"
111 download "$base_url/$file" "$file" 119 download "$base_url/$file" "$file"
112done 120done