Skip to content
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

[chore] Update Windows signing Cert #2452

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 43 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,28 @@ env:
- config:
# Human identifier for the job.
name: Windows
runs-on: windows-2019
runs-on: [self-hosted, windows-sign-pc]
# The value is a string representing a JSON document.
# Setting this to null causes the job to run directly in the runner machine instead of in a container.
container: |
null
# Name of the secret that contains the certificate.
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
certificate-secret: INSTALLER_CERT_WINDOWS_CER
# Name of the secret that contains the certificate password.
certificate-password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
certificate-password-secret: INSTALLER_CERT_WINDOWS_PASSWORD
# File extension for the certificate.
certificate-extension: pfx
# Container for windows cert signing
certificate-container: INSTALLER_CERT_WINDOWS_CONTAINER
# Quoting on the value is required here to allow the same comparison expression syntax to be used for this
# and the companion needs.select-targets.outputs.merge-channel-files property (output values always have string
# type).
mergeable-channel-file: 'false'
# as this runs on a self hosted runner, we need to avoid building with the default working directory path,
# otherwise paths in the build job will be too long for `light.exe`
# we use the below as a Symbolic link (just changing the wd will break the checkout action)
# this is a work around (see: https://github.com/actions/checkout/issues/197).
working-directory: 'C:\a'
artifacts:
- path: '*Windows_64bit.exe'
name: Windows_X86-64_interactive_installer
Expand Down Expand Up @@ -270,6 +277,14 @@ jobs:
env:
# Location of artifacts generated by build.
BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts
# to skip passing signing credentials to electron-builder
IS_WINDOWS_CONFIG: ${{ matrix.config.name == 'Windows' }}
INSTALLER_CERT_WINDOWS_CER: "/tmp/cert.cer"
# We are hardcoding the path for signtool because is not present on the windows PATH env var by default.
# Keep in mind that this path could change when upgrading to a new runner version
SIGNTOOL_PATH: "C:/Program Files (x86)/Windows Kits/10/bin/10.0.19041.0/x86/signtool.exe"
WIN_CERT_PASSWORD: ${{ secrets.INSTALLER_CERT_WINDOWS_PASSWORD }}
WIN_CERT_CONTAINER_NAME: ${{ secrets.INSTALLER_CERT_WINDOWS_CONTAINER }}
strategy:
matrix:
config: ${{ fromJson(needs.select-targets.outputs.build-matrix) }}
Expand All @@ -283,6 +298,12 @@ jobs:
timeout-minutes: 90

steps:
- name: Symlink custom working directory
shell: cmd
if: runner.os == 'Windows' && matrix.config.working-directory
run: |
if not exist "${{ matrix.config.working-directory }}" mklink /d "${{ matrix.config.working-directory }}" "C:\actions-runner\_work\arduino-ide\arduino-ide"

- name: Checkout
if: fromJSON(matrix.config.container) == null
uses: actions/checkout@v4
Expand All @@ -293,42 +314,42 @@ jobs:
uses: actions/checkout@v3

- name: Install Node.js
if: fromJSON(matrix.config.container) == null
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: 'https://registry.npmjs.org'
cache: 'yarn'

- name: Install Python 3.x
if: fromJSON(matrix.config.container) == null
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
uses: actions/setup-python@v5
with:
python-version: '3.11.x'

- name: Install Go
if: fromJSON(matrix.config.container) == null
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}

- name: Install Go
# actions/setup-go@v5 has dependency on a higher version of glibc than available in the Linux container.
if: fromJSON(matrix.config.container) != null
if: fromJSON(matrix.config.container) != null && runner.os != 'Windows'
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Install Taskfile
if: fromJSON(matrix.config.container) == null
if: fromJSON(matrix.config.container) == null && runner.os != 'Windows'
uses: arduino/setup-task@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
version: 3.x

- name: Install Taskfile
# actions/setup-task@v2 has dependency on a higher version of glibc than available in the Linux container.
if: fromJSON(matrix.config.container) != null
if: fromJSON(matrix.config.container) != null && runner.os != 'Windows'
uses: arduino/setup-task@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -350,9 +371,10 @@ jobs:
CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }}
CREATE_PASSWORD: ${{ secrets.CREATE_PASSWORD }}
CREATE_CLIENT_SECRET: ${{ secrets.CREATE_CLIENT_SECRET }}
working-directory: ${{ runner.os == 'Windows' && matrix.config.working-directory || './' }}
run: |
# See: https://www.electron.build/code-signing
if [ $CAN_SIGN = false ]; then
if [ $CAN_SIGN = false ] || [ $IS_WINDOWS_CONFIG = true ]; then
echo "Skipping the app signing: certificate not provided."
else
export CSC_LINK="${{ runner.temp }}/signing_certificate.${{ matrix.config.certificate-extension }}"
Expand All @@ -372,13 +394,14 @@ jobs:
yarn --cwd electron-app rebuild
yarn --cwd electron-app build
yarn --cwd electron-app package

# Both macOS jobs generate a "channel update info file" with same path and name. The second job to complete would
# overwrite the file generated by the first in the workflow artifact.
- name: Stage channel file for merge
if: >
needs.select-targets.outputs.merge-channel-files == 'true' &&
matrix.config.mergeable-channel-file == 'true'
working-directory: ${{ runner.os == 'Windows' && matrix.config.working-directory || './' }}
run: |
staged_channel_files_path="${{ runner.temp }}/staged-channel-files"
mkdir "$staged_channel_files_path"
Expand All @@ -398,13 +421,20 @@ jobs:
with:
if-no-files-found: error
name: ${{ env.STAGED_CHANNEL_FILES_ARTIFACT }}
path: ${{ env.STAGED_CHANNEL_FILES_PATH }}
path: ${{ runner.os == 'Windows' && matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.STAGED_CHANNEL_FILES_PATH) || env.STAGED_CHANNEL_FILES_PATH }}


