Skip to content

Support identity object #272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
31 changes: 20 additions & 11 deletions src/flat.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import { debugLog, debugWarn, execFileAsync, validateOptsApp, validateOptsPlatfo
import { Identity, findIdentities } from './util-identities';

import { FlatOptions, ValidatedFlatOptions } from './types';
import { isIdentity } from './isIdentity';

const pkgVersion = require('../../package.json').version as string;

@@ -13,7 +14,7 @@ const pkgVersion = require('../../package.json').version as string;
* @param {Object} opts - Options.
* @returns {Promise} Promise.
*/
async function validateFlatOpts (opts: FlatOptions): Promise<ValidatedFlatOptions> {
async function validateFlatOpts(opts: FlatOptions): Promise<ValidatedFlatOptions> {
await validateOptsApp(opts);

let pkg = opts.pkg;
@@ -24,7 +25,7 @@ async function validateFlatOpts (opts: FlatOptions): Promise<ValidatedFlatOption
}
} else {
debugWarn(
'No `pkg` passed in arguments, will fallback to default inferred from the given application.'
'No `pkg` passed in arguments, will fallback to default inferred from the given application.',
);
pkg = path.join(path.dirname(opts.app), path.basename(opts.app, '.app') + '.pkg');
}
@@ -43,7 +44,7 @@ async function validateFlatOpts (opts: FlatOptions): Promise<ValidatedFlatOption
...opts,
pkg,
install,
platform: await validateOptsPlatform(opts)
platform: await validateOptsPlatform(opts),
};
}

