From 51bced88a113ddc819400a1d7a0304a06829894b Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 17 Jan 2024 22:12:19 +0100 Subject: [PATCH 1/4] notarize dmg --- packages/hadron-build/lib/notary-service.js | 87 +++++++++++++++++++++ packages/hadron-build/lib/target.js | 72 +++++------------ 2 files changed, 108 insertions(+), 51 deletions(-) create mode 100644 packages/hadron-build/lib/notary-service.js diff --git a/packages/hadron-build/lib/notary-service.js b/packages/hadron-build/lib/notary-service.js new file mode 100644 index 00000000000..a3726599d8f --- /dev/null +++ b/packages/hadron-build/lib/notary-service.js @@ -0,0 +1,87 @@ +const download = require('download'); +const path = require('path'); +const { promises: fs } = require('fs'); +const debug = require('debug')('hadron-build:target'); +const { promisify } = require('util'); +const childProcess = require('child_process'); +const execFile = promisify(childProcess.execFile); + +async function setupMacosNotary() { + try { + await fs.access('macnotary/macnotary'); + } catch (err) { + await download(process.env.MACOS_NOTARY_CLIENT_URL, 'macnotary', { + extract: true, + strip: 1 // remove leading platform + arch directory + }); + await fs.chmod('macnotary/macnotary', 0o755); // ensure +x is set + } +} + +/** + * Notarize a resource with the macOS notary service. + * https://wiki.corp.mongodb.com/display/BUILD/How+to+use+MacOS+notary+service + * + * Notarization is a three step process: + * 1. All the files to be notarized are zipped up into a single file. + * 2. The zip file is uploaded to the notary service. It signs all the + * files in the zip file and returns a new zip file with the signed files. + * 3. The orginal files are removed and the signed files are unzipped into + * their place. + * + * @param {string} src The path to the resource to notarize. It can be a directory or a file. + * @param {object} notarizeOptions + * @param {string} notarizeOptions.bundleId + * @param {string} [notarizeOptions.macosEntitlements] + */ +async function notarize(src, notarizeOptions) { + debug(`Signing and notarizing "${src}"`); + + await setupMacosNotary(); + + const fileName = path.basename(src); + const unsignedArchive = `${fileName}.zip`; + const signedArchive = `${fileName}.signed.zip`; + + // Step:1 - zip up the file/folder to unsignedArchive + debug(`running "zip -y -r '${unsignedArchive}' '${fileName}'"`); + await execFile('zip', ['-y', '-r', unsignedArchive, fileName], { + cwd: path.dirname(src) + }); + + // Step:2 - send the zip to notary service and save the result to signedArchive + debug(`sending file to notary service (bundle id = ${notarizeOptions.bundleId})`); + const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [ + '-t', 'app', + '-m', 'notarizeAndSign', + '-u', process.env.MACOS_NOTARY_API_URL, + '-b', notarizeOptions.bundleId, + '-f', unsignedArchive, + '-o', signedArchive, + '--verify', + ...(notarizeOptions.macosEntitlements ? ['-e', notarizeOptions.macosEntitlements] : []) + ], { + cwd: path.dirname(src), + encoding: 'utf8' + }); + debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr); + debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + + // Step:3 - clean up. remove existing src, unzip signedArchive to src, remove signedArchive and unsignedArchive + debug('removing existing directory contents'); + await execFile('rm', ['-r', fileName], { + cwd: path.dirname(src) + }); + debug(`unzipping with "unzip -u ${signedArchive}"`); + await execFile('unzip', ['-u', signedArchive], { + cwd: path.dirname(src), + encoding: 'utf8' + }); + debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + debug(`removing ${signedArchive} and ${unsignedArchive}`); + await execFile('rm', ['-r', signedArchive, unsignedArchive], { + cwd: path.dirname(src) + }); +} + +module.exports = { notarize }; diff --git a/packages/hadron-build/lib/target.js b/packages/hadron-build/lib/target.js index 1d00b7585ef..64a91aef28d 100644 --- a/packages/hadron-build/lib/target.js +++ b/packages/hadron-build/lib/target.js @@ -1,25 +1,22 @@ // eslint-disable-next-line strict 'use strict'; const chalk = require('chalk'); -const childProcess = require('child_process'); -const download = require('download'); const fs = require('fs'); const _ = require('lodash'); const semver = require('semver'); const path = require('path'); -const { promisify } = require('util'); const normalizePkg = require('normalize-package-data'); const parseGitHubRepoURL = require('parse-github-repo-url'); const ffmpegAfterExtract = require('electron-packager-plugin-non-proprietary-codecs-ffmpeg').default; const windowsInstallerVersion = require('./windows-installer-version'); const debug = require('debug')('hadron-build:target'); -const execFile = promisify(childProcess.execFile); const mongodbNotaryServiceClient = require('@mongodb-js/mongodb-notary-service-client'); const which = require('which'); const plist = require('plist'); const { signtool } = require('./signtool'); const { sign: garasign } = require('@mongodb-js/signing-utils'); const tarGz = require('./tar-gz'); +const { notarize } = require('./notary-service'); async function signLocallyWithGpg(src) { debug('Signing locally with gpg ... %s', src); @@ -564,7 +561,6 @@ class Target { }; this.createInstaller = async() => { - const appDirectoryName = `${this.productName}.app`; const appPath = this.appPath; { @@ -582,58 +578,32 @@ class Target { await fs.promises.writeFile(plistFilePath, plist.build(plistContents)); } - if (process.env.MACOS_NOTARY_KEY && - process.env.MACOS_NOTARY_SECRET && - process.env.MACOS_NOTARY_CLIENT_URL && - process.env.MACOS_NOTARY_API_URL) { - debug(`Signing and notarizing "${appPath}"`); - // https://wiki.corp.mongodb.com/display/BUILD/How+to+use+MacOS+notary+service - debug(`Downloading the notary client from ${process.env.MACOS_NOTARY_CLIENT_URL} to ${path.resolve('macnotary')}`); - await download(process.env.MACOS_NOTARY_CLIENT_URL, 'macnotary', { - extract: true, - strip: 1 // remove leading platform + arch directory - }); - await fs.promises.chmod('macnotary/macnotary', 0o755); // ensure +x is set + const isNotarizationPossible = process.env.MACOS_NOTARY_KEY && + process.env.MACOS_NOTARY_SECRET && + process.env.MACOS_NOTARY_CLIENT_URL && + process.env.MACOS_NOTARY_API_URL; - debug(`running "zip -y -r '${appDirectoryName}.zip' '${appDirectoryName}'"`); - await execFile('zip', ['-y', '-r', `${appDirectoryName}.zip`, appDirectoryName], { - cwd: path.dirname(appPath) - }); - debug(`sending file to notary service (bundle id = ${this.bundleId})`); - const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [ - '-t', 'app', - '-m', 'notarizeAndSign', - '-u', process.env.MACOS_NOTARY_API_URL, - '-b', this.bundleId, - '-f', `${appDirectoryName}.zip`, - '-o', `${appDirectoryName}.signed.zip`, - '--verify', - ...(this.macosEntitlements ? ['-e', this.macosEntitlements] : []) - ], { - cwd: path.dirname(appPath), - encoding: 'utf8' - }); - debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr); - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(appPath), encoding: 'utf8' })).stdout); - debug('removing existing directory contents'); - await execFile('rm', ['-r', appDirectoryName], { - cwd: path.dirname(appPath) - }); - debug(`unzipping with "unzip -u '${appDirectoryName}.signed.zip'"`); - await execFile('unzip', ['-u', `${appDirectoryName}.signed.zip`], { - cwd: path.dirname(appPath), - encoding: 'utf8' - }); - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(appPath), encoding: 'utf8' })).stdout); - debug(`removing '${appDirectoryName}.signed.zip' and '${appDirectoryName}.zip'`); - await fs.promises.unlink(`${appPath}.signed.zip`); - await fs.promises.unlink(`${appPath}.zip`); + const notarizationOptions = { + bundleId: this.bundleId, + macosEntitlements: this.macosEntitlements + }; + + if (isNotarizationPossible) { + await notarize(appPath, notarizationOptions); } else { console.error(chalk.yellow.bold( - 'WARNING: macos notary service credentials not set -- skipping signing and notarization!')); + 'WARNING: macos notary service credentials not set -- skipping signing and notarization of .app!')); } + const createDMG = require('electron-installer-dmg'); await createDMG(this.installerOptions); + + if (isNotarizationPossible) { + await notarize(this.installerOptions.dmgPath, notarizationOptions); + } else { + console.error(chalk.yellow.bold( + 'WARNING: macos notary service credentials not set -- skipping signing and notarization of .dmg!')); + } }; } From 4f787be824f0dea29466c6a7de927bf5d9074e5b Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 18 Jan 2024 08:51:51 +0100 Subject: [PATCH 2/4] pr feedback --- ...otary-service.js => mac-notary-service.js} | 75 +++++++++++-------- packages/hadron-build/lib/target.js | 2 +- 2 files changed, 43 insertions(+), 34 deletions(-) rename packages/hadron-build/lib/{notary-service.js => mac-notary-service.js} (51%) diff --git a/packages/hadron-build/lib/notary-service.js b/packages/hadron-build/lib/mac-notary-service.js similarity index 51% rename from packages/hadron-build/lib/notary-service.js rename to packages/hadron-build/lib/mac-notary-service.js index a3726599d8f..fb170fd65f1 100644 --- a/packages/hadron-build/lib/notary-service.js +++ b/packages/hadron-build/lib/mac-notary-service.js @@ -1,7 +1,7 @@ const download = require('download'); const path = require('path'); const { promises: fs } = require('fs'); -const debug = require('debug')('hadron-build:target'); +const debug = require('debug')('hadron-build:macos-notarization'); const { promisify } = require('util'); const childProcess = require('child_process'); const execFile = promisify(childProcess.execFile); @@ -9,7 +9,9 @@ const execFile = promisify(childProcess.execFile); async function setupMacosNotary() { try { await fs.access('macnotary/macnotary'); + debug('macnotary already downloaded'); } catch (err) { + debug('downloading macnotary'); await download(process.env.MACOS_NOTARY_CLIENT_URL, 'macnotary', { extract: true, strip: 1 // remove leading platform + arch directory @@ -49,39 +51,46 @@ async function notarize(src, notarizeOptions) { cwd: path.dirname(src) }); - // Step:2 - send the zip to notary service and save the result to signedArchive - debug(`sending file to notary service (bundle id = ${notarizeOptions.bundleId})`); - const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [ - '-t', 'app', - '-m', 'notarizeAndSign', - '-u', process.env.MACOS_NOTARY_API_URL, - '-b', notarizeOptions.bundleId, - '-f', unsignedArchive, - '-o', signedArchive, - '--verify', - ...(notarizeOptions.macosEntitlements ? ['-e', notarizeOptions.macosEntitlements] : []) - ], { - cwd: path.dirname(src), - encoding: 'utf8' - }); - debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr); - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + try { + // Step:2 - send the zip to notary service and save the result to signedArchive + debug(`sending file to notary service (bundle id = ${notarizeOptions.bundleId})`); + const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [ + '-t', 'app', + '-m', 'notarizeAndSign', + '-u', process.env.MACOS_NOTARY_API_URL, + '-b', notarizeOptions.bundleId, + '-f', unsignedArchive, + '-o', signedArchive, + '--verify', + ...(notarizeOptions.macosEntitlements ? ['-e', notarizeOptions.macosEntitlements] : []) + ], { + cwd: path.dirname(src), + encoding: 'utf8' + }); + debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr); + debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + + // Step:3 - remove existing src, unzip signedArchive to src + debug('removing existing directory contents'); + await execFile('rm', ['-r', fileName], { + cwd: path.dirname(src) + }); + debug(`unzipping with "unzip -u ${signedArchive}"`); + await execFile('unzip', ['-u', signedArchive], { + cwd: path.dirname(src), + encoding: 'utf8' + }); + } finally { + // cleanup - remove signedArchive and unsignedArchive + debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + debug(`removing ${signedArchive} and ${unsignedArchive}`); + await execFile('rm', ['-r', signedArchive, unsignedArchive], { + cwd: path.dirname(src), + }).catch(err => { + debug('error cleaning up', err); + }); + } - // Step:3 - clean up. remove existing src, unzip signedArchive to src, remove signedArchive and unsignedArchive - debug('removing existing directory contents'); - await execFile('rm', ['-r', fileName], { - cwd: path.dirname(src) - }); - debug(`unzipping with "unzip -u ${signedArchive}"`); - await execFile('unzip', ['-u', signedArchive], { - cwd: path.dirname(src), - encoding: 'utf8' - }); - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); - debug(`removing ${signedArchive} and ${unsignedArchive}`); - await execFile('rm', ['-r', signedArchive, unsignedArchive], { - cwd: path.dirname(src) - }); } module.exports = { notarize }; diff --git a/packages/hadron-build/lib/target.js b/packages/hadron-build/lib/target.js index 64a91aef28d..9132c529024 100644 --- a/packages/hadron-build/lib/target.js +++ b/packages/hadron-build/lib/target.js @@ -16,7 +16,7 @@ const plist = require('plist'); const { signtool } = require('./signtool'); const { sign: garasign } = require('@mongodb-js/signing-utils'); const tarGz = require('./tar-gz'); -const { notarize } = require('./notary-service'); +const { notarize } = require('./mac-notary-service'); async function signLocallyWithGpg(src) { debug('Signing locally with gpg ... %s', src); From dd78b46ffe6902e9f4e4c5bd7b21903d10c5cf6f Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 18 Jan 2024 08:53:12 +0100 Subject: [PATCH 3/4] lint --- packages/hadron-build/lib/mac-notary-service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/hadron-build/lib/mac-notary-service.js b/packages/hadron-build/lib/mac-notary-service.js index fb170fd65f1..b59293d8b1d 100644 --- a/packages/hadron-build/lib/mac-notary-service.js +++ b/packages/hadron-build/lib/mac-notary-service.js @@ -90,7 +90,6 @@ async function notarize(src, notarizeOptions) { debug('error cleaning up', err); }); } - } module.exports = { notarize }; From b32a6bbdc4c46bb16c61620b7a259946784aad58 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 18 Jan 2024 12:53:04 +0100 Subject: [PATCH 4/4] pr feedback --- .../hadron-build/lib/mac-notary-service.js | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/hadron-build/lib/mac-notary-service.js b/packages/hadron-build/lib/mac-notary-service.js index b59293d8b1d..d45d0bb4464 100644 --- a/packages/hadron-build/lib/mac-notary-service.js +++ b/packages/hadron-build/lib/mac-notary-service.js @@ -1,3 +1,5 @@ +// eslint-disable-next-line strict +'use strict'; const download = require('download'); const path = require('path'); const { promises: fs } = require('fs'); @@ -45,11 +47,14 @@ async function notarize(src, notarizeOptions) { const unsignedArchive = `${fileName}.zip`; const signedArchive = `${fileName}.signed.zip`; + const execOpts = { + cwd: path.dirname(src), + encoding: 'utf8', + }; + // Step:1 - zip up the file/folder to unsignedArchive debug(`running "zip -y -r '${unsignedArchive}' '${fileName}'"`); - await execFile('zip', ['-y', '-r', unsignedArchive, fileName], { - cwd: path.dirname(src) - }); + await execFile('zip', ['-y', '-r', unsignedArchive, fileName], execOpts); try { // Step:2 - send the zip to notary service and save the result to signedArchive @@ -63,30 +68,20 @@ async function notarize(src, notarizeOptions) { '-o', signedArchive, '--verify', ...(notarizeOptions.macosEntitlements ? ['-e', notarizeOptions.macosEntitlements] : []) - ], { - cwd: path.dirname(src), - encoding: 'utf8' - }); + ], execOpts); debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr); - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + debug('ls', (await execFile('ls', ['-lh'], execOpts)).stdout); // Step:3 - remove existing src, unzip signedArchive to src debug('removing existing directory contents'); - await execFile('rm', ['-r', fileName], { - cwd: path.dirname(src) - }); + await execFile('rm', ['-r', fileName], execOpts); debug(`unzipping with "unzip -u ${signedArchive}"`); - await execFile('unzip', ['-u', signedArchive], { - cwd: path.dirname(src), - encoding: 'utf8' - }); + await execFile('unzip', ['-u', signedArchive], execOpts); } finally { // cleanup - remove signedArchive and unsignedArchive - debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(src), encoding: 'utf8' })).stdout); + debug('ls', (await execFile('ls', ['-lh'], execOpts)).stdout); debug(`removing ${signedArchive} and ${unsignedArchive}`); - await execFile('rm', ['-r', signedArchive, unsignedArchive], { - cwd: path.dirname(src), - }).catch(err => { + await execFile('rm', ['-r', signedArchive, unsignedArchive], execOpts).catch(err => { debug('error cleaning up', err); }); }