diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd277983e..ecf765c55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ 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: | @@ -75,16 +75,10 @@ env: artifacts: - path: '*Windows_64bit.exe' name: Windows_X86-64_interactive_installer - - path: '*Windows_64bit_unsigned.exe' - name: Windows_X86-64_interactive_installer_unsigned - path: '*Windows_64bit.msi' name: Windows_X86-64_MSI - - path: '*Windows_64bit_unsigned.msi' - name: Windows_X86-64_MSI_unsigned - path: '*Windows_64bit.zip' name: Windows_X86-64_zip - - path: '*Windows_64bit_unsigned.zip' - name: Windows_X86-64_zip_unsigned - config: name: Linux runs-on: ubuntu-latest @@ -278,6 +272,7 @@ jobs: env: # Location of artifacts generated by build. BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts + IS_WINDOWS_CONFIG: ${{ matrix.config.name == 'Windows' }} strategy: matrix: config: ${{ fromJson(needs.select-targets.outputs.build-matrix) }} @@ -301,7 +296,7 @@ jobs: uses: actions/checkout@v3 - name: Install Node.js - if: fromJSON(matrix.config.container) == null + if: fromJSON(matrix.config.container) == null && env.IS_WINDOWS_CONFIG == false uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -309,26 +304,26 @@ jobs: cache: 'yarn' - name: Install Python 3.x - if: fromJSON(matrix.config.container) == null + if: fromJSON(matrix.config.container) == null && env.IS_WINDOWS_CONFIG == false 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 && env.IS_WINDOWS_CONFIG == false 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 && env.IS_WINDOWS_CONFIG == false 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 && env.IS_WINDOWS_CONFIG == false uses: arduino/setup-task@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -336,7 +331,7 @@ jobs: - 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 && env.IS_WINDOWS_CONFIG == false uses: arduino/setup-task@v1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} @@ -353,7 +348,6 @@ jobs: IS_NIGHTLY: ${{ needs.build-type-determination.outputs.is-nightly }} IS_RELEASE: ${{ needs.build-type-determination.outputs.is-release }} CAN_SIGN: ${{ secrets[matrix.config.certificate-secret] != '' }} - IS_WINDOWS_CONFIG: ${{ matrix.config.name == 'Windows' }} # The CREATE_* environment vars are only used to run tests. These secrets are optional. Dependent tests will # be skipped if not available. CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }} @@ -415,76 +409,11 @@ jobs: name: ${{ env.JOB_TRANSFER_ARTIFACT }} path: ${{ env.BUILD_ARTIFACTS_PATH }} - sign-windows: - runs-on: [self-hosted, windows-sign-pc] - needs: build - - defaults: - run: - shell: bash - - env: - BUILD_ARTIFACTS_PATH: electron-app/dist/build-artifacts - 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" - - steps: - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: ${{ env.JOB_TRANSFER_ARTIFACT }} - path: ${{ env.BUILD_ARTIFACTS_PATH }} - - - name: Find and process exe and msi artifacts - shell: bash - env: - CERT_PASSWORD: ${{ secrets.INSTALLER_CERT_WINDOWS_PASSWORD }} - CONTAINER_NAME: ${{ secrets.INSTALLER_CERT_WINDOWS_CONTAINER }} - # https://stackoverflow.com/questions/17927895/automate-extended-validation-ev-code-signing-with-safenet-etoken - run: | - shopt -s nullglob - for ARTIFACT in "${{ env.BUILD_ARTIFACTS_PATH }}"/*_unsigned.{exe,msi}; do - echo "Processing $ARTIFACT" - FILENAME=$(basename "$ARTIFACT") - BASE_NAME="${FILENAME%.*}" - EXTENSION="${FILENAME##*.}" - # Remove '_unsigned' from the base name - SIGNED_BASE_NAME="${BASE_NAME%_unsigned}" - - # Sign and rename EXE and MSI files - if [[ "$EXTENSION" == "exe" || "$EXTENSION" == "msi" ]]; then - echo "Signing $ARTIFACT" - "${{ env.SIGNTOOL_PATH }}" sign -d "Arduino IDE" -f ${{ env.INSTALLER_CERT_WINDOWS_CER }} -csp "eToken Base Cryptographic Provider" -k "[{{${{ env.CERT_PASSWORD }}}}]=${{ env.CONTAINER_NAME }}" -fd sha256 -tr http://timestamp.digicert.com -td SHA256 -v "$ARTIFACT" - SIGNED_ARTIFACT_PATH="${{ env.BUILD_ARTIFACTS_PATH }}/${SIGNED_BASE_NAME}.${EXTENSION}" - mv "$ARTIFACT" "$SIGNED_ARTIFACT_PATH" - echo "Renamed $ARTIFACT to $SIGNED_ARTIFACT_PATH" - fi - done - - - name: Upload signed EXE - uses: actions/upload-artifact@v3 - with: - name: Windows_X86-64_interactive_installer - path: ${{ env.BUILD_ARTIFACTS_PATH }}/*Windows_64bit.exe - - - name: Upload signed MSI - uses: actions/upload-artifact@v3 - with: - name: Windows_X86-64_MSI - path: ${{ env.BUILD_ARTIFACTS_PATH }}/*Windows_64bit.msi - - # This step is needed because the self hosted runner does not delete files automatically - - name: Clean up artifacts - run: rm -rf ${{ env.BUILD_ARTIFACTS_PATH }} - merge-channel-files: needs: - build-type-determination - select-targets - build - - sign-windows if: needs.select-targets.outputs.merge-channel-files == 'true' runs-on: ubuntu-latest permissions: {} @@ -548,7 +477,6 @@ jobs: needs: - select-targets - build - - sign-windows if: always() && needs.build.result != 'skipped' runs-on: ubuntu-latest @@ -573,7 +501,6 @@ jobs: needs: - build-type-determination - build - - sign-windows runs-on: ubuntu-latest outputs: BODY: ${{ steps.changelog.outputs.BODY }} @@ -623,7 +550,6 @@ jobs: - build-type-determination - merge-channel-files - changelog - - sign-windows if: > always() && needs.build-type-determination.result == 'success' && @@ -657,7 +583,6 @@ jobs: - build-type-determination - merge-channel-files - changelog - - sign-windows if: > always() && needs.build-type-determination.result == 'success' && @@ -709,7 +634,6 @@ jobs: - publish - release - artifacts - - sign-windows if: always() && needs.build.result != 'skipped' runs-on: ubuntu-latest diff --git a/electron-app/package.json b/electron-app/package.json index 3e42fdd0d..0451107f1 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -133,7 +133,8 @@ "msi", "nsis", "zip" - ] + ], + "sign": "./scripts/windowsCustomSign.js" }, "mac": { "darkModeSupport": true, diff --git a/electron-app/scripts/package.js b/electron-app/scripts/package.js index 83fb0b4d1..87a597094 100644 --- a/electron-app/scripts/package.js +++ b/electron-app/scripts/package.js @@ -100,7 +100,7 @@ async function getArtifactName(version) { switch (platform) { case 'win32': { if (arch === 'x64') { - return `${name}_${version}_Windows_64bit_unsigned.\$\{ext}`; + return `${name}_${version}_Windows_64bit.\$\{ext}`; } throw new Error(`Unsupported platform, arch: ${platform}, ${arch}`); } diff --git a/electron-app/scripts/windowsCustomSign.js b/electron-app/scripts/windowsCustomSign.js new file mode 100644 index 000000000..19db2b300 --- /dev/null +++ b/electron-app/scripts/windowsCustomSign.js @@ -0,0 +1,26 @@ +const childProcess = require('child_process'); + +exports.default = async function (configuration) { + const SIGNTOOL_PATH = process.env.SIGNTOOL_PATH; + const INSTALLER_CERT_WINDOWS_CER = process.env.INSTALLER_CERT_WINDOWS_CER; + const CERT_PASSWORD = process.env.CERT_PASSWORD; + const CONTAINER_NAME = process.env.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: SIGNTOOL_PATH, INSTALLER_CERT_WINDOWS_CER, CERT_PASSWORD, and CONTAINER_NAME environment variables were not provided.' + ); + process.exit(1); + } +};