From 3a653e190823edd33d8f86d9f6afc2970cfec36f Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Fri, 23 Feb 2024 03:14:34 +0000 Subject: [PATCH] WIP Signed-off-by: Joaquim Rocha --- .github/workflows/app-artifacts-mac.yml | 110 +++++++++++- .github/workflows/app-artifacts-win.yml | 6 + app/mac/scripts/codeSign.js | 25 +++ app/mac/scripts/esrp-notarize.js | 198 +++++++++++++++++++++ app/mac/scripts/notarize.js | 20 --- app/package.json | 8 +- app/scripts/esrp.js | 224 ++++++++++++++++++++++++ app/windows/codesign.js | 2 +- app/windows/sign.js | 4 +- 9 files changed, 562 insertions(+), 35 deletions(-) create mode 100644 app/mac/scripts/codeSign.js create mode 100644 app/mac/scripts/esrp-notarize.js delete mode 100644 app/mac/scripts/notarize.js create mode 100644 app/scripts/esrp.js diff --git a/.github/workflows/app-artifacts-mac.yml b/.github/workflows/app-artifacts-mac.yml index 81fa28ac1f8..8466479c8fa 100644 --- a/.github/workflows/app-artifacts-mac.yml +++ b/.github/workflows/app-artifacts-mac.yml @@ -41,22 +41,114 @@ jobs: env: APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - - name: Build Notarized App Mac + - name: Build App Mac if: ${{ inputs.signBinaries }} + env: + # This will trigger codesign. See app/mac/scripts/codeSign.js + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: | make app-mac - env: - APPLEID: ${{ secrets.APPLEID }} - APPLEIDPASS: ${{ secrets.APPLEIDPASS }} - APPLETEAMID: ${{ secrets.APPLETEAMID }} - - name: Build App Mac + - name: Early staple (only if we are not notarizing the app) if: ${{ ! inputs.signBinaries }} run: | - make app-mac + xcrun stapler staple ./app/dist/Headlamp*.dmg - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: DMGs - path: ./app/dist/Headlamp*.* + name: dmgs + path: ./app/dist/Headlamp*.dmg if-no-files-found: error retention-days: 1 + notarize: + runs-on: windows-latest + needs: build-mac + if: ${{ inputs.signBinaries }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.buildBranch }} + - name: Setup nodejs + uses: actions/setup-node@v4 + with: + node-version: 18.x + cache: 'npm' + cache-dependency-path: | + app/package-lock.json + frontend/package-lock.json + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: dmgs + path: ./dmgs + - name: Fetch certificates + if: ${{ inputs.signBinaries }} + shell: pwsh + run: | + az login --service-principal -u ${{ secrets.WINDOWS_CLIENT_ID }} -p ${{ secrets.AZ_LOGIN_PASS }} --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47 + az keyvault secret download --subscription ${{ secrets.AZ_SUBSCRIPTION_ID }} --vault-name headlamp --name HeadlampAuthCert --file c:\HeadlampAuthCert.pfx --encoding base64 + az keyvault secret download --subscription ${{ secrets.AZ_SUBSCRIPTION_ID }} --vault-name headlamp --name ESRPHeadlampReqCert --file c:\HeadlampReqCert.pfx --encoding base64 + - name: Set up certificates + if: ${{ inputs.signBinaries }} + shell: pwsh + run: | + Import-PfxCertificate -FilePath c:\HeadlampAuthCert.pfx -CertStoreLocation Cert:\LocalMachine\My -Exportable + Import-PfxCertificate -FilePath c:\HeadlampReqCert.pfx -CertStoreLocation Cert:\LocalMachine\My -Exportable + - name: Download and Set up ESRPClient + if: ${{ inputs.signBinaries }} + shell: pwsh + run: | + nuget.exe sources add -name esrp -source ${{ secrets.ESRP_NUGET_INDEX_URL }} -username headlamp -password ${{ secrets.AZ_DEVOPS_TOKEN }} + nuget.exe install Microsoft.EsrpClient -Version 1.2.87 -source ${{ secrets.ESRP_NUGET_INDEX_URL }} | out-null + - name: Sign App + shell: pwsh + run: | + if ("${{ inputs.signBinaries }}" -eq "true") { + $env:ESRP_PATH="$(Get-Location)\Microsoft.EsrpClient.1.2.87\tools\EsrpClient.exe" + $env:HEADLAMP_WINDOWS_CLIENT_ID="${{ secrets.WINDOWS_CLIENT_ID }}" + $env:HEADLAMP_WINDOWS_SIGN_EMAIL="${{ secrets.WINDOWS_SIGN_EMAIL }}" + } else { + echo "Not signing binaries" + } + cd ./app/mac/scripts + node ./esrp-notarize.js SIGN ../../../dmgs/ + - name: Notarize App + shell: pwsh + run: | + if ("${{ inputs.signBinaries }}" -eq "true") { + $env:ESRP_PATH="$(Get-Location)\Microsoft.EsrpClient.1.2.87\tools\EsrpClient.exe" + $env:HEADLAMP_WINDOWS_CLIENT_ID="${{ secrets.WINDOWS_CLIENT_ID }}" + $env:HEADLAMP_WINDOWS_SIGN_EMAIL="${{ secrets.WINDOWS_SIGN_EMAIL }}" + } else { + echo "Not signing binaries" + } + cd ./app/mac/scripts + node ./esrp-notarize.js NOTARIZE ../../../dmgs/ + - name: Upload Notarized + uses: actions/upload-artifact@v4 + with: + name: dmgs + path: ./dmgs/Headlamp*.dmg + if-no-files-found: error + overwrite: true + retention-days: 2 + stapler: + runs-on: macos-latest + needs: notarize + if: ${{ inputs.signBinaries }} + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: dmgs + path: ./dmgs + - name: Staple + run: | + xcrun stapler staple ./dmgs/Headlamp*.dmg + - name: Upload Stapled + uses: actions/upload-artifact@v4 + with: + name: dmgs + path: ./dmgs/Headlamp*.dmg + if-no-files-found: error + overwrite: true + retention-days: 2 \ No newline at end of file diff --git a/.github/workflows/app-artifacts-win.yml b/.github/workflows/app-artifacts-win.yml index 82da9830dac..be1365896f1 100644 --- a/.github/workflows/app-artifacts-win.yml +++ b/.github/workflows/app-artifacts-win.yml @@ -28,9 +28,15 @@ jobs: uses: actions/setup-node@v4 with: node-version: 18.x + cache: 'npm' + cache-dependency-path: | + headlamp/app/package-lock.json + headlamp/frontend/package-lock.json - uses: actions/setup-go@v5 with: go-version: '1.20.*' + cache-dependency-path: | + headlamp/backend/go.sum - name: Dependencies uses: crazy-max/ghaction-chocolatey@v1 with: diff --git a/app/mac/scripts/codeSign.js b/app/mac/scripts/codeSign.js new file mode 100644 index 00000000000..a5889b6e89f --- /dev/null +++ b/app/mac/scripts/codeSign.js @@ -0,0 +1,25 @@ +require('dotenv').config(); +const { execSync } = require('child_process'); +const path = require('path'); + +exports.default = async function codeSign(config) { + const teamID = process.env.APPLE_TEAM_ID; + + if (!teamID) { + console.log('Mac codesign: No Apple Team ID found, skipping codesign'); + return; + } + + const entitlementsPath = path.resolve(path.join(__dirname, '..', 'entitlements.mac.plist')); + + let exitCode = 0; + try { + execSync( + `codesign -s ${teamID} --deep --force --options runtime --entitlements ${entitlementsPath} ${config.app}` + ); + } catch (e) { + exitCode = e.status !== null ? e.status : 1; + } + + console.log('Mac codesign:', exitCode === 0 ? 'Success' : `Failed (${exitCode})`); +}; diff --git a/app/mac/scripts/esrp-notarize.js b/app/mac/scripts/esrp-notarize.js new file mode 100644 index 00000000000..104bccf2f03 --- /dev/null +++ b/app/mac/scripts/esrp-notarize.js @@ -0,0 +1,198 @@ +/** + * This script is used to sign and notarize the Headlamp app for MacOS + * using a tool from ESRP (Windows only). It is mainly called from CI. + * + * Usage: node esrp-notarize.js SIGN|NOTARIZE path-to-sign + **/ + +const crypto = require('crypto'); +const { execSync } = require('child_process'); +const path = require('path'); +const os = require('os'); +const fs = require('fs'); + +const SIGN_JSON_TEMPLATE = { + Version: '1.0.0', + DriEmail: [`${process.env.HEADLAMP_WINDOWS_SIGN_EMAIL}`], + GroupId: null, + CorrelationVector: null, + SignBatches: [], +}; + +const POLICY_JSON = { + Version: '1.0.0', + Intent: '', + ContentType: '', + ContentOrigin: '', + ProductState: '', + Audience: '', +}; + +const AUTH_JSON = { + Version: '1.0.0', + AuthenticationType: 'AAD_CERT', + ClientId: `${process.env.HEADLAMP_WINDOWS_CLIENT_ID}`, + AuthCert: { + SubjectName: `CN=${process.env.HEADLAMP_WINDOWS_CLIENT_ID}.microsoft.com`, + StoreLocation: 'LocalMachine', + StoreName: 'My', + SendX5c: 'true', + }, + RequestSigningCert: { + SubjectName: `CN=${process.env.HEADLAMP_WINDOWS_CLIENT_ID}`, + StoreLocation: 'LocalMachine', + StoreName: 'My', + }, +}; + +function getFileList(rootDir) { + let files = {}; + let dirs = ['.']; + while (dirs.length > 0) { + const dirName = dirs.shift(); + const curDir = path.join(rootDir, dirName); + + fs.readdirSync(curDir).forEach(file => { + if (['node_modules', '.git'].includes(file)) { + return; + } + const filepath = path.resolve(rootDir, dirName, file); + const stat = fs.statSync(filepath); + if (stat.isDirectory() && !files[file]) { + dirs.push(path.join(dirName, file)); + files[file] = []; + } else { + if (!files[dirName]) { + files[dirName] = []; + } + + files[dirName].push({ + path: file, + hash: getSHA256(filepath), + }); + } + }); + } + return files; +} + +function getSHA256(filePath) { + const hash = crypto.createHash('sha256'); + const data = fs.readFileSync(filePath); + + hash.update(data); + return hash.digest('hex'); +} + +const signOp = { + KeyCode: 'CP-401337-Apple', + + OperationCode: 'MacAppDeveloperSign', + Parameters: { + Hardening: '--options=runtime', + }, + ToolName: 'sign', + + ToolVersion: '1.0', +}; +const notarizeOp = { + KeyCode: 'CP-401337-Apple', + OperationCode: 'MacAppNotarize', + Parameters: { + BundleId: 'com.microsoft.Headlamp', + }, + ToolName: 'sign', + ToolVersion: '1.0', +}; + +function createSignJson(pathToSign, fileName = 'test_SignInput.json') { + return createJson(pathToSign, signOp, fileName); +} + +function createNotarizeJson(pathToSign, fileName = 'test_SignInput.json') { + return createJson(pathToSign, notarizeOp, fileName); +} + +function createJson(pathToSign, op, fileName = 'test_SignInput.json') { + let rootDir = pathToSign; + let files = {}; + + // Check if we are signing one single file or all files in a directory + const stat = fs.statSync(pathToSign); + if (stat.isFile()) { + rootDir = path.dirname(pathToSign); + files = { + '.': [ + { + path: path.basename(pathToSign), + hash: getSHA256(pathToSign), + }, + ], + }; + } else { + files = getFileList(pathToSign); + } + + const filesJson = (dir, files) => { + return { + SourceLocationType: 'UNC', + SourceRootDirectory: path.resolve(rootDir, dir), + SignRequestFiles: files.map(f => ({ + SourceLocation: f.path, + SourceHash: f.hash ?? '', + HashType: (f.hash && 'SHA256') || null, + Name: f.path, + })), + SigningInfo: { + Operations: [op], + }, + }; + }; + + SIGN_JSON_TEMPLATE.SignBatches = Object.keys(files) + .map(dir => filesJson(dir, files[dir])) + .filter(f => f.SignRequestFiles.length > 0); + const filePath = path.join(os.tmpdir(), fileName); + fs.writeFileSync(filePath, JSON.stringify(SIGN_JSON_TEMPLATE, undefined, 2)); + + return filePath; +} + +/** + * Signs the given file, or all files in a given directory if that's what's passed to it. + * @param esrpTool - The path to the ESRP tool. + * @param op - The operation to perform. Either 'SIGN' or 'NOTARIZE'. + * @param pathToSign - A path to a file or directory. + */ +function sign(esrpTool, op, pathToSign) { + const absPathToSign = path.resolve(pathToSign); + const signJsonBase = path.basename(absPathToSign).split('.')[0]; + let signInputJson = ''; + if (op === 'SIGN') { + signInputJson = createSignJson(absPathToSign, `${signJsonBase}-SignInput.json`); + } else if (op === 'NOTARIZE') { + signInputJson = createNotarizeJson(absPathToSign, `${signJsonBase}-SignInput.json`); + } else { + throw new Error('Invalid operation'); + } + + const policyJson = path.resolve(os.tmpdir(), 'Policy.json'); + fs.writeFileSync(policyJson, JSON.stringify(POLICY_JSON, undefined, 2)); + + const authJson = path.resolve(os.tmpdir(), 'Auth.json'); + fs.writeFileSync(authJson, JSON.stringify(AUTH_JSON, undefined, 2)); + + try { + execSync(`${esrpTool} Sign -l Verbose -a ${authJson} -p ${policyJson} -i ${signInputJson}`); + } catch (e) { + console.error('Failed to sign:', e); + process.exit(e.status !== null ? e.status ?? 1 : 1); + } +} + +if (require.main === module) { + const wantedOp = process.argv[2]; + const pathToSign = process.argv[3]; + sign(process.env.ESRP_PATH, wantedOp, pathToSign); + process.exit(0); +} diff --git a/app/mac/scripts/notarize.js b/app/mac/scripts/notarize.js deleted file mode 100644 index 0900834a40f..00000000000 --- a/app/mac/scripts/notarize.js +++ /dev/null @@ -1,20 +0,0 @@ -require('dotenv').config(); -const { notarize } = require('@electron/notarize'); - -exports.default = async function notarizing(context) { - const { electronPlatformName, appOutDir } = context; - if (electronPlatformName !== 'darwin' || !process.env.APPLEID) { - return; - } - - const appName = context.packager.appInfo.productFilename; - - return await notarize({ - appBundleId: 'io.kinvolk.Headlamp', - appPath: `${appOutDir}/${appName}.app`, - appleId: process.env.APPLEID, - appleIdPassword: process.env.APPLEIDPASS, - ascProvider: process.env.ASCPROVIDER, - teamId: process.env.APPLETEAMID, - }); -}; diff --git a/app/package.json b/app/package.json index 4400b966016..5328e6b4bc3 100644 --- a/app/package.json +++ b/app/package.json @@ -20,7 +20,7 @@ "test": "jest" }, "build": { - "appId": "com.kinvolk.headlamp", + "appId": "com.microsoft.Headlamp", "beforeBuild": "./scripts/build-backend.js", "afterPack": "./scripts/after-pack.js", "afterSign": "mac/scripts/notarize.js", @@ -77,6 +77,8 @@ ] }, "mac": { + "appId": "com.microsoft.Headlamp", + "sign": "./mac/scripts/codeSign.js", "target": [ { "target": "dmg", @@ -86,8 +88,8 @@ ] } ], - "hardenedRuntime": true, "gatekeeperAssess": false, + "notarize": false, "entitlements": "mac/entitlements.mac.plist", "entitlementsInherit": "mac/entitlements.mac.plist", "extraResources": [ @@ -148,7 +150,7 @@ "@babel/preset-typescript": "^7.15.0", "@electron/notarize": "^2.1.0", "electron": "^27.0.4", - "electron-builder": "^24.0.0", + "electron-builder": "^24.9.1", "fs-extra": "^8.1.0", "i18next-parser": "^7.7.0", "jest": "^27.4.7", diff --git a/app/scripts/esrp.js b/app/scripts/esrp.js new file mode 100644 index 00000000000..9e5b7236308 --- /dev/null +++ b/app/scripts/esrp.js @@ -0,0 +1,224 @@ +const crypto = require('crypto'); +const { execSync } = require('child_process'); +const path = require('path'); +const os = require('os'); +const fs = require('fs'); + +const SIGN_JSON_TEMPLATE = { + Version: '1.0.0', + DriEmail: [`${process.env.HEADLAMP_WINDOWS_SIGN_EMAIL}`], + GroupId: null, + CorrelationVector: null, + SignBatches: [], +}; + +const POLICY_JSON = { + Version: '1.0.0', + Intent: '', + ContentType: '', + ContentOrigin: '', + ProductState: '', + Audience: '', +}; + +const AUTH_JSON = { + Version: '1.0.0', + AuthenticationType: 'AAD_CERT', + ClientId: `${process.env.HEADLAMP_WINDOWS_CLIENT_ID}`, + AuthCert: { + SubjectName: `CN=${process.env.HEADLAMP_WINDOWS_CLIENT_ID}.microsoft.com`, + StoreLocation: 'LocalMachine', + StoreName: 'My', + SendX5c: 'true', + }, + RequestSigningCert: { + SubjectName: `CN=${process.env.HEADLAMP_WINDOWS_CLIENT_ID}`, + StoreLocation: 'LocalMachine', + StoreName: 'My', + }, +}; + +function getFileList(rootDir) { + let files = {}; + let dirs = ['.']; + while (dirs.length > 0) { + const dirName = dirs.shift(); + const curDir = path.join(rootDir, dirName); + + fs.readdirSync(curDir).forEach(file => { + if (['node_modules', '.git'].includes(file)) { + return; + } + const filepath = path.resolve(rootDir, dirName, file); + const stat = fs.statSync(filepath); + if (stat.isDirectory() && !files[file]) { + dirs.push(path.join(dirName, file)); + files[file] = []; + } else { + if (!files[dirName]) { + files[dirName] = []; + } + + files[dirName].push({ + path: file, + hash: getSHA256(filepath), + }); + } + }); + } + return files; +} + +function getSHA256(filePath) { + const hash = crypto.createHash('sha256'); + const data = fs.readFileSync(filePath); + + hash.update(data); + return hash.digest('hex'); +} + +const macSignOp = { + KeyCode: 'CP-401337-Apple', + + OperationCode: 'MacAppDeveloperSign', + Parameters: { + Hardening: '--options=runtime', + }, + ToolName: 'sign', + + ToolVersion: '1.0', +}; +const macNotarizeOp = { + KeyCode: 'CP-401337-Apple', + OperationCode: 'MacAppNotarize', + Parameters: { + BundleId: 'com.microsoft.Headlamp', + }, + ToolName: 'sign', + ToolVersion: '1.0', +}; + +const winSignOps = [ + { + KeyCode: 'CP-231522', + OperationCode: 'SigntoolSign', + Parameters: { + OpusName: 'Microsoft', + OpusInfo: 'http://www.microsoft.com', + Append: '/as', + FileDigest: '/fd "SHA256"', + PageHash: '/NPH', + TimeStamp: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256', + }, + ToolName: 'sign', + ToolVersion: '1.0', + }, + { + KeyCode: 'CP-231522', + OperationCode: 'SigntoolVerify', + Parameters: {}, + ToolName: 'sign', + ToolVersion: '1.0', + }, +]; + +function createMacSignJson(pathToSign, fileName = 'test_SignInput.json') { + return createJson(pathToSign, [macSignOp], fileName); +} + +function createMacNotarizeJson(pathToSign, fileName = 'test_SignInput.json') { + return createJson(pathToSign, [macNotarizeOp], fileName); +} + +function createWinSignJson(pathToSign, fileName = 'test_SignInput.json') { + return createJson(pathToSign, winSignOps, fileName); +} + +function createJson(pathToSign, op, fileName = 'test_SignInput.json') { + let rootDir = pathToSign; + let files = {}; + + // Check if we are signing one single file or all files in a directory + const stat = fs.statSync(pathToSign); + if (stat.isFile()) { + rootDir = path.dirname(pathToSign); + files = { + '.': [ + { + path: path.basename(pathToSign), + hash: getSHA256(pathToSign), + }, + ], + }; + } else { + files = getFileList(pathToSign); + } + + const filesJson = (dir, files) => { + return { + SourceLocationType: 'UNC', + SourceRootDirectory: path.resolve(rootDir, dir), + SignRequestFiles: files.map(f => ({ + SourceLocation: f.path, + SourceHash: f.hash ?? '', + HashType: (f.hash && 'SHA256') || null, + Name: f.path, + })), + SigningInfo: { + Operations: [...op], + }, + }; + }; + + SIGN_JSON_TEMPLATE.SignBatches = Object.keys(files) + .map(dir => filesJson(dir, files[dir])) + .filter(f => f.SignRequestFiles.length > 0); + const filePath = path.join(os.tmpdir(), fileName); + fs.writeFileSync(filePath, JSON.stringify(SIGN_JSON_TEMPLATE, undefined, 2)); + + return filePath; +} + +/** + * Signs the given file, or all files in a given directory if that's what's passed to it. + * @param esrpTool - The path to the ESRP tool. + * @param op - The operation to perform. Either 'SIGN' or 'NOTARIZE'. + * @param pathToSign - A path to a file or directory. + */ +function sign(esrpTool, op, pathToSign) { + const absPathToSign = path.resolve(pathToSign); + const signJsonBase = path.basename(absPathToSign).split('.')[0]; + let signInputJson = ''; + if (op === 'SIGN') { + signInputJson = createMacSignJson(absPathToSign, `${signJsonBase}-SignInput.json`); + } else if (op === 'NOTARIZE') { + signInputJson = createMacNotarizeJson(absPathToSign, `${signJsonBase}-SignInput.json`); + } else if (op === 'WINSIGN') { + signInputJson = createWinSignJson(absPathToSign, `${signJsonBase}-SignInput.json`); + } else { + throw new Error('Invalid operation'); + } + + const policyJson = path.resolve(os.tmpdir(), 'Policy.json'); + fs.writeFileSync(policyJson, JSON.stringify(POLICY_JSON, undefined, 2)); + const authJson = path.resolve(os.tmpdir(), 'Auth.json'); + fs.writeFileSync(authJson, JSON.stringify(AUTH_JSON, undefined, 2)); + + try { + execSync(`${esrpTool} Sign -l Verbose -a ${authJson} -p ${policyJson} -i ${signInputJson}`); + } catch (e) { + console.error('Failed to sign:', e); + process.exit(e.status !== null ? e.status ?? 1 : 1); + } +} + +module.exports = { + sign, +}; + +if (require.main === module) { + const wantedOp = process.argv[2]; + const pathToSign = process.argv[3]; + sign(process.env.ESRP_PATH, wantedOp, pathToSign); + process.exit(0); +} diff --git a/app/windows/codesign.js b/app/windows/codesign.js index 7f87a657d47..b2fe489cfb7 100644 --- a/app/windows/codesign.js +++ b/app/windows/codesign.js @@ -119,7 +119,7 @@ function createJson(pathToSign, fileName = 'test_SignInput.json') { SIGN_JSON_TEMPLATE.SignBatches = Object.keys(files) .map(dir => filesJson(dir, files[dir])) .filter(f => f.SignRequestFiles.length > 0); - + console.log('>>>>>TEST', JSON.stringify(SIGN_JSON_TEMPLATE, undefined, 2)); const filePath = path.join(os.tmpdir(), fileName); fs.writeFileSync(filePath, JSON.stringify(SIGN_JSON_TEMPLATE, undefined, 2)); diff --git a/app/windows/sign.js b/app/windows/sign.js index 6c6cee1bd67..9f56c5f8bff 100644 --- a/app/windows/sign.js +++ b/app/windows/sign.js @@ -1,4 +1,4 @@ -const { sign } = require('./codesign'); +const { sign } = require('../scripts/esrp'); exports.default = async function (configuration) { const esrpTool = process.env.ESRP_PATH; @@ -15,7 +15,7 @@ exports.default = async function (configuration) { console.log('Signing', configuration.path); try { - sign(esrpTool, configuration.path); + sign(esrpTool, 'WINSIGN', configuration.path); } catch (e) { console.log('Failed to sign: ', e); process.exit(1);