fix: harden Xcode release provenance

This commit is contained in:
冰朔 2026-08-19 05:10:54 +08:00
commit fdc1bd3c0a
2 changed files with 27 additions and 7 deletions

View file

@ -120,8 +120,9 @@ export function materializeUpdaterPrivateKey(env, readText = (file) => fs.readFi
readText(privateKeyPath),
'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PRIVATE_KEY_EMPTY',
)
const { TAURI_SIGNING_PRIVATE_KEY_PATH: _privateKeyPath, ...childEnvironment } = env
return {
...env,
...childEnvironment,
TAURI_SIGNING_PRIVATE_KEY: privateKey,
}
}
@ -234,6 +235,18 @@ function localDateDirectory() {
return `${date.getFullYear()}-${part(date.getMonth() + 1)}-${part(date.getDate())}`
}
function unsignedExecutableSha256(executable) {
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'hololake-unsigned-executable-'))
const copy = path.join(temporary, path.basename(executable))
try {
fs.copyFileSync(executable, copy)
run('/usr/bin/codesign', ['--remove-signature', copy])
return sha256File(copy)
} finally {
fs.rmSync(temporary, { recursive: true, force: true })
}
}
export function formatPlistDate(date) {
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
@ -260,7 +273,9 @@ function createXcodeArchive(application, input) {
add('Name', 'string', `HoloLake ${input.version} ${input.sourceCommit.slice(0, 12)}`)
add('SchemeName', 'string', 'HoloLake')
add('HoloLakeSourceCommit', 'string', input.sourceCommit)
add('HoloLakeExecutableSha256', 'string', sha256File(path.join(application, 'Contents/MacOS/hololake-native-desktop')))
const executable = path.join(application, 'Contents/MacOS/hololake-native-desktop')
add('HoloLakePreNotarizationExecutableSha256', 'string', sha256File(executable))
add('HoloLakeUnsignedExecutableSha256', 'string', unsignedExecutableSha256(executable))
add('ApplicationProperties', 'dict', '')
add('ApplicationProperties:ApplicationPath', 'string', 'Applications/HoloLake.app')
add('ApplicationProperties:ArchiveVersion', 'integer', '2')
@ -298,11 +313,13 @@ function resolveNotarizedApplication(argument, input) {
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_IDENTITY_MISMATCH')
}
const archive = findPreparedXcodeArchive(input)
const expectedExecutableSha256 = run('/usr/libexec/PlistBuddy', [
'-c', 'Print :HoloLakeExecutableSha256', path.join(archive, 'Info.plist'),
const expectedUnsignedExecutableSha256 = run('/usr/libexec/PlistBuddy', [
'-c', 'Print :HoloLakeUnsignedExecutableSha256', path.join(archive, 'Info.plist'),
], { capture: true }).trim()
const actualExecutableSha256 = sha256File(path.join(application, 'Contents/MacOS/hololake-native-desktop'))
if (actualExecutableSha256 !== expectedExecutableSha256) {
const actualUnsignedExecutableSha256 = unsignedExecutableSha256(
path.join(application, 'Contents/MacOS/hololake-native-desktop'),
)
if (actualUnsignedExecutableSha256 !== expectedUnsignedExecutableSha256) {
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_SOURCE_BINARY_MISMATCH')
}
return application

View file

@ -102,6 +102,7 @@ test('release pipeline materializes a protected updater key path only inside the
return 'encrypted-private-key-material'
})
assert.equal(hydrated.TAURI_SIGNING_PRIVATE_KEY, 'encrypted-private-key-material')
assert.equal(hydrated.TAURI_SIGNING_PRIVATE_KEY_PATH, undefined)
assert.equal(source.TAURI_SIGNING_PRIVATE_KEY, undefined)
})
@ -109,7 +110,9 @@ test('release pipeline verifies the updater signature against embedded product t
const source = readFileSync(new URL('./release-pipeline.mjs', import.meta.url), 'utf8')
assert.match(source, /--example',\s*'verify_updater_signature'/)
assert.match(source, /src-tauri\/release-trust\.json/)
assert.match(source, /HoloLakeExecutableSha256/)
assert.match(source, /HoloLakePreNotarizationExecutableSha256/)
assert.match(source, /HoloLakeUnsignedExecutableSha256/)
assert.match(source, /--remove-signature/)
assert.match(source, /NOTARIZED_APP_SOURCE_BINARY_MISMATCH/)
assert.match(source, /APPLE_APP_NOTARIZATION_ACCEPTED_AND_STAPLED_DMG_CONTAINS_NOTARIZED_APP/)
assert.match(source, /path\.resolve\(appExecutable, '\.\.\/\.\.\/\.\.'\)/)