Skip to content

Commit d78f5d7

Browse files
committed
fix: make Sparkle signature verification robust in release script
The previous verification logic had multiple issues: - sleep 3 was too short for GitHub to process uploads - No verification that downloaded file was actually a ZIP - sed replacement could fail silently - Only re-signed if SHAs differed (race condition) New approach: - Wait 10s + retry loop with exponential backoff (up to 5 attempts) - Verify downloaded file is a valid ZIP archive - ALWAYS re-sign with the actual GitHub file (not conditional) - Use Python for reliable XML replacement - Verify appcast.xml was actually updated before continuing - Exit with error if any step fails This ensures Sparkle signatures always match what users download.
1 parent 5c9c19e commit d78f5d7

1 file changed

Lines changed: 85 additions & 22 deletions

File tree

scripts/release.sh

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -435,38 +435,101 @@ gh release create "v${VERSION}" \
435435

436436
echo -e "${GREEN}GitHub release created${NC}"
437437

438-
# CRITICAL: Verify uploaded ZIP and re-sign if needed
439-
echo "Verifying uploaded ZIP..."
440-
sleep 3 # Give GitHub time to process
438+
# CRITICAL: Verify uploaded ZIP and re-sign with the ACTUAL GitHub file
439+
# This ensures Sparkle signature always matches what users download
440+
echo "Waiting for GitHub to process upload..."
441+
sleep 10 # Give GitHub more time to process
442+
443+
GITHUB_ZIP_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${APP_NAME}-v${VERSION}.zip"
444+
GITHUB_ZIP_PATH="/tmp/github-uploaded-${VERSION}.zip"
445+
446+
# Download with retries and verification
447+
MAX_RETRIES=5
448+
RETRY_DELAY=5
449+
for i in $(seq 1 $MAX_RETRIES); do
450+
echo "Downloading GitHub ZIP (attempt $i/$MAX_RETRIES)..."
451+
curl -L -s -o "$GITHUB_ZIP_PATH" "$GITHUB_ZIP_URL"
452+
453+
# Verify it's actually a ZIP file (not HTML error page)
454+
if file "$GITHUB_ZIP_PATH" | grep -q "Zip archive"; then
455+
echo -e "${GREEN}Downloaded valid ZIP file${NC}"
456+
break
457+
else
458+
echo -e "${YELLOW}Download failed or not a ZIP file, retrying in ${RETRY_DELAY}s...${NC}"
459+
rm -f "$GITHUB_ZIP_PATH"
460+
sleep $RETRY_DELAY
461+
RETRY_DELAY=$((RETRY_DELAY * 2))
462+
fi
463+
464+
if [ $i -eq $MAX_RETRIES ]; then
465+
echo -e "${RED}ERROR: Failed to download valid ZIP from GitHub after $MAX_RETRIES attempts${NC}"
466+
echo "Please verify manually and update appcast.xml"
467+
exit 1
468+
fi
469+
done
470+
471+
# ALWAYS re-sign with the actual GitHub file to ensure signature matches
472+
# This is the ONLY way to guarantee the signature is correct
473+
echo "Signing the actual GitHub ZIP file..."
474+
GITHUB_FILE_SIZE=$(stat -f%z "$GITHUB_ZIP_PATH")
475+
476+
echo "$SPARKLE_PRIVATE_KEY" > /tmp/sparkle_key_verify
477+
GITHUB_SIGNATURE_OUTPUT=$("$SPARKLE_SIGN" --ed-key-file /tmp/sparkle_key_verify "$GITHUB_ZIP_PATH")
478+
rm -f /tmp/sparkle_key_verify
479+
480+
GITHUB_ED_SIGNATURE=$(echo "$GITHUB_SIGNATURE_OUTPUT" | grep -o 'sparkle:edSignature="[^"]*"' | cut -d'"' -f2)
481+
482+
if [ -z "$GITHUB_ED_SIGNATURE" ]; then
483+
echo -e "${RED}ERROR: Failed to generate signature for GitHub ZIP${NC}"
484+
exit 1
485+
fi
486+
487+
echo "GitHub ZIP size: $GITHUB_FILE_SIZE bytes"
488+
echo "GitHub ZIP signature: ${GITHUB_ED_SIGNATURE:0:30}..."
489+
490+
# Update appcast.xml with the CORRECT signature for the GitHub file
491+
# Use Python for reliable XML-safe replacement
492+
python3 << PYEOF
493+
import re
494+
495+
version = "${VERSION}"
496+
new_signature = "${GITHUB_ED_SIGNATURE}"
497+
new_size = "${GITHUB_FILE_SIZE}"
441498
442-
curl -L -s -o "/tmp/github-uploaded-${VERSION}.zip" \
443-
"https://github.com/${REPO}/releases/download/v${VERSION}/${APP_NAME}-v${VERSION}.zip"
499+
with open('appcast.xml', 'r') as f:
500+
content = f.read()
444501
445-
LOCAL_SHA=$(shasum -a 256 "build/${APP_NAME}-v${VERSION}.zip" | cut -d' ' -f1)
446-
GITHUB_SHA=$(shasum -a 256 "/tmp/github-uploaded-${VERSION}.zip" | cut -d' ' -f1)
502+
# Find and update the enclosure for this version
503+
# Match the enclosure line for this specific version
504+
pattern = r'(download/v' + re.escape(version) + r'/[^"]+\.zip"\s+sparkle:edSignature=")[^"]*("\s+length=")[^"]*(")'
505+
replacement = r'\g<1>' + new_signature + r'\g<2>' + new_size + r'\g<3>'
447506
448-
if [ "$LOCAL_SHA" != "$GITHUB_SHA" ]; then
449-
echo -e "${YELLOW}Warning: GitHub ZIP differs from local! Re-signing with GitHub version...${NC}"
507+
new_content, count = re.subn(pattern, replacement, content)
450508
451-
# Re-sign the GitHub version
452-
echo "$SPARKLE_PRIVATE_KEY" > /tmp/sparkle_key_verify
453-
GITHUB_SIGNATURE_OUTPUT=$("$SPARKLE_SIGN" --ed-key-file /tmp/sparkle_key_verify "/tmp/github-uploaded-${VERSION}.zip")
454-
rm -f /tmp/sparkle_key_verify
509+
if count == 0:
510+
print(f"ERROR: Could not find enclosure for version {version} in appcast.xml")
511+
exit(1)
455512
456-
GITHUB_ED_SIGNATURE=$(echo "$GITHUB_SIGNATURE_OUTPUT" | grep -o 'sparkle:edSignature="[^"]*"' | cut -d'"' -f2)
457-
GITHUB_FILE_SIZE=$(stat -f%z "/tmp/github-uploaded-${VERSION}.zip")
513+
with open('appcast.xml', 'w') as f:
514+
f.write(new_content)
458515
459-
# Update appcast.xml with correct signature
460-
sed -i '' "s|sparkle:edSignature=\"${ED_SIGNATURE}\"|sparkle:edSignature=\"${GITHUB_ED_SIGNATURE}\"|g" appcast.xml
461-
sed -i '' "s|length=\"${FILE_SIZE}\"|length=\"${GITHUB_FILE_SIZE}\"|g" appcast.xml
516+
print(f"Updated appcast.xml: signature and length for v{version}")
517+
PYEOF
462518

463-
ED_SIGNATURE="$GITHUB_ED_SIGNATURE"
464-
FILE_SIZE="$GITHUB_FILE_SIZE"
519+
# Verify the update was successful
520+
if ! grep -q "sparkle:edSignature=\"${GITHUB_ED_SIGNATURE}\"" appcast.xml; then
521+
echo -e "${RED}ERROR: Failed to update appcast.xml with new signature${NC}"
522+
exit 1
523+
fi
465524

466-
echo -e "${GREEN}Appcast updated with correct GitHub signature${NC}"
525+
if ! grep -q "length=\"${GITHUB_FILE_SIZE}\"" appcast.xml; then
526+
echo -e "${RED}ERROR: Failed to update appcast.xml with new file size${NC}"
527+
exit 1
467528
fi
468529

469-
rm -f "/tmp/github-uploaded-${VERSION}.zip"
530+
echo -e "${GREEN}Appcast verified: signature and size match GitHub ZIP${NC}"
531+
532+
rm -f "$GITHUB_ZIP_PATH"
470533

471534
# Git commit and push
472535
git add -A

0 commit comments

Comments
 (0)