@hasna/recordings 0.2.10 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +350 -35
- package/bun.lock +3 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts +2 -0
- package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -0
- package/dist/cli/index.js +1728 -320
- package/dist/cli/macos-permissions.d.ts +13 -0
- package/dist/cli/macos-permissions.d.ts.map +1 -0
- package/dist/cli/options.d.ts +12 -0
- package/dist/cli/options.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/db/recordings.d.ts +2 -1
- package/dist/db/recordings.d.ts.map +1 -1
- package/dist/db/remote-storage.d.ts +5 -1
- package/dist/db/remote-storage.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +628 -147
- package/dist/lib/bun-runtime.d.ts +11 -0
- package/dist/lib/bun-runtime.d.ts.map +1 -0
- package/dist/lib/config.d.ts +8 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/enhancer.d.ts +10 -2
- package/dist/lib/enhancer.d.ts.map +1 -1
- package/dist/lib/install-maintenance.d.ts +24 -0
- package/dist/lib/install-maintenance.d.ts.map +1 -0
- package/dist/lib/machine.d.ts +2 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/recorder.d.ts +2 -1
- package/dist/lib/recorder.d.ts.map +1 -1
- package/dist/lib/recording-create-identity.d.ts +13 -0
- package/dist/lib/recording-create-identity.d.ts.map +1 -0
- package/dist/lib/release-install-policy.d.ts +31 -0
- package/dist/lib/release-install-policy.d.ts.map +1 -0
- package/dist/lib/transcriber.d.ts.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +977 -194
- package/dist/sdk/index.js +5 -1
- package/dist/sdk/v1.generated.d.ts +4 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/cloud-config.d.ts +9 -0
- package/dist/server/cloud-config.d.ts.map +1 -0
- package/dist/server/cloud-readiness.d.ts +10 -0
- package/dist/server/cloud-readiness.d.ts.map +1 -0
- package/dist/server/cloud.d.ts +9 -18
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.js +1654 -304
- package/dist/server/migrate-command.d.ts +11 -0
- package/dist/server/migrate-command.d.ts.map +1 -0
- package/dist/server/openapi.d.ts +22 -0
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/repo.d.ts +8 -1
- package/dist/server/repo.d.ts.map +1 -1
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage.js +515 -80
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +7 -4
- package/packaging/macos/Empty.entitlements +5 -0
- package/packaging/macos/Library/LaunchDaemons/com.hasna.recordings.updater.plist +34 -0
- package/packaging/macos/Verifier.entitlements +10 -0
- package/packaging/macos/artifact-verifier.sb +33 -0
- package/packaging/macos/build_release_pkg.sh +872 -0
- package/packaging/macos/managed_bootstrap.sh +623 -0
- package/packaging/macos/pkgutil_fingerprint.awk +37 -0
- package/packaging/macos/release_lifecycle.ts +463 -0
- package/packaging/macos/scripts/postinstall +565 -0
- package/packaging/macos/scripts/preinstall +297 -0
- package/scripts/build_companion_cli.sh +337 -0
- package/scripts/build_native_fs_guard.sh +59 -0
- package/scripts/generate-sdk.ts +19 -1
- package/scripts/install_macos_app.sh +1922 -77
- package/scripts/macos_artifact.ts +5316 -0
- package/scripts/migrate.ts +38 -10
- package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
- package/scripts/native/recordings_fs_guard.c +1167 -0
- package/scripts/native_fs_guard.ts +158 -0
- package/scripts/release-guard.ts +20 -1
- package/scripts/resolve_tailscale_cli.sh +389 -0
- package/scripts/smoke_macos_app.sh +573 -0
- package/src/native/Recordings/App/ContentView.swift +83 -0
- package/src/native/Recordings/App/MenuBarStatusView.swift +102 -0
- package/src/native/Recordings/App/RecordWorkspaceView.swift +468 -0
- package/src/native/Recordings/App/RecordingDetailView.swift +138 -0
- package/src/native/Recordings/App/RecordingsApp.swift +367 -44
- package/src/native/Recordings/App/RecordingsListView.swift +130 -0
- package/src/native/Recordings/App/RecordingsStore.swift +206 -0
- package/src/native/Recordings/App/RuntimeSmoke.swift +158 -0
- package/src/native/Recordings/App/SidebarView.swift +212 -0
- package/src/native/Recordings/App/Theme.swift +87 -0
- package/src/native/Recordings/Package.resolved +5 -5
- package/src/native/Recordings/Package.swift +62 -2
- package/src/native/Recordings/RecordingsLib/AccessibilityPromptGate.swift +104 -0
- package/src/native/Recordings/RecordingsLib/BrandAssets.swift +0 -32
- package/src/native/Recordings/RecordingsLib/ChromeSurface.swift +17 -0
- package/src/native/Recordings/RecordingsLib/Info.plist +7 -3
- package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +37 -0
- package/src/native/Recordings/RecordingsLib/NativeAppDiagnostics.swift +32 -2
- package/src/native/Recordings/RecordingsLib/NativeMachineIdentity.swift +24 -0
- package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +262 -23
- package/src/native/Recordings/RecordingsLib/OpenAIAPIKeyStore.swift +56 -0
- package/src/native/Recordings/RecordingsLib/PermissionRequestLaunchPlan.swift +56 -0
- package/src/native/Recordings/RecordingsLib/ProjectStore.swift +321 -25
- package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +605 -90
- package/src/native/Recordings/RecordingsLib/Recording.swift +141 -0
- package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +3704 -376
- package/src/native/Recordings/RecordingsLib/RecordingStartControlPresentation.swift +29 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.entitlements +10 -0
- package/src/native/Recordings/RecordingsLib/RecordingsCLI.swift +222 -0
- package/src/native/Recordings/RecordingsLib/SettingsView.swift +45 -11
- package/src/native/Recordings/RecordingsLib/SpeechIntent.swift +331 -0
- package/src/native/Recordings/RecordingsLib/SpeechIntentClassifier.swift +232 -0
- package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +17 -7
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1343 -0
- package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +98 -0
- package/src/native/Recordings/RecordingsTests/NativeAppDiagnosticsTests.swift +20 -0
- package/src/native/Recordings/RecordingsTests/NativePCMRecorderTests.swift +370 -1
- package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +987 -1
- package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +385 -0
- package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +835 -8
- package/src/native/Recordings/RecordingsTests/RecordingBridgeTests.swift +180 -0
- package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +760 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartControlPresentationTests.swift +42 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +264 -0
- package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +129 -0
- package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +600 -0
- package/src/native/Recordings/RecordingsTests/TranscriptResolutionTests.swift +62 -1
- package/src/native/Recordings/RecordingsTests/TranscriptionResultIdentityTests.swift +21 -0
- package/src/native/Recordings/Updater/BootstrapPreflight/BootstrapPreflightMain.swift +739 -0
- package/src/native/Recordings/Updater/Broker/ActivationRecoveryPolicy.swift +254 -0
- package/src/native/Recordings/Updater/Broker/ApplicationNamespace.swift +463 -0
- package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +55 -0
- package/src/native/Recordings/Updater/Broker/ArtifactIngest.swift +341 -0
- package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +477 -0
- package/src/native/Recordings/Updater/Broker/BrokerMain.swift +624 -0
- package/src/native/Recordings/Updater/Broker/CanonicalTreeCopy.swift +63 -0
- package/src/native/Recordings/Updater/Broker/CodeValidation.swift +561 -0
- package/src/native/Recordings/Updater/Broker/DarwinACLValidator.swift +66 -0
- package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +50 -0
- package/src/native/Recordings/Updater/Broker/InstallJournal.swift +362 -0
- package/src/native/Recordings/Updater/Broker/InstallRecovery.swift +662 -0
- package/src/native/Recordings/Updater/Broker/MonotonicState.swift +456 -0
- package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +274 -0
- package/src/native/Recordings/Updater/Broker/VerifierRunner.swift +81 -0
- package/src/native/Recordings/Updater/BrokerTests/ActivationRecoveryPolicyTests.swift +259 -0
- package/src/native/Recordings/Updater/BrokerTests/CanonicalReleaseOrderTests.swift +21 -0
- package/src/native/Recordings/Updater/Client/ClientMain.swift +119 -0
- package/src/native/Recordings/Updater/Protocol/BoundedProcess.swift +159 -0
- package/src/native/Recordings/Updater/Protocol/CandidateMetadataPolicy.swift +214 -0
- package/src/native/Recordings/Updater/Protocol/HostOSVersionPolicy.swift +55 -0
- package/src/native/Recordings/Updater/Protocol/MonotonicReleasePolicy.swift +152 -0
- package/src/native/Recordings/Updater/Protocol/ReleaseEnvelope.swift +229 -0
- package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +119 -0
- package/src/native/Recordings/Updater/ProtocolTests/BoundedProcessRunnerTests.swift +60 -0
- package/src/native/Recordings/Updater/ProtocolTests/CandidateMetadataPolicyTests.swift +168 -0
- package/src/native/Recordings/Updater/ProtocolTests/HostOSVersionPolicyTests.swift +62 -0
- package/src/native/Recordings/Updater/ProtocolTests/MonotonicReleasePolicyTests.swift +172 -0
- package/src/native/Recordings/Updater/ProtocolTests/ReleaseEnvelopeValidationTests.swift +65 -0
- package/src/native/Recordings/Updater/Signer/SignerMain.swift +210 -0
- package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +725 -0
- package/src/native/Recordings/Updater/VerifierLauncher/include/RecordingsVerifierLauncher.h +33 -0
- package/src/native/Recordings/build.sh +1990 -29
- package/src/native/Recordings/RecordingsLib/MenuBarPopover.swift +0 -380
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Assemble, sign, notarize, and verify the root-owned Recordings updater bootstrap PKG.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
umask 077
|
|
6
|
+
|
|
7
|
+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
8
|
+
export LC_ALL=C
|
|
9
|
+
export LANG=C
|
|
10
|
+
export TZ=UTC0
|
|
11
|
+
|
|
12
|
+
APP=""
|
|
13
|
+
ARTIFACT_BASENAME=""
|
|
14
|
+
BROKER=""
|
|
15
|
+
VERIFIER=""
|
|
16
|
+
PUBLIC_KEY=""
|
|
17
|
+
VERSION=""
|
|
18
|
+
SOURCE_SHA=""
|
|
19
|
+
EXPECTED_TEAM_ID=""
|
|
20
|
+
INSTALLER_IDENTITY=""
|
|
21
|
+
NOTARY_PROFILE=""
|
|
22
|
+
OUTPUT_DIR=""
|
|
23
|
+
BUN_EXECUTABLE=""
|
|
24
|
+
RELEASE_SEQUENCE=""
|
|
25
|
+
KEY_EPOCH=""
|
|
26
|
+
EXPIRES_AT_UTC=""
|
|
27
|
+
ENVELOPE_SIGNER=""
|
|
28
|
+
SOURCE_ROOT=""
|
|
29
|
+
ENVELOPE_PRIVATE_KEY=""
|
|
30
|
+
APP_ARCHIVE=""
|
|
31
|
+
MANIFEST=""
|
|
32
|
+
BOOTSTRAP_PREFLIGHT_VERIFIER=""
|
|
33
|
+
PACKAGE_STAGE_DIR=""
|
|
34
|
+
PACKAGE_FINAL_DIR=""
|
|
35
|
+
PACKAGE_RESERVATION=""
|
|
36
|
+
PACKAGE_RESERVATION_OWNED=0
|
|
37
|
+
PACKAGE_DIRECTORY_PUBLISHED=0
|
|
38
|
+
PUBLICATION_IDENTITY_SHA256=""
|
|
39
|
+
WORK_DIR=""
|
|
40
|
+
|
|
41
|
+
require_no_extended_acl() {
|
|
42
|
+
local path="$1"
|
|
43
|
+
local acl_listing acl_status
|
|
44
|
+
acl_listing="$(/bin/ls -lade "$path")" || {
|
|
45
|
+
echo "Package builder could not inspect extended ACLs on: $path" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
49
|
+
NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
50
|
+
NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
|
|
51
|
+
END { exit found ? 0 : 1 }
|
|
52
|
+
'; then
|
|
53
|
+
echo "Package input trust path has an unexpected extended ACL: $path" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
else
|
|
56
|
+
acl_status=$?
|
|
57
|
+
[ "$acl_status" -eq 1 ] || {
|
|
58
|
+
echo "Package builder could not evaluate extended ACLs on: $path" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
}
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
require_tree_without_extended_acl() {
|
|
65
|
+
local tree="$1"
|
|
66
|
+
local acl_listing acl_status
|
|
67
|
+
require_no_extended_acl "$tree"
|
|
68
|
+
[ -d "$tree" ] || return 0
|
|
69
|
+
acl_listing="$(/bin/ls -laeR "$tree")" || {
|
|
70
|
+
echo "Package builder could not recursively inspect extended ACLs on: $tree" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
}
|
|
73
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
74
|
+
length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
75
|
+
$1 ~ /^[0-9]+:$/ { found = 1 }
|
|
76
|
+
END { exit found ? 0 : 1 }
|
|
77
|
+
'; then
|
|
78
|
+
echo "Package input tree has an unexpected extended ACL: $tree" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
else
|
|
81
|
+
acl_status=$?
|
|
82
|
+
[ "$acl_status" -eq 1 ] || {
|
|
83
|
+
echo "Package builder could not evaluate recursive extended ACLs on: $tree" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
require_exact_regular_file_mode() {
|
|
90
|
+
local path="$1"
|
|
91
|
+
local expected_mode="$2"
|
|
92
|
+
local label="$3"
|
|
93
|
+
local mode_match
|
|
94
|
+
[ -f "$path" ] && [ ! -L "$path" ] || {
|
|
95
|
+
echo "$label is missing, linked, or not a regular file: $path" >&2
|
|
96
|
+
exit 1
|
|
97
|
+
}
|
|
98
|
+
mode_match="$(/usr/bin/find "$path" -xdev -type f -links 1 -perm "$expected_mode" -print -quit)" || {
|
|
99
|
+
echo "Package builder could not inspect $label mode and link count: $path" >&2
|
|
100
|
+
exit 1
|
|
101
|
+
}
|
|
102
|
+
[ "$mode_match" = "$path" ] || {
|
|
103
|
+
echo "$label must be singly linked with exact mode $expected_mode: $path" >&2
|
|
104
|
+
exit 1
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
require_exact_binary_architectures() {
|
|
109
|
+
local binary="$1"
|
|
110
|
+
shift
|
|
111
|
+
local actual_architectures actual_architecture expected_architecture
|
|
112
|
+
local actual_count=0 expected_count="$#" matches
|
|
113
|
+
actual_architectures="$(/usr/bin/lipo -archs "$binary")" || {
|
|
114
|
+
echo "Package builder could not read binary architectures: $binary" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
}
|
|
117
|
+
for actual_architecture in $actual_architectures; do
|
|
118
|
+
actual_count=$((actual_count + 1))
|
|
119
|
+
case " $* " in
|
|
120
|
+
*" $actual_architecture "*) ;;
|
|
121
|
+
*) echo "Package input contains unsupported architecture $actual_architecture: $binary" >&2; exit 1 ;;
|
|
122
|
+
esac
|
|
123
|
+
done
|
|
124
|
+
[ "$actual_count" -eq "$expected_count" ] || {
|
|
125
|
+
echo "Package input architecture count does not match release policy: $binary" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
}
|
|
128
|
+
for expected_architecture in "$@"; do
|
|
129
|
+
matches=0
|
|
130
|
+
for actual_architecture in $actual_architectures; do
|
|
131
|
+
[ "$actual_architecture" = "$expected_architecture" ] && matches=$((matches + 1))
|
|
132
|
+
done
|
|
133
|
+
[ "$matches" -eq 1 ] || {
|
|
134
|
+
echo "Package input must contain exactly one $expected_architecture slice: $binary" >&2
|
|
135
|
+
exit 1
|
|
136
|
+
}
|
|
137
|
+
done
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
require_safe_signed_app_bundle_modes() {
|
|
141
|
+
local app="$1"
|
|
142
|
+
local unexpected hardlinked bad_mode directory
|
|
143
|
+
local main="$app/Contents/MacOS/Recordings"
|
|
144
|
+
local companion="$app/Contents/Helpers/recordings"
|
|
145
|
+
local client="$app/Contents/Helpers/recordings-update-client"
|
|
146
|
+
|
|
147
|
+
for directory in "$app" "$app/Contents" "$app/Contents/MacOS" "$app/Contents/Helpers"; do
|
|
148
|
+
[ -d "$directory" ] && [ ! -L "$directory" ] || {
|
|
149
|
+
echo "Signed app launch ancestry is missing, linked, or not a directory: $directory" >&2
|
|
150
|
+
exit 1
|
|
151
|
+
}
|
|
152
|
+
done
|
|
153
|
+
unexpected="$(/usr/bin/find "$app" -xdev ! -type d ! -type f -print -quit)" || {
|
|
154
|
+
echo "Package builder could not inspect the signed app tree structure." >&2
|
|
155
|
+
exit 1
|
|
156
|
+
}
|
|
157
|
+
[ -z "$unexpected" ] || {
|
|
158
|
+
echo "Signed app tree contains a symbolic link or special file: $unexpected" >&2
|
|
159
|
+
exit 1
|
|
160
|
+
}
|
|
161
|
+
hardlinked="$(/usr/bin/find "$app" -xdev -type f -links +1 -print -quit)" || {
|
|
162
|
+
echo "Package builder could not inspect signed app regular-file link counts." >&2
|
|
163
|
+
exit 1
|
|
164
|
+
}
|
|
165
|
+
[ -z "$hardlinked" ] || {
|
|
166
|
+
echo "Signed app tree contains a multiply-linked regular file: $hardlinked" >&2
|
|
167
|
+
exit 1
|
|
168
|
+
}
|
|
169
|
+
bad_mode="$(/usr/bin/find "$app" -xdev -type d ! -perm 0755 -print -quit)" || {
|
|
170
|
+
echo "Package builder could not inspect signed app directory modes." >&2
|
|
171
|
+
exit 1
|
|
172
|
+
}
|
|
173
|
+
[ -z "$bad_mode" ] || {
|
|
174
|
+
echo "Signed app directory must have exact mode 0755: $bad_mode" >&2
|
|
175
|
+
exit 1
|
|
176
|
+
}
|
|
177
|
+
require_exact_regular_file_mode "$main" 0755 "Signed app main executable"
|
|
178
|
+
require_exact_regular_file_mode "$companion" 0755 "Signed app companion helper"
|
|
179
|
+
require_exact_regular_file_mode "$client" 0755 "Signed app updater client"
|
|
180
|
+
bad_mode="$(/usr/bin/find "$app" -xdev -type f \
|
|
181
|
+
! -path "$main" \
|
|
182
|
+
! -path "$companion" \
|
|
183
|
+
! -path "$client" \
|
|
184
|
+
! -perm 0644 -print -quit)" || {
|
|
185
|
+
echo "Package builder could not inspect signed app data-file modes." >&2
|
|
186
|
+
exit 1
|
|
187
|
+
}
|
|
188
|
+
[ -z "$bad_mode" ] || {
|
|
189
|
+
echo "Signed app data file must have exact mode 0644: $bad_mode" >&2
|
|
190
|
+
exit 1
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
while [ "$#" -gt 0 ]; do
|
|
195
|
+
case "$1" in
|
|
196
|
+
--app) APP="${2:-}"; shift 2 ;;
|
|
197
|
+
--artifact-basename) ARTIFACT_BASENAME="${2:-}"; shift 2 ;;
|
|
198
|
+
--broker) BROKER="${2:-}"; shift 2 ;;
|
|
199
|
+
--verifier) VERIFIER="${2:-}"; shift 2 ;;
|
|
200
|
+
--public-key) PUBLIC_KEY="${2:-}"; shift 2 ;;
|
|
201
|
+
--version) VERSION="${2:-}"; shift 2 ;;
|
|
202
|
+
--source-sha) SOURCE_SHA="${2:-}"; shift 2 ;;
|
|
203
|
+
--team-id) EXPECTED_TEAM_ID="${2:-}"; shift 2 ;;
|
|
204
|
+
--installer-identity) INSTALLER_IDENTITY="${2:-}"; shift 2 ;;
|
|
205
|
+
--notary-profile) NOTARY_PROFILE="${2:-}"; shift 2 ;;
|
|
206
|
+
--output-dir) OUTPUT_DIR="${2:-}"; shift 2 ;;
|
|
207
|
+
--bun-executable) BUN_EXECUTABLE="${2:-}"; shift 2 ;;
|
|
208
|
+
--release-sequence) RELEASE_SEQUENCE="${2:-}"; shift 2 ;;
|
|
209
|
+
--key-epoch) KEY_EPOCH="${2:-}"; shift 2 ;;
|
|
210
|
+
--expires-at-utc) EXPIRES_AT_UTC="${2:-}"; shift 2 ;;
|
|
211
|
+
--publication-identity-sha256) PUBLICATION_IDENTITY_SHA256="${2:-}"; shift 2 ;;
|
|
212
|
+
--envelope-signer) ENVELOPE_SIGNER="${2:-}"; shift 2 ;;
|
|
213
|
+
--source-root) SOURCE_ROOT="${2:-}"; shift 2 ;;
|
|
214
|
+
--envelope-private-key) ENVELOPE_PRIVATE_KEY="${2:-}"; shift 2 ;;
|
|
215
|
+
--app-archive) APP_ARCHIVE="${2:-}"; shift 2 ;;
|
|
216
|
+
--manifest) MANIFEST="${2:-}"; shift 2 ;;
|
|
217
|
+
--bootstrap-preflight-verifier) BOOTSTRAP_PREFLIGHT_VERIFIER="${2:-}"; shift 2 ;;
|
|
218
|
+
*) echo "Unknown argument: $1" >&2; exit 2 ;;
|
|
219
|
+
esac
|
|
220
|
+
done
|
|
221
|
+
|
|
222
|
+
case "$VERSION" in
|
|
223
|
+
''|*[!0-9A-Za-z._+-]*) echo "Package version is missing or invalid." >&2; exit 2 ;;
|
|
224
|
+
esac
|
|
225
|
+
[ "$ARTIFACT_BASENAME" = "Recordings-${VERSION}-macos-initial-bootstrap" ] || {
|
|
226
|
+
echo "Initial-bootstrap package artifact basename is missing or mode-incompatible." >&2
|
|
227
|
+
exit 2
|
|
228
|
+
}
|
|
229
|
+
[[ "$SOURCE_SHA" =~ ^[0-9a-f]{40}$ ]] || { echo "Package source SHA must be an exact commit." >&2; exit 2; }
|
|
230
|
+
[[ "$EXPECTED_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] || { echo "Package Team ID must be exact." >&2; exit 2; }
|
|
231
|
+
[ -n "$INSTALLER_IDENTITY" ] || { echo "Developer ID Installer identity is required." >&2; exit 2; }
|
|
232
|
+
[ -n "$NOTARY_PROFILE" ] || { echo "Notary keychain profile is required." >&2; exit 2; }
|
|
233
|
+
[[ "$RELEASE_SEQUENCE" =~ ^[1-9][0-9]*$ ]] || { echo "Release sequence must be a positive integer." >&2; exit 2; }
|
|
234
|
+
[[ "$KEY_EPOCH" =~ ^[1-9][0-9]*$ ]] || { echo "Key epoch must be a positive integer." >&2; exit 2; }
|
|
235
|
+
[ -n "$EXPIRES_AT_UTC" ] || { echo "Release-envelope expiry is required." >&2; exit 2; }
|
|
236
|
+
[[ "$PUBLICATION_IDENTITY_SHA256" =~ ^[a-f0-9]{64}$ ]] || {
|
|
237
|
+
echo "Package publication identity SHA-256 is missing or invalid." >&2
|
|
238
|
+
exit 2
|
|
239
|
+
}
|
|
240
|
+
case "$BUN_EXECUTABLE" in /*) ;; *) echo "Bun executable must be an explicit absolute path." >&2; exit 2 ;; esac
|
|
241
|
+
[ -f "$BUN_EXECUTABLE" ] && [ -x "$BUN_EXECUTABLE" ] || { echo "Bun executable is missing or unsafe." >&2; exit 1; }
|
|
242
|
+
case "$ENVELOPE_SIGNER" in /*) ;; *) echo "Envelope signer must be an absolute executable path." >&2; exit 2 ;; esac
|
|
243
|
+
[ -x "$ENVELOPE_SIGNER" ] || { echo "Envelope signer is missing or not executable." >&2; exit 1; }
|
|
244
|
+
case "$BOOTSTRAP_PREFLIGHT_VERIFIER" in
|
|
245
|
+
/*) ;;
|
|
246
|
+
*) echo "Bootstrap preflight verifier must be an absolute executable path." >&2; exit 2 ;;
|
|
247
|
+
esac
|
|
248
|
+
[ -x "$BOOTSTRAP_PREFLIGHT_VERIFIER" ] && [ ! -L "$BOOTSTRAP_PREFLIGHT_VERIFIER" ] || {
|
|
249
|
+
echo "Bootstrap preflight verifier is missing or unsafe." >&2
|
|
250
|
+
exit 1
|
|
251
|
+
}
|
|
252
|
+
case "$ENVELOPE_PRIVATE_KEY" in /*) ;; *) echo "Envelope private-key path must be absolute." >&2; exit 2 ;; esac
|
|
253
|
+
for release_input in "$APP_ARCHIVE" "$MANIFEST" "$ENVELOPE_PRIVATE_KEY"; do
|
|
254
|
+
[ -f "$release_input" ] && [ ! -L "$release_input" ] || { echo "Release input is missing or unsafe." >&2; exit 1; }
|
|
255
|
+
require_no_extended_acl "$release_input"
|
|
256
|
+
done
|
|
257
|
+
case "$OUTPUT_DIR" in
|
|
258
|
+
/*) ;;
|
|
259
|
+
*) echo "Package output directory must be absolute." >&2; exit 2 ;;
|
|
260
|
+
esac
|
|
261
|
+
case "$SOURCE_ROOT" in /*) ;; *) echo "Archived source root must be absolute." >&2; exit 2 ;; esac
|
|
262
|
+
[ -d "$SOURCE_ROOT" ] && [ ! -L "$SOURCE_ROOT" ] || { echo "Archived source root is missing or unsafe." >&2; exit 1; }
|
|
263
|
+
[ ! -e "$SOURCE_ROOT/.git" ] || { echo "Package inputs must come from an archived source tree, not a worktree." >&2; exit 1; }
|
|
264
|
+
|
|
265
|
+
SCRIPT_DIR="$(cd "$(/usr/bin/dirname "$0")" && /bin/pwd -P)"
|
|
266
|
+
[ "$SCRIPT_DIR" = "$(cd "$SOURCE_ROOT/packaging/macos" && /bin/pwd -P)" ] || {
|
|
267
|
+
echo "Package builder is not executing from the pinned archived source tree." >&2
|
|
268
|
+
exit 1
|
|
269
|
+
}
|
|
270
|
+
LAUNCHD_PLIST="$SCRIPT_DIR/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
271
|
+
VERIFIER_SANDBOX="$SCRIPT_DIR/artifact-verifier.sb"
|
|
272
|
+
PKG_SCRIPTS="$SCRIPT_DIR/scripts"
|
|
273
|
+
PKGUTIL_FINGERPRINT_PARSER="$SCRIPT_DIR/pkgutil_fingerprint.awk"
|
|
274
|
+
CLIENT="$APP/Contents/Helpers/recordings-update-client"
|
|
275
|
+
|
|
276
|
+
for path in "$APP" "$BROKER" "$CLIENT" "$VERIFIER" "$BOOTSTRAP_PREFLIGHT_VERIFIER" "$PUBLIC_KEY" "$LAUNCHD_PLIST" "$VERIFIER_SANDBOX" "$PKG_SCRIPTS/preinstall" "$PKG_SCRIPTS/postinstall" "$PKGUTIL_FINGERPRINT_PARSER"; do
|
|
277
|
+
[ -e "$path" ] || { echo "Package input is missing: $path" >&2; exit 1; }
|
|
278
|
+
[ ! -L "$path" ] || { echo "Package input trust boundary cannot be a symbolic link: $path" >&2; exit 1; }
|
|
279
|
+
require_no_extended_acl "$path"
|
|
280
|
+
done
|
|
281
|
+
require_tree_without_extended_acl "$APP"
|
|
282
|
+
require_safe_signed_app_bundle_modes "$APP"
|
|
283
|
+
require_exact_regular_file_mode "$BROKER" 0755 "Signed updater broker"
|
|
284
|
+
require_exact_regular_file_mode "$VERIFIER" 0755 "Signed artifact verifier launcher"
|
|
285
|
+
require_exact_regular_file_mode "$BOOTSTRAP_PREFLIGHT_VERIFIER" 0755 "Signed bootstrap preflight verifier"
|
|
286
|
+
require_exact_binary_architectures "$APP/Contents/MacOS/Recordings" arm64 x86_64
|
|
287
|
+
require_exact_binary_architectures "$APP/Contents/Helpers/recordings" arm64 x86_64
|
|
288
|
+
require_exact_binary_architectures "$CLIENT" arm64 x86_64
|
|
289
|
+
require_exact_binary_architectures "$BROKER" arm64 x86_64
|
|
290
|
+
require_exact_binary_architectures "$BOOTSTRAP_PREFLIGHT_VERIFIER" arm64 x86_64
|
|
291
|
+
require_exact_binary_architectures "$VERIFIER" arm64 x86_64
|
|
292
|
+
[ "$(/usr/bin/stat -f '%z' "$PUBLIC_KEY")" = "32" ] || { echo "Ed25519 public key must be exactly 32 raw bytes." >&2; exit 1; }
|
|
293
|
+
[ "$(/usr/bin/stat -f '%z' "$ENVELOPE_PRIVATE_KEY")" = "32" ] || { echo "Ed25519 private key must be exactly 32 raw bytes." >&2; exit 1; }
|
|
294
|
+
[ "$(/usr/bin/stat -f '%u' "$ENVELOPE_PRIVATE_KEY")" = "$(/usr/bin/id -u)" ] && \
|
|
295
|
+
[ $((8#$(/usr/bin/stat -f '%Lp' "$ENVELOPE_PRIVATE_KEY") & 8#077)) -eq 0 ] || {
|
|
296
|
+
echo "Envelope private key must be owner-only and owned by the isolated builder." >&2
|
|
297
|
+
exit 1
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
ATTESTATION="/Library/Application Support/Hasna/Recordings/BuildTrust/isolated-builder-v1"
|
|
301
|
+
TRUSTED_BUILD_ROOT="/private/var/recordings-build"
|
|
302
|
+
CURRENT_UID="$(/usr/bin/id -u)"
|
|
303
|
+
[ "$(/usr/bin/id -un)" = "_recordingsbuild" ] || {
|
|
304
|
+
echo "Release PKGs may only be assembled by the isolated _recordingsbuild identity." >&2
|
|
305
|
+
exit 1
|
|
306
|
+
}
|
|
307
|
+
for trusted_path in "$ATTESTATION" "$TRUSTED_BUILD_ROOT"; do
|
|
308
|
+
[ -e "$trusted_path" ] && [ ! -L "$trusted_path" ] || {
|
|
309
|
+
echo "Managed isolated-builder trust path is missing or unsafe: $trusted_path" >&2
|
|
310
|
+
exit 1
|
|
311
|
+
}
|
|
312
|
+
require_no_extended_acl "$trusted_path"
|
|
313
|
+
done
|
|
314
|
+
[ -f "$ATTESTATION" ] && [ "$(/usr/bin/stat -f '%u' "$ATTESTATION")" = "0" ] || {
|
|
315
|
+
echo "Isolated-builder attestation is not a root-owned regular file." >&2
|
|
316
|
+
exit 1
|
|
317
|
+
}
|
|
318
|
+
case "$(/usr/bin/stat -f '%Lp' "$ATTESTATION")" in 400|440|444) ;; *) echo "Isolated-builder attestation mode is unsafe." >&2; exit 1 ;; esac
|
|
319
|
+
[ "$(/bin/cat "$ATTESTATION")" = "recordings-isolated-builder-v1" ] || {
|
|
320
|
+
echo "Isolated-builder attestation content is invalid." >&2
|
|
321
|
+
exit 1
|
|
322
|
+
}
|
|
323
|
+
[ -d "$TRUSTED_BUILD_ROOT" ] && [ "$(/usr/bin/stat -f '%u' "$TRUSTED_BUILD_ROOT")" = "$CURRENT_UID" ] && \
|
|
324
|
+
[ "$(/usr/bin/stat -f '%Lp' "$TRUSTED_BUILD_ROOT")" = "700" ] || {
|
|
325
|
+
echo "Managed release build root must be owned by _recordingsbuild with mode 0700." >&2
|
|
326
|
+
exit 1
|
|
327
|
+
}
|
|
328
|
+
TRUSTED_BUILD_PARENT="$(/usr/bin/dirname "$TRUSTED_BUILD_ROOT")"
|
|
329
|
+
[ "$(/usr/bin/stat -f '%u' "$TRUSTED_BUILD_PARENT")" = "0" ] || {
|
|
330
|
+
echo "Managed release build parent must be root-owned." >&2
|
|
331
|
+
exit 1
|
|
332
|
+
}
|
|
333
|
+
require_no_extended_acl "$TRUSTED_BUILD_PARENT"
|
|
334
|
+
case "$(/usr/bin/stat -f '%Lp' "$TRUSTED_BUILD_PARENT")" in
|
|
335
|
+
*[2367][0-9]|*[0-9][2367]) echo "Managed release build parent is group/other writable." >&2; exit 1 ;;
|
|
336
|
+
esac
|
|
337
|
+
|
|
338
|
+
case "$OUTPUT_DIR" in
|
|
339
|
+
"$TRUSTED_BUILD_ROOT"/*) ;;
|
|
340
|
+
*) echo "Package output directory must remain inside the managed release build root." >&2; exit 1 ;;
|
|
341
|
+
esac
|
|
342
|
+
[ "$(cd "$OUTPUT_DIR" && /bin/pwd -P)" = "$OUTPUT_DIR" ] || {
|
|
343
|
+
echo "Package output directory must be canonical and contain no linked ancestor." >&2
|
|
344
|
+
exit 1
|
|
345
|
+
}
|
|
346
|
+
[ -d "$OUTPUT_DIR" ] && [ ! -L "$OUTPUT_DIR" ] && \
|
|
347
|
+
[ "$(/usr/bin/stat -f '%u' "$OUTPUT_DIR")" = "$CURRENT_UID" ] && \
|
|
348
|
+
[ "$(/usr/bin/stat -f '%Lp' "$OUTPUT_DIR")" = "700" ] || {
|
|
349
|
+
echo "Package output directory must be an existing isolated-builder-owned 0700 directory." >&2
|
|
350
|
+
exit 1
|
|
351
|
+
}
|
|
352
|
+
require_no_extended_acl "$OUTPUT_DIR"
|
|
353
|
+
output_relative="${OUTPUT_DIR#"$TRUSTED_BUILD_ROOT"/}"
|
|
354
|
+
output_ancestor="$TRUSTED_BUILD_ROOT"
|
|
355
|
+
IFS='/' read -r -a output_components <<<"$output_relative"
|
|
356
|
+
for output_component in "${output_components[@]}"; do
|
|
357
|
+
output_ancestor="$output_ancestor/$output_component"
|
|
358
|
+
[ -d "$output_ancestor" ] && [ ! -L "$output_ancestor" ] && \
|
|
359
|
+
[ "$(/usr/bin/stat -f '%u' "$output_ancestor")" = "$CURRENT_UID" ] && \
|
|
360
|
+
[ "$(/usr/bin/stat -f '%Lp' "$output_ancestor")" = "700" ] || {
|
|
361
|
+
echo "Package output ancestry must remain isolated-builder-owned mode 0700: $output_ancestor" >&2
|
|
362
|
+
exit 1
|
|
363
|
+
}
|
|
364
|
+
require_no_extended_acl "$output_ancestor"
|
|
365
|
+
done
|
|
366
|
+
|
|
367
|
+
WORK_DIR="$(/usr/bin/mktemp -d "$TRUSTED_BUILD_ROOT/recordings-release-pkg.XXXXXX")"
|
|
368
|
+
/bin/chmod -N "$WORK_DIR"
|
|
369
|
+
/bin/chmod 0700 "$WORK_DIR"
|
|
370
|
+
require_no_extended_acl "$WORK_DIR"
|
|
371
|
+
cleanup() {
|
|
372
|
+
local status=$?
|
|
373
|
+
trap - EXIT
|
|
374
|
+
/bin/rm -rf "$WORK_DIR"
|
|
375
|
+
if [ "$PACKAGE_DIRECTORY_PUBLISHED" -eq 0 ] && [ -n "$PACKAGE_STAGE_DIR" ]; then
|
|
376
|
+
/bin/rm -rf "$PACKAGE_STAGE_DIR"
|
|
377
|
+
fi
|
|
378
|
+
if [ "$PACKAGE_RESERVATION_OWNED" -eq 1 ] && \
|
|
379
|
+
[ ! -e "$PACKAGE_FINAL_DIR" ] && [ ! -L "$PACKAGE_FINAL_DIR" ]; then
|
|
380
|
+
/bin/rm -f "$PACKAGE_RESERVATION/.recordings-publication.json" 2>/dev/null || true
|
|
381
|
+
/bin/rmdir "$PACKAGE_RESERVATION" 2>/dev/null || true
|
|
382
|
+
fi
|
|
383
|
+
exit "$status"
|
|
384
|
+
}
|
|
385
|
+
trap cleanup EXIT
|
|
386
|
+
|
|
387
|
+
sha256_file() {
|
|
388
|
+
/usr/bin/shasum -a 256 "$1" | /usr/bin/awk '{ print $1 }'
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
verify_developer_id_application() {
|
|
392
|
+
local code_path="$1"
|
|
393
|
+
local expected_identifier="${2:-}"
|
|
394
|
+
local details authority team identifier timestamp
|
|
395
|
+
/usr/bin/codesign --verify --strict --all-architectures --verbose=2 "$code_path"
|
|
396
|
+
details="$(/usr/bin/codesign -d --verbose=4 "$code_path" 2>&1)"
|
|
397
|
+
authority="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Authority=/ { print $2; exit }')"
|
|
398
|
+
team="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^TeamIdentifier=/ { print $2; exit }')"
|
|
399
|
+
identifier="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Identifier=/ { print $2; exit }')"
|
|
400
|
+
timestamp="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Timestamp=/ { print $2; exit }')"
|
|
401
|
+
[[ "$authority" == "Developer ID Application:"* ]] || { echo "$code_path is not Developer ID Application signed." >&2; exit 1; }
|
|
402
|
+
[ "$team" = "$EXPECTED_TEAM_ID" ] || { echo "$code_path has the wrong TeamIdentifier." >&2; exit 1; }
|
|
403
|
+
[ -z "$expected_identifier" ] || [ "$identifier" = "$expected_identifier" ] || {
|
|
404
|
+
echo "$code_path has the wrong signing identifier." >&2
|
|
405
|
+
exit 1
|
|
406
|
+
}
|
|
407
|
+
case "$timestamp" in ''|none|None|NONE) echo "$code_path has no trusted timestamp." >&2; exit 1 ;; esac
|
|
408
|
+
[[ "$details" == *"flags="*"runtime"* ]] || { echo "$code_path lacks hardened runtime." >&2; exit 1; }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
verify_developer_id_application "$BROKER" "com.hasna.recordings.updater"
|
|
412
|
+
verify_developer_id_application "$CLIENT" "com.hasna.recordings.update-client"
|
|
413
|
+
verify_developer_id_application "$VERIFIER" "com.hasna.recordings.artifact-verifier"
|
|
414
|
+
verify_developer_id_application "$BOOTSTRAP_PREFLIGHT_VERIFIER" "com.hasna.recordings.bootstrap-preflight"
|
|
415
|
+
/usr/bin/codesign --verify --deep --strict --all-architectures --verbose=2 "$APP"
|
|
416
|
+
|
|
417
|
+
PUBLIC_KEY_SNAPSHOT="$WORK_DIR/release-envelope-public.raw"
|
|
418
|
+
PUBLIC_KEY_DIGEST="$("$VERIFIER" snapshot-regular-file \
|
|
419
|
+
--source "$PUBLIC_KEY" \
|
|
420
|
+
--destination "$PUBLIC_KEY_SNAPSHOT" \
|
|
421
|
+
--maximum-bytes 32 \
|
|
422
|
+
--expected-bytes 32)"
|
|
423
|
+
PUBLIC_KEY="$PUBLIC_KEY_SNAPSHOT"
|
|
424
|
+
|
|
425
|
+
COMPUTED_PUBLICATION_IDENTITY_SHA256="$("$VERIFIER" release-publication-identity \
|
|
426
|
+
--component "release_kind=initial-bootstrap-updater" \
|
|
427
|
+
--component "source_sha=$SOURCE_SHA" \
|
|
428
|
+
--component "version=$VERSION" \
|
|
429
|
+
--component "team_id=$EXPECTED_TEAM_ID" \
|
|
430
|
+
--component "installer_identity=$INSTALLER_IDENTITY" \
|
|
431
|
+
--component "notary_profile=$NOTARY_PROFILE" \
|
|
432
|
+
--component "release_sequence=$RELEASE_SEQUENCE" \
|
|
433
|
+
--component "key_epoch=$KEY_EPOCH" \
|
|
434
|
+
--component "expires_at_utc=$EXPIRES_AT_UTC" \
|
|
435
|
+
--component "app_archive_sha256=$(sha256_file "$APP_ARCHIVE")" \
|
|
436
|
+
--component "manifest_sha256=$(sha256_file "$MANIFEST")" \
|
|
437
|
+
--component "broker_sha256=$(sha256_file "$BROKER")" \
|
|
438
|
+
--component "artifact_verifier_sha256=$(sha256_file "$VERIFIER")" \
|
|
439
|
+
--component "bootstrap_preflight_sha256=$(sha256_file "$BOOTSTRAP_PREFLIGHT_VERIFIER")" \
|
|
440
|
+
--component "envelope_public_key_sha256=$(sha256_file "$PUBLIC_KEY")" \
|
|
441
|
+
--component "envelope_signer_sha256=$(sha256_file "$ENVELOPE_SIGNER")")"
|
|
442
|
+
[ "$COMPUTED_PUBLICATION_IDENTITY_SHA256" = "$PUBLICATION_IDENTITY_SHA256" ] || {
|
|
443
|
+
echo "Package publication identity does not match the exact release inputs." >&2
|
|
444
|
+
exit 1
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
PACKAGE_SET_BASENAME="${ARTIFACT_BASENAME}-updater"
|
|
448
|
+
PKG_LEAF="${PACKAGE_SET_BASENAME}.pkg"
|
|
449
|
+
NOTARY_SUBMISSION_LEAF="${PACKAGE_SET_BASENAME}.notary-submit.json"
|
|
450
|
+
NOTARY_LOG_LEAF="${PACKAGE_SET_BASENAME}.notary-log.json"
|
|
451
|
+
PKG_SHA256_LEAF="${PACKAGE_SET_BASENAME}.pkg.sha256"
|
|
452
|
+
BOOTSTRAP_ENVELOPE_LEAF="${PACKAGE_SET_BASENAME}.bootstrap-envelope.json"
|
|
453
|
+
COMPATIBLE_COHORT_LEAF="${PACKAGE_SET_BASENAME}.compatible-cohort.json"
|
|
454
|
+
COMPATIBLE_COHORT_SHA256_LEAF="${COMPATIBLE_COHORT_LEAF}.sha256"
|
|
455
|
+
PACKAGE_FINAL_DIR="$OUTPUT_DIR/${PACKAGE_SET_BASENAME}.release"
|
|
456
|
+
PACKAGE_RESERVATION="$OUTPUT_DIR/.${PACKAGE_SET_BASENAME}.reservation"
|
|
457
|
+
|
|
458
|
+
if [ -e "$PACKAGE_FINAL_DIR" ] || [ -L "$PACKAGE_FINAL_DIR" ]; then
|
|
459
|
+
"$VERIFIER" complete-release-publication \
|
|
460
|
+
--destination "$PACKAGE_FINAL_DIR" \
|
|
461
|
+
--reservation "$PACKAGE_RESERVATION" \
|
|
462
|
+
--output-root "$OUTPUT_DIR" \
|
|
463
|
+
--publication-identity-sha256 "$PUBLICATION_IDENTITY_SHA256"
|
|
464
|
+
"$VERIFIER" assert-release-publication-complete \
|
|
465
|
+
--destination "$PACKAGE_FINAL_DIR" \
|
|
466
|
+
--output-root "$OUTPUT_DIR" \
|
|
467
|
+
--publication-identity-sha256 "$PUBLICATION_IDENTITY_SHA256"
|
|
468
|
+
printf 'Recovered authenticated same-version updater publication: %s\n' "$PACKAGE_FINAL_DIR"
|
|
469
|
+
exit 0
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
for reserved_output in \
|
|
473
|
+
"$PACKAGE_FINAL_DIR" \
|
|
474
|
+
"$OUTPUT_DIR/$PKG_LEAF" \
|
|
475
|
+
"$OUTPUT_DIR/$NOTARY_SUBMISSION_LEAF" \
|
|
476
|
+
"$OUTPUT_DIR/$NOTARY_LOG_LEAF" \
|
|
477
|
+
"$OUTPUT_DIR/$PKG_SHA256_LEAF" \
|
|
478
|
+
"$OUTPUT_DIR/$BOOTSTRAP_ENVELOPE_LEAF" \
|
|
479
|
+
"$OUTPUT_DIR/$COMPATIBLE_COHORT_LEAF" \
|
|
480
|
+
"$OUTPUT_DIR/$COMPATIBLE_COHORT_SHA256_LEAF"; do
|
|
481
|
+
[ ! -e "$reserved_output" ] && [ ! -L "$reserved_output" ] || {
|
|
482
|
+
echo "Package release output already exists; same-version publication is immutable: $reserved_output" >&2
|
|
483
|
+
exit 1
|
|
484
|
+
}
|
|
485
|
+
done
|
|
486
|
+
if ! /bin/mkdir -m 0700 "$PACKAGE_RESERVATION"; then
|
|
487
|
+
echo "Package release output is already reserved by another or interrupted builder." >&2
|
|
488
|
+
exit 1
|
|
489
|
+
fi
|
|
490
|
+
PACKAGE_RESERVATION_OWNED=1
|
|
491
|
+
require_no_extended_acl "$PACKAGE_RESERVATION"
|
|
492
|
+
|
|
493
|
+
# Recheck after taking the exclusive reservation so a builder that completed
|
|
494
|
+
# between the initial probe and mkdir cannot be replaced.
|
|
495
|
+
for reserved_output in \
|
|
496
|
+
"$PACKAGE_FINAL_DIR" \
|
|
497
|
+
"$OUTPUT_DIR/$PKG_LEAF" \
|
|
498
|
+
"$OUTPUT_DIR/$NOTARY_SUBMISSION_LEAF" \
|
|
499
|
+
"$OUTPUT_DIR/$NOTARY_LOG_LEAF" \
|
|
500
|
+
"$OUTPUT_DIR/$PKG_SHA256_LEAF" \
|
|
501
|
+
"$OUTPUT_DIR/$BOOTSTRAP_ENVELOPE_LEAF" \
|
|
502
|
+
"$OUTPUT_DIR/$COMPATIBLE_COHORT_LEAF" \
|
|
503
|
+
"$OUTPUT_DIR/$COMPATIBLE_COHORT_SHA256_LEAF"; do
|
|
504
|
+
[ ! -e "$reserved_output" ] && [ ! -L "$reserved_output" ] || {
|
|
505
|
+
echo "Package release output appeared while acquiring its reservation." >&2
|
|
506
|
+
exit 1
|
|
507
|
+
}
|
|
508
|
+
done
|
|
509
|
+
PACKAGE_STAGE_DIR="$(/usr/bin/mktemp -d "$OUTPUT_DIR/.${PACKAGE_SET_BASENAME}.staging.XXXXXX")"
|
|
510
|
+
/bin/chmod -N "$PACKAGE_STAGE_DIR"
|
|
511
|
+
/bin/chmod 0700 "$PACKAGE_STAGE_DIR"
|
|
512
|
+
require_no_extended_acl "$PACKAGE_STAGE_DIR"
|
|
513
|
+
|
|
514
|
+
ROOT="$WORK_DIR/root"
|
|
515
|
+
SCRIPTS="$WORK_DIR/scripts"
|
|
516
|
+
PACKAGE_BOOTSTRAP_ID="$(/usr/bin/uuidgen | /usr/bin/tr '[:upper:]' '[:lower:]')"
|
|
517
|
+
[[ "$PACKAGE_BOOTSTRAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] || {
|
|
518
|
+
echo "Package builder could not create a valid bootstrap identity." >&2
|
|
519
|
+
exit 1
|
|
520
|
+
}
|
|
521
|
+
/bin/mkdir -p \
|
|
522
|
+
"$ROOT/Applications" \
|
|
523
|
+
"$ROOT/Library/PrivilegedHelperTools" \
|
|
524
|
+
"$ROOT/Library/LaunchDaemons" \
|
|
525
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Trust/envelope-keys" \
|
|
526
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap" \
|
|
527
|
+
"$SCRIPTS"
|
|
528
|
+
require_no_extended_acl "$PACKAGE_STAGE_DIR"
|
|
529
|
+
|
|
530
|
+
/usr/bin/ditto "$APP" "$ROOT/Applications/Recordings.app"
|
|
531
|
+
/bin/cp "$BROKER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
532
|
+
/bin/cp "$VERIFIER" "$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
533
|
+
/bin/cp "$PUBLIC_KEY" "$ROOT/Library/Application Support/Hasna/Recordings/Trust/envelope-keys/${KEY_EPOCH}.raw"
|
|
534
|
+
/bin/cp "$LAUNCHD_PLIST" "$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
535
|
+
/bin/cp "$VERIFIER_SANDBOX" "$ROOT/Library/Application Support/Hasna/Recordings/Trust/artifact-verifier.sb"
|
|
536
|
+
for package_script_name in preinstall postinstall; do
|
|
537
|
+
package_script_source="$PKG_SCRIPTS/$package_script_name"
|
|
538
|
+
package_script_target="$SCRIPTS/$package_script_name"
|
|
539
|
+
[ "$(/usr/bin/grep -c '__RECORDINGS_PACKAGE_BOOTSTRAP_ID__' "$package_script_source")" = "1" ] || {
|
|
540
|
+
echo "Package $package_script_name must contain exactly one bootstrap-identity placeholder." >&2
|
|
541
|
+
exit 1
|
|
542
|
+
}
|
|
543
|
+
/usr/bin/sed "s/__RECORDINGS_PACKAGE_BOOTSTRAP_ID__/$PACKAGE_BOOTSTRAP_ID/" \
|
|
544
|
+
"$package_script_source" >"$package_script_target"
|
|
545
|
+
! /usr/bin/grep -F '__RECORDINGS_PACKAGE_BOOTSTRAP_ID__' "$package_script_target" >/dev/null || {
|
|
546
|
+
echo "Package $package_script_name retained an unresolved bootstrap identity." >&2
|
|
547
|
+
exit 1
|
|
548
|
+
}
|
|
549
|
+
/usr/bin/grep -F "PACKAGE_BOOTSTRAP_ID=\"$PACKAGE_BOOTSTRAP_ID\"" \
|
|
550
|
+
"$package_script_target" >/dev/null || {
|
|
551
|
+
echo "Package $package_script_name did not bind the generated bootstrap identity." >&2
|
|
552
|
+
exit 1
|
|
553
|
+
}
|
|
554
|
+
done
|
|
555
|
+
/bin/cp "$BOOTSTRAP_PREFLIGHT_VERIFIER" "$SCRIPTS/recordings-bootstrap-preflight"
|
|
556
|
+
/usr/bin/printf '%s\n' "$RELEASE_SEQUENCE" >"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/release-sequence"
|
|
557
|
+
/usr/bin/printf '%s\n' "$KEY_EPOCH" >"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/key-epoch"
|
|
558
|
+
/usr/bin/printf '%s\n' "$SOURCE_SHA" >"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/source-sha"
|
|
559
|
+
/usr/bin/printf '{"schema_version":1,"signing_team_identifier":"%s","allowed_client_identifiers":["com.hasna.recordings.update-client"],"application_identifier":"com.hasna.recordings","initial_key_epoch":%s,"allowed_key_epochs":[%s],"lifecycle":"bootstrap-v1-app-updates-only","root_maintenance_supported":false,"key_rotation_supported":false}\n' \
|
|
560
|
+
"$EXPECTED_TEAM_ID" "$KEY_EPOCH" "$KEY_EPOCH" \
|
|
561
|
+
>"$ROOT/Library/Application Support/Hasna/Recordings/Trust/broker-policy.json"
|
|
562
|
+
/bin/chmod -RN "$ROOT" "$SCRIPTS"
|
|
563
|
+
require_tree_without_extended_acl "$ROOT"
|
|
564
|
+
require_tree_without_extended_acl "$SCRIPTS"
|
|
565
|
+
/bin/chmod 0555 \
|
|
566
|
+
"$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater" \
|
|
567
|
+
"$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier" \
|
|
568
|
+
"$SCRIPTS/preinstall" "$SCRIPTS/postinstall" "$SCRIPTS/recordings-bootstrap-preflight"
|
|
569
|
+
/bin/chmod 0444 \
|
|
570
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Trust/envelope-keys/${KEY_EPOCH}.raw" \
|
|
571
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Trust/broker-policy.json" \
|
|
572
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Trust/artifact-verifier.sb" \
|
|
573
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/release-sequence" \
|
|
574
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/key-epoch" \
|
|
575
|
+
"$ROOT/Library/Application Support/Hasna/Recordings/Bootstrap/source-sha" \
|
|
576
|
+
"$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
577
|
+
/usr/bin/plutil -lint "$ROOT/Library/LaunchDaemons/com.hasna.recordings.updater.plist" >/dev/null
|
|
578
|
+
|
|
579
|
+
STAGED_APP="$ROOT/Applications/Recordings.app"
|
|
580
|
+
STAGED_BROKER="$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
581
|
+
STAGED_CLIENT="$STAGED_APP/Contents/Helpers/recordings-update-client"
|
|
582
|
+
STAGED_VERIFIER="$ROOT/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
583
|
+
STAGED_BOOTSTRAP_PREFLIGHT="$SCRIPTS/recordings-bootstrap-preflight"
|
|
584
|
+
require_safe_signed_app_bundle_modes "$STAGED_APP"
|
|
585
|
+
require_exact_regular_file_mode "$STAGED_BROKER" 0555 "Staged updater broker"
|
|
586
|
+
require_exact_regular_file_mode "$STAGED_VERIFIER" 0555 "Staged artifact verifier launcher"
|
|
587
|
+
require_exact_regular_file_mode "$STAGED_BOOTSTRAP_PREFLIGHT" 0555 "Staged bootstrap preflight verifier"
|
|
588
|
+
require_exact_binary_architectures "$STAGED_APP/Contents/MacOS/Recordings" arm64 x86_64
|
|
589
|
+
require_exact_binary_architectures "$STAGED_APP/Contents/Helpers/recordings" arm64 x86_64
|
|
590
|
+
require_exact_binary_architectures "$STAGED_CLIENT" arm64 x86_64
|
|
591
|
+
require_exact_binary_architectures "$STAGED_BROKER" arm64 x86_64
|
|
592
|
+
require_exact_binary_architectures "$STAGED_BOOTSTRAP_PREFLIGHT" arm64 x86_64
|
|
593
|
+
require_exact_binary_architectures "$STAGED_VERIFIER" arm64 x86_64
|
|
594
|
+
verify_developer_id_application "$STAGED_BROKER" "com.hasna.recordings.updater"
|
|
595
|
+
verify_developer_id_application "$STAGED_CLIENT" "com.hasna.recordings.update-client"
|
|
596
|
+
verify_developer_id_application "$STAGED_VERIFIER" "com.hasna.recordings.artifact-verifier"
|
|
597
|
+
verify_developer_id_application "$STAGED_BOOTSTRAP_PREFLIGHT" "com.hasna.recordings.bootstrap-preflight"
|
|
598
|
+
/usr/bin/codesign --verify --deep --strict --all-architectures --verbose=2 "$STAGED_APP"
|
|
599
|
+
PROVENANCE="$STAGED_APP/Contents/Resources/recordings-build-provenance.json"
|
|
600
|
+
[ -f "$PROVENANCE" ] && [ ! -L "$PROVENANCE" ] || { echo "Signed app provenance is missing." >&2; exit 1; }
|
|
601
|
+
[ "$(/usr/bin/plutil -extract git_sha raw -o - "$PROVENANCE")" = "$SOURCE_SHA" ] || {
|
|
602
|
+
echo "Signed app provenance does not bind the package source commit." >&2
|
|
603
|
+
exit 1
|
|
604
|
+
}
|
|
605
|
+
[ "$(/usr/bin/plutil -extract team_id raw -o - "$PROVENANCE")" = "$EXPECTED_TEAM_ID" ] || {
|
|
606
|
+
echo "Signed app provenance does not bind the package TeamIdentifier." >&2
|
|
607
|
+
exit 1
|
|
608
|
+
}
|
|
609
|
+
RELEASE_ID="$(/usr/bin/uuidgen | /usr/bin/tr '[:upper:]' '[:lower:]')"
|
|
610
|
+
STAGED_CLIENT_SHA256="$(/usr/bin/shasum -a 256 "$STAGED_CLIENT" | /usr/bin/awk '{ print $1 }')"
|
|
611
|
+
STAGED_BROKER_SHA256="$(/usr/bin/shasum -a 256 "$STAGED_BROKER" | /usr/bin/awk '{ print $1 }')"
|
|
612
|
+
STAGED_VERIFIER_SHA256="$(/usr/bin/shasum -a 256 "$STAGED_VERIFIER" | /usr/bin/awk '{ print $1 }')"
|
|
613
|
+
STAGED_APP_TREE_SHA256="$(/usr/bin/plutil -extract binding.bundle_tree_sha256 raw -o - "$MANIFEST")"
|
|
614
|
+
ACTUAL_STAGED_APP_TREE_SHA256="$("$STAGED_VERIFIER" tree-digest --path "$STAGED_APP")"
|
|
615
|
+
[ "$ACTUAL_STAGED_APP_TREE_SHA256" = "$STAGED_APP_TREE_SHA256" ] || {
|
|
616
|
+
echo "Staged PKG application tree does not match the finalized manifest binding." >&2
|
|
617
|
+
exit 1
|
|
618
|
+
}
|
|
619
|
+
BOOTSTRAP_MARKER="$ROOT/Library/Application Support/Hasna/Recordings/Trust/bootstrap-marker.json"
|
|
620
|
+
/usr/bin/printf '{"schema_version":1,"key_epoch":%s,"release_sequence":%s,"release_id":"%s","version":"%s","source_commit":"%s","signing_team_identifier":"%s","app_tree_sha256":"%s","update_client_sha256":"%s","update_broker_sha256":"%s","artifact_verifier_sha256":"%s","lifecycle":"bootstrap-v1-app-updates-only","root_maintenance_supported":false,"key_rotation_supported":false}\n' \
|
|
621
|
+
"$KEY_EPOCH" "$RELEASE_SEQUENCE" "$RELEASE_ID" "$VERSION" "$SOURCE_SHA" "$EXPECTED_TEAM_ID" \
|
|
622
|
+
"$STAGED_APP_TREE_SHA256" "$STAGED_CLIENT_SHA256" "$STAGED_BROKER_SHA256" "$STAGED_VERIFIER_SHA256" \
|
|
623
|
+
>"$BOOTSTRAP_MARKER"
|
|
624
|
+
/bin/chmod 0444 "$BOOTSTRAP_MARKER"
|
|
625
|
+
/usr/bin/plutil -lint "$BOOTSTRAP_MARKER" >/dev/null
|
|
626
|
+
BOOTSTRAP_MARKER_SHA256="$(/usr/bin/shasum -a 256 "$BOOTSTRAP_MARKER" | /usr/bin/awk '{ print $1 }')"
|
|
627
|
+
|
|
628
|
+
PKG="$PACKAGE_STAGE_DIR/$PKG_LEAF"
|
|
629
|
+
NOTARY_SUBMISSION="$PACKAGE_STAGE_DIR/$NOTARY_SUBMISSION_LEAF"
|
|
630
|
+
NOTARY_LOG="$PACKAGE_STAGE_DIR/$NOTARY_LOG_LEAF"
|
|
631
|
+
PKG_SHA256="$PACKAGE_STAGE_DIR/$PKG_SHA256_LEAF"
|
|
632
|
+
ENVELOPE_PAYLOAD="$WORK_DIR/bootstrap-envelope-payload.json"
|
|
633
|
+
BOOTSTRAP_ENVELOPE="$PACKAGE_STAGE_DIR/$BOOTSTRAP_ENVELOPE_LEAF"
|
|
634
|
+
COMPATIBLE_COHORT="$PACKAGE_STAGE_DIR/$COMPATIBLE_COHORT_LEAF"
|
|
635
|
+
COMPATIBLE_COHORT_SHA256="$PACKAGE_STAGE_DIR/$COMPATIBLE_COHORT_SHA256_LEAF"
|
|
636
|
+
|
|
637
|
+
require_tree_without_extended_acl "$ROOT"
|
|
638
|
+
require_tree_without_extended_acl "$SCRIPTS"
|
|
639
|
+
require_no_extended_acl "$PACKAGE_STAGE_DIR"
|
|
640
|
+
/usr/bin/pkgbuild \
|
|
641
|
+
--root "$ROOT" \
|
|
642
|
+
--scripts "$SCRIPTS" \
|
|
643
|
+
--identifier com.hasna.recordings.updater \
|
|
644
|
+
--version "$VERSION" \
|
|
645
|
+
--install-location / \
|
|
646
|
+
--ownership recommended \
|
|
647
|
+
--timestamp \
|
|
648
|
+
--sign "$INSTALLER_IDENTITY" \
|
|
649
|
+
"$PKG"
|
|
650
|
+
|
|
651
|
+
verify_pkg_signature() {
|
|
652
|
+
local signature_output
|
|
653
|
+
signature_output="$(/usr/sbin/pkgutil --check-signature "$PKG")"
|
|
654
|
+
/usr/bin/grep -F "Status: signed by a certificate trusted by macOS" <<<"$signature_output" >/dev/null || \
|
|
655
|
+
/usr/bin/grep -F "Status: signed by a certificate trusted by Mac OS X" <<<"$signature_output" >/dev/null
|
|
656
|
+
/usr/bin/grep -E "^[[:space:]]*1\\. Developer ID Installer: .+ \\(${EXPECTED_TEAM_ID}\\)$" <<<"$signature_output" >/dev/null
|
|
657
|
+
}
|
|
658
|
+
verify_pkg_signature
|
|
659
|
+
SUBMITTED_PKG_SHA256="$(sha256_file "$PKG")"
|
|
660
|
+
/usr/bin/xcrun notarytool submit "$PKG" \
|
|
661
|
+
--keychain-profile "$NOTARY_PROFILE" \
|
|
662
|
+
--wait --output-format json >"$NOTARY_SUBMISSION"
|
|
663
|
+
[ "$(/usr/bin/plutil -extract status raw -o - "$NOTARY_SUBMISSION")" = "Accepted" ] || {
|
|
664
|
+
echo "Updater PKG notarization was not accepted." >&2
|
|
665
|
+
exit 1
|
|
666
|
+
}
|
|
667
|
+
NOTARY_ID="$(/usr/bin/plutil -extract id raw -o - "$NOTARY_SUBMISSION")"
|
|
668
|
+
[ -n "$NOTARY_ID" ] || { echo "Updater PKG notarization omitted its submission ID." >&2; exit 1; }
|
|
669
|
+
/usr/bin/xcrun notarytool log "$NOTARY_ID" --keychain-profile "$NOTARY_PROFILE" >"$NOTARY_LOG"
|
|
670
|
+
"$STAGED_VERIFIER" assert-notary-log \
|
|
671
|
+
--notary-log "$NOTARY_LOG" \
|
|
672
|
+
--submission-id "$NOTARY_ID" \
|
|
673
|
+
--submitted-archive-sha256 "$SUBMITTED_PKG_SHA256" || {
|
|
674
|
+
echo "Updater PKG notarization log is not accepted, issue-free, or byte-bound." >&2
|
|
675
|
+
exit 1
|
|
676
|
+
}
|
|
677
|
+
NOTARY_ARCHIVE_SHA256="$SUBMITTED_PKG_SHA256"
|
|
678
|
+
/usr/bin/xcrun stapler staple "$PKG"
|
|
679
|
+
/usr/bin/xcrun stapler validate "$PKG"
|
|
680
|
+
/usr/sbin/spctl --assess --type install --verbose=2 "$PKG"
|
|
681
|
+
verify_pkg_signature
|
|
682
|
+
PKG_PUBLISHED_SHA256="$(sha256_file "$PKG")"
|
|
683
|
+
/usr/bin/printf '%s %s\n' "$PKG_PUBLISHED_SHA256" "$PKG_LEAF" >"$PKG_SHA256"
|
|
684
|
+
|
|
685
|
+
designated_requirement() {
|
|
686
|
+
local requirement
|
|
687
|
+
requirement="$(/usr/bin/codesign -d -r- "$1" 2>&1 | /usr/bin/sed -n 's/^designated => //p' | /usr/bin/head -n 1)"
|
|
688
|
+
[ -n "$requirement" ] || { echo "Signed component is missing its designated requirement." >&2; exit 1; }
|
|
689
|
+
printf '%s\n' "$requirement"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
APP_ARCHIVE_SHA256="$(sha256_file "$APP_ARCHIVE")"
|
|
693
|
+
MANIFEST_SHA256="$(sha256_file "$MANIFEST")"
|
|
694
|
+
PKG_DIGEST="$(sha256_file "$PKG")"
|
|
695
|
+
CLIENT_DIGEST="$(sha256_file "$STAGED_CLIENT")"
|
|
696
|
+
BROKER_DIGEST="$(sha256_file "$STAGED_BROKER")"
|
|
697
|
+
VERIFIER_DIGEST="$(sha256_file "$STAGED_VERIFIER")"
|
|
698
|
+
PUBLIC_KEY_DIGEST="$(sha256_file "$PUBLIC_KEY")"
|
|
699
|
+
APP_REQUIREMENT="$(designated_requirement "$STAGED_APP")"
|
|
700
|
+
CLIENT_REQUIREMENT="$(designated_requirement "$STAGED_CLIENT")"
|
|
701
|
+
BROKER_REQUIREMENT="$(designated_requirement "$STAGED_BROKER")"
|
|
702
|
+
VERIFIER_REQUIREMENT="$(designated_requirement "$STAGED_VERIFIER")"
|
|
703
|
+
[ "$(/usr/bin/plutil -extract archive.sha256 raw -o - "$MANIFEST")" = "$APP_ARCHIVE_SHA256" ] || {
|
|
704
|
+
echo "Final app manifest does not bind the release archive." >&2
|
|
705
|
+
exit 1
|
|
706
|
+
}
|
|
707
|
+
[ "$(/usr/bin/plutil -extract git_sha raw -o - "$MANIFEST")" = "$SOURCE_SHA" ] || {
|
|
708
|
+
echo "Final app manifest does not bind the source commit." >&2
|
|
709
|
+
exit 1
|
|
710
|
+
}
|
|
711
|
+
[ "$(/usr/bin/plutil -extract team_id raw -o - "$MANIFEST")" = "$EXPECTED_TEAM_ID" ] || {
|
|
712
|
+
echo "Final app manifest does not bind the TeamIdentifier." >&2
|
|
713
|
+
exit 1
|
|
714
|
+
}
|
|
715
|
+
[ "$(/usr/bin/plutil -extract bundle_version raw -o - "$MANIFEST")" = "$VERSION" ] || {
|
|
716
|
+
echo "Final app manifest does not bind the package version." >&2
|
|
717
|
+
exit 1
|
|
718
|
+
}
|
|
719
|
+
MANIFEST_ARCHITECTURES="$(/usr/bin/plutil -extract architectures json -o - "$MANIFEST" | /usr/bin/tr -d '[:space:]')"
|
|
720
|
+
[ "$MANIFEST_ARCHITECTURES" = '["arm64","x86_64"]' ] || {
|
|
721
|
+
echo "Release app manifest must bind exactly arm64 and x86_64." >&2
|
|
722
|
+
exit 1
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
PKG_SIGNATURE_OUTPUT="$(/usr/sbin/pkgutil --check-signature "$PKG")"
|
|
726
|
+
INSTALLER_CERTIFICATE_SHA256="$(printf '%s\n' "$PKG_SIGNATURE_OUTPUT" | \
|
|
727
|
+
/usr/bin/awk -f "$PKGUTIL_FINGERPRINT_PARSER")" || {
|
|
728
|
+
echo "Could not parse the Developer ID Installer certificate fingerprint." >&2
|
|
729
|
+
exit 1
|
|
730
|
+
}
|
|
731
|
+
[[ "$INSTALLER_CERTIFICATE_SHA256" =~ ^[a-f0-9]{64}$ ]] || {
|
|
732
|
+
echo "Could not bind the Developer ID Installer certificate fingerprint." >&2
|
|
733
|
+
exit 1
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
"$BUN_EXECUTABLE" "$SOURCE_ROOT/packaging/macos/release_lifecycle.ts" write-compatible-cohort \
|
|
737
|
+
--artifact-verifier-designated-requirement "$VERIFIER_REQUIREMENT" \
|
|
738
|
+
--artifact-verifier-sha256 "$VERIFIER_DIGEST" \
|
|
739
|
+
--bootstrap-marker-sha256 "$BOOTSTRAP_MARKER_SHA256" \
|
|
740
|
+
--envelope-public-key-sha256 "$PUBLIC_KEY_DIGEST" \
|
|
741
|
+
--installer-certificate-sha256 "$INSTALLER_CERTIFICATE_SHA256" \
|
|
742
|
+
--key-epoch "$KEY_EPOCH" \
|
|
743
|
+
--minimum-broker-version 1.0.0 \
|
|
744
|
+
--output "$COMPATIBLE_COHORT" \
|
|
745
|
+
--package-sha256 "$PKG_DIGEST" \
|
|
746
|
+
--team-id "$EXPECTED_TEAM_ID" \
|
|
747
|
+
--update-broker-designated-requirement "$BROKER_REQUIREMENT" \
|
|
748
|
+
--update-broker-sha256 "$BROKER_DIGEST"
|
|
749
|
+
[ -f "$COMPATIBLE_COHORT" ] && [ ! -L "$COMPATIBLE_COHORT" ] && [ -s "$COMPATIBLE_COHORT" ] || {
|
|
750
|
+
echo "Compatible-cohort helper did not emit a schema-v2 manifest." >&2
|
|
751
|
+
exit 1
|
|
752
|
+
}
|
|
753
|
+
COMPATIBLE_COHORT_DIGEST="$(sha256_file "$COMPATIBLE_COHORT")"
|
|
754
|
+
/usr/bin/printf '%s %s\n' "$COMPATIBLE_COHORT_DIGEST" "$COMPATIBLE_COHORT_LEAF" \
|
|
755
|
+
>"$COMPATIBLE_COHORT_SHA256"
|
|
756
|
+
|
|
757
|
+
ISSUED_AT_UTC="$(/bin/date -u '+%Y-%m-%dT%H:%M:%S.000Z')"
|
|
758
|
+
/usr/bin/plutil -create json "$ENVELOPE_PAYLOAD"
|
|
759
|
+
/usr/bin/plutil -insert purpose -string bootstrap "$ENVELOPE_PAYLOAD"
|
|
760
|
+
/usr/bin/plutil -insert schema_version -integer 1 "$ENVELOPE_PAYLOAD"
|
|
761
|
+
/usr/bin/plutil -insert key_epoch -integer "$KEY_EPOCH" "$ENVELOPE_PAYLOAD"
|
|
762
|
+
/usr/bin/plutil -insert release_sequence -integer "$RELEASE_SEQUENCE" "$ENVELOPE_PAYLOAD"
|
|
763
|
+
/usr/bin/plutil -insert release_id -string "$RELEASE_ID" "$ENVELOPE_PAYLOAD"
|
|
764
|
+
/usr/bin/plutil -insert version -string "$VERSION" "$ENVELOPE_PAYLOAD"
|
|
765
|
+
/usr/bin/plutil -insert build -string "$(/usr/bin/plutil -extract bundle_build_version raw -o - "$MANIFEST")" "$ENVELOPE_PAYLOAD"
|
|
766
|
+
/usr/bin/plutil -insert source_commit -string "$SOURCE_SHA" "$ENVELOPE_PAYLOAD"
|
|
767
|
+
/usr/bin/plutil -insert artifact_sha256 -string "$PKG_DIGEST" "$ENVELOPE_PAYLOAD"
|
|
768
|
+
/usr/bin/plutil -insert artifact_byte_count -integer "$(/usr/bin/stat -f '%z' "$PKG")" "$ENVELOPE_PAYLOAD"
|
|
769
|
+
/usr/bin/plutil -insert manifest_sha256 -string "$MANIFEST_SHA256" "$ENVELOPE_PAYLOAD"
|
|
770
|
+
/usr/bin/plutil -insert manifest_byte_count -integer "$(/usr/bin/stat -f '%z' "$MANIFEST")" "$ENVELOPE_PAYLOAD"
|
|
771
|
+
/usr/bin/plutil -insert candidate_tree_sha256 -string "$STAGED_APP_TREE_SHA256" "$ENVELOPE_PAYLOAD"
|
|
772
|
+
/usr/bin/plutil -insert package_sha256 -string "$PKG_DIGEST" "$ENVELOPE_PAYLOAD"
|
|
773
|
+
/usr/bin/plutil -insert bootstrap_marker_sha256 -string "$BOOTSTRAP_MARKER_SHA256" "$ENVELOPE_PAYLOAD"
|
|
774
|
+
/usr/bin/plutil -insert update_client_sha256 -string "$CLIENT_DIGEST" "$ENVELOPE_PAYLOAD"
|
|
775
|
+
/usr/bin/plutil -insert update_broker_sha256 -string "$BROKER_DIGEST" "$ENVELOPE_PAYLOAD"
|
|
776
|
+
/usr/bin/plutil -insert artifact_verifier_sha256 -string "$VERIFIER_DIGEST" "$ENVELOPE_PAYLOAD"
|
|
777
|
+
/usr/bin/plutil -insert architectures -json '["arm64","x86_64"]' "$ENVELOPE_PAYLOAD"
|
|
778
|
+
/usr/bin/plutil -insert minimum_os_version -string "$(/usr/bin/plutil -extract minimum_macos raw -o - "$MANIFEST")" "$ENVELOPE_PAYLOAD"
|
|
779
|
+
/usr/bin/plutil -insert minimum_broker_version -string 1.0.0 "$ENVELOPE_PAYLOAD"
|
|
780
|
+
/usr/bin/plutil -insert signing_team_identifier -string "$EXPECTED_TEAM_ID" "$ENVELOPE_PAYLOAD"
|
|
781
|
+
/usr/bin/plutil -insert application_designated_requirement -string "$APP_REQUIREMENT" "$ENVELOPE_PAYLOAD"
|
|
782
|
+
/usr/bin/plutil -insert update_client_designated_requirement -string "$CLIENT_REQUIREMENT" "$ENVELOPE_PAYLOAD"
|
|
783
|
+
/usr/bin/plutil -insert update_broker_designated_requirement -string "$BROKER_REQUIREMENT" "$ENVELOPE_PAYLOAD"
|
|
784
|
+
/usr/bin/plutil -insert artifact_verifier_designated_requirement -string "$VERIFIER_REQUIREMENT" "$ENVELOPE_PAYLOAD"
|
|
785
|
+
/usr/bin/plutil -insert installer_certificate_sha256 -string "$INSTALLER_CERTIFICATE_SHA256" "$ENVELOPE_PAYLOAD"
|
|
786
|
+
/usr/bin/plutil -insert issued_at_utc -string "$ISSUED_AT_UTC" "$ENVELOPE_PAYLOAD"
|
|
787
|
+
/usr/bin/plutil -insert expires_at_utc -string "$EXPIRES_AT_UTC" "$ENVELOPE_PAYLOAD"
|
|
788
|
+
|
|
789
|
+
"$ENVELOPE_SIGNER" \
|
|
790
|
+
--payload "$ENVELOPE_PAYLOAD" \
|
|
791
|
+
--private-key "$ENVELOPE_PRIVATE_KEY" \
|
|
792
|
+
--public-key "$PUBLIC_KEY" \
|
|
793
|
+
--output "$BOOTSTRAP_ENVELOPE"
|
|
794
|
+
[ -s "$BOOTSTRAP_ENVELOPE" ] || { echo "Envelope signer did not emit a bootstrap sidecar." >&2; exit 1; }
|
|
795
|
+
|
|
796
|
+
verify_package_release_set() {
|
|
797
|
+
local output
|
|
798
|
+
for output in \
|
|
799
|
+
"$PKG" \
|
|
800
|
+
"$NOTARY_SUBMISSION" \
|
|
801
|
+
"$NOTARY_LOG" \
|
|
802
|
+
"$PKG_SHA256" \
|
|
803
|
+
"$BOOTSTRAP_ENVELOPE" \
|
|
804
|
+
"$COMPATIBLE_COHORT" \
|
|
805
|
+
"$COMPATIBLE_COHORT_SHA256"; do
|
|
806
|
+
[ -f "$output" ] && [ ! -L "$output" ] && [ -s "$output" ] || {
|
|
807
|
+
echo "Package release staging is incomplete or unsafe: $output" >&2
|
|
808
|
+
exit 1
|
|
809
|
+
}
|
|
810
|
+
require_no_extended_acl "$output"
|
|
811
|
+
/bin/chmod 0444 "$output"
|
|
812
|
+
done
|
|
813
|
+
[ "$(/usr/bin/awk 'NR == 1 { print $1 }' "$PKG_SHA256")" = "$(sha256_file "$PKG")" ] || {
|
|
814
|
+
echo "Published package digest does not match the finalized PKG bytes." >&2
|
|
815
|
+
exit 1
|
|
816
|
+
}
|
|
817
|
+
[ "$(/usr/bin/awk 'NR == 1 { print $2 }' "$PKG_SHA256")" = "$PKG_LEAF" ] || {
|
|
818
|
+
echo "Published package digest names the wrong PKG artifact." >&2
|
|
819
|
+
exit 1
|
|
820
|
+
}
|
|
821
|
+
[ "$(/usr/bin/awk 'NR == 1 { print $1 }' "$COMPATIBLE_COHORT_SHA256")" = "$(sha256_file "$COMPATIBLE_COHORT")" ] || {
|
|
822
|
+
echo "Published compatible-cohort digest does not match the schema-v2 manifest." >&2
|
|
823
|
+
exit 1
|
|
824
|
+
}
|
|
825
|
+
[ "$(/usr/bin/awk 'NR == 1 { print $2 }' "$COMPATIBLE_COHORT_SHA256")" = "$COMPATIBLE_COHORT_LEAF" ] || {
|
|
826
|
+
echo "Published compatible-cohort digest names the wrong manifest." >&2
|
|
827
|
+
exit 1
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
verify_package_release_set
|
|
832
|
+
require_no_extended_acl "$PACKAGE_STAGE_DIR"
|
|
833
|
+
[ ! -e "$PACKAGE_FINAL_DIR" ] && [ ! -L "$PACKAGE_FINAL_DIR" ] || {
|
|
834
|
+
echo "Package release destination appeared before publication." >&2
|
|
835
|
+
exit 1
|
|
836
|
+
}
|
|
837
|
+
"$STAGED_VERIFIER" prepare-release-publication \
|
|
838
|
+
--staging "$PACKAGE_STAGE_DIR" \
|
|
839
|
+
--destination "$PACKAGE_FINAL_DIR" \
|
|
840
|
+
--reservation "$PACKAGE_RESERVATION" \
|
|
841
|
+
--publication-identity-sha256 "$PUBLICATION_IDENTITY_SHA256" \
|
|
842
|
+
--alias "$PKG_LEAF" \
|
|
843
|
+
--alias "$NOTARY_SUBMISSION_LEAF" \
|
|
844
|
+
--alias "$NOTARY_LOG_LEAF" \
|
|
845
|
+
--alias "$PKG_SHA256_LEAF" \
|
|
846
|
+
--alias "$BOOTSTRAP_ENVELOPE_LEAF" \
|
|
847
|
+
--alias "$COMPATIBLE_COHORT_LEAF" \
|
|
848
|
+
--alias "$COMPATIBLE_COHORT_SHA256_LEAF"
|
|
849
|
+
"$STAGED_VERIFIER" publish-release-directory \
|
|
850
|
+
--staging "$PACKAGE_STAGE_DIR" \
|
|
851
|
+
--destination "$PACKAGE_FINAL_DIR"
|
|
852
|
+
PACKAGE_DIRECTORY_PUBLISHED=1
|
|
853
|
+
PACKAGE_STAGE_DIR=""
|
|
854
|
+
"$STAGED_VERIFIER" complete-release-publication \
|
|
855
|
+
--destination "$PACKAGE_FINAL_DIR" \
|
|
856
|
+
--reservation "$PACKAGE_RESERVATION" \
|
|
857
|
+
--output-root "$OUTPUT_DIR" \
|
|
858
|
+
--publication-identity-sha256 "$PUBLICATION_IDENTITY_SHA256"
|
|
859
|
+
"$STAGED_VERIFIER" assert-release-publication-complete \
|
|
860
|
+
--destination "$PACKAGE_FINAL_DIR" \
|
|
861
|
+
--output-root "$OUTPUT_DIR" \
|
|
862
|
+
--publication-identity-sha256 "$PUBLICATION_IDENTITY_SHA256"
|
|
863
|
+
PACKAGE_RESERVATION_OWNED=0
|
|
864
|
+
|
|
865
|
+
PKG="$OUTPUT_DIR/$PKG_LEAF"
|
|
866
|
+
BOOTSTRAP_ENVELOPE="$OUTPUT_DIR/$BOOTSTRAP_ENVELOPE_LEAF"
|
|
867
|
+
COMPATIBLE_COHORT="$OUTPUT_DIR/$COMPATIBLE_COHORT_LEAF"
|
|
868
|
+
COMPATIBLE_COHORT_SHA256="$OUTPUT_DIR/$COMPATIBLE_COHORT_SHA256_LEAF"
|
|
869
|
+
printf 'Built signed, notarized updater PKG from source %s: %s\n' "$SOURCE_SHA" "$PKG"
|
|
870
|
+
printf 'Built signed external bootstrap envelope: %s\n' "$BOOTSTRAP_ENVELOPE"
|
|
871
|
+
printf 'Built schema-v2 compatible-cohort manifest: %s\n' "$COMPATIBLE_COHORT"
|
|
872
|
+
printf 'Built compatible-cohort manifest digest: %s\n' "$COMPATIBLE_COHORT_SHA256"
|