fix: harden Xcode release provenance
This commit is contained in:
parent
7619036c6f
commit
fdc1bd3c0a
2 changed files with 27 additions and 7 deletions
|
|
@ -120,8 +120,9 @@ export function materializeUpdaterPrivateKey(env, readText = (file) => fs.readFi
|
||||||
readText(privateKeyPath),
|
readText(privateKeyPath),
|
||||||
'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PRIVATE_KEY_EMPTY',
|
'HOLOLAKE_RELEASE_PIPELINE_UPDATER_PRIVATE_KEY_EMPTY',
|
||||||
)
|
)
|
||||||
|
const { TAURI_SIGNING_PRIVATE_KEY_PATH: _privateKeyPath, ...childEnvironment } = env
|
||||||
return {
|
return {
|
||||||
...env,
|
...childEnvironment,
|
||||||
TAURI_SIGNING_PRIVATE_KEY: privateKey,
|
TAURI_SIGNING_PRIVATE_KEY: privateKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,6 +235,18 @@ function localDateDirectory() {
|
||||||
return `${date.getFullYear()}-${part(date.getMonth() + 1)}-${part(date.getDate())}`
|
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) {
|
export function formatPlistDate(date) {
|
||||||
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
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('Name', 'string', `HoloLake ${input.version} ${input.sourceCommit.slice(0, 12)}`)
|
||||||
add('SchemeName', 'string', 'HoloLake')
|
add('SchemeName', 'string', 'HoloLake')
|
||||||
add('HoloLakeSourceCommit', 'string', input.sourceCommit)
|
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', 'dict', '')
|
||||||
add('ApplicationProperties:ApplicationPath', 'string', 'Applications/HoloLake.app')
|
add('ApplicationProperties:ApplicationPath', 'string', 'Applications/HoloLake.app')
|
||||||
add('ApplicationProperties:ArchiveVersion', 'integer', '2')
|
add('ApplicationProperties:ArchiveVersion', 'integer', '2')
|
||||||
|
|
@ -298,11 +313,13 @@ function resolveNotarizedApplication(argument, input) {
|
||||||
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_IDENTITY_MISMATCH')
|
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_IDENTITY_MISMATCH')
|
||||||
}
|
}
|
||||||
const archive = findPreparedXcodeArchive(input)
|
const archive = findPreparedXcodeArchive(input)
|
||||||
const expectedExecutableSha256 = run('/usr/libexec/PlistBuddy', [
|
const expectedUnsignedExecutableSha256 = run('/usr/libexec/PlistBuddy', [
|
||||||
'-c', 'Print :HoloLakeExecutableSha256', path.join(archive, 'Info.plist'),
|
'-c', 'Print :HoloLakeUnsignedExecutableSha256', path.join(archive, 'Info.plist'),
|
||||||
], { capture: true }).trim()
|
], { capture: true }).trim()
|
||||||
const actualExecutableSha256 = sha256File(path.join(application, 'Contents/MacOS/hololake-native-desktop'))
|
const actualUnsignedExecutableSha256 = unsignedExecutableSha256(
|
||||||
if (actualExecutableSha256 !== expectedExecutableSha256) {
|
path.join(application, 'Contents/MacOS/hololake-native-desktop'),
|
||||||
|
)
|
||||||
|
if (actualUnsignedExecutableSha256 !== expectedUnsignedExecutableSha256) {
|
||||||
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_SOURCE_BINARY_MISMATCH')
|
fail('HOLOLAKE_RELEASE_PIPELINE_NOTARIZED_APP_SOURCE_BINARY_MISMATCH')
|
||||||
}
|
}
|
||||||
return application
|
return application
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ test('release pipeline materializes a protected updater key path only inside the
|
||||||
return 'encrypted-private-key-material'
|
return 'encrypted-private-key-material'
|
||||||
})
|
})
|
||||||
assert.equal(hydrated.TAURI_SIGNING_PRIVATE_KEY, '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)
|
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')
|
const source = readFileSync(new URL('./release-pipeline.mjs', import.meta.url), 'utf8')
|
||||||
assert.match(source, /--example',\s*'verify_updater_signature'/)
|
assert.match(source, /--example',\s*'verify_updater_signature'/)
|
||||||
assert.match(source, /src-tauri\/release-trust\.json/)
|
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, /NOTARIZED_APP_SOURCE_BINARY_MISMATCH/)
|
||||||
assert.match(source, /APPLE_APP_NOTARIZATION_ACCEPTED_AND_STAPLED_DMG_CONTAINS_NOTARIZED_APP/)
|
assert.match(source, /APPLE_APP_NOTARIZATION_ACCEPTED_AND_STAPLED_DMG_CONTAINS_NOTARIZED_APP/)
|
||||||
assert.match(source, /path\.resolve\(appExecutable, '\.\.\/\.\.\/\.\.'\)/)
|
assert.match(source, /path\.resolve\(appExecutable, '\.\.\/\.\.\/\.\.'\)/)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue