@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,623 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Noninteractive MDM/base-image entry point for bootstrap and exact crash recovery.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
umask 077
|
|
6
|
+
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
|
|
7
|
+
export LC_ALL=C
|
|
8
|
+
export LANG=C
|
|
9
|
+
export TZ=UTC0
|
|
10
|
+
|
|
11
|
+
PKG=""
|
|
12
|
+
MANIFEST=""
|
|
13
|
+
ENVELOPE=""
|
|
14
|
+
EXPECTED_PACKAGE_SHA256=""
|
|
15
|
+
EXPECTED_INSTALLER_TEAM_ID=""
|
|
16
|
+
EXPECTED_INSTALLER_CERTIFICATE_SHA256=""
|
|
17
|
+
while [ "$#" -gt 0 ]; do
|
|
18
|
+
case "$1" in
|
|
19
|
+
--artifact) PKG="${2:-}"; shift 2 ;;
|
|
20
|
+
--manifest) MANIFEST="${2:-}"; shift 2 ;;
|
|
21
|
+
--envelope) ENVELOPE="${2:-}"; shift 2 ;;
|
|
22
|
+
--expected-package-sha256) EXPECTED_PACKAGE_SHA256="${2:-}"; shift 2 ;;
|
|
23
|
+
--expected-installer-team-id) EXPECTED_INSTALLER_TEAM_ID="${2:-}"; shift 2 ;;
|
|
24
|
+
--expected-installer-certificate-sha256) EXPECTED_INSTALLER_CERTIFICATE_SHA256="${2:-}"; shift 2 ;;
|
|
25
|
+
*) echo "Unknown argument: $1" >&2; exit 2 ;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
[[ "$EXPECTED_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
|
30
|
+
echo "Managed bootstrap requires an out-of-band pinned package SHA-256." >&2
|
|
31
|
+
exit 2
|
|
32
|
+
}
|
|
33
|
+
[[ "$EXPECTED_INSTALLER_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] || {
|
|
34
|
+
echo "Managed bootstrap requires an exact out-of-band Installer Team ID." >&2
|
|
35
|
+
exit 2
|
|
36
|
+
}
|
|
37
|
+
[[ "$EXPECTED_INSTALLER_CERTIFICATE_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
|
38
|
+
echo "Managed bootstrap requires an exact out-of-band Installer certificate SHA-256." >&2
|
|
39
|
+
exit 2
|
|
40
|
+
}
|
|
41
|
+
[ "$(/usr/bin/id -u)" = "0" ] || { echo "Managed bootstrap must run as root through MDM." >&2; exit 1; }
|
|
42
|
+
|
|
43
|
+
path_exists_or_symlink() {
|
|
44
|
+
[ -e "$1" ] || [ -L "$1" ]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
require_no_extended_acl() {
|
|
48
|
+
local path="$1"
|
|
49
|
+
local acl_listing acl_status
|
|
50
|
+
acl_listing="$(/bin/ls -lade "$path")" || {
|
|
51
|
+
echo "Managed bootstrap could not inspect extended ACLs on: $path" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
}
|
|
54
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
55
|
+
NR == 1 && length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
56
|
+
NR > 1 && $1 ~ /^[0-9]+:$/ { found = 1 }
|
|
57
|
+
END { exit found ? 0 : 1 }
|
|
58
|
+
'; then
|
|
59
|
+
echo "Managed bootstrap trust path has an unexpected extended ACL: $path" >&2
|
|
60
|
+
exit 1
|
|
61
|
+
else
|
|
62
|
+
acl_status=$?
|
|
63
|
+
[ "$acl_status" -eq 1 ] || {
|
|
64
|
+
echo "Managed bootstrap could not evaluate extended ACLs on: $path" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
}
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
require_tree_without_extended_acl() {
|
|
71
|
+
local tree="$1"
|
|
72
|
+
local acl_listing acl_status
|
|
73
|
+
require_no_extended_acl "$tree"
|
|
74
|
+
[ -d "$tree" ] || return 0
|
|
75
|
+
acl_listing="$(/bin/ls -laeR "$tree")" || {
|
|
76
|
+
echo "Managed bootstrap could not recursively inspect extended ACLs on: $tree" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
}
|
|
79
|
+
if /usr/bin/printf '%s\n' "$acl_listing" | /usr/bin/awk '
|
|
80
|
+
length($1) == 11 && substr($1, 11, 1) == "+" { found = 1 }
|
|
81
|
+
$1 ~ /^[0-9]+:$/ { found = 1 }
|
|
82
|
+
END { exit found ? 0 : 1 }
|
|
83
|
+
'; then
|
|
84
|
+
echo "Managed bootstrap tree has an unexpected extended ACL: $tree" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
else
|
|
87
|
+
acl_status=$?
|
|
88
|
+
[ "$acl_status" -eq 1 ] || {
|
|
89
|
+
echo "Managed bootstrap could not evaluate recursive extended ACLs on: $tree" >&2
|
|
90
|
+
exit 1
|
|
91
|
+
}
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
require_root_owned_nonwritable_directory() {
|
|
96
|
+
local directory="$1"
|
|
97
|
+
local mode
|
|
98
|
+
[ -d "$directory" ] && [ ! -L "$directory" ] && \
|
|
99
|
+
[ "$(/usr/bin/stat -f '%u' "$directory")" = 0 ] || {
|
|
100
|
+
echo "Managed bootstrap target ancestry is missing, linked, or not root-owned: $directory" >&2
|
|
101
|
+
exit 1
|
|
102
|
+
}
|
|
103
|
+
mode="$(/usr/bin/stat -f '%Lp' "$directory")"
|
|
104
|
+
[ $((8#$mode & 8#022)) -eq 0 ] || {
|
|
105
|
+
echo "Managed bootstrap target ancestry is group/other writable: $directory" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
}
|
|
108
|
+
require_no_extended_acl "$directory"
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Standard macOS permits the admin group to install app bundles. Executable
|
|
112
|
+
# authority remains the pinned Developer ID/audit-token policy, so accept only
|
|
113
|
+
# that exact OS topology and reject every ACL or broader mode.
|
|
114
|
+
require_managed_applications_directory() {
|
|
115
|
+
local mode group_id
|
|
116
|
+
[ -d "/Applications" ] && [ ! -L "/Applications" ] && \
|
|
117
|
+
[ "$(/usr/bin/stat -f '%u' "/Applications")" = 0 ] || {
|
|
118
|
+
echo "Managed bootstrap requires a root-owned canonical /Applications directory." >&2
|
|
119
|
+
exit 1
|
|
120
|
+
}
|
|
121
|
+
require_no_extended_acl "/Applications"
|
|
122
|
+
mode="$(/usr/bin/stat -f '%Lp' "/Applications")"
|
|
123
|
+
group_id="$(/usr/bin/stat -f '%g' "/Applications")"
|
|
124
|
+
case "$mode:$group_id" in
|
|
125
|
+
755:*|775:80) ;;
|
|
126
|
+
*)
|
|
127
|
+
echo "Managed bootstrap requires ACL-free /Applications mode 0755 or root:admin mode 0775." >&2
|
|
128
|
+
exit 1
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
require_expected_var_link() {
|
|
134
|
+
local var_target
|
|
135
|
+
[ -L "/var" ] || {
|
|
136
|
+
echo "Managed bootstrap requires the canonical macOS /var system link." >&2
|
|
137
|
+
exit 1
|
|
138
|
+
}
|
|
139
|
+
var_target="$(/usr/bin/readlink "/var")" || {
|
|
140
|
+
echo "Managed bootstrap could not inspect the macOS /var system link." >&2
|
|
141
|
+
exit 1
|
|
142
|
+
}
|
|
143
|
+
[ "$var_target" = "private/var" ] && \
|
|
144
|
+
[ "$(cd "/var" && /bin/pwd -P)" = "/private/var" ] || {
|
|
145
|
+
echo "Managed bootstrap found a noncanonical macOS /var system link." >&2
|
|
146
|
+
exit 1
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
validate_target_ancestry() {
|
|
151
|
+
local required_target_ancestor optional_target_ancestor
|
|
152
|
+
require_expected_var_link
|
|
153
|
+
require_managed_applications_directory
|
|
154
|
+
for required_target_ancestor in \
|
|
155
|
+
"/" \
|
|
156
|
+
"/Library" \
|
|
157
|
+
"/Library/Application Support" \
|
|
158
|
+
"/Library/PrivilegedHelperTools" \
|
|
159
|
+
"/Library/LaunchDaemons" \
|
|
160
|
+
"/private" \
|
|
161
|
+
"/private/var" \
|
|
162
|
+
"/private/var/db"; do
|
|
163
|
+
require_root_owned_nonwritable_directory "$required_target_ancestor"
|
|
164
|
+
done
|
|
165
|
+
for optional_target_ancestor in \
|
|
166
|
+
"/Library/Application Support/Hasna" \
|
|
167
|
+
"/Library/Application Support/Hasna/Recordings"; do
|
|
168
|
+
if path_exists_or_symlink "$optional_target_ancestor"; then
|
|
169
|
+
require_root_owned_nonwritable_directory "$optional_target_ancestor"
|
|
170
|
+
fi
|
|
171
|
+
done
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
validate_target_ancestry
|
|
175
|
+
|
|
176
|
+
CLIENT="/Applications/Recordings.app/Contents/Helpers/recordings-update-client"
|
|
177
|
+
STATE="/var/db/com.hasna.recordings.updater/release-state.json"
|
|
178
|
+
AUTHORIZATION_ROOT="/private/var/db/com.hasna.recordings.bootstrap-authorization"
|
|
179
|
+
AUTHORIZATION_JOURNAL="${AUTHORIZATION_ROOT}/journal"
|
|
180
|
+
AUTHORIZATION_NEXT="${AUTHORIZATION_ROOT}/.journal.next"
|
|
181
|
+
COHORT_PATHS=(
|
|
182
|
+
"/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
183
|
+
"/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
184
|
+
"/Library/LaunchDaemons/com.hasna.recordings.updater.plist"
|
|
185
|
+
"/Applications/Recordings.app"
|
|
186
|
+
"$CLIENT"
|
|
187
|
+
"/Library/Application Support/Hasna/Recordings/Trust"
|
|
188
|
+
"/Library/Application Support/Hasna/Recordings/Bootstrap"
|
|
189
|
+
"/Library/Application Support/Hasna/Recordings/Updates"
|
|
190
|
+
"/var/db/com.hasna.recordings.updater"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
validate_managed_input() {
|
|
194
|
+
local input="$1"
|
|
195
|
+
local parent canonical_parent leaf expected relative current component mode
|
|
196
|
+
case "$input" in
|
|
197
|
+
/*) ;;
|
|
198
|
+
*) echo "Managed bootstrap inputs must be absolute." >&2; exit 2 ;;
|
|
199
|
+
esac
|
|
200
|
+
case "$input" in
|
|
201
|
+
*$'\n'*|*'//'*|*'/./'*|*'/../'*|*/.|*/..) echo "Managed bootstrap input path is not canonical." >&2; exit 1 ;;
|
|
202
|
+
esac
|
|
203
|
+
parent="$(/usr/bin/dirname "$input")"
|
|
204
|
+
leaf="$(/usr/bin/basename "$input")"
|
|
205
|
+
canonical_parent="$(cd "$parent" && /bin/pwd -P)" || {
|
|
206
|
+
echo "Managed bootstrap input parent is unavailable." >&2
|
|
207
|
+
exit 1
|
|
208
|
+
}
|
|
209
|
+
if [ "$canonical_parent" = / ]; then expected="/$leaf"; else expected="$canonical_parent/$leaf"; fi
|
|
210
|
+
[ "$input" = "$expected" ] || { echo "Managed bootstrap input path is not canonical." >&2; exit 1; }
|
|
211
|
+
require_root_owned_nonwritable_directory "/"
|
|
212
|
+
relative="${canonical_parent#/}"
|
|
213
|
+
current=""
|
|
214
|
+
IFS='/' read -r -a input_components <<<"$relative"
|
|
215
|
+
for component in "${input_components[@]}"; do
|
|
216
|
+
[ -n "$component" ] || continue
|
|
217
|
+
current="$current/$component"
|
|
218
|
+
require_root_owned_nonwritable_directory "$current"
|
|
219
|
+
done
|
|
220
|
+
[ -f "$input" ] && [ ! -L "$input" ] && [ "$(/usr/bin/stat -f '%u' "$input")" = 0 ] || {
|
|
221
|
+
echo "Managed bootstrap input is missing, linked, or not root-owned." >&2
|
|
222
|
+
exit 1
|
|
223
|
+
}
|
|
224
|
+
mode="$(/usr/bin/stat -f '%Lp' "$input")"
|
|
225
|
+
[ $((8#$mode & 8#022)) -eq 0 ] || {
|
|
226
|
+
echo "Managed bootstrap inputs must not be group/other writable." >&2
|
|
227
|
+
exit 1
|
|
228
|
+
}
|
|
229
|
+
require_no_extended_acl "$input"
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for input in "$PKG" "$MANIFEST" "$ENVELOPE"; do validate_managed_input "$input"; done
|
|
233
|
+
|
|
234
|
+
SNAPSHOT_PARENT="/private/var/db"
|
|
235
|
+
require_root_owned_nonwritable_directory "$SNAPSHOT_PARENT"
|
|
236
|
+
WORK_DIR="$(/usr/bin/mktemp -d "$SNAPSHOT_PARENT/recordings-bootstrap.XXXXXX")"
|
|
237
|
+
/bin/chmod -N "$WORK_DIR"
|
|
238
|
+
/bin/chmod 0700 "$WORK_DIR"
|
|
239
|
+
require_no_extended_acl "$WORK_DIR"
|
|
240
|
+
cleanup() {
|
|
241
|
+
local status=$?
|
|
242
|
+
trap - EXIT
|
|
243
|
+
/bin/rm -rf "$WORK_DIR"
|
|
244
|
+
exit "$status"
|
|
245
|
+
}
|
|
246
|
+
trap cleanup EXIT
|
|
247
|
+
|
|
248
|
+
snapshot_input() {
|
|
249
|
+
local source="$1"
|
|
250
|
+
local target="$2"
|
|
251
|
+
local before after copied
|
|
252
|
+
before="$(/usr/bin/shasum -a 256 "$source" | /usr/bin/awk '{ print $1 }')"
|
|
253
|
+
/bin/cp "$source" "$target"
|
|
254
|
+
/bin/chmod -N "$target"
|
|
255
|
+
/bin/chmod 0400 "$target"
|
|
256
|
+
require_no_extended_acl "$target"
|
|
257
|
+
copied="$(/usr/bin/shasum -a 256 "$target" | /usr/bin/awk '{ print $1 }')"
|
|
258
|
+
after="$(/usr/bin/shasum -a 256 "$source" | /usr/bin/awk '{ print $1 }')"
|
|
259
|
+
[ "$before" = "$copied" ] && [ "$before" = "$after" ] || {
|
|
260
|
+
echo "Managed bootstrap input changed while taking a root-private snapshot." >&2
|
|
261
|
+
exit 1
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
SNAPSHOT_PKG="$WORK_DIR/bootstrap.pkg"
|
|
266
|
+
SNAPSHOT_MANIFEST="$WORK_DIR/manifest.json"
|
|
267
|
+
SNAPSHOT_ENVELOPE="$WORK_DIR/envelope.json"
|
|
268
|
+
snapshot_input "$PKG" "$SNAPSHOT_PKG"
|
|
269
|
+
snapshot_input "$MANIFEST" "$SNAPSHOT_MANIFEST"
|
|
270
|
+
snapshot_input "$ENVELOPE" "$SNAPSHOT_ENVELOPE"
|
|
271
|
+
ACTUAL_PKG_SHA256="$(/usr/bin/shasum -a 256 "$SNAPSHOT_PKG" | /usr/bin/awk '{ print $1 }')"
|
|
272
|
+
[ "$EXPECTED_PACKAGE_SHA256" = "$ACTUAL_PKG_SHA256" ] || {
|
|
273
|
+
echo "Supplied PKG does not match the management-pinned digest." >&2
|
|
274
|
+
exit 1
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME=/var/empty TMPDIR="$WORK_DIR" \
|
|
278
|
+
/usr/sbin/spctl --assess --type install --verbose=2 "$SNAPSHOT_PKG"
|
|
279
|
+
PKG_SIGNATURE="$(/usr/sbin/pkgutil --check-signature "$SNAPSHOT_PKG")"
|
|
280
|
+
/usr/bin/grep -E "^[[:space:]]*1\\. Developer ID Installer: .+ \\(${EXPECTED_INSTALLER_TEAM_ID}\\)$" \
|
|
281
|
+
<<<"$PKG_SIGNATURE" >/dev/null
|
|
282
|
+
SCRIPT_DIR="$(cd "$(/usr/bin/dirname "$0")" && /bin/pwd -P)"
|
|
283
|
+
PKGUTIL_FINGERPRINT_PARSER="$SCRIPT_DIR/pkgutil_fingerprint.awk"
|
|
284
|
+
[ -f "$PKGUTIL_FINGERPRINT_PARSER" ] && [ ! -L "$PKGUTIL_FINGERPRINT_PARSER" ] || {
|
|
285
|
+
echo "Managed bootstrap fingerprint parser is missing or unsafe." >&2
|
|
286
|
+
exit 1
|
|
287
|
+
}
|
|
288
|
+
ACTUAL_INSTALLER_CERTIFICATE_SHA256="$(printf '%s\n' "$PKG_SIGNATURE" | \
|
|
289
|
+
/usr/bin/awk -f "$PKGUTIL_FINGERPRINT_PARSER")" || {
|
|
290
|
+
echo "Could not parse the Installer certificate SHA-256 fingerprint." >&2
|
|
291
|
+
exit 1
|
|
292
|
+
}
|
|
293
|
+
[ "$ACTUAL_INSTALLER_CERTIFICATE_SHA256" = "$EXPECTED_INSTALLER_CERTIFICATE_SHA256" ] || {
|
|
294
|
+
echo "Supplied PKG has the wrong Installer certificate fingerprint." >&2
|
|
295
|
+
exit 1
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
EXPANDED="$WORK_DIR/expanded"
|
|
299
|
+
/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME=/var/empty TMPDIR="$WORK_DIR" \
|
|
300
|
+
/usr/sbin/pkgutil --expand-full "$SNAPSHOT_PKG" "$EXPANDED"
|
|
301
|
+
/bin/chmod -RN "$EXPANDED"
|
|
302
|
+
require_tree_without_extended_acl "$EXPANDED"
|
|
303
|
+
PAYLOAD="$EXPANDED/Payload"
|
|
304
|
+
PREFLIGHT="$EXPANDED/Scripts/recordings-bootstrap-preflight"
|
|
305
|
+
EXPANDED_PREINSTALL="$EXPANDED/Scripts/preinstall"
|
|
306
|
+
EXPANDED_POSTINSTALL="$EXPANDED/Scripts/postinstall"
|
|
307
|
+
PAYLOAD_CLIENT="$PAYLOAD/Applications/Recordings.app/Contents/Helpers/recordings-update-client"
|
|
308
|
+
[ -d "$PAYLOAD" ] && [ ! -L "$PAYLOAD" ] && \
|
|
309
|
+
[ -x "$PREFLIGHT" ] && [ ! -L "$PREFLIGHT" ] && \
|
|
310
|
+
[ -x "$EXPANDED_PREINSTALL" ] && [ ! -L "$EXPANDED_PREINSTALL" ] && \
|
|
311
|
+
[ -x "$EXPANDED_POSTINSTALL" ] && [ ! -L "$EXPANDED_POSTINSTALL" ] && \
|
|
312
|
+
[ -x "$PAYLOAD_CLIENT" ] && [ ! -L "$PAYLOAD_CLIENT" ] || {
|
|
313
|
+
echo "Verified PKG does not contain the expected expanded payload and preflight verifier." >&2
|
|
314
|
+
exit 1
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
extract_package_bootstrap_id() {
|
|
318
|
+
local script="$1"
|
|
319
|
+
local identity
|
|
320
|
+
identity="$(/usr/bin/sed -n 's/^PACKAGE_BOOTSTRAP_ID="\([0-9a-f-]*\)"$/\1/p' "$script")"
|
|
321
|
+
[[ "$identity" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] || {
|
|
322
|
+
echo "Verified PKG script does not carry one valid package bootstrap identity." >&2
|
|
323
|
+
exit 1
|
|
324
|
+
}
|
|
325
|
+
printf '%s\n' "$identity"
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
PREINSTALL_BOOTSTRAP_ID="$(extract_package_bootstrap_id "$EXPANDED_PREINSTALL")"
|
|
329
|
+
POSTINSTALL_BOOTSTRAP_ID="$(extract_package_bootstrap_id "$EXPANDED_POSTINSTALL")"
|
|
330
|
+
[ "$PREINSTALL_BOOTSTRAP_ID" = "$POSTINSTALL_BOOTSTRAP_ID" ] || {
|
|
331
|
+
echo "Verified PKG scripts carry conflicting bootstrap identities." >&2
|
|
332
|
+
exit 1
|
|
333
|
+
}
|
|
334
|
+
PACKAGE_BOOTSTRAP_ID="$PREINSTALL_BOOTSTRAP_ID"
|
|
335
|
+
|
|
336
|
+
verify_preflight_identity() {
|
|
337
|
+
local details authority team identifier timestamp flags
|
|
338
|
+
/usr/bin/codesign --verify --strict --all-architectures --verbose=2 "$PREFLIGHT"
|
|
339
|
+
details="$(/usr/bin/codesign -d --verbose=4 "$PREFLIGHT" 2>&1)"
|
|
340
|
+
authority="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Authority=/ { print $2; exit }')"
|
|
341
|
+
team="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^TeamIdentifier=/ { print $2; exit }')"
|
|
342
|
+
identifier="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Identifier=/ { print $2; exit }')"
|
|
343
|
+
timestamp="$(printf '%s\n' "$details" | /usr/bin/awk -F= '/^Timestamp=/ { print $2; exit }')"
|
|
344
|
+
flags="$(printf '%s\n' "$details" | /usr/bin/sed -n 's/^CodeDirectory .*flags=[^(]*(\([^)]*\)).*/\1/p' | /usr/bin/head -n 1)"
|
|
345
|
+
[[ "$authority" == "Developer ID Application:"* ]] && \
|
|
346
|
+
[ "$team" = "$EXPECTED_INSTALLER_TEAM_ID" ] && \
|
|
347
|
+
[ "$identifier" = "com.hasna.recordings.bootstrap-preflight" ] || {
|
|
348
|
+
echo "Bootstrap preflight verifier has the wrong signed identity." >&2
|
|
349
|
+
exit 1
|
|
350
|
+
}
|
|
351
|
+
case ",$flags," in *,runtime,*) ;; *) echo "Bootstrap preflight verifier lacks hardened runtime." >&2; exit 1 ;; esac
|
|
352
|
+
case "$timestamp" in ''|none|None|NONE) echo "Bootstrap preflight verifier lacks a trusted timestamp." >&2; exit 1 ;; esac
|
|
353
|
+
}
|
|
354
|
+
verify_preflight_identity
|
|
355
|
+
|
|
356
|
+
run_preflight() {
|
|
357
|
+
local require_committed="${1:-false}"
|
|
358
|
+
local args=(
|
|
359
|
+
--package "$SNAPSHOT_PKG"
|
|
360
|
+
--manifest "$SNAPSHOT_MANIFEST"
|
|
361
|
+
--envelope "$SNAPSHOT_ENVELOPE"
|
|
362
|
+
--payload-root "$PAYLOAD"
|
|
363
|
+
--expected-team-id "$EXPECTED_INSTALLER_TEAM_ID"
|
|
364
|
+
--installer-certificate-sha256 "$ACTUAL_INSTALLER_CERTIFICATE_SHA256"
|
|
365
|
+
)
|
|
366
|
+
if path_exists_or_symlink "$STATE"; then
|
|
367
|
+
[ -f "$STATE" ] && [ ! -L "$STATE" ] || {
|
|
368
|
+
echo "Installed release state is missing or unsafe." >&2
|
|
369
|
+
exit 1
|
|
370
|
+
}
|
|
371
|
+
require_no_extended_acl "$STATE"
|
|
372
|
+
args+=(--release-state "$STATE")
|
|
373
|
+
fi
|
|
374
|
+
if [ "$require_committed" = true ]; then args+=(--require-committed-state); fi
|
|
375
|
+
/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME=/var/empty TMPDIR="$WORK_DIR" \
|
|
376
|
+
LC_ALL=C LANG=C TZ=UTC0 "$PREFLIGHT" "${args[@]}"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
read_journal_value() {
|
|
380
|
+
local key="$1"
|
|
381
|
+
/usr/bin/awk -F= -v key="$key" '
|
|
382
|
+
$1 == key { count += 1; value = substr($0, length(key) + 2) }
|
|
383
|
+
END { if (count != 1) exit 1; print value }
|
|
384
|
+
' "$AUTHORIZATION_JOURNAL"
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
require_authorization_journal() {
|
|
388
|
+
path_exists_or_symlink "$AUTHORIZATION_ROOT" && \
|
|
389
|
+
[ -d "$AUTHORIZATION_ROOT" ] && [ ! -L "$AUTHORIZATION_ROOT" ] && \
|
|
390
|
+
[ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_ROOT")" = "0:0:700" ] || {
|
|
391
|
+
echo "Managed bootstrap authorization evidence is missing or unsafe." >&2
|
|
392
|
+
exit 1
|
|
393
|
+
}
|
|
394
|
+
[ -f "$AUTHORIZATION_JOURNAL" ] && [ ! -L "$AUTHORIZATION_JOURNAL" ] && \
|
|
395
|
+
[ "$(/usr/bin/stat -f '%u:%g:%Lp' "$AUTHORIZATION_JOURNAL")" = "0:0:400" ] && \
|
|
396
|
+
[ "$(/usr/bin/stat -f '%l' "$AUTHORIZATION_JOURNAL")" = "1" ] && \
|
|
397
|
+
[ "$(/usr/bin/wc -l <"$AUTHORIZATION_JOURNAL" | /usr/bin/tr -d ' ')" = "6" ] || {
|
|
398
|
+
echo "Managed bootstrap authorization journal is missing or unsafe." >&2
|
|
399
|
+
exit 1
|
|
400
|
+
}
|
|
401
|
+
require_no_extended_acl "$AUTHORIZATION_ROOT"
|
|
402
|
+
require_no_extended_acl "$AUTHORIZATION_JOURNAL"
|
|
403
|
+
[ "$(read_journal_value schema_version)" = "1" ] || { echo "Managed bootstrap authorization journal schema is invalid." >&2; exit 1; }
|
|
404
|
+
JOURNAL_PHASE="$(read_journal_value phase)"
|
|
405
|
+
JOURNAL_PACKAGE_SHA256="$(read_journal_value package_sha256)"
|
|
406
|
+
JOURNAL_PACKAGE_BOOTSTRAP_ID="$(read_journal_value package_bootstrap_id)"
|
|
407
|
+
JOURNAL_INSTALLER_TEAM_ID="$(read_journal_value installer_team_id)"
|
|
408
|
+
JOURNAL_INSTALLER_CERTIFICATE_SHA256="$(read_journal_value installer_certificate_sha256)"
|
|
409
|
+
case "$JOURNAL_PHASE" in authorized|installer-started) ;; *) echo "Managed bootstrap authorization journal phase is invalid." >&2; exit 1 ;; esac
|
|
410
|
+
[[ "$JOURNAL_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ ]] && \
|
|
411
|
+
[[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] && \
|
|
412
|
+
[[ "$JOURNAL_INSTALLER_TEAM_ID" =~ ^[A-Z0-9]{10}$ ]] && \
|
|
413
|
+
[[ "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" =~ ^[0-9a-f]{64}$ ]] || {
|
|
414
|
+
echo "Managed bootstrap authorization journal values are malformed." >&2
|
|
415
|
+
exit 1
|
|
416
|
+
}
|
|
417
|
+
[ "$JOURNAL_PACKAGE_SHA256" = "$ACTUAL_PKG_SHA256" ] && \
|
|
418
|
+
[ "$JOURNAL_PACKAGE_BOOTSTRAP_ID" = "$PACKAGE_BOOTSTRAP_ID" ] && \
|
|
419
|
+
[ "$JOURNAL_INSTALLER_TEAM_ID" = "$EXPECTED_INSTALLER_TEAM_ID" ] && \
|
|
420
|
+
[ "$JOURNAL_INSTALLER_CERTIFICATE_SHA256" = "$ACTUAL_INSTALLER_CERTIFICATE_SHA256" ] || {
|
|
421
|
+
echo "Managed bootstrap authorization journal belongs to a different authenticated package." >&2
|
|
422
|
+
exit 1
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
write_authorization_journal() {
|
|
427
|
+
local temporary_journal
|
|
428
|
+
! path_exists_or_symlink "$AUTHORIZATION_ROOT" && ! path_exists_or_symlink "$AUTHORIZATION_JOURNAL" || {
|
|
429
|
+
echo "Managed bootstrap refuses to replace existing authorization evidence." >&2
|
|
430
|
+
exit 1
|
|
431
|
+
}
|
|
432
|
+
/bin/mkdir -m 0700 "$AUTHORIZATION_ROOT"
|
|
433
|
+
/usr/sbin/chown root:wheel "$AUTHORIZATION_ROOT"
|
|
434
|
+
/bin/chmod -N "$AUTHORIZATION_ROOT"
|
|
435
|
+
/bin/chmod 0700 "$AUTHORIZATION_ROOT"
|
|
436
|
+
temporary_journal="$(/usr/bin/mktemp "$AUTHORIZATION_ROOT/.journal.XXXXXX")"
|
|
437
|
+
/usr/bin/printf 'schema_version=1\nphase=authorized\npackage_sha256=%s\npackage_bootstrap_id=%s\ninstaller_team_id=%s\ninstaller_certificate_sha256=%s\n' \
|
|
438
|
+
"$ACTUAL_PKG_SHA256" "$PACKAGE_BOOTSTRAP_ID" "$EXPECTED_INSTALLER_TEAM_ID" \
|
|
439
|
+
"$ACTUAL_INSTALLER_CERTIFICATE_SHA256" >"$temporary_journal"
|
|
440
|
+
/usr/sbin/chown root:wheel "$temporary_journal"
|
|
441
|
+
/bin/chmod -N "$temporary_journal"
|
|
442
|
+
/bin/chmod 0400 "$temporary_journal"
|
|
443
|
+
require_no_extended_acl "$temporary_journal"
|
|
444
|
+
/bin/mv "$temporary_journal" "$AUTHORIZATION_JOURNAL"
|
|
445
|
+
/bin/sync
|
|
446
|
+
require_authorization_journal
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
remove_authorization_journal() {
|
|
450
|
+
require_authorization_journal
|
|
451
|
+
if path_exists_or_symlink "$AUTHORIZATION_NEXT"; then
|
|
452
|
+
[ -f "$AUTHORIZATION_NEXT" ] && [ ! -L "$AUTHORIZATION_NEXT" ] && \
|
|
453
|
+
[ "$(/usr/bin/stat -f '%u:%g' "$AUTHORIZATION_NEXT")" = "0:0" ] && \
|
|
454
|
+
[ "$(/usr/bin/stat -f '%l' "$AUTHORIZATION_NEXT")" = "1" ] || {
|
|
455
|
+
echo "Managed bootstrap authorization journal staging is unsafe." >&2
|
|
456
|
+
exit 1
|
|
457
|
+
}
|
|
458
|
+
case "$(/usr/bin/stat -f '%Lp' "$AUTHORIZATION_NEXT")" in 400|600) ;; *) echo "Managed bootstrap authorization journal staging mode is unsafe." >&2; exit 1 ;; esac
|
|
459
|
+
require_no_extended_acl "$AUTHORIZATION_NEXT"
|
|
460
|
+
/bin/rm -f "$AUTHORIZATION_NEXT"
|
|
461
|
+
fi
|
|
462
|
+
/bin/rm -f "$AUTHORIZATION_JOURNAL"
|
|
463
|
+
/bin/rmdir "$AUTHORIZATION_ROOT"
|
|
464
|
+
/bin/sync
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
require_safe_existing_cohort_path() {
|
|
468
|
+
local path="$1"
|
|
469
|
+
local expected_mode="$2"
|
|
470
|
+
local entry listing device mode owner links invalid=0
|
|
471
|
+
[ ! -L "$path" ] && [ "$(/usr/bin/stat -f '%u' "$path")" = "0" ] || {
|
|
472
|
+
echo "Managed bootstrap cohort evidence is linked or not root-owned: $path" >&2
|
|
473
|
+
exit 1
|
|
474
|
+
}
|
|
475
|
+
mode="$(/usr/bin/stat -f '%Lp' "$path")"
|
|
476
|
+
case "$expected_mode:$mode" in
|
|
477
|
+
"$mode:$mode"|700-or-755:700|700-or-755:755) ;;
|
|
478
|
+
*)
|
|
479
|
+
echo "Managed bootstrap cohort evidence has ownership/mode drift: $path" >&2
|
|
480
|
+
exit 1
|
|
481
|
+
;;
|
|
482
|
+
esac
|
|
483
|
+
require_tree_without_extended_acl "$path"
|
|
484
|
+
if [ -f "$path" ]; then
|
|
485
|
+
[ "$(/usr/bin/stat -f '%l' "$path")" = "1" ] || { echo "Managed bootstrap cohort evidence is hard-linked: $path" >&2; exit 1; }
|
|
486
|
+
return
|
|
487
|
+
fi
|
|
488
|
+
[ -d "$path" ] || { echo "Managed bootstrap cohort evidence is not a regular file or directory: $path" >&2; exit 1; }
|
|
489
|
+
listing="$(/usr/bin/mktemp -t recordings-bootstrap-cohort)"
|
|
490
|
+
device="$(/usr/bin/stat -f '%d' "$path")"
|
|
491
|
+
/usr/bin/find -x "$path" -print0 >"$listing" || { /bin/rm -f "$listing"; exit 1; }
|
|
492
|
+
while IFS= read -r -d '' entry; do
|
|
493
|
+
owner="$(/usr/bin/stat -f '%u' "$entry")" || { invalid=1; break; }
|
|
494
|
+
mode="$(/usr/bin/stat -f '%Lp' "$entry")" || { invalid=1; break; }
|
|
495
|
+
[ ! -L "$entry" ] && [ "$owner" = "0" ] && \
|
|
496
|
+
[ "$(/usr/bin/stat -f '%d' "$entry")" = "$device" ] && \
|
|
497
|
+
[ $((8#$mode & 8#022)) -eq 0 ] || { invalid=1; break; }
|
|
498
|
+
if [ -f "$entry" ]; then
|
|
499
|
+
links="$(/usr/bin/stat -f '%l' "$entry")" || { invalid=1; break; }
|
|
500
|
+
[ "$links" = "1" ] || { invalid=1; break; }
|
|
501
|
+
elif [ ! -d "$entry" ]; then
|
|
502
|
+
invalid=1
|
|
503
|
+
break
|
|
504
|
+
fi
|
|
505
|
+
done <"$listing"
|
|
506
|
+
/bin/rm -f "$listing"
|
|
507
|
+
[ "$invalid" -eq 0 ] || { echo "Managed bootstrap cohort evidence contains unsafe structure: $path" >&2; exit 1; }
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
cohort_expected_mode() {
|
|
511
|
+
case "$1" in
|
|
512
|
+
"/Library/PrivilegedHelperTools/com.hasna.recordings.updater"|\
|
|
513
|
+
"/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier") printf '555\n' ;;
|
|
514
|
+
"/Library/LaunchDaemons/com.hasna.recordings.updater.plist") printf '444\n' ;;
|
|
515
|
+
"/Applications/Recordings.app"|"$CLIENT") printf '755\n' ;;
|
|
516
|
+
"/Library/Application Support/Hasna/Recordings/Trust") printf '700-or-755\n' ;;
|
|
517
|
+
"/Library/Application Support/Hasna/Recordings/Bootstrap"|\
|
|
518
|
+
"/Library/Application Support/Hasna/Recordings/Updates"|\
|
|
519
|
+
"/var/db/com.hasna.recordings.updater") printf '700\n' ;;
|
|
520
|
+
*) return 1 ;;
|
|
521
|
+
esac
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# Cryptographic release preflight is mandatory before Installer or any exact
|
|
525
|
+
# bootstrap recovery replay. No packaged script or release key is trusted first.
|
|
526
|
+
run_preflight false
|
|
527
|
+
|
|
528
|
+
present_count=0
|
|
529
|
+
for cohort_path in "${COHORT_PATHS[@]}"; do
|
|
530
|
+
if path_exists_or_symlink "$cohort_path"; then
|
|
531
|
+
present_count=$((present_count + 1))
|
|
532
|
+
require_safe_existing_cohort_path "$cohort_path" "$(cohort_expected_mode "$cohort_path")"
|
|
533
|
+
fi
|
|
534
|
+
done
|
|
535
|
+
service_present=false
|
|
536
|
+
if /bin/launchctl print system/com.hasna.recordings.updater >/dev/null 2>&1; then service_present=true; fi
|
|
537
|
+
|
|
538
|
+
if path_exists_or_symlink "$AUTHORIZATION_ROOT" || path_exists_or_symlink "$AUTHORIZATION_JOURNAL"; then
|
|
539
|
+
require_authorization_journal
|
|
540
|
+
else
|
|
541
|
+
if [ "$present_count" -ne 0 ] || [ "$service_present" = true ] || path_exists_or_symlink "$STATE"; then
|
|
542
|
+
if path_exists_or_symlink "$STATE"; then
|
|
543
|
+
echo "Managed bootstrap refuses a committed different cohort or any committed cohort without its exact authorization journal." >&2
|
|
544
|
+
else
|
|
545
|
+
echo "Managed bootstrap refuses an existing unrelated cohort without exact-package authorization." >&2
|
|
546
|
+
fi
|
|
547
|
+
exit 1
|
|
548
|
+
fi
|
|
549
|
+
write_authorization_journal
|
|
550
|
+
fi
|
|
551
|
+
|
|
552
|
+
BOOTSTRAP_MODE=""
|
|
553
|
+
if [ "$service_present" = true ]; then
|
|
554
|
+
[ "$present_count" -eq "${#COHORT_PATHS[@]}" ] || {
|
|
555
|
+
echo "Recordings updater cohort evidence is partial or conflicting while the service is registered; recovery is refused." >&2
|
|
556
|
+
exit 1
|
|
557
|
+
}
|
|
558
|
+
BOOTSTRAP_MODE="recover"
|
|
559
|
+
else
|
|
560
|
+
if path_exists_or_symlink "$STATE"; then
|
|
561
|
+
echo "Recordings updater refuses a committed cohort with no registered service." >&2
|
|
562
|
+
exit 1
|
|
563
|
+
fi
|
|
564
|
+
BOOTSTRAP_MODE="repair"
|
|
565
|
+
if [ "$present_count" -eq "${#COHORT_PATHS[@]}" ]; then
|
|
566
|
+
echo "Managed bootstrap will repair an authorized full cohort without a registered service." >&2
|
|
567
|
+
elif [ "$present_count" -gt 0 ]; then
|
|
568
|
+
echo "Managed bootstrap will repair an authorized partial cohort." >&2
|
|
569
|
+
fi
|
|
570
|
+
fi
|
|
571
|
+
|
|
572
|
+
if [ "$BOOTSTRAP_MODE" = repair ]; then
|
|
573
|
+
validate_target_ancestry
|
|
574
|
+
/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME=/var/empty TMPDIR="$WORK_DIR" \
|
|
575
|
+
/usr/sbin/installer -pkg "$SNAPSHOT_PKG" -target /
|
|
576
|
+
for cohort_path in "${COHORT_PATHS[@]}"; do
|
|
577
|
+
path_exists_or_symlink "$cohort_path" && [ ! -L "$cohort_path" ] || {
|
|
578
|
+
echo "Installer did not produce a complete immutable updater cohort." >&2
|
|
579
|
+
exit 1
|
|
580
|
+
}
|
|
581
|
+
require_safe_existing_cohort_path "$cohort_path" "$(cohort_expected_mode "$cohort_path")"
|
|
582
|
+
done
|
|
583
|
+
/bin/launchctl print system/com.hasna.recordings.updater >/dev/null 2>&1 || {
|
|
584
|
+
echo "Installer did not start the immutable updater broker." >&2
|
|
585
|
+
exit 1
|
|
586
|
+
}
|
|
587
|
+
fi
|
|
588
|
+
|
|
589
|
+
if path_exists_or_symlink "$STATE"; then
|
|
590
|
+
STATE_PHASE="$(/usr/bin/plutil -extract phase raw -o - "$STATE")" || {
|
|
591
|
+
echo "Installed release state does not expose a valid bootstrap phase." >&2
|
|
592
|
+
exit 1
|
|
593
|
+
}
|
|
594
|
+
if [ "$STATE_PHASE" = committed ]; then
|
|
595
|
+
run_preflight true
|
|
596
|
+
remove_authorization_journal
|
|
597
|
+
exit 0
|
|
598
|
+
fi
|
|
599
|
+
[ "$STATE_PHASE" = seen ] || {
|
|
600
|
+
echo "Installed release state is neither an exact recoverable nor committed bootstrap." >&2
|
|
601
|
+
exit 1
|
|
602
|
+
}
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# Reauthenticate the root-private payload immediately before executing its
|
|
606
|
+
# signed client. Never execute the installed app copy during bootstrap or
|
|
607
|
+
# recovery; only the package copy bound by the signed envelope is authoritative.
|
|
608
|
+
validate_target_ancestry
|
|
609
|
+
require_tree_without_extended_acl "$EXPANDED"
|
|
610
|
+
run_preflight false
|
|
611
|
+
/usr/bin/env -i PATH=/usr/bin:/bin:/usr/sbin:/sbin HOME=/var/empty TMPDIR="$WORK_DIR" \
|
|
612
|
+
"$PAYLOAD_CLIENT" bootstrap \
|
|
613
|
+
--artifact "$SNAPSHOT_PKG" \
|
|
614
|
+
--manifest "$SNAPSHOT_MANIFEST" \
|
|
615
|
+
--envelope "$SNAPSHOT_ENVELOPE"
|
|
616
|
+
|
|
617
|
+
[ -f "$STATE" ] && [ ! -L "$STATE" ] && [ "$(/usr/bin/stat -f '%u' "$STATE")" = 0 ] || {
|
|
618
|
+
echo "Broker did not initialize root-owned anti-rollback state." >&2
|
|
619
|
+
exit 1
|
|
620
|
+
}
|
|
621
|
+
require_no_extended_acl "$STATE"
|
|
622
|
+
run_preflight true
|
|
623
|
+
remove_authorization_journal
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Extract exactly one SHA-256 fingerprint from pkgutil --check-signature output.
|
|
2
|
+
# macOS has emitted both same-line and indented following-line forms. Accept
|
|
3
|
+
# whitespace/colon separators only and fail closed on malformed or missing data.
|
|
4
|
+
BEGIN {
|
|
5
|
+
collecting = 0
|
|
6
|
+
candidate = ""
|
|
7
|
+
found = 0
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function consume(value) {
|
|
11
|
+
gsub(/[[:space:]:]/, "", value)
|
|
12
|
+
if (value == "") return
|
|
13
|
+
if (value ~ /[^0-9A-Fa-f]/) exit 1
|
|
14
|
+
candidate = candidate value
|
|
15
|
+
if (length(candidate) > 64) exit 1
|
|
16
|
+
if (length(candidate) == 64) {
|
|
17
|
+
print tolower(candidate)
|
|
18
|
+
found = 1
|
|
19
|
+
exit 0
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
!collecting && /SHA256 Fingerprint:/ {
|
|
24
|
+
collecting = 1
|
|
25
|
+
value = $0
|
|
26
|
+
sub(/^.*SHA256 Fingerprint:[[:space:]]*/, "", value)
|
|
27
|
+
consume(value)
|
|
28
|
+
next
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
collecting {
|
|
32
|
+
consume($0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
END {
|
|
36
|
+
if (!found) exit 1
|
|
37
|
+
}
|