@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -13,6 +13,22 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { spawnSync, execFileSync } = require('child_process')
|
||||
|
||||
/**
|
||||
* macOS entitlements embedded in every signed Bare standalone binary.
|
||||
*
|
||||
* Bare (V8) needs a JIT CodeRange; under Hardened Runtime (`runtime` flag) that
|
||||
* requires `allow-jit` + `allow-unsigned-executable-memory`. Native addons in
|
||||
* the bundle (e.g. bare-type's linker-signed prebuild) dlopen from a temp dir
|
||||
* and are signed by a different/absent team — `disable-library-validation` is
|
||||
* required so their Team ID mismatch does not abort the process.
|
||||
*/
|
||||
const ENTITLEMENTS = path.join(__dirname, '..', 'build', 'bare-os.entitlements')
|
||||
const REQUIRED_ENTITLEMENTS = [
|
||||
'com.apple.security.cs.allow-jit',
|
||||
'com.apple.security.cs.allow-unsigned-executable-memory',
|
||||
'com.apple.security.cs.disable-library-validation',
|
||||
]
|
||||
|
||||
function log(...a) {
|
||||
console.log('[sign-macos]', ...a)
|
||||
}
|
||||
@@ -102,9 +118,18 @@ function codesignDarwin(binPath, id) {
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const args = ['--force', '--sign', id, '--timestamp=none', binPath]
|
||||
const args = [
|
||||
'--force',
|
||||
'--sign',
|
||||
id,
|
||||
'--entitlements',
|
||||
ENTITLEMENTS,
|
||||
binPath,
|
||||
]
|
||||
if (id !== '-') {
|
||||
args.splice(3, 1, '--options', 'runtime', '--timestamp')
|
||||
args.push('--options', 'runtime', '--timestamp')
|
||||
} else {
|
||||
args.push('--timestamp=none')
|
||||
}
|
||||
const r = spawnSync('codesign', args, { encoding: 'utf8' })
|
||||
if (r.status !== 0) {
|
||||
@@ -116,9 +141,27 @@ function codesignDarwin(binPath, id) {
|
||||
if (v.status !== 0) {
|
||||
throw new Error(`codesign verify failed:\n${v.stderr || v.stdout}`)
|
||||
}
|
||||
verifyEntitlements(binPath)
|
||||
log('verify ok')
|
||||
}
|
||||
|
||||
function verifyEntitlements(binPath) {
|
||||
const r = spawnSync('codesign', ['-d', '--entitlements', '-', binPath], {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
if (r.status !== 0) {
|
||||
throw new Error(`entitlement inspection failed:\n${r.stderr || r.stdout}`)
|
||||
}
|
||||
const out = r.stdout || ''
|
||||
const missing = REQUIRED_ENTITLEMENTS.filter((key) => !out.includes(key))
|
||||
if (missing.length) {
|
||||
throw new Error(
|
||||
`entitlements missing from signed binary: ${missing.join(', ')}\n${out}`
|
||||
)
|
||||
}
|
||||
log('entitlements ok')
|
||||
}
|
||||
|
||||
function codesignRcodesign(binPath) {
|
||||
const bin = which('rcodesign')
|
||||
if (!bin) {
|
||||
@@ -137,6 +180,8 @@ function codesignRcodesign(binPath) {
|
||||
password,
|
||||
'--code-signature-flags',
|
||||
'runtime',
|
||||
'--entitlements-xml-file',
|
||||
ENTITLEMENTS,
|
||||
binPath,
|
||||
],
|
||||
{ encoding: 'utf8' }
|
||||
@@ -144,9 +189,45 @@ function codesignRcodesign(binPath) {
|
||||
if (r.status !== 0) {
|
||||
throw new Error(`rcodesign sign failed:\n${r.stderr || r.stdout}`)
|
||||
}
|
||||
verifyEntitlementsRcodesign(bin, binPath)
|
||||
log('rcodesign ok')
|
||||
}
|
||||
|
||||
/** Best-effort check that the entitlements plist made it into the Mach-O. */
|
||||
function verifyEntitlementsRcodesign(bin, binPath) {
|
||||
const r = spawnSync(
|
||||
bin,
|
||||
['print-signature-info', '--json', binPath],
|
||||
{ encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }
|
||||
)
|
||||
if (r.status !== 0) {
|
||||
log(
|
||||
`WARN: could not inspect entitlements via rcodesign (continuing): ${
|
||||
r.stderr || r.stdout
|
||||
}`
|
||||
)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const info = JSON.parse(r.stdout)
|
||||
const ent = info.entitlements || info.entitlements_plist || {}
|
||||
const text = JSON.stringify(ent)
|
||||
const missing = REQUIRED_ENTITLEMENTS.filter((key) => !text.includes(key))
|
||||
if (missing.length) {
|
||||
throw new Error(
|
||||
`entitlements missing from rcodesign-signed binary: ${missing.join(', ')}`
|
||||
)
|
||||
}
|
||||
log('entitlements ok')
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
log('WARN: could not parse rcodesign signature info (continuing)')
|
||||
return
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} binPath
|
||||
* @returns {Promise<void>}
|
||||
@@ -157,6 +238,9 @@ async function signBinary(binPath) {
|
||||
return
|
||||
}
|
||||
if (!fs.existsSync(binPath)) throw new Error(`missing binary: ${binPath}`)
|
||||
if (!fs.existsSync(ENTITLEMENTS)) {
|
||||
throw new Error(`missing entitlements file: ${ENTITLEMENTS}`)
|
||||
}
|
||||
const id = identity()
|
||||
if (process.platform === 'darwin') {
|
||||
codesignDarwin(binPath, id)
|
||||
|
||||
@@ -112,6 +112,33 @@ function assertSize(product, host, file) {
|
||||
return st.size
|
||||
}
|
||||
|
||||
/** Darwin JIT/library-validation entitlements required under Hardened Runtime. */
|
||||
const REQUIRED_ENTITLEMENTS = [
|
||||
'com.apple.security.cs.allow-jit',
|
||||
'com.apple.security.cs.allow-unsigned-executable-memory',
|
||||
'com.apple.security.cs.disable-library-validation',
|
||||
]
|
||||
|
||||
function assertDarwinEntitlements(product, host, file) {
|
||||
if (!host.startsWith('darwin') || process.platform !== 'darwin') return
|
||||
const r = spawnSync('codesign', ['-d', '--entitlements', '-', file], {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
if (r.status !== 0) {
|
||||
throw new Error(
|
||||
`[verify] ${product}/${host}: codesign -d --entitlements failed: ${r.stderr || r.stdout}`
|
||||
)
|
||||
}
|
||||
const out = r.stdout || ''
|
||||
const missing = REQUIRED_ENTITLEMENTS.filter((key) => !out.includes(key))
|
||||
if (missing.length) {
|
||||
throw new Error(
|
||||
`[verify] ${product}/${host}: missing macOS entitlements: ${missing.join(', ')}`
|
||||
)
|
||||
}
|
||||
console.log(`[verify] ${product}/${host}: darwin entitlements ok`)
|
||||
}
|
||||
|
||||
function assertMarkers(file) {
|
||||
// Sample start of file as latin1 for cheap string checks on embedded bundle
|
||||
const fd = fs.openSync(file, 'r')
|
||||
@@ -190,6 +217,7 @@ function main() {
|
||||
}
|
||||
assertSize(product, host, file)
|
||||
assertMarkers(file)
|
||||
assertDarwinEntitlements(product, host, file)
|
||||
checked++
|
||||
if (product === 'booter' && host === nativeHost) {
|
||||
smokeNativeBooter(file)
|
||||
|
||||
Reference in New Issue
Block a user