mateclaw/mateclaw-desktop/scripts/sign-jar-natives.cjs

69 lines
2.9 KiB
JavaScript

const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { execFileSync } = require('node:child_process')
const { readZipEntries, createZipBuffer, ZIP_STORED } = require('./trim-playwright-driver.cjs')
function isMachO(data) {
if (data.length < 8) return false
const magic = data.readUInt32BE(0)
if ([0xfeedface, 0xcefaedfe, 0xfeedfacf, 0xcffaedfe].includes(magic)) return true
// Java class files share CAFEBABE with fat Mach-O, but contain a class version here.
if ([0xcafebabe, 0xcafebabf].includes(magic)) return data.readUInt32BE(4) > 0 && data.readUInt32BE(4) < 32
if ([0xbebafeca, 0xbfbafeca].includes(magic)) return data.readUInt32LE(4) > 0 && data.readUInt32LE(4) < 32
return false
}
async function rewriteArchive(buffer, signBinary, label = 'app.jar') {
let count = 0
const entries = readZipEntries(buffer)
for (const entry of entries) {
if (isMachO(entry.data)) {
entry.data = await signBinary(entry.data, `${label}/${entry.name}`)
count++
} else if (entry.name.endsWith('.jar')) {
const nested = await rewriteArchive(entry.data, signBinary, `${label}/${entry.name}`)
if (nested.count) {
entry.data = nested.buffer
count += nested.count
// Spring Boot requires nested dependencies to be stored without compression.
if (entry.name.startsWith('BOOT-INF/lib/')) entry.method = ZIP_STORED
}
}
}
if (!count) return { buffer, count }
// Upstream JAR signatures become invalid when their native resources change.
const unsigned = entries.filter(entry => !/^META-INF\/[^/]+\.(SF|RSA|DSA|EC)$/i.test(entry.name))
return { buffer: createZipBuffer(unsigned), count }
}
async function signAppJar(appJar, options) {
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'mateclaw-jar-sign-'))
const output = `${appJar}.signed-tmp`
try {
const result = await rewriteArchive(fs.readFileSync(appJar), async (data, label) => {
// Never extract archive-controlled paths onto the filesystem.
const binary = path.join(temp, 'native')
fs.writeFileSync(binary, data, { mode: 0o755 })
const args = ['--force', '--sign', options.identity, '--timestamp', '--options', 'runtime']
if (options.keychain) args.push('--keychain', options.keychain)
if (options.entitlements) args.push('--entitlements', options.entitlements)
args.push(binary)
console.log(`[sign-jar-natives] ${label}`)
execFileSync('/usr/bin/codesign', args, { stdio: 'pipe' })
execFileSync('/usr/bin/codesign', ['--verify', '--strict', binary], { stdio: 'pipe' })
return fs.readFileSync(binary)
})
if (result.count) {
fs.writeFileSync(output, result.buffer)
fs.renameSync(output, appJar)
}
return result.count
} finally {
fs.rmSync(temp, { recursive: true, force: true })
fs.rmSync(output, { force: true })
}
}
module.exports = { isMachO, rewriteArchive, signAppJar }