forked from steipete/CodexBar
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify_appcast.sh
More file actions
executable file
·92 lines (75 loc) · 2.68 KB
/
verify_appcast.sh
File metadata and controls
executable file
·92 lines (75 loc) · 2.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env bash
set -euo pipefail
# Verifies that the appcast entry for the given version has a valid ed25519 signature
# and that the enclosure length matches the downloaded archive.
#
# Usage: SPARKLE_PRIVATE_KEY_FILE=/path/to/key ./Scripts/verify_appcast.sh [version]
ROOT=$(cd "$(dirname "$0")/.." && pwd)
VERSION=${1:-$(source "$ROOT/version.env" && echo "$MARKETING_VERSION")}
APPCAST="${ROOT}/appcast.xml"
if [[ -z "${SPARKLE_PRIVATE_KEY_FILE:-}" ]]; then
echo "SPARKLE_PRIVATE_KEY_FILE is required" >&2
exit 1
fi
if [[ ! -f "$SPARKLE_PRIVATE_KEY_FILE" ]]; then
echo "Sparkle key file not found: $SPARKLE_PRIVATE_KEY_FILE" >&2
exit 1
fi
if [[ ! -f "$APPCAST" ]]; then
echo "appcast.xml not found at $APPCAST" >&2
exit 1
fi
# Clean the key file: strip comments/blank lines and require exactly one line of base64.
function cleaned_key_path() {
local tmp key_lines
key_lines=$(grep -v '^[[:space:]]*#' "$SPARKLE_PRIVATE_KEY_FILE" | sed '/^[[:space:]]*$/d')
if [[ $(printf "%s\n" "$key_lines" | wc -l) -ne 1 ]]; then
echo "Sparkle key file must contain exactly one base64 line (no comments/blank lines)." >&2
exit 1
fi
tmp=$(mktemp)
printf "%s" "$key_lines" > "$tmp"
echo "$tmp"
}
KEY_FILE=$(cleaned_key_path)
trap 'rm -f "$KEY_FILE" "$TMP_ZIP"' EXIT
TMP_ZIP=$(mktemp /tmp/codexbar-enclosure.XXXX.zip)
python3 - "$APPCAST" "$VERSION" >"$TMP_ZIP.meta" <<'PY'
import sys, xml.etree.ElementTree as ET
appcast = sys.argv[1]
version = sys.argv[2]
tree = ET.parse(appcast)
root = tree.getroot()
ns = {"sparkle": "http://www.andymatuschak.org/xml-namespaces/sparkle"}
entry = None
for item in root.findall("./channel/item"):
sv = item.findtext("sparkle:shortVersionString", default="", namespaces=ns)
if sv == version:
entry = item
break
if entry is None:
sys.exit("No appcast entry found for version {}".format(version))
enclosure = entry.find("enclosure")
url = enclosure.get("url")
sig = enclosure.get("{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature")
length = enclosure.get("length")
if not all([url, sig, length]):
sys.exit("Missing url/signature/length in appcast for version {}".format(version))
print(url)
print(sig)
print(length)
PY
readarray -t META <"$TMP_ZIP.meta"
URL="${META[0]}"
SIG="${META[1]}"
LEN_EXPECTED="${META[2]}"
echo "Downloading enclosure: $URL"
curl -L -o "$TMP_ZIP" "$URL"
LEN_ACTUAL=$(stat -f%z "$TMP_ZIP")
if [[ "$LEN_ACTUAL" != "$LEN_EXPECTED" ]]; then
echo "Length mismatch: expected $LEN_EXPECTED, got $LEN_ACTUAL" >&2
exit 1
fi
echo "Verifying Sparkle signature…"
sign_update --verify "$TMP_ZIP" "$SIG" --ed-key-file "$KEY_FILE"
echo "Appcast entry for $VERSION verified (signature and length match)."