vibetunnel/mac/scripts/validate-sparkle-signature.sh
Peter Steinberger 982c1ff927 Improve release scripts based on beta 9 lessons learned
- Add state tracking and resume capability to release process
- Create release-state.sh for tracking 9 major release steps
- Add --resume and --status flags to release.sh
- Fix private key format handling for sign_update tool
- Create clean key file (sparkle_ed_private_key) automatically
- Handle missing custom Node.js builds gracefully
- Add DerivedData app location fallback
- Create comprehensive release-checklist.sh script
- Update release documentation with critical learnings
- Enhance Stats.store documentation with setup instructions
- Add troubleshooting for 'Application not found' error
- Document fallback options for direct GitHub URLs
- Update all scripts to handle clean key file format

These improvements make the release process more reliable and
resilient to interruptions, with better error handling throughout.
2025-07-11 11:29:49 +02:00

218 lines
No EOL
7 KiB
Bash
Executable file

#!/bin/bash
#
# Validate Sparkle signatures are correct before release
#
# This script helps prevent the "improperly signed" error by verifying
# that signatures in appcast files match the actual DMG files.
set -euo pipefail
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Configuration
# Use the clean key file without comments for sign_update
SPARKLE_PRIVATE_KEY_PATH="${SPARKLE_PRIVATE_KEY_PATH:-private/sparkle_ed_private_key}"
# Fallback to commented key file if clean one doesn't exist
if [ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ] && [ -f "private/sparkle_private_key" ]; then
# Extract just the key from the commented file
KEY_LINE=$(grep -E '^[A-Za-z0-9+/]+=*$' "private/sparkle_private_key" | head -1)
if [ -n "$KEY_LINE" ]; then
echo "$KEY_LINE" > "private/sparkle_ed_private_key"
SPARKLE_PRIVATE_KEY_PATH="private/sparkle_ed_private_key"
else
SPARKLE_PRIVATE_KEY_PATH="private/sparkle_private_key"
fi
fi
EXPECTED_PUBLIC_KEY="AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI="
# Function to print colored output
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
# Check if sign_update is available
if ! command -v sign_update >/dev/null 2>&1; then
print_error "sign_update not found in PATH"
echo "Please install Sparkle tools or add them to PATH"
exit 1
fi
# Verify private key exists
if [ ! -f "$SPARKLE_PRIVATE_KEY_PATH" ]; then
print_error "Sparkle private key not found at: $SPARKLE_PRIVATE_KEY_PATH"
exit 1
fi
print_info "Using private key at: $SPARKLE_PRIVATE_KEY_PATH"
print_info "Expected public key: $EXPECTED_PUBLIC_KEY"
echo
# Function to validate appcast file
validate_appcast() {
local appcast_file=$1
local channel_name=$2
if [ ! -f "$appcast_file" ]; then
print_warning "$appcast_file not found, skipping"
return
fi
print_info "Validating $channel_name channel ($appcast_file)..."
# Extract all DMG URLs and signatures from appcast
local entries=$(grep -A 5 "<enclosure" "$appcast_file" | grep -E "(url=|sparkle:edSignature=)" | paste -d' ' - - | sed 's/.*url="\([^"]*\)".*/\1/' | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
local found_issues=0
while IFS=' ' read -r dmg_url signature; do
[ -z "$dmg_url" ] && continue
# Extract filename from URL
local dmg_filename=$(basename "$dmg_url")
# Skip if not a DMG URL
if [[ ! "$dmg_filename" =~ \.dmg$ ]]; then
continue
fi
print_info "Checking $dmg_filename..."
# Download DMG if not already present
if [ ! -f "/tmp/$dmg_filename" ]; then
print_info " Downloading from GitHub..."
if ! curl -sL "$dmg_url" -o "/tmp/$dmg_filename" 2>/dev/null; then
print_error " Failed to download $dmg_filename"
((found_issues++))
continue
fi
fi
# Generate signature with correct private key
print_info " Generating signature with file-based key..."
local correct_signature=$(sign_update -f "$SPARKLE_PRIVATE_KEY_PATH" "/tmp/$dmg_filename" 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
if [ -z "$correct_signature" ]; then
print_error " Failed to generate signature for $dmg_filename"
((found_issues++))
continue
fi
# Compare signatures
if [ "$signature" = "$correct_signature" ]; then
print_success " ✓ Signature is correct"
else
print_error " ✗ Signature mismatch!"
echo " Appcast has: $signature"
echo " Should be: $correct_signature"
((found_issues++))
fi
# Clean up
rm -f "/tmp/$dmg_filename"
done < <(xmllint --format "$appcast_file" 2>/dev/null | grep -A 1 'url=".*\.dmg"' | grep -E "(url=|sparkle:edSignature=)" | paste -d' ' - - | sed 's/.*url="\([^"]*\)".*/\1 /' | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
if [ $found_issues -eq 0 ]; then
print_success "✓ All signatures in $channel_name channel are correct!"
else
print_error "✗ Found $found_issues signature issues in $channel_name channel"
return 1
fi
}
# Function to test sign_update with different methods
test_signing_methods() {
print_info "Testing different signing methods..."
# Create a test file
echo "test" > /tmp/sparkle_test.txt
# Test with file-based key (correct)
print_info "Testing with file-based key (-f flag)..."
local file_sig=$(sign_update -f "$SPARKLE_PRIVATE_KEY_PATH" /tmp/sparkle_test.txt 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
# Test without -f flag (may use keychain)
print_info "Testing without -f flag (may use keychain)..."
local keychain_sig=$(sign_update /tmp/sparkle_test.txt 2>/dev/null | grep "sparkle:edSignature" | sed 's/.*sparkle:edSignature="\([^"]*\)".*/\1/')
rm -f /tmp/sparkle_test.txt
if [ -n "$file_sig" ]; then
print_success "File-based key produces signature: ${file_sig:0:20}..."
else
print_error "Failed to generate signature with file-based key"
fi
if [ -n "$keychain_sig" ]; then
print_warning "Keychain key produces signature: ${keychain_sig:0:20}..."
if [ "$file_sig" != "$keychain_sig" ]; then
print_error "⚠️ WARNING: Keychain has a DIFFERENT key than the file!"
print_error "⚠️ ALWAYS use the -f flag to ensure correct signatures!"
else
print_info "Keychain and file keys match (this is good)"
fi
else
print_info "No keychain key found (this is fine)"
fi
echo
}
# Main execution
main() {
echo "=== Sparkle Signature Validation Tool ==="
echo
# Test signing methods
test_signing_methods
# Validate appcast files
local exit_code=0
if ! validate_appcast "../appcast.xml" "Stable"; then
exit_code=1
fi
echo
if ! validate_appcast "../appcast-prerelease.xml" "Pre-release"; then
exit_code=1
fi
echo
echo "=== Summary ==="
if [ $exit_code -eq 0 ]; then
print_success "All signatures are valid! ✅"
print_info "Your appcast files are correctly signed."
else
print_error "Signature validation failed! ❌"
print_warning "To fix:"
echo "1. Download the DMG files from GitHub"
echo "2. Generate correct signatures with:"
echo " sign_update -f $SPARKLE_PRIVATE_KEY_PATH [dmg-file]"
echo "3. Update the appcast XML files with the correct signatures"
echo "4. Commit and push the updated appcast files"
fi
exit $exit_code
}
# Run main function
main "$@"