electron-builder
electron-builder copied to clipboard
June 2023 Code Signing with HSM APIs
Hi
Is electron-builder able to use services like Digicert Keylocker
https://knowledge.digicert.com/generalinformation/new-private-key-storage-requirement-for-standard-code-signing-certificates-november-2022.html says I now need a hardware token (mine or cloud) but as I use Github Actions CI to build - I can't use a USB token so https://knowledge.digicert.com/solution/digicert-keylocker.html sounds more likely
Refer https://github.com/electron/windows-installer/issues/473#issuecomment-1581441658
Would I be able to use https://docs.digicert.com/en/digicert-one/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html
https://docs.digicert.com/en/digicert-one/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html looks like it might do
https://docs.digicert.com/en/digicert-one/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html looks like it might do
Did this work for you? I'm in the same boat, and the path forward is not clear to me.
I am awaiting our procurement dept - they have to assist with activating Keylocker subscription / reissuing certificate - before I can give it a go.
If you get to work on it before I can, let me know if you managed - otherwise I'll make sure to update this ticket with more details.
Sounds like its going to be painful so would be good to document for others' sake for sure!
Hey I bought a cert from GlobalSign, I have created it using Key Vault, but now unsure how to connect to the electron builder process to sign the app for the in-built auto updating. Can anyone help me please?
Hey I bought a cert from GlobalSign, I have created it using Key Vault, but now unsure how to connect to the electron builder process to sign the app for the in-built auto updating. Can anyone help me please?
Digicert has a workflow https://docs.digicert.com/en/digicert-one/digicert-keylocker/ci-cd-integrations/plugins/github-custom-action-for-keypair-signing.html perhaps contact Global sign, give them that link and ask if they have similar?
I was finally able to get this to work with a lot of trial and error. My scenario isn't exactly the same as @petervanderwalt because we use AppVeyor for CI, but the concepts should be similar, I think.
package.json
This script sets up the build system to perform code signing and is called from packaging script at the appropriate time.
"setup-keylocker": "pwsh -NoProfile -ExecutionPolicy Unrestricted -Command ./script/setup-keylocker.ps1"
setup-keylocker.ps1
This is PowerShell, but it should be easy enough to understand and port to whatever else you may be using.
try {
$whoami = $MyInvocation.MyCommand
# Verify that all required environment variables are set
$required = @(
'SM_API_KEY',
'SM_CERTIFICATE_FINGERPRINT',
'SM_CLIENT_CERT_FILE',
'SM_CLIENT_CERT_PASSWORD',
'SM_HOST',
'SM_TOOLS_URI',
'SM_INSTALL_DIR',
'SIGNTOOL_32_BIT',
'SIGNTOOL_64_BIT'
)
foreach ($variable in $required) {
if (!$(Test-Path "env:$variable")) {
throw "Unable to sign files because $variable is not set in the environment."
}
}
# Download SM Tools
Write-Host "[$whoami] Downloading SM Tools..."
$params = @{
Method = 'Get'
Headers = @{
'x-api-key' = $env:SM_API_KEY
}
Uri = $env:SM_TOOLS_URI
OutFile = 'smtools.msi'
}
Invoke-WebRequest @params
# Install SM Tools
Write-Host "[$whoami] Installing SM Tools..."
msiexec.exe /i smtools.msi /quiet /qn | Wait-Process
Write-Host "[$whoami] Verifying SM Tools install..."
& "${env:SM_INSTALL_DIR}\smctl.exe" healthcheck --all
} catch {
throw $PSItem
}
electron-builder.yml (snippet)
win:
target: nsis
forceCodeSigning: true
icon: 'app/static/logos/icon-logo.ico'
requestedExecutionLevel: requireAdministrator
rfc3161TimeStampServer: 'http://timestamp.digicert.com'
sign: 'script/customSign.js'
signDlls: true
signingHashAlgorithms: ['sha256']
customSign.js
Note here that signtool.exe
embeds errors in stdout, so you have to manually check it for errors.
/* eslint-disable no-useless-escape */
'use strict'
exports.default = async function (configuration) {
const { execSync } = require('child_process')
const whoami = 'customSign.js'
if (!process.env.SM_INSTALL_DIR) {
throw `Unable to sign files because the path to smctl.exe is not set in the environment.`
}
if (!process.env.SIGNTOOL_32_BIT || !process.env.SIGNTOOL_64_BIT) {
throw `Unable to sign files because the path to signtool.exe is not set in the environment.`
}
// Common
const filePath = `"${configuration.path.replace(/\\/g, '/')}"`
const smctlDir = `"${process.env.SM_INSTALL_DIR}"`
const signToolVersionDir =
process.env.SIGNTOOL_64_BIT || process.env.SIGNTOOL_32_BIT
const signToolDir = `"${signToolVersionDir}"`
try {
const signCommand = `./script/sign.ps1`
const keyPairAlias = `"Key1"`
const sign = [
`pwsh`,
`-NoProfile`,
`-ExecutionPolicy Unrestricted`,
`-Command \"$Input | ${signCommand}`,
`-FilePath '${filePath}'`,
`-KeyPairAlias '${keyPairAlias}'`,
`-SmctlDir '${smctlDir}'`,
`-SignToolDir '${signToolDir}'\"`,
]
const signStdout = execSync(sign.join(' ')).toString()
if (signStdout.match(/FAILED/)) {
console.error(
`[${whoami}] Error detected in ${signCommand}: [${signStdout}]`
)
throw `Error detected in ${signCommand}: [${signStdout}]`
}
} catch (e) {
throw `Exception thrown during code signing: ${e.message}`
}
// Verify the signature
try {
const verifyCommand = `./script/verify.ps1`
const fingerprint = `"${process.env.SM_CERTIFICATE_FINGERPRINT}"`
const verify = [
`pwsh`,
`-NoProfile`,
`-ExecutionPolicy Unrestricted`,
`-Command \"$Input | ${verifyCommand}`,
`-FilePath '${filePath}'`,
`-Fingerprint '${fingerprint}'`,
`-SmctlDir '${smctlDir}'`,
`-SignToolDir '${signToolDir}'\"`,
]
const verifyStdout = execSync(verify.join(' ')).toString()
if (verifyStdout.match(/FAILED/)) {
console.error(
`[${whoami}] Error detected in ${verifyCommand}: [${verifyStdout}]`
)
throw `Error detected in ${verifyCommand}: [${verifyStdout}]`
}
} catch (e) {
throw `Exception thrown during signature verification: ${e.message}`
}
}
sign.ps1
[OutputType([Void])]
Param(
[Parameter(Mandatory)]
[String]
$FilePath,
[Parameter(Mandatory)]
[String]
$KeyPairAlias,
[Parameter(Mandatory)]
[String]
$SmctlDir,
[Parameter(Mandatory)]
[String]
$SignToolDir
)
# Set the path
$env:Path = @(
[System.Environment]::GetEnvironmentVariable('Path', 'Machine'),
[System.Environment]::GetEnvironmentVariable('Path', 'User'),
$SignToolDir
) -join ';'
# Get the smctl.exe executable
$smctl = "$SmctlDir/smctl.exe"
& "$smctl" sign --input="$FilePath" --keypair-alias="$KeyPairAlias" --verbose
verify.ps1
[OutputType([Void])]
Param(
[Parameter(Mandatory)]
[String]
$FilePath,
[Parameter(Mandatory)]
[String]
$Fingerprint,
[Parameter(Mandatory)]
[String]
$SmctlDir,
[Parameter(Mandatory)]
[String]
$SignToolDir
)
# Set the path
$env:Path = @(
[System.Environment]::GetEnvironmentVariable('Path', 'Machine'),
[System.Environment]::GetEnvironmentVariable('Path', 'User'),
$SignToolDir
) -join ';'
# Get the smctl.exe executable
$smctl = "$SmctlDir/smctl.exe"
& "$smctl" sign verify --input="$FilePath" --fingerprint="$Fingerprint"
This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.
Still waiting on feedback from @electron-userland team on this, seems the documentation has not been updated to reflect the newer requirements that all CAs are forcing
I think with Azure Key Vault Premium you can just host your cert and use a URL to point to it with a password. well that is what am hoping, I haven't hooked it up yet. I'll report back
@petervanderwalt can you clarify what needs to be added to the documentation? Happy to accept a PR adding a page to the documentation? Seems it would be very windows-specific and could be inserted here https://github.com/electron-userland/electron-builder/blob/master/docs/code-signing.md
Side note, it seems that github handle/tag doesn't ping me. I'm the only maintainer but not officially on the team list I guess 🤔
Thanks for checking in
Have a read through https://knowledge.digicert.com/generalinformation/new-private-key-storage-requirement-for-standard-code-signing-certificates-november-2022.html - theres a new standard around where one can no longer store your key locally, has to be on a token, or hosted in an online.
Different vendors handles each differently, so its painful, but in essence the https://github.com/electron-userland/electron-builder/blob/master/docs/code-signing.md?plain=1#L14 - password to decrypt the key, that was exported along with the certificates as a pfx, as it used to be, no longer works. In particular for CI toolchains (signing on Github actions for example)
I still haven't completed ours - ordering the Keylocker subscription is still stuck with our procurement - so have not gone down the rabbit hole myself yet - but just overall the world changed and theres a new way to do it now and its overly complex (:
We're with the same issue, but we actually use Squirrel to updates and it also used to work with PFX files.
Any release will be launch to help us building the project easier?
I got this working on CircleCI with electron-builder with the following changes, where I use a Windows executor with the bash.exe
shell as my workflow shell.
Added to .circleci/config.yml
:
# Need to setup the signing tools from DigiCert to allow us to access our certificate
# See https://docs.digicert.com/nl/digicert-keylocker/ci-cd-integrations/script-integrations/circleci-integration-ksp.html
# for more info.
- run:
name: Setup signing tools
shell: powershell.exe
command: |
cd C:\
curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi
msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process
New-Item C:\Certificate.p12.b64
Set-Content -Path C:\Certificate.p12.b64 -Value $env:SM_CLIENT_CERT_FILE_B64
certutil -decode Certificate.p12.b64 Certificate.p12
- run:
name: Set bash path for signing tools
# first export is for KSP stuff for DigiCert
# the second export is for signtool.exe that smctl internally calls
command: |
echo 'export PATH=/c/Program\ Files/DigiCert/DigiCert\ One\ Signing\ Manager\ Tools:$PATH' >> $BASH_ENV
echo 'export PATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/10/App\ Certification\ Kit:$PATH' >> $BASH_ENV
- run:
name: Sync certificates
command: |
sync_output=$(smksp_cert_sync)
echo ${sync_output}
if [[ ${sync_output} != *"${KEYPAIR_ALIAS}"* ]]; then
echo 'Could not sync certificate matching $KEYPAIR_ALIAS env var'
exit 1
fi
My winSign.js
script (with sign
key in my package.json
under win
pointing at this file):
// Custom Sign hook for use with electron-builder + DigiCert KSP
// Adapted from https://docs.digicert.com/nl/digicert-keylocker/signing-tools/sign-authenticode-with-electron-builder-using-ksp-integration.html
const { execSync } = require('child_process');
exports.default = async (config) => {
const keypairAlias = process.env.KEYPAIR_ALIAS;
const path = config.path ? String(config.path) : '';
if (process.platform !== 'win32' || !keypairAlias || !path) {
return;
}
const output = execSync(
`smctl sign --keypair-alias=${keypairAlias} --input="${path}" --verbose`,
)
.toString()
.trim();
if (!output.includes('Done Adding Additional Store')) {
throw new Error(`Failed to sign executable: ${output}`);
}
};
My SMCLIENT_CERT_FILE
variable was /c/Certificate.p12
and I had an additional env var KEYPAIR_ALIAS
to match what I'd sync with smksp_cert_sync
.
I did make CSC_LINK
the same value as SMCLIENT_CERT_FILE
(with password env var also the same) as that was necessary to trigger my custom script, but I don't think the values are actually read/used so they could probably be anything.
We use Azure Key Vault to store our customer's keys on an HSM. We have used code signing certs from both Digicert and GlobalSign, they both work perfectly on Azure Key Vault.
The process is a little complicated because you need to generate a CSR on Azure Key Vault and then use that during the provisioning step on Digicert/GlobalSign dashboard. But once it's set up, it works perfectly. I would strongly recommend, it's what we use for storing our customers cert with our Electron service.
I've got AzureSignTool working from Actions i think am on the last error and will setup to publish straight to GH releases..
Ok I got the customSign file to run using AzureSignTool via GitHub actions. I publish the releases to S3 and the application is notified there is an updated version available
One question tho, how do you ignore the customSign.js and process while packaging locally ?
We have our customSign.js
script read in values from process.env
to determine whether or not to sign the app. This was a similar strategy as we used back when we had a similar script to run @electron/notarize
for macos builds.
awesome thanks ill do that too
Document https://docs.digicert.com/en/digicert-keylocker/ci-cd-integrations/script-integrations/github-integration-ksp.html really helps.
Here's how is our application signed by KeyLocker: https://github.com/nervosnetwork/neuron/pull/2913
There are mainly two steps:
- Setup signing runtime: https://github.com/nervosnetwork/neuron/pull/2913/files#diff-170ebc8e4dc40acf23cbe0ecce5f3e2aef1652511f59860db704106b197e1d52R54-R85
- Sign application: https://github.com/nervosnetwork/neuron/pull/2913/files#diff-f1a2ada293a9fd7da045908348b61a30018539ff94b2cf54461bd122f03736ccR13-R15
I got this working on CircleCI with electron-builder with the following changes, where I use a Windows executor with the
bash.exe
shell as my workflow shell.Added to
.circleci/config.yml
:# Need to setup the signing tools from DigiCert to allow us to access our certificate # See https://docs.digicert.com/nl/digicert-keylocker/ci-cd-integrations/script-integrations/circleci-integration-ksp.html # for more info. - run: name: Setup signing tools shell: powershell.exe command: | cd C:\ curl.exe -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:$env:SM_API_KEY" -o smtools-windows-x64.msi msiexec.exe /i smtools-windows-x64.msi /quiet /qn | Wait-Process New-Item C:\Certificate.p12.b64 Set-Content -Path C:\Certificate.p12.b64 -Value $env:SM_CLIENT_CERT_FILE_B64 certutil -decode Certificate.p12.b64 Certificate.p12 - run: name: Set bash path for signing tools # first export is for KSP stuff for DigiCert # the second export is for signtool.exe that smctl internally calls command: | echo 'export PATH=/c/Program\ Files/DigiCert/DigiCert\ One\ Signing\ Manager\ Tools:$PATH' >> $BASH_ENV echo 'export PATH=/c/Program\ Files\ \(x86\)/Windows\ Kits/10/App\ Certification\ Kit:$PATH' >> $BASH_ENV - run: name: Sync certificates command: | sync_output=$(smksp_cert_sync) echo ${sync_output} if [[ ${sync_output} != *"${KEYPAIR_ALIAS}"* ]]; then echo 'Could not sync certificate matching $KEYPAIR_ALIAS env var' exit 1 fi
My
winSign.js
script (withsign
key in mypackage.json
underwin
pointing at this file):// Custom Sign hook for use with electron-builder + DigiCert KSP // Adapted from https://docs.digicert.com/nl/digicert-keylocker/signing-tools/sign-authenticode-with-electron-builder-using-ksp-integration.html const { execSync } = require('child_process'); exports.default = async (config) => { const keypairAlias = process.env.KEYPAIR_ALIAS; const path = config.path ? String(config.path) : ''; if (process.platform !== 'win32' || !keypairAlias || !path) { return; } const output = execSync( `smctl sign --keypair-alias=${keypairAlias} --input="${path}" --verbose`, ) .toString() .trim(); if (!output.includes('Done Adding Additional Store')) { throw new Error(`Failed to sign executable: ${output}`); } };
My
SMCLIENT_CERT_FILE
variable was/c/Certificate.p12
and I had an additional env varKEYPAIR_ALIAS
to match what I'd sync withsmksp_cert_sync
.I did make
CSC_LINK
the same value asSMCLIENT_CERT_FILE
(with password env var also the same) as that was necessary to trigger my custom script, but I don't think the values are actually read/used so they could probably be anything.
@MasterOdin Are you using same windows executor for preparing build ? because I followed the same step. used executor: win/default but build not generated in dist. any thoughts please ?
@RamK777-stack yeah, we use win/default
:
version: 2.1
orbs:
win: circleci/[email protected]
jobs:
win:
executor:
name: win/default
shell: bash.exe
and our build
config has this:
"win": {
"publisherName": [
"PopSQL, Inc."
],
"icon": "resources/icon.ico",
"sign": "./build/winSign.js",
"target": {
"target": "nsis",
"arch": [
"x64",
"ia32"
]
}
},
"nsis": {
"artifactName": "${productName}-Setup-${version}.${ext}"
},
Where the JS script I posted above is ./build/winSign.js
and the steps are done as part our job before we do yarn prepackage && yarn electron-builder build --win
.
@MasterOdin Thanks for your helpful response!. yarn prepackage && yarn electron-builder build --win this command also run through executor: name: win/default right ?
this is my code
` - run:
name: build
shell: bash.exe
command: yarn electron-pack-win-publish`
But .exe and latest.yml is not generated in dist folder.
Yes, that's run on that same win/default
executor job.
Thanks @MasterOdin
I managed to sign my Electron app for Windows under Linux with the following in the most cost-effective way possible:
- A GlobalSign EV signing certificate (HSM-type)
- You can use alternative services too; you do NOT need something like DigiCert KeyLocker, which is expensive as heck and limits how many signatures you can do for the duration of your subscription
- Azure Key Vault (This would be akin to DigiCert's KeyLocker. It's just the normal key vault, NOT the managed HSM version; you do NOT need a managed HSM pool either)
-
jsign
(so Windows wouldn't be required to sign)
This would be the most cost-effective way to sign applications without a limit (excluding the EV cert cost, it'd be around < $5-10 a month for signing an app 10k times using Azure Key Vault).
I mainly used these articles to figure it out:
Creating a certificate:
- https://signmycode.com/resources/how-to-create-private-keys-csr-and-import-code-signing-certificate-in-azure-keyvault-hsm
- When creating the key vault, you want the
premium
pricing tier (which gives you access toRSA-HSM
key storage). - When creating a certificate in the key vault, you want to use
Certificate issued by a non-integrated CA
(GlobalSign does have direct integration with Azure Key Vault, but it's only for generating TLS certs, not code signing ones) - The subject line looks like this (copy and paste into the input box directly after replacing the values):
-
DC=<domain name>,CN=<domain name>,OU="<Company name>",O="<Company name>",L=<Company city>,ST=<Company 2-letter state>,C=<2-letter country code>
-
- Under
Advanced Policy Configuration
, the following needs to be added:- Add
1.3.6.1.5.5.7.3.3
to the EKU list (this enables the cert as a signing cert) -
Exportable private key: No (this exposes the
RSA-HSM
option) - Select the
RSA-HSM
option - You do not need to define the
Certificate Type
at all.
- Add
- Follow the article instructions to generate the CSR, which you would use with GlobalSign (or any alternative cert provider)
- Once you get the certificate from GlobalSign, you'll perform the merge request as stated in the article with it in the key vault
- When creating the key vault, you want the
Using the Azure Key Vault API (follow it to get an access token for use with jsign
)
-
https://www.c-sharpcorner.com/article/how-to-access-azure-key-vault-secrets-through-rest-api-using-postman/
-
Follow the article instructions to get the access token for use with the Key Vault API.
-
For the Azure Key Vault access policy, you only need the following items checked:
- Cryptographic Operations: Verify, Sign
- Certificate Management Operations: Get
-
Build your Electron Windows application
-
Once you have
jsign
installed, you can use the following command to sign the app
jsign --storetype AZUREKEYVAULT \
--keystore <name of the key vault> \
--storepass <access token> \
--tsaurl http://timestamp.digicert.com \
--replace \
--alias <certificate name from Certificates> "<your electron application>.exe"
(--tsaurl
is a rfc3161 timestamp server, and digitcert's is free to use)
You can get the access token via:
WINDOWS_SIGNING_ACCESS_TOKEN=$(curl -X POST "https://login.microsoftonline.com/${WINDOWS_SIGNING_TENANT_ID}/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: application/json" \
-d "grant_type=client_credentials&client_id=${WINDOWS_SIGNING_CLIENT_ID}&client_secret=${WINDOWS_SIGNING_CLIENT_SECRET}&scope=https://vault.azure.net/.default") || { echo "Curl command failed"; exit 1; }
WINDOWS_SIGNING_ACCESS_TOKEN=$(echo "$WINDOWS_SIGNING_ACCESS_TOKEN" | jq -r '.access_token') || { echo "jq command failed"; exit 1; }
If successful, you'll get the message:
Adding Authenticode Signature to <application>.exe
For verifying the signature on the application in Linux, I used osslsigncode
which is avail in Ubuntu via apt.
osslsigncode verify -in <file name>
A thing to note: You may get a PCKS7 error when running the check. PCKS7 is different than MS's Authenticode cert / check, so you can ignore the error related to it. As long as it says Number of verified signatures: 1
, then you're good to go. I also did check my signed binary using the Windows SDK signtool
in Windows to make sure that the signature was valid, and it was.
signtool verify /pa <exe file>
Hope that helps!
Note:
You'll want to also sign the application binary itself if you're using electron-builder
as it will only sign the installer.
You can do this using an afterSign
script:
module.exports = async (context) => {
const { appOutDir } = context;
const appName = context.packager.appInfo.productFilename;
// call jsign using "path.join(appOutDir, `${appName}.exe`)"
}
Note: If you use the electron-builder auto updater, if you're signing the installer after the electron-builder process, then you need to update the latest.yml
file to a new hash. You can use this link as the basis for the hashing, then update all the sha values in latest.yml
accordingly:
https://stackoverflow.com/questions/46407362/checksum-mismatch-after-code-sign-electron-builder-updater
our Electron service
@jcharnley We are trying to sign our windows electron app the same way. We already have EV certificate hosted in Azure key vault as HSM. Can you kindly provide the steps you used to sign the app using azuresigntool sign electron builder in Github action. stuck at this for some time.
Any help would be greatfull
Thanks.
my YML file. think u will need the Install azuresigntool and azure sign in and dotnet
"win": {
"target": "nsis",
"icon": "relative\\path\\to\\app_icon.ico",
"signingHashAlgorithms": ["sha256"],
"certificateSubjectName": "https://domainofCert",
"sign": "customSign.js"
},
name: build and publish windows desktop dev
on:
push:
branches:
- develop
jobs:
publish:
# To enable auto publishing to github, update your electron publisher
# config in package.json > "build" and remove the conditional below
runs-on: ${{ matrix.os }}
# if: github.ref == 'refs/heads/develop'
strategy:
matrix:
os: [windows-latest]
steps:
- name: Checkout git repo
uses: actions/checkout@v3
- name: Install dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
- name: Install azuresigntool
run: 'dotnet tool install --global AzureSignTool'
- name: Install Node and NPM
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- name: Install
run: |
npm install
- name: Azure Sign in
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Publish releases
env:
# These values are used for auto updates signing
KEYVAULT_AUTH: '${{secrets.AZURE_CREDENTIALS}}'
# This is used for uploading release assets to github
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# This is used for uploading release assets to s3 bucket that is used for auto updates
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
npx nx run shell-desktop:make:production --publishPolicy=always
this is my customSign.js file. you can tell electron-builder to use this file instead of the default thing they do
const cp = require('child_process');
function isEmpty(value) {
return !value || !value.length;
}
// uses configuration.path to sign the file, passes in the keyvault auth as an env variable
exports.default = async function (configuration) {
const timeserver = 'http://timestamp.digicert.com';
const azureURL = 'https://azureURLofTheCert/';
const certificateName = 'nameofcert';
const authRaw = process.env.KEYVAULT_AUTH;
const keyVault = JSON.parse(authRaw);
if (isEmpty(configuration.path)) {
throw new Error('Path to file is required');
}
// AzureSignTool command with all the required parameters
const command = [
'azuresigntool.exe sign -fd sha384',
'-kvu',
azureURL,
'-kvi',
keyVault.clientId,
'-kvt',
keyVault.tenantId,
'-kvs',
keyVault.clientSecret,
'-kvc',
certificateName,
'-tr',
timeserver,
'-td',
'sha384',
'-v',
];
// throws an error if non-0 exit code, that's what we want.
cp.execSync(`${command.join(' ')} "${configuration.path}"`, {
stdio: 'inherit',
});
};
my YML file. think u will need the Install azuresigntool and azure sign in and dotnet
"win": { "target": "nsis", "icon": "relative\\path\\to\\app_icon.ico", "signingHashAlgorithms": ["sha256"], "certificateSubjectName": "https://domainofCert", "sign": "customSign.js" },
name: build and publish windows desktop dev on: push: branches: - develop jobs: publish: # To enable auto publishing to github, update your electron publisher # config in package.json > "build" and remove the conditional below runs-on: ${{ matrix.os }} # if: github.ref == 'refs/heads/develop' strategy: matrix: os: [windows-latest] steps: - name: Checkout git repo uses: actions/checkout@v3 - name: Install dotnet uses: actions/setup-dotnet@v3 with: dotnet-version: '6.0.x' - name: Install azuresigntool run: 'dotnet tool install --global AzureSignTool' - name: Install Node and NPM uses: actions/setup-node@v3 with: node-version: 18 cache: npm - name: Install run: | npm install - name: Azure Sign in uses: azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Publish releases env: # These values are used for auto updates signing KEYVAULT_AUTH: '${{secrets.AZURE_CREDENTIALS}}' # This is used for uploading release assets to github GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This is used for uploading release assets to s3 bucket that is used for auto updates AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} run: | npx nx run shell-desktop:make:production --publishPolicy=always
this is my customSign.js file. you can tell electron-builder to use this file instead of the default thing they do
const cp = require('child_process'); function isEmpty(value) { return !value || !value.length; } // uses configuration.path to sign the file, passes in the keyvault auth as an env variable exports.default = async function (configuration) { const timeserver = 'http://timestamp.digicert.com'; const azureURL = 'https://azureURLofTheCert/'; const certificateName = 'nameofcert'; const authRaw = process.env.KEYVAULT_AUTH; const keyVault = JSON.parse(authRaw); if (isEmpty(configuration.path)) { throw new Error('Path to file is required'); } // AzureSignTool command with all the required parameters const command = [ 'azuresigntool.exe sign -fd sha384', '-kvu', azureURL, '-kvi', keyVault.clientId, '-kvt', keyVault.tenantId, '-kvs', keyVault.clientSecret, '-kvc', certificateName, '-tr', timeserver, '-td', 'sha384', '-v', ]; // throws an error if non-0 exit code, that's what we want. cp.execSync(`${command.join(' ')} "${configuration.path}"`, { stdio: 'inherit', }); };
@jcharnley Thanks so much for the input. This helped us to complete our setup. One change is we are using Global sign certificate instead of digicert. By any change, do you know the timestamp url for global sign instead of digicert. Also the exe file created after signing is opening without any issues in windows. But during download from chrome, still showing as malicious software by chrome. Any advice on this ?
In terms of it being "malicious", I'm wondering it it's the elevate.exe? Anyone willing to try this for win.sign: path-to-sign.js
const path = require('path')
const { doSign } = require('app-builder-lib/out/codeSign/windowsCodeSign')
/**
* @type {import("electron-builder").CustomWindowsSign} sign
*/
module.exports = async function sign(config, packager) {
// Do not sign if no certificate is provided.
if (!config.cscInfo) {
return
}
const targetPath = config.path
// Do not sign elevate file, because that prompts virus warning?
if (targetPath.endsWith('elevate.exe')) {
return
}
await doSign(config, packager)
}
@jcharnley Thanks so much for the input. This helped us to complete our setup. One change is we are using Global sign certificate instead of digicert. By any change, do you know the timestamp url for global sign instead of digicert. Also the exe file created after signing is opening without any issues in windows. But during download from chrome, still showing as malicious software by chrome. Any advice on this ?
I have noticed that too, I havnt looked at it for ages. I see the Publisher name is not on the exe cert part, but as this was just a test of doing it and not for production I have yet to investigate it
if u find out the issue please let me know