-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
June 2023 Code Signing with HSM APIs #7605
Comments
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. |
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 /* 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? 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 Added to
My // 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 I did make |
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 |
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: nervosnetwork/neuron#2913 There are mainly two steps:
|
@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 version: 2.1
orbs:
win: circleci/[email protected]
jobs:
win:
executor:
name: win/default
shell: bash.exe and our "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 |
@MasterOdin Thanks for your helpful response!. yarn prepackage && yarn electron-builder build --win this command also run through executor:
|
Yes, that's run on that same |
Thanks @MasterOdin |
I managed to sign my Electron app for Windows under Linux with the following in the most cost-effective way possible:
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:
Using the Azure Key Vault API (follow it to get an access token for use with
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" ( 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: For verifying the signature on the application in Linux, I used
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
Hope that helps! Note: You'll want to also sign the application binary itself if you're using You can do this using an 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 |
@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
this is my customSign.js file. you can tell electron-builder to use this file instead of the default thing they do
|
@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
|
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
<#7605 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AGACQNWI46QZK5YLI7O2AO3YSVCSFAVCNFSM6AAAAAAY6LTHWGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSMZUHE4TGOBWGI>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
did u fix the issue ? |
@darkangel081195 You can use any compliant timeserver. I used digicert's ( I signed in windows via:
I used Visual Studio to build the AzureSignTool since the published versions seem to be outdated. Our electron app is packaged using the |
@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
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. |
du -> du should have the url to your product website. |
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 |
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: Github Actions Config Part- 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 }} Electron Builder Config Part{
"win": {
certificateSha1: process.env.SM_CODE_SIGNING_CERT_SHA1_HASH
}
} No need for BTW. Below is my complete 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 electron/windows-installer#473 (comment)
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
The text was updated successfully, but these errors were encountered: