Closed petervanderwalt closed 2 months ago
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:
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/windows@2.4.0
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:
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:
premium
pricing tier (which gives you access to RSA-HSM
key storage).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)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>
Advanced Policy Configuration
, the following needs to be added:1.3.6.1.5.5.7.3.3
to the EKU list (this enables the cert as a signing cert)RSA-HSM
option)RSA-HSM
optionCertificate Type
at all.Using the Azure Key Vault API (follow it to get an access token for use with jsign
)
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:
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:
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
@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
@jcharnley i will definitely check it. As a next step, have you implemented auto updates in the app. If yes, can you kindly provide any reference to it for Mac OS arm64 and x64 builds
Thanks.
You just call the auto.updater or something like that and u need to publish the versions. I put them on s3 and the auto updating works
On Thu, 8 Feb 2024 at 21:54, dark angel @.***> wrote:
@jcharnley https://github.com/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
@jcharnley https://github.com/jcharnley i will definitely check it. As a next step, have you implemented auto updates in the app. If yes, can you kindly provide any reference to it for Mac OS arm64 and x64 builds
Thanks.
— Reply to this email directly, view it on GitHub https://github.com/electron-userland/electron-builder/issues/7605#issuecomment-1934993862, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGACQNWI46QZK5YLI7O2AO3YSVCSFAVCNFSM6AAAAAAY6LTHWGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSMZUHE4TGOBWGI . You are receiving this because you were mentioned.Message ID: @.***>
@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
@jcharnley i will definitely check it. As a next step, have you implemented auto updates in the app. If yes, can you kindly provide any reference to it for Mac OS arm64 and x64 builds
Thanks.
did u fix the issue ?
@darkangel081195 You can use any compliant timeserver. I used digicert's (http://timestamp.digicert.com
) with our globalsign cert signing.
I signed in windows via:
- name: Sign windows electron binary
shell: powershell
working-directory: C:\AzureSignTool
run: |
AzureSignTool.exe sign `
-du "https://<host>" `
-fd "sha256" `
-kvu "https://${{ SIGNING_KEY_VAULT_NAME }}.vault.azure.net" `
-kvi "${{ _SIGNING_CLIENT_ID }}" `
-kvt "${{ SIGNING_TENANT_ID }}" `
-kvs "${{ SIGNING_CLIENT_SECRET }}" `
-kvc "${{ SIGNING_CERTIFICATE_NAME }}" `
-tr "http://timestamp.digicert.com" `
-td "sha256" `
-v `
-s "C:\<path to file>.exe"
I used Visual Studio to build the AzureSignTool since the published versions seem to be outdated.
Our electron app is packaged using the electron-builder
one-click installer, and I made sure that the installer and application binary was signed. I did not sign elevate.exe
, but our app doesn't require admin permissions to begin with. I store it in a zip file and download from chrome and get no warnings around authenticode when installing or using the app.
@darkangel081195 You can use any compliant timeserver. I used digicert's (
http://timestamp.digicert.com
) with our globalsign cert signing.I signed in windows via:
- name: Sign windows electron binary shell: powershell working-directory: C:\AzureSignTool run: | AzureSignTool.exe sign ` -du "https://<host>" ` -fd "sha256" ` -kvu "https://${{ SIGNING_KEY_VAULT_NAME }}.vault.azure.net" ` -kvi "${{ _SIGNING_CLIENT_ID }}" ` -kvt "${{ SIGNING_TENANT_ID }}" ` -kvs "${{ SIGNING_CLIENT_SECRET }}" ` -kvc "${{ SIGNING_CERTIFICATE_NAME }}" ` -tr "http://timestamp.digicert.com" ` -td "sha256" ` -v ` -s "C:\<path to file>.exe"
I used Visual Studio to build the AzureSignTool since the published versions seem to be outdated.
Our electron app is packaged using the
electron-builder
one-click installer, and I made sure that the installer and application binary was signed. I did not signelevate.exe
, but our app doesn't require admin permissions to begin with. I store it in a zip file and download from chrome and get no warnings around authenticode when installing or using the app.
@theogravity what does the -du "https://
@darkangel081195 You can use any compliant timeserver. I used digicert's (
http://timestamp.digicert.com
) with our globalsign cert signing. I signed in windows via:- name: Sign windows electron binary shell: powershell working-directory: C:\AzureSignTool run: | AzureSignTool.exe sign ` -du "https://<host>" ` -fd "sha256" ` -kvu "https://${{ SIGNING_KEY_VAULT_NAME }}.vault.azure.net" ` -kvi "${{ _SIGNING_CLIENT_ID }}" ` -kvt "${{ SIGNING_TENANT_ID }}" ` -kvs "${{ SIGNING_CLIENT_SECRET }}" ` -kvc "${{ SIGNING_CERTIFICATE_NAME }}" ` -tr "http://timestamp.digicert.com" ` -td "sha256" ` -v ` -s "C:\<path to file>.exe"
I used Visual Studio to build the AzureSignTool since the published versions seem to be outdated. Our electron app is packaged using the
electron-builder
one-click installer, and I made sure that the installer and application binary was signed. I did not signelevate.exe
, but our app doesn't require admin permissions to begin with. I store it in a zip file and download from chrome and get no warnings around authenticode when installing or using the app.@theogravity what does the -du "https://" stand for, I think that is the part am missing in my sign command
See options here:
https://github.com/vcsjones/AzureSignTool
--description-url [short: -du, required: no]: A URL with more information of the signed content. This parameter serves the same purpose as the /du option in the Windows SDK signtool. If this parameter is not supplied, the signature will not contain a URL description.
I just set it to our website http://switchboard.app. Just goes to the homepage. Not sure if it needs to be any more than that.
@darkangel081195 You can use any compliant timeserver. I used digicert's (
http://timestamp.digicert.com
) with our globalsign cert signing. I signed in windows via:- name: Sign windows electron binary shell: powershell working-directory: C:\AzureSignTool run: | AzureSignTool.exe sign ` -du "https://<host>" ` -fd "sha256" ` -kvu "https://${{ SIGNING_KEY_VAULT_NAME }}.vault.azure.net" ` -kvi "${{ _SIGNING_CLIENT_ID }}" ` -kvt "${{ SIGNING_TENANT_ID }}" ` -kvs "${{ SIGNING_CLIENT_SECRET }}" ` -kvc "${{ SIGNING_CERTIFICATE_NAME }}" ` -tr "http://timestamp.digicert.com" ` -td "sha256" ` -v ` -s "C:\<path to file>.exe"
I used Visual Studio to build the AzureSignTool since the published versions seem to be outdated. Our electron app is packaged using the
electron-builder
one-click installer, and I made sure that the installer and application binary was signed. I did not signelevate.exe
, but our app doesn't require admin permissions to begin with. I store it in a zip file and download from chrome and get no warnings around authenticode when installing or using the app.@theogravity what does the -du "https://" stand for, I think that is the part am missing in my sign command
du -> du should have the url to your product website.
@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
@jcharnley i will definitely check it. As a next step, have you implemented auto updates in the app. If yes, can you kindly provide any reference to it for Mac OS arm64 and x64 builds Thanks.
did u fix the issue ?
Not yet. Was caught up in other work. Will try this week and let you know
Finally got mine sorted.
Using digicert + keylocker to store the cert, and Github actions + electron builder to build and sign the app
https://github.com/OpenBuilds/OpenBuilds-CONTROL/issues/321#issuecomment-2044444457
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.
Hey guys. After my investigation and testing, I found a solution that requires the least amount of changes:
- name: Setup Code Signing (1/2)
env:
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
run: |
CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12
echo "$SM_CLIENT_CERT_FILE_B64" | base64 --decode > $CERTIFICATE_PATH
echo "SM_CLIENT_CERT_FILE=$CERTIFICATE_PATH" >> "$GITHUB_ENV"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH
shell: bash
- name: Setup Code Signing (2/2)
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o Keylockertools-windows-x64.msi
msiexec /i Keylockertools-windows-x64.msi /quiet /qn
smksp_cert_sync.exe
shell: cmd
- name: Release
# This is a must. See: https://github.com/electron-userland/electron-builder/pull/8384#issuecomment-2257632066
shell: powershell
run: pnpm release
env:
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
{
"win": {
certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH
}
}
No need for customSign.js
, perfectly compatible with electron-builder
.
BTW. Below is my complete win
configuration:
import { doSign } from "app-builder-lib/out/codeSign/windowsCodeSign.js";
const fingerprint = process.env.SM_CODE_SIGNING_CERT_SHA1_HASH;
{
"win": {
// If there are no special requirements, I suggest using only sha256,
// because starting from Windows 7, applications must use sha256 signatures to run properly,
// and Microsoft has deprecated sha1 signature support.
// Of course, the most important thing is:
// KeyLocker has a limit on the number of times it can be used (1000 times a year).
// If each file is signed twice, the quota will be exhausted quickly.
// See: https://learn.microsoft.com/en-us/sysinternals/announce/sha1deprecation
signingHashAlgorithms: ["sha256"],
// According to the existing information, it is necessary to sign the node addon, otherwise,
// it will encounter: anti-virus software that detects a *.node file as malicious.
// See: https://github.com/electron-userland/electron-builder/issues/1723
signExts: [".exe", ".dll", ".node"],
certificateSha1: fingerprint,
sign: async (configuration, packager) => {
// Allow local normal packaging
if (fingerprint === undefined) {
return;
}
// Ignore signing
if (configuration.path.includes(path.join("resources", "need_ignore_dir"))) {
return;
}
// Let electron-builder execute the signing step.
return await doSign(configuration, packager);
},
}
}
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