@@ -53,7 +54,7 @@ async function validateFlatOpts (opts: FlatOptions): Promise<ValidatedFlatOption
* @param {Object} opts - Options.
* @returns {Promise} Promise.
*/
async function buildApplicationPkg (opts: ValidatedFlatOptions, identity: Identity) {
async function buildApplicationPkg(opts: ValidatedFlatOptions, identity: Identity) {
const args = ['--component', opts.app, opts.install, '--sign', identity.name, opts.pkg];
if (opts.keychain) {
args.unshift('--keychain', opts.keychain);
@@ -69,7 +70,7 @@ async function buildApplicationPkg (opts: ValidatedFlatOptions, identity: Identi
/**
* This function is exported and returns a promise flattening the application.
*/
export async function buildPkg (_opts: FlatOptions) {
export async function buildPkg(_opts: FlatOptions) {
debugLog('@electron/osx-sign@%s', pkgVersion);
const validatedOptions = await validateFlatOpts(_opts);
let identities: Identity[] = [];
@@ -80,23 +81,31 @@ export async function buildPkg (_opts: FlatOptions) {
if (validatedOptions.identityValidation === false) {
// Do nothing
} else {
identities = await findIdentities(validatedOptions.keychain || null, validatedOptions.identity);
identities = await findIdentities(
validatedOptions.keychain || null,
isIdentity(validatedOptions.identity)
? validatedOptions.identity.name
: validatedOptions.identity,
);
}
} else {
debugWarn('No `identity` passed in arguments...');
if (validatedOptions.platform === 'mas') {
debugLog(
'Finding `3rd Party Mac Developer Installer` certificate for flattening app distribution in the Mac App Store...'
'Finding `3rd Party Mac Developer Installer` certificate for flattening app distribution in the Mac App Store...',
);
identities = await findIdentities(
validatedOptions.keychain || null,
'3rd Party Mac Developer Installer:'
'3rd Party Mac Developer Installer:',
);
} else {
debugLog(
'Finding `Developer ID Application` certificate for distribution outside the Mac App Store...'
'Finding `Developer ID Application` certificate for distribution outside the Mac App Store...',
);
identities = await findIdentities(
validatedOptions.keychain || null,
'Developer ID Installer:',
);
identities = await findIdentities(validatedOptions.keychain || null, 'Developer ID Installer:');
}
}

@@ -129,7 +138,7 @@ export async function buildPkg (_opts: FlatOptions) {
validatedOptions.identity,
'\n',
'> Scripts:',
validatedOptions.scripts
validatedOptions.scripts,
);
await buildApplicationPkg(validatedOptions, identityInUse);

8 changes: 8 additions & 0 deletions src/isIdentity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Identity } from './types';

export const isIdentity = (element: string | Identity): element is Identity => {
return (
typeof (element as Identity).name !== 'undefined' &&
typeof (element as Identity).hash !== 'undefined'
);
};
98 changes: 56 additions & 42 deletions src/sign.ts
Original file line number Diff line number Diff line change
@@ -11,12 +11,18 @@ import {
execFileAsync,
validateOptsApp,
validateOptsPlatform,
walkAsync
walkAsync,
} from './util';
import { Identity, findIdentities } from './util-identities';
import { preEmbedProvisioningProfile, getProvisioningProfile } from './util-provisioning-profiles';
import { preAutoEntitlements } from './util-entitlements';
import { ElectronMacPlatform, PerFileSignOptions, SignOptions, ValidatedSignOptions } from './types';
import {
ElectronMacPlatform,
PerFileSignOptions,
SignOptions,
ValidatedSignOptions,
} from './types';
import { isIdentity } from './isIdentity';

const pkgVersion: string = require('../../package.json').version;

@@ -25,7 +31,7 @@ const osRelease = os.release();
/**
* This function returns a promise validating opts.binaries, the additional binaries to be signed along with the discovered enclosed components.
*/
async function validateOptsBinaries (opts: SignOptions) {
async function validateOptsBinaries(opts: SignOptions) {
if (opts.binaries) {
if (!Array.isArray(opts.binaries)) {
throw new Error('Additional binaries should be an Array.');
@@ -34,7 +40,7 @@ async function validateOptsBinaries (opts: SignOptions) {
}
}

function validateOptsIgnore (ignore: SignOptions['ignore']): ValidatedSignOptions['ignore'] {
function validateOptsIgnore(ignore: SignOptions['ignore']): ValidatedSignOptions['ignore'] {
if (ignore && !(ignore instanceof Array)) {
return [ignore];
}
@@ -43,7 +49,7 @@ function validateOptsIgnore (ignore: SignOptions['ignore']): ValidatedSignOption
/**
* This function returns a promise validating all options passed in opts.
*/
async function validateSignOpts (opts: SignOptions): Promise<Readonly<ValidatedSignOptions>> {
async function validateSignOpts(opts: SignOptions): Promise<Readonly<ValidatedSignOptions>> {
await validateOptsBinaries(opts);
await validateOptsApp(opts);

@@ -60,15 +66,15 @@ async function validateSignOpts (opts: SignOptions): Promise<Readonly<ValidatedS
...opts,
ignore: validateOptsIgnore(opts.ignore),
type: opts.type || 'distribution',
platform
platform,
};
return cloned;
}

/**
* This function returns a promise verifying the code sign of application bundle.
*/
async function verifySignApplication (opts: ValidatedSignOptions) {
async function verifySignApplication(opts: ValidatedSignOptions) {
// Verify with codesign
debugLog('Verifying application bundle with codesign...');

@@ -80,15 +86,15 @@ async function verifySignApplication (opts: ValidatedSignOptions) {
'--strict' +
(opts.strictVerify
? '=' + opts.strictVerify // Array should be converted to a comma separated string
: '')
: ''),
]
: [],
['--verbose=2', opts.app]
)
['--verbose=2', opts.app],
),
);
}

function defaultOptionsForFile (filePath: string, platform: ElectronMacPlatform) {
function defaultOptionsForFile(filePath: string, platform: ElectronMacPlatform) {
const entitlementsFolder = path.resolve(__dirname, '..', '..', 'entitlements');

let entitlementsFile: string;
@@ -101,12 +107,12 @@ function defaultOptionsForFile (filePath: string, platform: ElectronMacPlatform)
// c.f. https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/helper-plugin-entitlements.plist
if (filePath.includes('(Plugin).app')) {
entitlementsFile = path.resolve(entitlementsFolder, 'default.darwin.plugin.plist');
// GPU Helper
// c.f. https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/helper-gpu-entitlements.plist
// GPU Helper
// c.f. https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/helper-gpu-entitlements.plist
} else if (filePath.includes('(GPU).app')) {
entitlementsFile = path.resolve(entitlementsFolder, 'default.darwin.gpu.plist');
// Renderer Helper
// c.f. https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/helper-renderer-entitlements.plist
// Renderer Helper
// c.f. https://source.chromium.org/chromium/chromium/src/+/main:chrome/app/helper-renderer-entitlements.plist
} else if (filePath.includes('(Renderer).app')) {
entitlementsFile = path.resolve(entitlementsFolder, 'default.darwin.renderer.plist');
}
@@ -126,22 +132,25 @@ function defaultOptionsForFile (filePath: string, platform: ElectronMacPlatform)
hardenedRuntime: true,
requirements: undefined as string | undefined,
signatureFlags: undefined as string | string[] | undefined,
timestamp: undefined as string | undefined
timestamp: undefined as string | undefined,
};
}

async function mergeOptionsForFile (
async function mergeOptionsForFile(
opts: PerFileSignOptions | null,
defaults: ReturnType<typeof defaultOptionsForFile>
defaults: ReturnType<typeof defaultOptionsForFile>,
) {
const mergedPerFileOptions = { ...defaults };
if (opts) {
if (opts.entitlements !== undefined) {
if (Array.isArray(opts.entitlements)) {
const entitlements = opts.entitlements.reduce<Record<string, any>>((dict, entitlementKey) => ({
...dict,
[entitlementKey]: true
}), {});
const entitlements = opts.entitlements.reduce<Record<string, any>>(
(dict, entitlementKey) => ({
...dict,
[entitlementKey]: true,
}),
{},
);
const dir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'tmp-entitlements-'));
const entitlementsPath = path.join(dir, 'entitlements.plist');
await fs.writeFile(entitlementsPath, plist.build(entitlements), 'utf8');
@@ -164,8 +173,8 @@ async function mergeOptionsForFile (
/**
* This function returns a promise codesigning only.
*/
async function signApplication (opts: ValidatedSignOptions, identity: Identity) {
function shouldIgnoreFilePath (filePath: string) {
async function signApplication(opts: ValidatedSignOptions, identity: Identity) {
function shouldIgnoreFilePath(filePath: string) {
if (opts.ignore) {
return opts.ignore.some(function (ignore) {
if (typeof ignore === 'function') {
@@ -205,7 +214,7 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)

const perFileOptions = await mergeOptionsForFile(
opts.optionsForFile ? opts.optionsForFile(filePath) : null,
defaultOptionsForFile(filePath, opts.platform)
defaultOptionsForFile(filePath, opts.platform),
);

if (opts.preAutoEntitlements === false) {
@@ -214,15 +223,15 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)
debugLog(
'Pre-sign operation enabled for entitlements automation with versions >= `1.1.1`:',
'\n',
'* Disable by setting `pre-auto-entitlements` to `false`.'
'* Disable by setting `pre-auto-entitlements` to `false`.',
);
if (!opts.version || compareVersion(opts.version, '1.1.1') >= 0) {
// Enable Mac App Store sandboxing without using temporary-exception, introduced in Electron v1.1.1. Relates to electron#5601
const newEntitlements = await preAutoEntitlements(opts, perFileOptions, {
identity,
provisioningProfile: opts.provisioningProfile
? await getProvisioningProfile(opts.provisioningProfile, opts.keychain)
: undefined
: undefined,
});

// preAutoEntitlements may provide us new entitlements, if so we update our options
@@ -266,7 +275,7 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)
} else {
// Remove runtime if passed in with --signature-flags
debugLog(
'Not enabling hardened runtime, current macOS version too low, requires 10.13.6 and higher'
'Not enabling hardened runtime, current macOS version too low, requires 10.13.6 and higher',
);
optionsArguments = optionsArguments.filter((arg) => {
return arg !== 'runtime';
@@ -280,7 +289,7 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)

await execFileAsync(
'codesign',
perFileArgs.concat('--entitlements', perFileOptions.entitlements, filePath)
perFileArgs.concat('--entitlements', perFileOptions.entitlements, filePath),
);
}

@@ -295,7 +304,7 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)
'--display',
'--entitlements',
':-', // Write to standard output and strip off the blob header
opts.app
opts.app,
]);

debugLog('Entitlements:', '\n', result);
@@ -304,7 +313,7 @@ async function signApplication (opts: ValidatedSignOptions, identity: Identity)
/**
* This function returns a promise signing the application.
*/
export async function signApp (_opts: SignOptions) {
export async function signApp(_opts: SignOptions) {
debugLog('electron-osx-sign@%s', pkgVersion);
const validatedOpts = await validateSignOpts(_opts);
let identities: Identity[] = [];
@@ -314,34 +323,39 @@ export async function signApp (_opts: SignOptions) {
if (validatedOpts.identity) {
debugLog('`identity` passed in arguments.');
if (validatedOpts.identityValidation === false) {
identityInUse = new Identity(validatedOpts.identity);
identityInUse = new Identity(
isIdentity(validatedOpts.identity) ? validatedOpts.identity.name : validatedOpts.identity,
);
} else {
identities = await findIdentities(validatedOpts.keychain || null, validatedOpts.identity);
identities = await findIdentities(
validatedOpts.keychain || null,
isIdentity(validatedOpts.identity) ? validatedOpts.identity.name : validatedOpts.identity,
);
}
} else {
debugWarn('No `identity` passed in arguments...');
if (validatedOpts.platform === 'mas') {
if (validatedOpts.type === 'distribution') {
debugLog(
'Finding `3rd Party Mac Developer Application` certificate for signing app distribution in the Mac App Store...'
'Finding `3rd Party Mac Developer Application` certificate for signing app distribution in the Mac App Store...',
);
identities = await findIdentities(
validatedOpts.keychain || null,
'3rd Party Mac Developer Application:'
'3rd Party Mac Developer Application:',
);
} else {
debugLog(
'Finding `Mac Developer` certificate for signing app in development for the Mac App Store signing...'
'Finding `Mac Developer` certificate for signing app in development for the Mac App Store signing...',
);
identities = await findIdentities(validatedOpts.keychain || null, 'Mac Developer:');
}
} else {
debugLog(
'Finding `Developer ID Application` certificate for distribution outside the Mac App Store...'
'Finding `Developer ID Application` certificate for distribution outside the Mac App Store...',
);
identities = await findIdentities(
validatedOpts.keychain || null,
'Developer ID Application:'
'Developer ID Application:',
);
}
}
@@ -366,19 +380,19 @@ export async function signApp (_opts: SignOptions) {
debugWarn(
'Pre-sign operation disabled for provisioning profile embedding:',
'\n',
'* Enable by setting `pre-embed-provisioning-profile` to `true`.'
'* Enable by setting `pre-embed-provisioning-profile` to `true`.',
);
} else {
debugLog(
'Pre-sign operation enabled for provisioning profile:',
'\n',
'* Disable by setting `pre-embed-provisioning-profile` to `false`.'
'* Disable by setting `pre-embed-provisioning-profile` to `false`.',
);
await preEmbedProvisioningProfile(
validatedOpts,
validatedOpts.provisioningProfile
? await getProvisioningProfile(validatedOpts.provisioningProfile, validatedOpts.keychain)
: null
: null,
);
}

@@ -395,7 +409,7 @@ export async function signApp (_opts: SignOptions) {
validatedOpts.binaries,
'\n',
'> Identity:',
validatedOpts.identity
validatedOpts.identity,
);
await signApplication(validatedOpts, identityInUse);

23 changes: 16 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
export type ElectronMacPlatform = 'darwin' | 'mas';
type SigningDistributionType = 'development' | 'distribution';

export type Identity = {
name: string;
hash: string;
};

export type BaseSignOptions = Readonly<{
app: string;
identity?: string;
identity?: string | Identity;
platform?: ElectronMacPlatform;
keychain?: string;
}>;
@@ -40,7 +45,7 @@ export type PerFileSignOptions = {
* timestamp server.
*/
timestamp?: string;
}
};

type OnlySignOptions = {
binaries?: string[];
@@ -74,10 +79,14 @@ type OnlyValidatedFlatOptions = {

type ValidatedForm<UnValidated, Validated> = Omit<UnValidated, keyof Validated> & Validated;

export type ValidatedBaseSignOptions = Readonly<ValidatedForm<BaseSignOptions, OnlyValidatedBaseSignOptions>>;
export type ValidatedBaseSignOptions = Readonly<
ValidatedForm<BaseSignOptions, OnlyValidatedBaseSignOptions>
>;
export type SignOptions = Readonly<OnlySignOptions & BaseSignOptions>;
export type ValidatedSignOptions = Readonly<ValidatedForm<OnlySignOptions, OnlyValidatedSignOptions> &
ValidatedBaseSignOptions>;
export type ValidatedSignOptions = Readonly<
ValidatedForm<OnlySignOptions, OnlyValidatedSignOptions> & ValidatedBaseSignOptions
>;
export type FlatOptions = Readonly<OnlyFlatOptions & BaseSignOptions>;
export type ValidatedFlatOptions = Readonly<ValidatedForm<OnlyFlatOptions, OnlyValidatedFlatOptions> &
ValidatedBaseSignOptions>;
export type ValidatedFlatOptions = Readonly<
ValidatedForm<OnlyFlatOptions, OnlyValidatedFlatOptions> & ValidatedBaseSignOptions
>;