Skip to content
This repository has been archived by the owner on Jan 13, 2024. It is now read-only.

feature: allow macOS code signing by including payload in str table #1164

Merged
merged 2 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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')
Expand Down Expand Up @@ -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':
Expand All @@ -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
Expand Down Expand Up @@ -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]
);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
Expand Down
58 changes: 58 additions & 0 deletions lib/mach-o.ts
Original file line number Diff line number Diff line change
@@ -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 };