build: check the firmware hash against the downloaded file
What changed, and why it matters
This commit adds a verification step to Blockstream Jade's internal release script. The script now checks that each firmware file's actual contents match a published hash after decompression. Previously, the script only checked that the hash file existed, not that it matched the firmware. This closes a gap where a corrupted or tampered firmware file could be distributed without being caught by the release-check process.
No immediate user action needed. This is a release-process improvement. Ensure the .hash files are generated consistently with the same decompression method, and consider adding similar integrity checks to any other distribution or update pipelines.
Security signals we found
Missing integrity check: prior code verified existence of hash file but not hash value
Supply-chain / release-process hardening
Firmware integrity verification added
Script aborts on hash mismatch
Evidence from the diff
The change is in release/scripts/checkfwsvr.sh, a server-side release validation script. It adds a get_uncompressed_hash() helper that prepends a gzip header to zlib-compressed firmware so gzip can decompress it, then computes a SHA-256 hash. The loop that iterates over firmware files now compares this computed hash against the stored .hash file and aborts on mismatch. Before, only the presence of the .hash file was verified, not its correctness.
Changed components
release/scripts/checkfwsvr.shInspect captured patch +12 / −1
diff --git a/release/scripts/checkfwsvr.sh b/release/scripts/checkfwsvr.sh
index b7d2a20..0b447ad 100755
--- a/release/scripts/checkfwsvr.sh
+++ b/release/scripts/checkfwsvr.sh
@@ -28,9 +28,15 @@ gcloud storage rsync ${GCLOUD_BUCKET} ${LOCAL_DIR} --recursive --delete-unmatche
# We always get this file too even though it doesn't appear to exist in the bucket.
rm -f ${LOCAL_DIR}/_.gstmp
-echo "Checking firmware server files and indices"
+echo "Checking firmware server files, hashes and indices"
rm -f "${MISSING_LOG}"
+function get_uncompressed_hash() {
+ # We add a gzip header to allow gzip to decompress the zlib data
+ printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - $1 | \
+ gzip -qdc 2>/dev/null | sha256sum | cut -d ' ' -f 1
+}
+
for hwdir in ${HWDIRS}; do
for index_name in index.json LATEST BETA PREVIOUS; do
index_file="${LOCAL_DIR}/${hwdir}/${index_name}"
@@ -57,6 +63,11 @@ for hwdir in ${HWDIRS}; do
if [ ! -f "${fw_file}.hash" ]; then
echo "Missing hash for ${fw_file}" >> "${MISSING_LOG}"
fi
+ fw_hash=$(get_uncompressed_hash "${fw_file}")
+ if [ "${fw_hash}" != $(cat "${fw_file}.hash") ]; then
+ echo "ERROR: Hash mismatch for ${fw_file}!"
+ exit 1
+ fi
;;
esac
done
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.