@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,739 @@
|
|
|
1
|
+
import CryptoKit
|
|
2
|
+
import Darwin
|
|
3
|
+
import Foundation
|
|
4
|
+
import RecordingsUpdateProtocol
|
|
5
|
+
import Security
|
|
6
|
+
|
|
7
|
+
@main
|
|
8
|
+
enum RecordingsBootstrapPreflight {
|
|
9
|
+
static func main() {
|
|
10
|
+
do {
|
|
11
|
+
let arguments = try Arguments.parse(CommandLine.arguments)
|
|
12
|
+
try verify(arguments)
|
|
13
|
+
} catch {
|
|
14
|
+
let message = "recordings-bootstrap-preflight: \(sanitized(error))\n"
|
|
15
|
+
FileHandle.standardError.write(Data(message.utf8))
|
|
16
|
+
Darwin.exit(1)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private static func verify(_ arguments: Arguments) throws {
|
|
21
|
+
guard geteuid() == 0 else { throw PreflightError.notRoot }
|
|
22
|
+
let packageDigest = try sha256RegularFile(arguments.packagePath, maximumBytes: 2 * 1024 * 1024 * 1024)
|
|
23
|
+
let packageSize = try regularFileSize(arguments.packagePath, maximumBytes: 2 * 1024 * 1024 * 1024)
|
|
24
|
+
let manifestData = try readRegularFile(arguments.manifestPath, maximumBytes: 16 * 1024 * 1024)
|
|
25
|
+
let envelopeData = try readRegularFile(arguments.envelopePath, maximumBytes: 1024 * 1024)
|
|
26
|
+
let envelope = try JSONDecoder().decode(SignedReleaseEnvelope.self, from: envelopeData)
|
|
27
|
+
|
|
28
|
+
let key = try findSolePublicKey(payloadRoot: arguments.payloadRoot)
|
|
29
|
+
let payload = try envelope.verify(publicKeyData: key.data)
|
|
30
|
+
guard payload.purpose == "bootstrap",
|
|
31
|
+
payload.keyEpoch == key.epoch,
|
|
32
|
+
payload.signingTeamIdentifier == arguments.expectedTeamIdentifier,
|
|
33
|
+
payload.installerCertificateSHA256 == arguments.installerCertificateSHA256,
|
|
34
|
+
payload.packageSHA256 == packageDigest,
|
|
35
|
+
payload.artifactSHA256 == packageDigest,
|
|
36
|
+
payload.artifactByteCount == UInt64(packageSize),
|
|
37
|
+
payload.manifestSHA256 == sha256(manifestData),
|
|
38
|
+
payload.manifestByteCount == UInt64(manifestData.count)
|
|
39
|
+
else {
|
|
40
|
+
throw PreflightError.envelopeBindingMismatch
|
|
41
|
+
}
|
|
42
|
+
do {
|
|
43
|
+
try HostOSVersionPolicy.validate(
|
|
44
|
+
candidateMinimumOSVersion: payload.minimumOSVersion,
|
|
45
|
+
hostProductVersion: try hostProductVersion()
|
|
46
|
+
)
|
|
47
|
+
} catch {
|
|
48
|
+
throw PreflightError.hostCompatibilityMismatch
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let manifest = try JSONDecoder().decode(ReleaseManifest.self, from: manifestData)
|
|
52
|
+
try validateManifest(manifest, payload: payload)
|
|
53
|
+
|
|
54
|
+
let app = arguments.payloadRoot + "/Applications/Recordings.app"
|
|
55
|
+
let client = app + "/" + RecordingsUpdateConstants.updateClientRelativePath
|
|
56
|
+
let broker = arguments.payloadRoot + "/Library/PrivilegedHelperTools/com.hasna.recordings.updater"
|
|
57
|
+
let artifactVerifier = arguments.payloadRoot
|
|
58
|
+
+ "/Library/PrivilegedHelperTools/com.hasna.recordings.artifact-verifier"
|
|
59
|
+
let marker = arguments.payloadRoot
|
|
60
|
+
+ "/Library/Application Support/Hasna/Recordings/Trust/bootstrap-marker.json"
|
|
61
|
+
|
|
62
|
+
try requireDigest(client, expected: payload.updateClientSHA256)
|
|
63
|
+
try requireDigest(broker, expected: payload.updateBrokerSHA256)
|
|
64
|
+
try requireDigest(artifactVerifier, expected: payload.artifactVerifierSHA256)
|
|
65
|
+
try requireDigest(marker, expected: payload.bootstrapMarkerSHA256)
|
|
66
|
+
try validateStaticCode(
|
|
67
|
+
path: app,
|
|
68
|
+
requirementText: payload.applicationDesignatedRequirement,
|
|
69
|
+
expectedTeam: payload.signingTeamIdentifier,
|
|
70
|
+
checkNestedCode: true
|
|
71
|
+
)
|
|
72
|
+
try validateStaticCode(
|
|
73
|
+
path: client,
|
|
74
|
+
requirementText: payload.updateClientDesignatedRequirement,
|
|
75
|
+
expectedTeam: payload.signingTeamIdentifier
|
|
76
|
+
)
|
|
77
|
+
try requireArchitectures(client, expected: payload.architectures)
|
|
78
|
+
try validateStaticCode(
|
|
79
|
+
path: broker,
|
|
80
|
+
requirementText: payload.updateBrokerDesignatedRequirement,
|
|
81
|
+
expectedTeam: payload.signingTeamIdentifier
|
|
82
|
+
)
|
|
83
|
+
try validateStaticCode(
|
|
84
|
+
path: artifactVerifier,
|
|
85
|
+
requirementText: payload.artifactVerifierDesignatedRequirement,
|
|
86
|
+
expectedTeam: payload.signingTeamIdentifier
|
|
87
|
+
)
|
|
88
|
+
try validateApplicationMetadata(app: app, manifest: manifest, payload: payload)
|
|
89
|
+
let actualTreeDigest = try treeDigest(using: artifactVerifier, app: app)
|
|
90
|
+
guard actualTreeDigest == payload.candidateTreeSHA256,
|
|
91
|
+
actualTreeDigest == manifest.binding.bundleTreeSHA256
|
|
92
|
+
else {
|
|
93
|
+
throw PreflightError.applicationBindingMismatch
|
|
94
|
+
}
|
|
95
|
+
try validateBootstrapMarker(path: marker, payload: payload)
|
|
96
|
+
|
|
97
|
+
if let statePath = arguments.releaseStatePath {
|
|
98
|
+
try validateReleaseState(
|
|
99
|
+
path: statePath,
|
|
100
|
+
payload: payload,
|
|
101
|
+
requireCommitted: arguments.requireCommittedState
|
|
102
|
+
)
|
|
103
|
+
} else if arguments.requireCommittedState {
|
|
104
|
+
throw PreflightError.releaseStateMismatch
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private static func findSolePublicKey(payloadRoot: String) throws -> (epoch: UInt64, data: Data) {
|
|
109
|
+
let directory = payloadRoot
|
|
110
|
+
+ "/Library/Application Support/Hasna/Recordings/Trust/envelope-keys"
|
|
111
|
+
var metadata = stat()
|
|
112
|
+
guard lstat(directory, &metadata) == 0,
|
|
113
|
+
(metadata.st_mode & S_IFMT) == S_IFDIR,
|
|
114
|
+
metadata.st_uid == 0,
|
|
115
|
+
(metadata.st_mode & 0o022) == 0
|
|
116
|
+
else {
|
|
117
|
+
throw PreflightError.unsafeInput
|
|
118
|
+
}
|
|
119
|
+
let entries = try FileManager.default.contentsOfDirectory(atPath: directory)
|
|
120
|
+
guard entries.count == 1,
|
|
121
|
+
entries[0].hasSuffix(".raw"),
|
|
122
|
+
let epoch = UInt64(entries[0].dropLast(4)),
|
|
123
|
+
epoch > 0
|
|
124
|
+
else {
|
|
125
|
+
throw PreflightError.publicKeyLayoutMismatch
|
|
126
|
+
}
|
|
127
|
+
let data = try readRegularFile(directory + "/" + entries[0], maximumBytes: 32)
|
|
128
|
+
guard data.count == 32 else { throw PreflightError.publicKeyLayoutMismatch }
|
|
129
|
+
return (epoch, data)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static func validateManifest(
|
|
133
|
+
_ manifest: ReleaseManifest,
|
|
134
|
+
payload: ReleaseEnvelopePayload
|
|
135
|
+
) throws {
|
|
136
|
+
guard manifest.schemaVersion == 4,
|
|
137
|
+
manifest.artifactType == "recordings-macos-app",
|
|
138
|
+
manifest.bundleIdentifier == "com.hasna.recordings",
|
|
139
|
+
manifest.bundleVersion == payload.version,
|
|
140
|
+
manifest.bundleBuildVersion == payload.build,
|
|
141
|
+
manifest.sourceCommit == payload.sourceCommit,
|
|
142
|
+
manifest.teamIdentifier == payload.signingTeamIdentifier,
|
|
143
|
+
manifest.signing.teamIdentifier == payload.signingTeamIdentifier,
|
|
144
|
+
manifest.signing.helperTeamIdentifier == payload.signingTeamIdentifier,
|
|
145
|
+
manifest.architectures == payload.architectures,
|
|
146
|
+
manifest.companion.architectures == payload.architectures,
|
|
147
|
+
manifest.minimumMacOS == payload.minimumOSVersion,
|
|
148
|
+
manifest.binding.bundleTreeSHA256 == payload.candidateTreeSHA256,
|
|
149
|
+
sha256(Data(payload.applicationDesignatedRequirement.utf8))
|
|
150
|
+
== manifest.signing.designatedRequirementSHA256,
|
|
151
|
+
isLowerSHA256(manifest.applicationSHA256),
|
|
152
|
+
isLowerSHA256(manifest.provenanceSHA256),
|
|
153
|
+
isLowerSHA256(manifest.companion.sha256),
|
|
154
|
+
isLowerSHA256(manifest.archive.sha256)
|
|
155
|
+
else {
|
|
156
|
+
throw PreflightError.manifestBindingMismatch
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private static func validateApplicationMetadata(
|
|
161
|
+
app: String,
|
|
162
|
+
manifest: ReleaseManifest,
|
|
163
|
+
payload: ReleaseEnvelopePayload
|
|
164
|
+
) throws {
|
|
165
|
+
let informationData = try readRegularFile(app + "/Contents/Info.plist", maximumBytes: 1024 * 1024)
|
|
166
|
+
guard let information = try PropertyListSerialization.propertyList(
|
|
167
|
+
from: informationData,
|
|
168
|
+
options: [],
|
|
169
|
+
format: nil
|
|
170
|
+
) as? [String: Any],
|
|
171
|
+
information["CFBundleIdentifier"] as? String == manifest.bundleIdentifier,
|
|
172
|
+
information["CFBundleShortVersionString"] as? String == payload.version,
|
|
173
|
+
information["CFBundleVersion"] as? String == payload.build,
|
|
174
|
+
information["LSMinimumSystemVersion"] as? String == payload.minimumOSVersion,
|
|
175
|
+
let executableName = information["CFBundleExecutable"] as? String,
|
|
176
|
+
executableName.range(
|
|
177
|
+
of: #"^[A-Za-z0-9._-]{1,128}$"#,
|
|
178
|
+
options: .regularExpression
|
|
179
|
+
) != nil
|
|
180
|
+
else {
|
|
181
|
+
throw PreflightError.applicationBindingMismatch
|
|
182
|
+
}
|
|
183
|
+
let executable = app + "/Contents/MacOS/" + executableName
|
|
184
|
+
try requireDigest(executable, expected: manifest.applicationSHA256)
|
|
185
|
+
try requireArchitectures(executable, expected: payload.architectures)
|
|
186
|
+
let companion = app + "/Contents/Helpers/recordings"
|
|
187
|
+
try requireDigest(companion, expected: manifest.companion.sha256)
|
|
188
|
+
try requireArchitectures(companion, expected: payload.architectures)
|
|
189
|
+
let provenance = app + "/Contents/Resources/recordings-build-provenance.json"
|
|
190
|
+
try requireDigest(provenance, expected: manifest.provenanceSHA256)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private static func validateBootstrapMarker(
|
|
194
|
+
path: String,
|
|
195
|
+
payload: ReleaseEnvelopePayload
|
|
196
|
+
) throws {
|
|
197
|
+
let data = try readRegularFile(path, maximumBytes: 1024 * 1024)
|
|
198
|
+
let marker = try JSONDecoder().decode(BootstrapMarker.self, from: data)
|
|
199
|
+
guard marker.schemaVersion == RecordingsUpdateConstants.protocolVersion,
|
|
200
|
+
marker.keyEpoch == payload.keyEpoch,
|
|
201
|
+
marker.releaseSequence == payload.releaseSequence,
|
|
202
|
+
marker.releaseID == payload.releaseID,
|
|
203
|
+
marker.version == payload.version,
|
|
204
|
+
marker.sourceCommit == payload.sourceCommit,
|
|
205
|
+
marker.signingTeamIdentifier == payload.signingTeamIdentifier,
|
|
206
|
+
marker.appTreeSHA256 == payload.candidateTreeSHA256,
|
|
207
|
+
marker.updateClientSHA256 == payload.updateClientSHA256,
|
|
208
|
+
marker.updateBrokerSHA256 == payload.updateBrokerSHA256,
|
|
209
|
+
marker.artifactVerifierSHA256 == payload.artifactVerifierSHA256,
|
|
210
|
+
marker.lifecycle == RecordingsUpdateConstants.lifecycle,
|
|
211
|
+
marker.rootMaintenanceSupported == RecordingsUpdateConstants.rootMaintenanceSupported,
|
|
212
|
+
marker.keyRotationSupported == RecordingsUpdateConstants.keyRotationSupported
|
|
213
|
+
else {
|
|
214
|
+
throw PreflightError.bootstrapMarkerMismatch
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private static func validateReleaseState(
|
|
219
|
+
path: String,
|
|
220
|
+
payload: ReleaseEnvelopePayload,
|
|
221
|
+
requireCommitted: Bool
|
|
222
|
+
) throws {
|
|
223
|
+
let data = try readRegularFile(path, maximumBytes: 64 * 1024, requireOwnerOnly: true)
|
|
224
|
+
let state = try JSONDecoder().decode(BootstrapReleaseState.self, from: data)
|
|
225
|
+
let payloadDigest = sha256(try payload.canonicalData())
|
|
226
|
+
guard state.schemaVersion == RecordingsUpdateConstants.protocolVersion,
|
|
227
|
+
state.purpose == "bootstrap",
|
|
228
|
+
(state.phase == "seen" || state.phase == "committed"),
|
|
229
|
+
(!requireCommitted || state.phase == "committed"),
|
|
230
|
+
state.keyEpoch == payload.keyEpoch,
|
|
231
|
+
state.releaseSequence == payload.releaseSequence,
|
|
232
|
+
state.releaseID == payload.releaseID,
|
|
233
|
+
state.version == payload.version,
|
|
234
|
+
state.build == payload.build,
|
|
235
|
+
state.cohortPackageSHA256 == payload.packageSHA256,
|
|
236
|
+
state.envelopePayloadSHA256 == payloadDigest
|
|
237
|
+
else {
|
|
238
|
+
throw PreflightError.releaseStateMismatch
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private static func validateStaticCode(
|
|
243
|
+
path: String,
|
|
244
|
+
requirementText: String,
|
|
245
|
+
expectedTeam: String,
|
|
246
|
+
checkNestedCode: Bool = false
|
|
247
|
+
) throws {
|
|
248
|
+
var staticCode: SecStaticCode?
|
|
249
|
+
let createStatus = SecStaticCodeCreateWithPath(URL(fileURLWithPath: path) as CFURL, [], &staticCode)
|
|
250
|
+
guard createStatus == errSecSuccess, let staticCode else {
|
|
251
|
+
throw PreflightError.codeIdentityRejected
|
|
252
|
+
}
|
|
253
|
+
var requirement: SecRequirement?
|
|
254
|
+
let requirementStatus = SecRequirementCreateWithString(requirementText as CFString, [], &requirement)
|
|
255
|
+
guard requirementStatus == errSecSuccess, let requirement else {
|
|
256
|
+
throw PreflightError.codeIdentityRejected
|
|
257
|
+
}
|
|
258
|
+
var rawFlags = kSecCSStrictValidate | kSecCSCheckAllArchitectures
|
|
259
|
+
if checkNestedCode { rawFlags |= kSecCSCheckNestedCode }
|
|
260
|
+
let validityStatus = SecStaticCodeCheckValidity(
|
|
261
|
+
staticCode,
|
|
262
|
+
SecCSFlags(rawValue: rawFlags),
|
|
263
|
+
requirement
|
|
264
|
+
)
|
|
265
|
+
guard validityStatus == errSecSuccess else { throw PreflightError.codeIdentityRejected }
|
|
266
|
+
var information: CFDictionary?
|
|
267
|
+
let informationStatus = SecCodeCopySigningInformation(
|
|
268
|
+
staticCode,
|
|
269
|
+
SecCSFlags(rawValue: kSecCSSigningInformation),
|
|
270
|
+
&information
|
|
271
|
+
)
|
|
272
|
+
guard informationStatus == errSecSuccess,
|
|
273
|
+
let values = information as? [String: Any],
|
|
274
|
+
values[kSecCodeInfoTeamIdentifier as String] as? String == expectedTeam
|
|
275
|
+
else {
|
|
276
|
+
throw PreflightError.codeIdentityRejected
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private static func treeDigest(using verifier: String, app: String) throws -> String {
|
|
281
|
+
let result: BoundedProcessResult
|
|
282
|
+
do {
|
|
283
|
+
result = try BoundedProcessRunner.run(
|
|
284
|
+
executablePath: verifier,
|
|
285
|
+
arguments: ["tree-digest", "--path", app],
|
|
286
|
+
environment: [
|
|
287
|
+
"HOME": "/var/empty",
|
|
288
|
+
"PATH": "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
289
|
+
"TMPDIR": "/private/var/empty",
|
|
290
|
+
"LC_ALL": "C",
|
|
291
|
+
"LANG": "C",
|
|
292
|
+
"TZ": "UTC0",
|
|
293
|
+
],
|
|
294
|
+
maximumOutputBytes: 4_096,
|
|
295
|
+
timeout: 10
|
|
296
|
+
)
|
|
297
|
+
} catch {
|
|
298
|
+
throw PreflightError.treeDigestFailed
|
|
299
|
+
}
|
|
300
|
+
guard result.exitedNormally,
|
|
301
|
+
result.terminationStatus == 0,
|
|
302
|
+
let value = String(data: result.standardOutput, encoding: .utf8)?
|
|
303
|
+
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
304
|
+
isLowerSHA256(value)
|
|
305
|
+
else {
|
|
306
|
+
throw PreflightError.treeDigestFailed
|
|
307
|
+
}
|
|
308
|
+
return value
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private static func requireArchitectures(_ path: String, expected: [String]) throws {
|
|
312
|
+
let result: BoundedProcessResult
|
|
313
|
+
do {
|
|
314
|
+
result = try BoundedProcessRunner.run(
|
|
315
|
+
executablePath: "/usr/bin/lipo",
|
|
316
|
+
arguments: ["-archs", path],
|
|
317
|
+
environment: [
|
|
318
|
+
"HOME": "/var/empty",
|
|
319
|
+
"PATH": "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
320
|
+
"TMPDIR": "/private/var/empty",
|
|
321
|
+
],
|
|
322
|
+
maximumOutputBytes: 4_096,
|
|
323
|
+
timeout: 5
|
|
324
|
+
)
|
|
325
|
+
} catch {
|
|
326
|
+
throw PreflightError.architectureMismatch
|
|
327
|
+
}
|
|
328
|
+
let actual = String(data: result.standardOutput, encoding: .utf8)?
|
|
329
|
+
.split(whereSeparator: \.isWhitespace)
|
|
330
|
+
.map(String.init) ?? []
|
|
331
|
+
guard result.exitedNormally,
|
|
332
|
+
result.terminationStatus == 0,
|
|
333
|
+
!actual.isEmpty,
|
|
334
|
+
Set(actual).count == actual.count,
|
|
335
|
+
actual.sorted(by: { $0.utf8.lexicographicallyPrecedes($1.utf8) }) == expected
|
|
336
|
+
else {
|
|
337
|
+
throw PreflightError.architectureMismatch
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private static func hostProductVersion() throws -> String {
|
|
342
|
+
let result = try BoundedProcessRunner.run(
|
|
343
|
+
executablePath: "/usr/bin/sw_vers",
|
|
344
|
+
arguments: ["-productVersion"],
|
|
345
|
+
environment: [
|
|
346
|
+
"HOME": "/var/empty",
|
|
347
|
+
"PATH": "/usr/bin:/bin:/usr/sbin:/sbin",
|
|
348
|
+
"TMPDIR": "/private/var/empty",
|
|
349
|
+
],
|
|
350
|
+
maximumOutputBytes: 65,
|
|
351
|
+
timeout: 2
|
|
352
|
+
)
|
|
353
|
+
guard result.exitedNormally,
|
|
354
|
+
result.terminationStatus == 0,
|
|
355
|
+
!result.standardOutput.isEmpty,
|
|
356
|
+
result.standardOutput.count <= 65,
|
|
357
|
+
let text = String(data: result.standardOutput, encoding: .utf8)
|
|
358
|
+
else {
|
|
359
|
+
throw PreflightError.hostCompatibilityMismatch
|
|
360
|
+
}
|
|
361
|
+
let value = text.hasSuffix("\n") ? String(text.dropLast()) : text
|
|
362
|
+
guard !value.isEmpty,
|
|
363
|
+
value.utf8.count <= 64,
|
|
364
|
+
!value.contains(where: { $0.isWhitespace || $0.isNewline })
|
|
365
|
+
else {
|
|
366
|
+
throw PreflightError.hostCompatibilityMismatch
|
|
367
|
+
}
|
|
368
|
+
return value
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private static func requireDigest(_ path: String, expected: String) throws {
|
|
372
|
+
guard try sha256RegularFile(path, maximumBytes: 2 * 1024 * 1024 * 1024) == expected else {
|
|
373
|
+
throw PreflightError.fileDigestMismatch
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private static func sha256RegularFile(_ path: String, maximumBytes: Int64) throws -> String {
|
|
378
|
+
let descriptor = Darwin.open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)
|
|
379
|
+
guard descriptor >= 0 else { throw PreflightError.unsafeInput }
|
|
380
|
+
defer { Darwin.close(descriptor) }
|
|
381
|
+
var before = stat()
|
|
382
|
+
guard fstat(descriptor, &before) == 0,
|
|
383
|
+
(before.st_mode & S_IFMT) == S_IFREG,
|
|
384
|
+
before.st_uid == 0,
|
|
385
|
+
(before.st_mode & 0o022) == 0,
|
|
386
|
+
before.st_nlink == 1,
|
|
387
|
+
before.st_size > 0,
|
|
388
|
+
before.st_size <= maximumBytes
|
|
389
|
+
else {
|
|
390
|
+
throw PreflightError.unsafeInput
|
|
391
|
+
}
|
|
392
|
+
var hasher = SHA256()
|
|
393
|
+
var offset: off_t = 0
|
|
394
|
+
var buffer = [UInt8](repeating: 0, count: 1024 * 1024)
|
|
395
|
+
while offset < before.st_size {
|
|
396
|
+
let requested = min(buffer.count, Int(before.st_size - offset))
|
|
397
|
+
let count = buffer.withUnsafeMutableBytes {
|
|
398
|
+
pread(descriptor, $0.baseAddress, requested, offset)
|
|
399
|
+
}
|
|
400
|
+
if count < 0, errno == EINTR { continue }
|
|
401
|
+
guard count > 0 else { throw PreflightError.inputChanged }
|
|
402
|
+
hasher.update(data: Data(buffer.prefix(count)))
|
|
403
|
+
offset += off_t(count)
|
|
404
|
+
}
|
|
405
|
+
var extra: UInt8 = 0
|
|
406
|
+
guard pread(descriptor, &extra, 1, before.st_size) == 0 else {
|
|
407
|
+
throw PreflightError.inputChanged
|
|
408
|
+
}
|
|
409
|
+
var after = stat()
|
|
410
|
+
guard fstat(descriptor, &after) == 0,
|
|
411
|
+
before.st_dev == after.st_dev,
|
|
412
|
+
before.st_ino == after.st_ino,
|
|
413
|
+
before.st_size == after.st_size,
|
|
414
|
+
before.st_mtimespec.tv_sec == after.st_mtimespec.tv_sec,
|
|
415
|
+
before.st_mtimespec.tv_nsec == after.st_mtimespec.tv_nsec,
|
|
416
|
+
before.st_ctimespec.tv_sec == after.st_ctimespec.tv_sec,
|
|
417
|
+
before.st_ctimespec.tv_nsec == after.st_ctimespec.tv_nsec
|
|
418
|
+
else {
|
|
419
|
+
throw PreflightError.inputChanged
|
|
420
|
+
}
|
|
421
|
+
return hasher.finalize().map { String(format: "%02x", $0) }.joined()
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
private static func regularFileSize(_ path: String, maximumBytes: Int64) throws -> Int64 {
|
|
425
|
+
let descriptor = Darwin.open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)
|
|
426
|
+
guard descriptor >= 0 else { throw PreflightError.unsafeInput }
|
|
427
|
+
defer { Darwin.close(descriptor) }
|
|
428
|
+
var metadata = stat()
|
|
429
|
+
guard fstat(descriptor, &metadata) == 0,
|
|
430
|
+
(metadata.st_mode & S_IFMT) == S_IFREG,
|
|
431
|
+
metadata.st_uid == 0,
|
|
432
|
+
(metadata.st_mode & 0o022) == 0,
|
|
433
|
+
metadata.st_nlink == 1,
|
|
434
|
+
metadata.st_size > 0,
|
|
435
|
+
metadata.st_size <= maximumBytes
|
|
436
|
+
else {
|
|
437
|
+
throw PreflightError.unsafeInput
|
|
438
|
+
}
|
|
439
|
+
return metadata.st_size
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private static func readRegularFile(
|
|
443
|
+
_ path: String,
|
|
444
|
+
maximumBytes: Int64,
|
|
445
|
+
requireOwnerOnly: Bool = false
|
|
446
|
+
) throws -> Data {
|
|
447
|
+
let descriptor = Darwin.open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW)
|
|
448
|
+
guard descriptor >= 0 else { throw PreflightError.unsafeInput }
|
|
449
|
+
defer { Darwin.close(descriptor) }
|
|
450
|
+
var before = stat()
|
|
451
|
+
guard fstat(descriptor, &before) == 0,
|
|
452
|
+
(before.st_mode & S_IFMT) == S_IFREG,
|
|
453
|
+
before.st_uid == 0,
|
|
454
|
+
(before.st_mode & (requireOwnerOnly ? 0o077 : 0o022)) == 0,
|
|
455
|
+
before.st_nlink == 1,
|
|
456
|
+
before.st_size > 0,
|
|
457
|
+
before.st_size <= maximumBytes
|
|
458
|
+
else {
|
|
459
|
+
throw PreflightError.unsafeInput
|
|
460
|
+
}
|
|
461
|
+
var data = Data(count: Int(before.st_size))
|
|
462
|
+
var offset: off_t = 0
|
|
463
|
+
try data.withUnsafeMutableBytes { bytes in
|
|
464
|
+
guard let base = bytes.baseAddress else { return }
|
|
465
|
+
while offset < before.st_size {
|
|
466
|
+
let count = pread(
|
|
467
|
+
descriptor,
|
|
468
|
+
base.advanced(by: Int(offset)),
|
|
469
|
+
Int(before.st_size - offset),
|
|
470
|
+
offset
|
|
471
|
+
)
|
|
472
|
+
if count < 0, errno == EINTR { continue }
|
|
473
|
+
guard count > 0 else { throw PreflightError.inputChanged }
|
|
474
|
+
offset += off_t(count)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
var extra: UInt8 = 0
|
|
478
|
+
guard pread(descriptor, &extra, 1, before.st_size) == 0 else {
|
|
479
|
+
throw PreflightError.inputChanged
|
|
480
|
+
}
|
|
481
|
+
var after = stat()
|
|
482
|
+
guard fstat(descriptor, &after) == 0,
|
|
483
|
+
before.st_dev == after.st_dev,
|
|
484
|
+
before.st_ino == after.st_ino,
|
|
485
|
+
before.st_size == after.st_size,
|
|
486
|
+
before.st_mtimespec.tv_sec == after.st_mtimespec.tv_sec,
|
|
487
|
+
before.st_mtimespec.tv_nsec == after.st_mtimespec.tv_nsec,
|
|
488
|
+
before.st_ctimespec.tv_sec == after.st_ctimespec.tv_sec,
|
|
489
|
+
before.st_ctimespec.tv_nsec == after.st_ctimespec.tv_nsec
|
|
490
|
+
else {
|
|
491
|
+
throw PreflightError.inputChanged
|
|
492
|
+
}
|
|
493
|
+
return data
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private static func sha256(_ data: Data) -> String {
|
|
497
|
+
SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private static func isLowerSHA256(_ value: String) -> Bool {
|
|
501
|
+
value.range(of: "^[a-f0-9]{64}$", options: .regularExpression) != nil
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
private static func sanitized(_ error: Error) -> String {
|
|
505
|
+
if let error = error as? PreflightError { return error.description }
|
|
506
|
+
if let error = error as? ReleaseEnvelopeValidationError { return error.description }
|
|
507
|
+
return "verification failed"
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private struct Arguments {
|
|
512
|
+
let packagePath: String
|
|
513
|
+
let manifestPath: String
|
|
514
|
+
let envelopePath: String
|
|
515
|
+
let payloadRoot: String
|
|
516
|
+
let expectedTeamIdentifier: String
|
|
517
|
+
let installerCertificateSHA256: String
|
|
518
|
+
let releaseStatePath: String?
|
|
519
|
+
let requireCommittedState: Bool
|
|
520
|
+
|
|
521
|
+
static func parse(_ values: [String]) throws -> Arguments {
|
|
522
|
+
var parsed: [String: String] = [:]
|
|
523
|
+
var requireCommittedState = false
|
|
524
|
+
var index = 1
|
|
525
|
+
while index < values.count {
|
|
526
|
+
if values[index] == "--require-committed-state" {
|
|
527
|
+
guard !requireCommittedState else { throw PreflightError.usage }
|
|
528
|
+
requireCommittedState = true
|
|
529
|
+
index += 1
|
|
530
|
+
continue
|
|
531
|
+
}
|
|
532
|
+
guard index + 1 < values.count,
|
|
533
|
+
values[index].hasPrefix("--"),
|
|
534
|
+
parsed[values[index]] == nil
|
|
535
|
+
else {
|
|
536
|
+
throw PreflightError.usage
|
|
537
|
+
}
|
|
538
|
+
parsed[values[index]] = values[index + 1]
|
|
539
|
+
index += 2
|
|
540
|
+
}
|
|
541
|
+
let required = [
|
|
542
|
+
"--package",
|
|
543
|
+
"--manifest",
|
|
544
|
+
"--envelope",
|
|
545
|
+
"--payload-root",
|
|
546
|
+
"--expected-team-id",
|
|
547
|
+
"--installer-certificate-sha256",
|
|
548
|
+
]
|
|
549
|
+
let requiredPaths = ["--package", "--manifest", "--envelope", "--payload-root"]
|
|
550
|
+
guard required.allSatisfy({ parsed[$0] != nil }),
|
|
551
|
+
requiredPaths.allSatisfy({ parsed[$0]?.hasPrefix("/") == true }),
|
|
552
|
+
let packagePath = parsed["--package"],
|
|
553
|
+
let manifestPath = parsed["--manifest"],
|
|
554
|
+
let envelopePath = parsed["--envelope"],
|
|
555
|
+
let payloadRoot = parsed["--payload-root"],
|
|
556
|
+
let expectedTeamIdentifier = parsed["--expected-team-id"],
|
|
557
|
+
let installerCertificateSHA256 = parsed["--installer-certificate-sha256"],
|
|
558
|
+
expectedTeamIdentifier.range(
|
|
559
|
+
of: "^[A-Z0-9]{10}$",
|
|
560
|
+
options: .regularExpression
|
|
561
|
+
) != nil,
|
|
562
|
+
installerCertificateSHA256.range(
|
|
563
|
+
of: "^[a-f0-9]{64}$",
|
|
564
|
+
options: .regularExpression
|
|
565
|
+
) != nil,
|
|
566
|
+
Set(parsed.keys).isSubset(of: Set(required + ["--release-state"])),
|
|
567
|
+
parsed["--release-state"] == nil || parsed["--release-state"]?.hasPrefix("/") == true
|
|
568
|
+
else {
|
|
569
|
+
throw PreflightError.usage
|
|
570
|
+
}
|
|
571
|
+
return Arguments(
|
|
572
|
+
packagePath: packagePath,
|
|
573
|
+
manifestPath: manifestPath,
|
|
574
|
+
envelopePath: envelopePath,
|
|
575
|
+
payloadRoot: payloadRoot,
|
|
576
|
+
expectedTeamIdentifier: expectedTeamIdentifier,
|
|
577
|
+
installerCertificateSHA256: installerCertificateSHA256,
|
|
578
|
+
releaseStatePath: parsed["--release-state"],
|
|
579
|
+
requireCommittedState: requireCommittedState
|
|
580
|
+
)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private struct ReleaseManifest: Codable {
|
|
585
|
+
let schemaVersion: Int
|
|
586
|
+
let artifactType: String
|
|
587
|
+
let bundleIdentifier: String
|
|
588
|
+
let bundleVersion: String
|
|
589
|
+
let bundleBuildVersion: String
|
|
590
|
+
let sourceCommit: String
|
|
591
|
+
let architectures: [String]
|
|
592
|
+
let teamIdentifier: String
|
|
593
|
+
let minimumMacOS: String
|
|
594
|
+
let applicationSHA256: String
|
|
595
|
+
let binding: Binding
|
|
596
|
+
let provenanceSHA256: String
|
|
597
|
+
let signing: Signing
|
|
598
|
+
let companion: Companion
|
|
599
|
+
let archive: Archive
|
|
600
|
+
|
|
601
|
+
enum CodingKeys: String, CodingKey {
|
|
602
|
+
case schemaVersion = "schema_version"
|
|
603
|
+
case artifactType = "artifact_type"
|
|
604
|
+
case bundleIdentifier = "bundle_id"
|
|
605
|
+
case bundleVersion = "bundle_version"
|
|
606
|
+
case bundleBuildVersion = "bundle_build_version"
|
|
607
|
+
case sourceCommit = "git_sha"
|
|
608
|
+
case architectures
|
|
609
|
+
case teamIdentifier = "team_id"
|
|
610
|
+
case minimumMacOS = "minimum_macos"
|
|
611
|
+
case applicationSHA256 = "app_sha256"
|
|
612
|
+
case binding
|
|
613
|
+
case provenanceSHA256 = "provenance_sha256"
|
|
614
|
+
case signing
|
|
615
|
+
case companion
|
|
616
|
+
case archive
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
struct Binding: Codable {
|
|
620
|
+
let bundleTreeSHA256: String
|
|
621
|
+
enum CodingKeys: String, CodingKey { case bundleTreeSHA256 = "bundle_tree_sha256" }
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
struct Signing: Codable {
|
|
625
|
+
let teamIdentifier: String
|
|
626
|
+
let helperTeamIdentifier: String
|
|
627
|
+
let designatedRequirementSHA256: String
|
|
628
|
+
enum CodingKeys: String, CodingKey {
|
|
629
|
+
case teamIdentifier = "team_id"
|
|
630
|
+
case helperTeamIdentifier = "helper_team_id"
|
|
631
|
+
case designatedRequirementSHA256 = "designated_requirement_sha256"
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
struct Companion: Codable {
|
|
636
|
+
let sha256: String
|
|
637
|
+
let architectures: [String]
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
struct Archive: Codable { let sha256: String }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
private struct BootstrapMarker: Codable {
|
|
644
|
+
let schemaVersion: Int
|
|
645
|
+
let keyEpoch: UInt64
|
|
646
|
+
let releaseSequence: UInt64
|
|
647
|
+
let releaseID: String
|
|
648
|
+
let version: String
|
|
649
|
+
let sourceCommit: String
|
|
650
|
+
let signingTeamIdentifier: String
|
|
651
|
+
let appTreeSHA256: String
|
|
652
|
+
let updateClientSHA256: String
|
|
653
|
+
let updateBrokerSHA256: String
|
|
654
|
+
let artifactVerifierSHA256: String
|
|
655
|
+
let lifecycle: String
|
|
656
|
+
let rootMaintenanceSupported: Bool
|
|
657
|
+
let keyRotationSupported: Bool
|
|
658
|
+
|
|
659
|
+
enum CodingKeys: String, CodingKey {
|
|
660
|
+
case schemaVersion = "schema_version"
|
|
661
|
+
case keyEpoch = "key_epoch"
|
|
662
|
+
case releaseSequence = "release_sequence"
|
|
663
|
+
case releaseID = "release_id"
|
|
664
|
+
case version
|
|
665
|
+
case sourceCommit = "source_commit"
|
|
666
|
+
case signingTeamIdentifier = "signing_team_identifier"
|
|
667
|
+
case appTreeSHA256 = "app_tree_sha256"
|
|
668
|
+
case updateClientSHA256 = "update_client_sha256"
|
|
669
|
+
case updateBrokerSHA256 = "update_broker_sha256"
|
|
670
|
+
case artifactVerifierSHA256 = "artifact_verifier_sha256"
|
|
671
|
+
case lifecycle
|
|
672
|
+
case rootMaintenanceSupported = "root_maintenance_supported"
|
|
673
|
+
case keyRotationSupported = "key_rotation_supported"
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private struct BootstrapReleaseState: Codable {
|
|
678
|
+
let schemaVersion: Int
|
|
679
|
+
let purpose: String
|
|
680
|
+
let phase: String
|
|
681
|
+
let keyEpoch: UInt64
|
|
682
|
+
let releaseSequence: UInt64
|
|
683
|
+
let releaseID: String
|
|
684
|
+
let version: String
|
|
685
|
+
let build: String
|
|
686
|
+
let cohortPackageSHA256: String
|
|
687
|
+
let envelopePayloadSHA256: String
|
|
688
|
+
|
|
689
|
+
enum CodingKeys: String, CodingKey {
|
|
690
|
+
case schemaVersion = "schema_version"
|
|
691
|
+
case purpose
|
|
692
|
+
case phase
|
|
693
|
+
case keyEpoch = "key_epoch"
|
|
694
|
+
case releaseSequence = "release_sequence"
|
|
695
|
+
case releaseID = "release_id"
|
|
696
|
+
case version
|
|
697
|
+
case build
|
|
698
|
+
case cohortPackageSHA256 = "cohort_package_sha256"
|
|
699
|
+
case envelopePayloadSHA256 = "envelope_payload_sha256"
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
private enum PreflightError: Error, CustomStringConvertible {
|
|
704
|
+
case usage
|
|
705
|
+
case notRoot
|
|
706
|
+
case unsafeInput
|
|
707
|
+
case inputChanged
|
|
708
|
+
case publicKeyLayoutMismatch
|
|
709
|
+
case envelopeBindingMismatch
|
|
710
|
+
case manifestBindingMismatch
|
|
711
|
+
case fileDigestMismatch
|
|
712
|
+
case codeIdentityRejected
|
|
713
|
+
case architectureMismatch
|
|
714
|
+
case hostCompatibilityMismatch
|
|
715
|
+
case treeDigestFailed
|
|
716
|
+
case applicationBindingMismatch
|
|
717
|
+
case bootstrapMarkerMismatch
|
|
718
|
+
case releaseStateMismatch
|
|
719
|
+
|
|
720
|
+
var description: String {
|
|
721
|
+
switch self {
|
|
722
|
+
case .usage: "invalid arguments"
|
|
723
|
+
case .notRoot: "preflight verification must run as root"
|
|
724
|
+
case .unsafeInput: "an input is missing, mutable, linked, or otherwise unsafe"
|
|
725
|
+
case .inputChanged: "an input changed while it was verified"
|
|
726
|
+
case .publicKeyLayoutMismatch: "the package does not contain one exact bootstrap public key"
|
|
727
|
+
case .envelopeBindingMismatch: "the signed bootstrap envelope does not bind the staged inputs"
|
|
728
|
+
case .manifestBindingMismatch: "the signed bootstrap envelope and app manifest disagree"
|
|
729
|
+
case .fileDigestMismatch: "a protected package component has the wrong digest"
|
|
730
|
+
case .codeIdentityRejected: "a protected package component has the wrong code identity"
|
|
731
|
+
case .architectureMismatch: "a protected package component has the wrong architectures"
|
|
732
|
+
case .hostCompatibilityMismatch: "the live host does not satisfy the signed minimum macOS version"
|
|
733
|
+
case .treeDigestFailed: "the packaged application tree could not be authenticated"
|
|
734
|
+
case .applicationBindingMismatch: "the packaged application does not match the signed release"
|
|
735
|
+
case .bootstrapMarkerMismatch: "the package bootstrap marker does not match the signed release"
|
|
736
|
+
case .releaseStateMismatch: "the installed bootstrap state conflicts with the signed release"
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|