- name: Upload [GitHub Actions]
uses: actions/upload-artifact@v3
with:
name: ${{ env.JOB_TRANSFER_ARTIFACT }}
path: ${{ env.BUILD_ARTIFACTS_PATH }}
path: ${{ runner.os == 'Windows' && matrix.config.working-directory && format('{0}/{1}', matrix.config.working-directory, env.BUILD_ARTIFACTS_PATH) || env.BUILD_ARTIFACTS_PATH }}

- name: Manual Clean up for self-hosted runners
if: runner.os == 'Windows' && matrix.config.working-directory
shell: cmd
run: |
rmdir /s /q "${{ matrix.config.working-directory }}\${{ env.BUILD_ARTIFACTS_PATH }}"

merge-channel-files:
needs:
Expand Down
65 changes: 42 additions & 23 deletions .github/workflows/check-certificates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ jobs:
- identifier: macOS signing certificate # Text used to identify certificate in notifications.
certificate-secret: APPLE_SIGNING_CERTIFICATE_P12 # Name of the secret that contains the certificate.
password-secret: KEYCHAIN_PASSWORD # Name of the secret that contains the certificate password.
type: pkcs12
- identifier: Windows signing certificate
certificate-secret: WINDOWS_SIGNING_CERTIFICATE_PFX
password-secret: WINDOWS_SIGNING_CERTIFICATE_PASSWORD
certificate-secret: INSTALLER_CERT_WINDOWS_CER
# The password for the Windows certificate is not needed, because its not a container, but a single certificate.
type: x509

steps:
- name: Set certificate path environment variable
Expand All @@ -95,7 +97,7 @@ jobs:
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
run: |
(
openssl pkcs12 \
openssl ${{ matrix.certificate.type }} \
-in "${{ env.CERTIFICATE_PATH }}" \
-legacy \
-noout \
Expand All @@ -122,26 +124,43 @@ jobs:
CERTIFICATE_PASSWORD: ${{ secrets[matrix.certificate.password-secret] }}
id: get-days-before-expiration
run: |
EXPIRATION_DATE="$(
(
openssl pkcs12 \
-in "${{ env.CERTIFICATE_PATH }}" \
-clcerts \
-legacy \
-nodes \
-passin env:CERTIFICATE_PASSWORD
) | (
openssl x509 \
-noout \
-enddate
) | (
grep \
--max-count=1 \
--only-matching \
--perl-regexp \
'notAfter=(\K.*)'
)
)"
if [[ ${{ matrix.certificate.type }} == "pkcs12" ]]; then
EXPIRATION_DATE="$(
(
openssl pkcs12 \
-in "${{ env.CERTIFICATE_PATH }}" \
-clcerts \
-legacy \
-nodes \
-passin env:CERTIFICATE_PASSWORD
) | (
openssl x509 \
-noout \
-enddate
) | (
grep \
--max-count=1 \
--only-matching \
--perl-regexp \
'notAfter=(\K.*)'
)
)"
elif [[ ${{ matrix.certificate.type }} == "x509" ]]; then
EXPIRATION_DATE="$(
(
openssl x509 \
-in ${{ env.CERTIFICATE_PATH }} \
-noout \
-enddate
) | (
grep \
--max-count=1 \
--only-matching \
--perl-regexp \
'notAfter=(\K.*)'
)
)"
fi

DAYS_BEFORE_EXPIRATION="$((($(date --utc --date="$EXPIRATION_DATE" +%s) - $(date --utc +%s)) / 60 / 60 / 24))"

Expand Down
3 changes: 2 additions & 1 deletion electron-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@
"msi",
"nsis",
"zip"
]
],
"sign": "./scripts/windowsCustomSign.js"
},
"mac": {
"darkModeSupport": true,
Expand Down
30 changes: 30 additions & 0 deletions electron-app/scripts/windowsCustomSign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const childProcess = require('child_process');

exports.default = async function (configuration) {
if (!process.env.GITHUB_ACTIONS) {
return;
}

const SIGNTOOL_PATH = process.env.SIGNTOOL_PATH;
const INSTALLER_CERT_WINDOWS_CER = process.env.INSTALLER_CERT_WINDOWS_CER;
const CERT_PASSWORD = process.env.WIN_CERT_PASSWORD;
const CONTAINER_NAME = process.env.WIN_CERT_CONTAINER_NAME;
const filePath = configuration.path;

if (
SIGNTOOL_PATH &&
INSTALLER_CERT_WINDOWS_CER &&
CERT_PASSWORD &&
CONTAINER_NAME
) {
childProcess.execSync(
`"${SIGNTOOL_PATH}" sign -d "Arduino IDE" -f "${INSTALLER_CERT_WINDOWS_CER}" -csp "eToken Base Cryptographic Provider" -k "[{{${CERT_PASSWORD}}}]=${CONTAINER_NAME}" -fd sha256 -tr http://timestamp.digicert.com -td SHA256 -v "${filePath}"`,
{ stdio: 'inherit' }
);
} else {
console.warn(
`Custom windows signing was no performed one of the following variables was not provided: SIGNTOOL_PATH (${SIGNTOOL_PATH}), INSTALLER_CERT_WINDOWS_CERT (${INSTALLER_CERT_WINDOWS_CER}), CERT_PASSWORD (${CERT_PASSWORD}), CONTAINER_NAME (${CONTAINER_NAME})`
);
process.exit(1);
}
};
Loading