diff --git a/lib/index.ts b/lib/index.ts index ccd03fbbe..968176bf7 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,9 +1,19 @@ /* eslint-disable require-atomic-updates */ -import { existsSync, mkdirp, readFile, remove, stat, readFileSync } from 'fs-extra'; -import { need, system } from 'pkg-fetch'; import assert from 'assert'; +import { execSync } from 'child_process'; +import { + existsSync, + mkdirp, + readFile, + remove, + stat, + readFileSync, + writeFileSync, + copyFileSync, +} from 'fs-extra'; import minimist from 'minimist'; +import { need, system } from 'pkg-fetch'; import path from 'path'; import { log, wasReported } from './log'; @@ -17,6 +27,7 @@ import { shutdown } from './fabricator'; import walk, { Marker, WalkerParams } from './walker'; import { Target, NodeTarget, SymLinks } from './types'; import { CompressType } from './compress_type'; +import { patchMachOExecutable } from './mach-o'; const { version } = JSON.parse( readFileSync(path.join(__dirname, '../package.json'), 'utf-8') @@ -269,7 +280,6 @@ export async function exec(argv2: string[]) { // doCompress const algo = argv.C || argv.compress || 'None'; - let doCompress: CompressType = CompressType.None; switch (algo.toLowerCase()) { case 'brotli': @@ -284,8 +294,9 @@ export async function exec(argv2: string[]) { break; default: // eslint-disable-next-line no-console - throw wasReported(`Invalid compression algorithm ${algo} ( should be None, Brotli or Gzip)`); - + throw wasReported( + `Invalid compression algorithm ${algo} ( should be None, Brotli or Gzip)` + ); } if (doCompress !== CompressType.None) { // eslint-disable-next-line no-console @@ -350,7 +361,7 @@ export async function exec(argv2: string[]) { if (!existsSync(inputBin)) { throw wasReported( 'Bin file does not exist (taken from package.json ' + - "'bin' property)", + "'bin' property)", [inputBin] ); } @@ -554,6 +565,16 @@ export async function exec(argv2: string[]) { if (f && bytecode) { f.binaryPath = await needViaCache(f as NodeTarget); + if (f.platform === 'macos') { + // ad-hoc sign the base binary temporarily to generate bytecode + // due to the new mandatory signing requirement + const signedBinaryPath = `${f.binaryPath}-signed`; + await remove(signedBinaryPath); + copyFileSync(f.binaryPath, signedBinaryPath); + execSync(`codesign --sign - ${signedBinaryPath}`); + f.binaryPath = signedBinaryPath; + } + if (f.platform !== 'win') { await plusx(f.binaryPath); } @@ -632,8 +653,28 @@ export async function exec(argv2: string[]) { } const slash = target.platform === 'win' ? '\\' : '/'; - await producer({ backpack, bakes, slash, target: target as Target, symLinks, doCompress }); + await producer({ + backpack, + bakes, + slash, + target: target as Target, + symLinks, + doCompress, + }); + if (target.platform !== 'win' && target.output) { + if (target.platform === 'macos') { + // patch executable to allow code signing + const buf = patchMachOExecutable(readFileSync(target.output)); + writeFileSync(target.output, buf); + + if (hostPlatform === 'macos') { + // sign executable ad-hoc to workaround the new mandatory signing requirement + // users can always replace the signature if necessary + execSync(`codesign --sign - ${target.output}`); + } + } + await plusx(target.output); } } diff --git a/lib/mach-o.ts b/lib/mach-o.ts new file mode 100644 index 000000000..c0e1719bf --- /dev/null +++ b/lib/mach-o.ts @@ -0,0 +1,58 @@ +function parseCStr(buf: Buffer) { + for (let i = 0; i < buf.length; i += 1) { + if (buf[i] === 0) { + return buf.slice(0, i).toString(); + } + } +} + +function patchCommand(type: number, buf: Buffer, file: Buffer) { + // segment_64 + if (type === 0x19) { + const name = parseCStr(buf.slice(0, 16)); + + if (name === '__LINKEDIT') { + const fileoff = buf.readBigUInt64LE(32); + const vmsize_patched = BigInt(file.length) - fileoff; + const filesize_patched = vmsize_patched; + + buf.writeBigUInt64LE(vmsize_patched, 24); + buf.writeBigUInt64LE(filesize_patched, 40); + } + } + + // symtab + if (type === 0x2) { + const stroff = buf.readUInt32LE(8); + const strsize_patched = file.length - stroff; + + buf.writeUInt32LE(strsize_patched, 12); + } +} + +function patchMachOExecutable(file: Buffer) { + const align = 8; + const hsize = 32; + + const ncmds = file.readUInt32LE(16); + const buf = file.slice(hsize); + + for (let offset = 0, i = 0; i < ncmds; i += 1) { + const type = buf.readUInt32LE(offset); + + offset += 4; + const size = buf.readUInt32LE(offset) - 8; + + offset += 4; + patchCommand(type, buf.slice(offset, offset + size), file); + + offset += size; + if (offset & align) { + offset += align - (offset & align); + } + } + + return file; +} + +export { patchMachOExecutable };