History

build(release): notarize macOS binaries (#118)

Peter Steinberger committed 4 months ago

Commit afaefcb1 parent 2c26a606 files · +130 −10Browse files

Showing the changes to scripts (4 of 6 files). Show all

Unified · Split

4 files
  1. scripts/codesign-macos.sh +44 −3
  2. scripts/package-release.sh +5 −1
  3. scripts/test-release.sh +74 −3
  4. scripts/verify-release.sh +1 −0
scripts/codesign-macos.sh+44−3
@@ -31,16 +31,57 @@ identity=${CODESIGN_IDENTITY:-} · 30 unmodified lines
31 echo "codesign: official macOS releases require $expected_authority" >&231 echo "codesign: official macOS releases require $expected_authority" >&2
32 exit 132 exit 1
33}33}
34[[ -n "${NOTARYTOOL_KEYCHAIN_PROFILE:-}" ]] || {
35 echo "codesign: NOTARYTOOL_KEYCHAIN_PROFILE is required at runtime" >&2
36 exit 1
37}
38
39for tool in codesign ditto mktemp mv plutil xcrun; do
40 command -v "$tool" >/dev/null 2>&1 || {
41 echo "codesign: missing required command: $tool" >&2
42 exit 1
43 }
44done
34 45
46binary_dir=$(cd "$(dirname "$binary")" && pwd)
47binary_name=$(basename "$binary")
48work_dir=$(mktemp -d "$binary_dir/.gitcrawl-notary.XXXXXX")
49candidate="$work_dir/$binary_name"
50submission="$work_dir/$binary_name.zip"
51trap 'rm -rf "$work_dir"' EXIT
52
53# Keep the GoReleaser output unchanged unless the complete signing and
54# notarization contract succeeds.
55cp -p "$binary" "$candidate"
35codesign --force \56codesign --force \
36 --options runtime \57 --options runtime \
37 --timestamp \58 --timestamp \
38 --identifier "$identifier" \59 --identifier "$identifier" \
39 --sign "$identity" \60 --sign "$identity" \
40 "$binary"61 "$candidate"
41codesign --verify --strict -R="$requirement" --verbose=2 "$binary"62codesign --verify --strict -R="$requirement" --verbose=2 "$candidate"
42 63
43signature=$(codesign -dvvv "$binary" 2>&1)64signature=$(codesign -dvvv "$candidate" 2>&1)
44grep -Fx "Identifier=$identifier" <<<"$signature" >/dev/null65grep -Fx "Identifier=$identifier" <<<"$signature" >/dev/null
45grep -Fx "TeamIdentifier=$expected_team_id" <<<"$signature" >/dev/null66grep -Fx "TeamIdentifier=$expected_team_id" <<<"$signature" >/dev/null
46grep -Fx "Authority=$expected_authority" <<<"$signature" >/dev/null67grep -Fx "Authority=$expected_authority" <<<"$signature" >/dev/null
68
69ditto -c -k --sequesterRsrc --keepParent "$candidate" "$submission"
70notary_result=$(xcrun notarytool submit "$submission" \
71 --keychain-profile "$NOTARYTOOL_KEYCHAIN_PROFILE" \
72 --no-s3-acceleration \
73 --wait \
74 --output-format json)
75notary_status=$(plutil -extract status raw -o - - <<<"$notary_result")
76notary_id=$(plutil -extract id raw -o - - <<<"$notary_result")
77[[ "$notary_status" == Accepted ]] || {
78 echo "codesign: notarization status is ${notary_status:-missing}, expected Accepted" >&2
79 exit 1
80}
81[[ "$notary_id" =~ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}$ ]] || {
82 echo "codesign: notarization response has an invalid submission id" >&2
83 exit 1
84}
85
86codesign --verify --strict --check-notarization -R=notarized "$candidate"
87mv -f "$candidate" "$binary"
scripts/package-release.sh+5−1
@@ -21,8 +21,12 @@ fi · 20 unmodified lines
21 echo "official releases require $EXPECTED_AUTHORITY" >&221 echo "official releases require $EXPECTED_AUTHORITY" >&2
22 exit 122 exit 1
23}23}
24[[ -n "${NOTARYTOOL_KEYCHAIN_PROFILE:-}" ]] || {
25 echo "official releases require NOTARYTOOL_KEYCHAIN_PROFILE at runtime" >&2
26 exit 1
27}
24 28
25for tool in codesign git go goreleaser lipo shasum tar; do29for tool in codesign ditto git go goreleaser lipo plutil shasum tar xcrun; do
26 command -v "$tool" >/dev/null || {30 command -v "$tool" >/dev/null || {
27 echo "missing required tool: $tool" >&231 echo "missing required tool: $tool" >&2
28 exit 132 exit 1
scripts/test-release.sh+74−3
@@ -25,6 +25,9 @@ EOF · 24 unmodified lines
25cat > "$FAKE_BIN/codesign" <<'EOF'25cat > "$FAKE_BIN/codesign" <<'EOF'
26#!/usr/bin/env bash26#!/usr/bin/env bash
27printf '%s\n' "$*" >> "${MOCK_CODESIGN_LOG:?}"27printf '%s\n' "$*" >> "${MOCK_CODESIGN_LOG:?}"
28if [[ " $* " == *' --check-notarization '* && "${MOCK_NOTARIZATION_REJECT:-0}" == 1 ]]; then
29 exit 1
30fi
28case " $* " in31case " $* " in
29 *' -dvvv '*)32 *' -dvvv '*)
30 {33 {
@@ -36,6 +39,32 @@ case " $* " in · 5 unmodified lines
36esac39esac
37EOF40EOF
38 41
42cat > "$FAKE_BIN/ditto" <<'EOF'
43#!/usr/bin/env bash
44previous=
45for arg in "$@"; do
46 source=$previous
47 previous=$arg
48done
49cp "$source" "$previous"
50printf 'ditto %s\n' "$*" >> "${MOCK_CODESIGN_LOG:?}"
51EOF
52
53cat > "$FAKE_BIN/xcrun" <<'EOF'
54#!/usr/bin/env bash
55printf 'xcrun %s\n' "$*" >> "${MOCK_CODESIGN_LOG:?}"
56printf '{"id":"12345678-1234-1234-1234-123456789abc","status":"%s"}\n' "${MOCK_NOTARY_STATUS:-Accepted}"
57EOF
58
59cat > "$FAKE_BIN/plutil" <<'EOF'
60#!/usr/bin/env bash
61case "${2:-}" in
62 status) printf '%s\n' "${MOCK_NOTARY_STATUS:-Accepted}" ;;
63 id) printf '%s\n' '12345678-1234-1234-1234-123456789abc' ;;
64 *) exit 1 ;;
65esac
66EOF
67
39cat > "$FAKE_BIN/lipo" <<'EOF'68cat > "$FAKE_BIN/lipo" <<'EOF'
40#!/usr/bin/env bash69#!/usr/bin/env bash
41case "${2:-}" in70case "${2:-}" in
@@ -47,6 +76,7 @@ EOF · 5 unmodified lines
47chmod 0755 "$FAKE_BIN"/*76chmod 0755 "$FAKE_BIN"/*
48export PATH="$FAKE_BIN:$PATH"77export PATH="$FAKE_BIN:$PATH"
49export MOCK_CODESIGN_LOG="$WORK_DIR/codesign.log"78export MOCK_CODESIGN_LOG="$WORK_DIR/codesign.log"
79unset NOTARYTOOL_KEYCHAIN_PROFILE
50 80
51test_binary="$WORK_DIR/gitcrawl"81test_binary="$WORK_DIR/gitcrawl"
52cat > "$test_binary" <<'EOF'82cat > "$test_binary" <<'EOF'
@@ -62,15 +92,48 @@ if CODESIGN_IDENTITY='Developer ID Application: Peter Steinberger (Y5PE65HELJ)' · 9 unmodified lines
62 echo "package script accepted personal signing identity" >&292 echo "package script accepted personal signing identity" >&2
63 exit 193 exit 1
64fi94fi
95if CODESIGN_IDENTITY="$EXPECTED_AUTHORITY" \
96 "$ROOT/scripts/package-release.sh" v0.7.1 >/dev/null 2>&1; then
97 echo "package script accepted a missing notary profile" >&2
98 exit 1
99fi
65if GITCRAWL_REQUIRE_CODESIGN=1 \100if GITCRAWL_REQUIRE_CODESIGN=1 \
66 CODESIGN_IDENTITY='Developer ID Application: Peter Steinberger (Y5PE65HELJ)' \101 CODESIGN_IDENTITY='Developer ID Application: Peter Steinberger (Y5PE65HELJ)' \
67 "$ROOT/scripts/codesign-macos.sh" "$test_binary" >/dev/null 2>&1; then102 "$ROOT/scripts/codesign-macos.sh" "$test_binary" >/dev/null 2>&1; then
68 echo "personal signing identity was accepted" >&2103 echo "personal signing identity was accepted" >&2
69 exit 1104 exit 1
70fi105fi
106if GITCRAWL_REQUIRE_CODESIGN=1 \
107 CODESIGN_IDENTITY="$EXPECTED_AUTHORITY" \
108 "$ROOT/scripts/codesign-macos.sh" "$test_binary" >/dev/null 2>&1; then
109 echo "official signing accepted a missing notary profile" >&2
110 exit 1
111fi
71GITCRAWL_REQUIRE_CODESIGN=1 \112GITCRAWL_REQUIRE_CODESIGN=1 \
72 CODESIGN_IDENTITY="$EXPECTED_AUTHORITY" \113 CODESIGN_IDENTITY="$EXPECTED_AUTHORITY" \
114 NOTARYTOOL_KEYCHAIN_PROFILE=test-profile \
73 "$ROOT/scripts/codesign-macos.sh" "$test_binary"115 "$ROOT/scripts/codesign-macos.sh" "$test_binary"
116grep -F -- '--identifier org.openclaw.gitcrawl' "$MOCK_CODESIGN_LOG" >/dev/null
117grep -F 'FWJYW4S8P8' "$MOCK_CODESIGN_LOG" >/dev/null
118grep -F -- 'notarytool submit' "$MOCK_CODESIGN_LOG" >/dev/null
119grep -F -- '--keychain-profile test-profile --no-s3-acceleration --wait --output-format json' "$MOCK_CODESIGN_LOG" >/dev/null
120signed_hash=$(shasum -a 256 "$test_binary" | awk '{ print $1 }')
121if GITCRAWL_REQUIRE_CODESIGN=1 \
122 CODESIGN_IDENTITY="$EXPECTED_AUTHORITY" \
123 NOTARYTOOL_KEYCHAIN_PROFILE=test-profile \
124 MOCK_NOTARY_STATUS=Invalid \
125 "$ROOT/scripts/codesign-macos.sh" "$test_binary" >/dev/null 2>&1; then
126 echo "invalid notarization status was accepted" >&2
127 exit 1
128fi
129[[ "$(shasum -a 256 "$test_binary" | awk '{ print $1 }')" == "$signed_hash" ]] || {
130 echo "failed notarization mutated the release binary" >&2
131 exit 1
132}
133if find "$WORK_DIR" -maxdepth 1 -name '.gitcrawl-notary.*' | grep -q .; then
134 echo "ephemeral notarization files were not removed" >&2
135 exit 1
136fi
74 137
75ARTIFACTS="$WORK_DIR/artifacts"138ARTIFACTS="$WORK_DIR/artifacts"
76mkdir -p "$ARTIFACTS"139mkdir -p "$ARTIFACTS"
@@ -84,7 +147,14 @@ for arch in amd64 arm64; do · 7 unmodified lines
84 shasum -a 256 "$ARTIFACTS/$archive" | awk -v name="$archive" '{ print $1 " " name }' >> "$ARTIFACTS/checksums.txt"147 shasum -a 256 "$ARTIFACTS/$archive" | awk -v name="$archive" '{ print $1 " " name }' >> "$ARTIFACTS/checksums.txt"
85done148done
86 149
150: > "$MOCK_CODESIGN_LOG"
87"$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS"151"$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS"
152[[ "$(grep -F -c -- '--verify --strict --check-notarization -R=notarized' "$MOCK_CODESIGN_LOG")" == 2 ]]
153if MOCK_NOTARIZATION_REJECT=1 \
154 "$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS" >/dev/null 2>&1; then
155 echo "release verifier accepted a missing notarization ticket" >&2
156 exit 1
157fi
88if MOCK_CODESIGN_AUTHORITY='Developer ID Application: Peter Steinberger (Y5PE65HELJ)' \158if MOCK_CODESIGN_AUTHORITY='Developer ID Application: Peter Steinberger (Y5PE65HELJ)' \
89 "$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS" >/dev/null 2>&1; then159 "$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS" >/dev/null 2>&1; then
90 echo "personal signature was accepted" >&2160 echo "personal signature was accepted" >&2
@@ -104,9 +174,6 @@ if "$ROOT/scripts/verify-release.sh" v0.7.1 "$ARTIFACTS" >/dev/null 2>&1; then · 13 unmodified lines
104 exit 1174 exit 1
105fi175fi
106 176
107grep -F -- '--identifier org.openclaw.gitcrawl' "$MOCK_CODESIGN_LOG" >/dev/null
108grep -F 'FWJYW4S8P8' "$MOCK_CODESIGN_LOG" >/dev/null
109
110release_workflow="$ROOT/.github/workflows/release-assets.yml"177release_workflow="$ROOT/.github/workflows/release-assets.yml"
111grep -F 'contents: write' "$release_workflow" >/dev/null178grep -F 'contents: write' "$release_workflow" >/dev/null
112grep -F "github.ref == format('refs/heads/{0}', github.event.repository.default_branch)" "$release_workflow" >/dev/null179grep -F "github.ref == format('refs/heads/{0}', github.event.repository.default_branch)" "$release_workflow" >/dev/null
@@ -121,6 +188,10 @@ grep -F 'persist-credentials: false' "$release_workflow" >/dev/null · 8 unmodified lines
121grep -F 'tag_name == $tag and (.draft == ($draft == "true"))' "$release_workflow" >/dev/null188grep -F 'tag_name == $tag and (.draft == ($draft == "true"))' "$release_workflow" >/dev/null
122grep -F 'Accept: application/octet-stream' "$release_workflow" >/dev/null189grep -F 'Accept: application/octet-stream' "$release_workflow" >/dev/null
123grep -F 'unset GH_TOKEN GITHUB_TOKEN' "$release_workflow" >/dev/null190grep -F 'unset GH_TOKEN GITHUB_TOKEN' "$release_workflow" >/dev/null
191if grep -R -F 'NOTARYTOOL_KEYCHAIN_PROFILE' "$ROOT/.github/workflows" >/dev/null; then
192 echo "notary profile must not be configured in GitHub Actions" >&2
193 exit 1
194fi
124if grep -F 'gh release download' "$release_workflow" >/dev/null; then195if grep -F 'gh release download' "$release_workflow" >/dev/null; then
125 echo "release workflow cannot resolve draft assets through gh release download" >&2196 echo "release workflow cannot resolve draft assets through gh release download" >&2
126 exit 1197 exit 1
scripts/verify-release.sh+1−0
@@ -73,6 +73,7 @@ for goarch in "${architectures[@]}"; do · 72 unmodified lines
73 }73 }
74 74
75 codesign --verify --strict -R="$REQUIREMENT" --verbose=2 "$binary"75 codesign --verify --strict -R="$REQUIREMENT" --verbose=2 "$binary"
76 codesign --verify --strict --check-notarization -R=notarized "$binary"
76 signature=$(codesign -dvvv "$binary" 2>&1)77 signature=$(codesign -dvvv "$binary" 2>&1)
77 grep -Fx "Identifier=$IDENTIFIER" <<<"$signature" >/dev/null78 grep -Fx "Identifier=$IDENTIFIER" <<<"$signature" >/dev/null
78 grep -Fx "TeamIdentifier=$EXPECTED_TEAM_ID" <<<"$signature" >/dev/null79 grep -Fx "TeamIdentifier=$EXPECTED_TEAM_ID" <<<"$signature" >/dev/null

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t