1 #!/usr/bin/env bash
2 export LC_ALL=C
3 set -e -o pipefail
4 5 # Source the common prelude, which:
6 # 1. Checks if we're at the top directory of the Bitcoin Core repository
7 # 2. Defines a few common functions and variables
8 #
9 # shellcheck source=libexec/prelude.bash
10 source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"
11 12 13 ###################
14 ## Sanity Checks ##
15 ###################
16 17 ################
18 # Required non-builtin commands should be invokable
19 ################
20 21 check_tools cat env basename mkdir diff sort
22 23 if [ -z "$NO_SIGN" ]; then
24 # make it possible to override the gpg binary
25 GPG=${GPG:-gpg}
26 27 # $GPG can contain extra arguments passed to the binary
28 # so let's check only the existence of arg[0]
29 # shellcheck disable=SC2206
30 GPG_ARRAY=($GPG)
31 check_tools "${GPG_ARRAY[0]}"
32 fi
33 34 ################
35 # Required env vars should be non-empty
36 ################
37 38 cmd_usage() {
39 cat <<EOF
40 Synopsis:
41 42 env GUIX_SIGS_REPO=<path/to/guix.sigs> \\
43 SIGNER=GPG_KEY_NAME[=SIGNER_NAME] \\
44 [ NO_SIGN=1 ]
45 ./contrib/guix/guix-attest
46 47 Example w/o overriding signing name:
48 49 env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
50 SIGNER=achow101 \\
51 ./contrib/guix/guix-attest
52 53 Example overriding signing name:
54 55 env GUIX_SIGS_REPO=/home/dongcarl/guix.sigs \\
56 SIGNER=0x96AB007F1A7ED999=dongcarl \\
57 ./contrib/guix/guix-attest
58 59 Example w/o signing, just creating SHA256SUMS:
60 61 env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
62 SIGNER=achow101 \\
63 NO_SIGN=1 \\
64 ./contrib/guix/guix-attest
65 66 EOF
67 }
68 69 if [ -z "$GUIX_SIGS_REPO" ] || [ -z "$SIGNER" ]; then
70 cmd_usage
71 exit 1
72 fi
73 74 ################
75 # GUIX_SIGS_REPO should exist as a directory
76 ################
77 78 if [ ! -d "$GUIX_SIGS_REPO" ]; then
79 cat << EOF
80 ERR: The specified GUIX_SIGS_REPO is not an existent directory:
81 82 '$GUIX_SIGS_REPO'
83 84 Hint: Please clone the guix.sigs repository and point to it with the
85 GUIX_SIGS_REPO environment variable.
86 87 EOF
88 cmd_usage
89 exit 1
90 fi
91 92 ################
93 # The key specified in SIGNER should be usable
94 ################
95 96 IFS='=' read -r gpg_key_name signer_name <<< "$SIGNER"
97 if [ -z "${signer_name}" ]; then
98 signer_name="$gpg_key_name"
99 fi
100 101 if [ -z "$NO_SIGN" ] && ! ${GPG} --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
102 echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
103 exit 1
104 fi
105 106 ################
107 # We should be able to find at least one output
108 ################
109 110 echo "Looking for build output SHA256SUMS fragments in ${OUTDIR_BASE}"
111 112 shopt -s nullglob
113 sha256sum_fragments=( "$OUTDIR_BASE"/*/SHA256SUMS.part ) # This expands to an array of directories...
114 shopt -u nullglob
115 116 noncodesigned_fragments=()
117 codesigned_fragments=()
118 119 if (( ${#sha256sum_fragments[@]} )); then
120 echo "Found build output SHA256SUMS fragments:"
121 for outdir in "${sha256sum_fragments[@]}"; do
122 echo " '$outdir'"
123 case "$outdir" in
124 "$OUTDIR_BASE"/*-codesigned/SHA256SUMS.part)
125 codesigned_fragments+=("$outdir")
126 ;;
127 *)
128 noncodesigned_fragments+=("$outdir")
129 ;;
130 esac
131 done
132 echo
133 else
134 echo "ERR: Could not find any build output SHA256SUMS fragments in ${OUTDIR_BASE}"
135 exit 1
136 fi
137 138 ##############
139 ## Attest ##
140 ##############
141 142 shasum_already_exists() {
143 cat <<EOF
144 --
145 146 ERR: An ${1} file already exists for '${VERSION}' and attests
147 differently. You likely previously attested to a partial build (e.g. one
148 where you specified the HOST environment variable).
149 150 See the diff above for more context.
151 152 Hint: You may wish to remove the existing attestations and their signatures by
153 invoking:
154 155 rm '${PWD}/${1}'{,.asc}
156 157 Then try running this script again.
158 159 EOF
160 }
161 162 echo "Attesting to build outputs for version: '${VERSION}'"
163 echo ""
164 165 # Given a SHA256SUMS file as stdin that has lines like:
166 # 0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6 a/b/d/c/d/s/bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
167 # ...
168 #
169 # Replace each line's file name with its basename:
170 # 0ba536819b221a91d3d42e978be016aac918f40984754d74058aa0c921cd3ea6 bitcoin-22.0rc2-riscv64-linux-gnu.tar.gz
171 # ...
172 #
173 basenameify_SHA256SUMS() {
174 sed -E 's@(^[[:xdigit:]]{64}[[:space:]]+).+/([^/]+$)@\1\2@'
175 }
176 177 outsigdir="$GUIX_SIGS_REPO/$VERSION/$signer_name"
178 mkdir -p "$outsigdir"
179 (
180 cd "$outsigdir"
181 182 temp_noncodesigned="$(mktemp)"
183 trap 'rm -rf -- "$temp_noncodesigned"' EXIT
184 185 if (( ${#noncodesigned_fragments[@]} )); then
186 cat "${noncodesigned_fragments[@]}" \
187 | sort -u \
188 | sort -k2 \
189 | basenameify_SHA256SUMS \
190 > "$temp_noncodesigned"
191 if [ -e noncodesigned.SHA256SUMS ]; then
192 # The SHA256SUMS already exists, make sure it's exactly what we
193 # expect, error out if not
194 if diff -u noncodesigned.SHA256SUMS "$temp_noncodesigned"; then
195 echo "A noncodesigned.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
196 else
197 shasum_already_exists noncodesigned.SHA256SUMS
198 exit 1
199 fi
200 else
201 mv "$temp_noncodesigned" noncodesigned.SHA256SUMS
202 fi
203 else
204 echo "ERR: No noncodesigned outputs found for '${VERSION}', exiting..."
205 exit 1
206 fi
207 208 temp_all="$(mktemp)"
209 trap 'rm -rf -- "$temp_all"' EXIT
210 211 if (( ${#codesigned_fragments[@]} )); then
212 # Note: all.SHA256SUMS attests to all of $sha256sum_fragments, but is
213 # not needed if there are no $codesigned_fragments
214 cat "${sha256sum_fragments[@]}" \
215 | sort -u \
216 | sort -k2 \
217 | basenameify_SHA256SUMS \
218 > "$temp_all"
219 if [ -e all.SHA256SUMS ]; then
220 # The SHA256SUMS already exists, make sure it's exactly what we
221 # expect, error out if not
222 if diff -u all.SHA256SUMS "$temp_all"; then
223 echo "An all.SHA256SUMS file already exists for '${VERSION}' and is up-to-date."
224 else
225 shasum_already_exists all.SHA256SUMS
226 exit 1
227 fi
228 else
229 mv "$temp_all" all.SHA256SUMS
230 fi
231 else
232 # It is fine to have the codesigned outputs be missing (perhaps the
233 # detached codesigs have not been published yet), just print a log
234 # message instead of erroring out
235 echo "INFO: No codesigned outputs found for '${VERSION}', skipping..."
236 fi
237 238 if [ -z "$NO_SIGN" ]; then
239 echo "Signing SHA256SUMS to produce SHA256SUMS.asc"
240 for i in *.SHA256SUMS; do
241 if [ ! -e "$i".asc ]; then
242 ${GPG} --detach-sign \
243 --digest-algo sha256 \
244 --local-user "$gpg_key_name" \
245 --armor \
246 --output "$i".asc "$i"
247 else
248 echo "Signature already there"
249 fi
250 done
251 else
252 echo "Not signing SHA256SUMS as \$NO_SIGN is not empty"
253 fi
254 echo ""
255 )
256