From 6a15db3a3689a26a6e2c6f4ac0cf04d2b5e5dadc Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 10 Oct 2025 11:18:43 +0100 Subject: [PATCH] Chore: Implement SSL eSigner for Windows app signing (#13397) --- .github/workflows/github-actions-main.yml | 26 +++++- packages/app-desktop/package.json | 1 + packages/app-desktop/sign.js | 96 +++++++++++++++++++++++ packages/tools/cspell/dictionary4.txt | 4 +- 4 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 packages/app-desktop/sign.js diff --git a/.github/workflows/github-actions-main.yml b/.github/workflows/github-actions-main.yml index f2ccf2fa97..de4904a5ec 100644 --- a/.github/workflows/github-actions-main.yml +++ b/.github/workflows/github-actions-main.yml @@ -50,6 +50,22 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + # - name: Test Windows app signing + # if: runner.os == 'Windows' + # env: + # GH_TOKEN: ${{ secrets.GH_TOKEN }} + # IS_CONTINUOUS_INTEGRATION: 1 + # BUILD_SEQUENCIAL: 1 + # SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }} + # SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }} + # SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }} + # SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }} + # SIGN_APPLICATION: 1 + # # To ensure that the operations stop on failure, all commands + # # should be on one line with "&&" in between. + # run: | + # yarn install && cd packages/app-desktop && yarn dist + - name: Run tests, build and publish Linux and macOS apps if: runner.os == 'Linux' || runner.os == 'macOs' env: @@ -71,11 +87,14 @@ jobs: - name: Build and publish Windows app if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/v') env: - CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }} - CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }} GH_TOKEN: ${{ secrets.GH_TOKEN }} IS_CONTINUOUS_INTEGRATION: 1 BUILD_SEQUENCIAL: 1 + SSL_ESIGNER_USER_NAME: ${{ secrets.SSL_ESIGNER_USER_NAME }} + SSL_ESIGNER_USER_PASSWORD: ${{ secrets.SSL_ESIGNER_USER_PASSWORD }} + SSL_ESIGNER_CREDENTIAL_ID: ${{ secrets.SSL_ESIGNER_CREDENTIAL_ID }} + SSL_ESIGNER_USER_TOTP: ${{ secrets.SSL_ESIGNER_USER_TOTP }} + SIGN_APPLICATION: 1 # To ensure that the operations stop on failure, all commands # should be on one line with "&&" in between. run: | @@ -195,5 +214,4 @@ jobs: if [[ "$actual_body" != "$expected_body" ]]; then echo 'Failed while checking the body response after request to /api/ping' exit 1; - fi - + fi \ No newline at end of file diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index 976c01a7eb..fb21121cb5 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -46,6 +46,7 @@ "asar": true, "asarUnpack": "./node_modules/node-notifier/vendor/**", "win": { + "sign": "./sign.js", "rfc3161TimeStampServer": "http://timestamp.digicert.com", "icon": "../../Assets/ImageSources/Joplin.ico", "target": [ diff --git a/packages/app-desktop/sign.js b/packages/app-desktop/sign.js new file mode 100644 index 0000000000..7df6e6ed92 --- /dev/null +++ b/packages/app-desktop/sign.js @@ -0,0 +1,96 @@ +/* eslint-disable no-console */ + +const { execSync } = require('child_process'); +const { chdir, cwd } = require('process'); +const { mkdirpSync, moveSync, pathExists } = require('fs-extra'); +const { readdirSync, writeFileSync } = require('fs'); + +const signToolName = 'CodeSignTool.bat'; + +const downloadSignTool = async () => { + const signToolUrl = 'https://www.ssl.com/download/codesigntool-for-windows/'; + const downloadDir = `${__dirname}/signToolDownloadTemp`; + const extractDir = `${__dirname}/signToolExtractTemp`; + + if (await pathExists(`${extractDir}/${signToolName}`)) { + console.info('sign.js: Sign tool has already been downloaded - skipping'); + return extractDir; + } + + mkdirpSync(downloadDir); + mkdirpSync(extractDir); + + const response = await fetch(signToolUrl); + if (!response.ok) throw new Error(`sign.js: HTTP error ${response.status}: ${response.statusText}`); + + const zipPath = `${downloadDir}/codeSignTool.zip`; + + const buffer = Buffer.from(await response.arrayBuffer()); + writeFileSync(zipPath, buffer); + + console.info('sign.js: Downloaded sign tool zip:', readdirSync(downloadDir)); + + mkdirpSync(extractDir); + + execSync( + `powershell -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${extractDir}' -Force"`, + { stdio: 'inherit' }, + ); + + console.info('sign.js: Extracted sign tool zip:', readdirSync(extractDir)); + + return extractDir; +}; + +exports.default = async (configuration) => { + const inputFilePath = configuration.path; + + const { + SSL_ESIGNER_USER_NAME, + SSL_ESIGNER_USER_PASSWORD, + SSL_ESIGNER_CREDENTIAL_ID, + SSL_ESIGNER_USER_TOTP, + SIGN_APPLICATION, + } = process.env; + + console.info('sign.js: File to sign:', inputFilePath); + + if (SIGN_APPLICATION !== '1') { + console.info('sign.js: SIGN_APPLICATION != 1 - not signing application'); + return; + } + + console.info('sign.js: SIGN_APPLICATION = 1 - signing application'); + + const signToolDir = await downloadSignTool(); + const tempDir = `${__dirname}/temp`; + + mkdirpSync(tempDir); + + const previousDir = cwd(); + chdir(signToolDir); + + try { + const cmd = [ + `${signToolName} sign`, + `-input_file_path="${inputFilePath}"`, + `-output_dir_path="${tempDir}"`, + `-credential_id="${SSL_ESIGNER_CREDENTIAL_ID}"`, + `-username="${SSL_ESIGNER_USER_NAME}"`, + `-password="${SSL_ESIGNER_USER_PASSWORD}"`, + `-totp_secret="${SSL_ESIGNER_USER_TOTP}"`, + ]; + + execSync(cmd.join(' ')); + + const createdFiles = readdirSync(tempDir); + console.info('sign.js: Created files:', createdFiles); + + moveSync(`${tempDir}/${createdFiles[0]}`, inputFilePath, { overwrite: true }); + } catch (error) { + console.error('sign.js: Could not sign file:', error); + process.exit(1); + } finally { + chdir(previousDir); + } +}; diff --git a/packages/tools/cspell/dictionary4.txt b/packages/tools/cspell/dictionary4.txt index 3996b2a805..50e4ef0865 100644 --- a/packages/tools/cspell/dictionary4.txt +++ b/packages/tools/cspell/dictionary4.txt @@ -202,4 +202,6 @@ vikasmanimc traspire Pokies Pokiesman -Gamstop \ No newline at end of file +Gamstop +ESIGNER +TOTP