diff --git a/.github/actions/win-sign-action/action.yml b/.github/actions/win-sign-action/action.yml new file mode 100644 index 000000000..a57e8f7b1 --- /dev/null +++ b/.github/actions/win-sign-action/action.yml @@ -0,0 +1,145 @@ +name: 'Windows Signing' +description: 'Sign files on Windows' +inputs: + base-dir: + description: 'The base directory to search for files' + required: true + file-extensions: + description: 'List of file extensions to sign, separated by comma' + required: true + username: + description: 'Username for signing' + required: true + password: + description: 'Password for signing' + required: true + recursive: + description: 'Whether to search recursively in subdirectories' + required: false + default: 'false' + sign-description: + description: 'Signature description' + required: false + default: 'Cryptomator' + sign-url: + description: 'Signature URL' + required: false + default: 'https://cryptomator.org' + +runs: + using: "composite" + steps: + - name: Download Actalis CodeSigner if not present + id: download-signer + run: | + if (! (Test-Path -Path '${{ env.SIGNER_PATH }}')) { + echo "Downloading Actalis CodeSigner..." + curl --output "${{ env.SIGNER_NAME }}.zip" -L "${{ env.SIGNER_URL }}" + if (!(Get-FileHash -Path "${{ env.SIGNER_NAME }}.zip" -Algorithm SHA256).Hash.ToLower().equals("${{ env.SIGNER_HASH }}")) { + echo "Signer hash mismatch, exiting." + exit 1 + } + Expand-Archive -Path "${{ env.SIGNER_NAME }}.zip" -DestinationPath "${{ env.SIGNER_NAME }}" -Force + } + env: + SIGNER_PATH: ${{ github.workspace }}/actalis-signer/ActalisCodeSigner.exe + SIGNER_NAME: actalis-signer + SIGNER_URL: 'https://static.cryptomator.org/other/CodeSigner-win-x64-latest.zip' + SIGNER_HASH: '44a1e09ab72707d049d3e59656e3e35de92e8cda357eec1cfc367016e45835ab' + shell: pwsh + - name: Generate, mask, and output the input secrets + id: set-secrets + run: | + echo "::add-mask::${{ inputs.username }}" + echo "::add-mask::${{ inputs.password }}" + echo "username=${{ inputs.username }}" >> "$GITHUB_OUTPUT" + echo "password=${{ inputs.password }}" >> "$GITHUB_OUTPUT" + shell: bash + - name: Sign DLLs with Actalis CodeSigner + run: | + $signerPath = '${{ env.SIGNER_PATH }}' + $username = '${{ steps.set-secrets.outputs.username }}' + $password = '${{ steps.set-secrets.outputs.password }}' + $signDescription = '${{ inputs.sign-description }}' + $signUrl = '${{ inputs.sign-url }}' + $extensions = '${{ inputs.file-extensions }}'.split(",") | ForEach-Object { "*.$($_.Trim())" } + $recursive = '${{ inputs.recursive }}' -eq 'true' + $files = Get-ChildItem -Path '${{ inputs.base-dir }}\*' -Include $extensions -Recurse:$recursive + + if($files.Count -eq 0) { + Write-Host "`nāŒ No files found to sign." + exit 1 + } + Write-Host "`nšŸ“ Found $($files.Count) files to sign:" + $files | ForEach-Object { Write-Host " - $($_.FullName)" } + + # Create log directory + $logDir = "~/.Acsi/log" + if (!(Test-Path $logDir)) { + New-Item -Path $logDir -ItemType Directory -Force | Out-Null + } + + $jobs = @() + foreach ($file in $files) { + # Run signing in a job + $job = Start-Job -ScriptBlock { + param($signerPath, $username, $password, $signDescription, $signUrl, $filePath) + + Write-Host "`nšŸ” Signing: $($filePath)" + $logFile = "~/.Acsi/log/$(Split-Path -Leaf $filePath).log" + $arguments = @( + '-ts', + 'http://timestamp.digicert.com', + '-fu', $username, + '-fp', $password, + '-pm', "`"$signDescription`"", + '--program-url', $signUrl, + '-in', "`"$filePath`"" + ) + $process = Start-Process -FilePath "$signerPath" -ArgumentList $arguments -Wait -PassThru -RedirectStandardOutput "$logFile" -NoNewWindow + + return @{ + FilePath = $filePath + ExitCode = $process.ExitCode + LogFile = $logFile + } + } -ArgumentList $signerPath, $username, $password, $signDescription, $signUrl, $file.FullName + $jobs += $job + + # Throttle to max 5 concurrent jobs + if ($jobs.Count -ge 5) { + $completed = $jobs | Wait-Job -Any + $result = $completed | Receive-Job + + # Check result and exit on failure + if ($result.ExitCode -ne 0) { + $jobs | Stop-Job | Remove-Job + Write-Host "āŒ Signing failed for $($result.FilePath) with exit code: $($result.ExitCode)" + exit 1 + } + Write-Host " āœ… Successfully signed $($result.FilePath)" + + $jobs = $jobs | Where-Object { $_.Id -ne $completed.Id } + $completed | Remove-Job + + } + } + # Wait for remaining jobs + $jobs | Wait-Job | Receive-Job | ForEach-Object { + if ($_.ExitCode -ne 0) { + Write-Host "āŒ Signing failed for $($_.FilePath) with exit code: $($_.ExitCode)" + exit 1 + } + Write-Host " āœ… Successfully signed $($_.FilePath)" + } + Write-Host "`nāœ… Successfully signed $($files.Count) files." + env: + SIGNER_PATH: ${{ github.workspace }}/actalis-signer/ActalisCodeSigner.exe + shell: pwsh + - name: Upload log on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: signing-log-${{ runner.arch }} + path: | + ~/.Acsi/log/*.log diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 70e05f892..629cb56be 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -12,6 +12,11 @@ on: description: 'Build debug version with console output' type: boolean default: false + sign: + description: 'Sign binaries' + required: false + type: boolean + default: false push: branches-ignore: - 'dependabot/**' @@ -205,16 +210,15 @@ jobs: New-Item -Path appdir/jpackage-jmod -ItemType Directory & $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod" Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir" - - name: Codesign - uses: skymatic/code-sign-action@v3 + - name: Sign DLLs with Actalis CodeSigner + if: inputs.sign || github.event_name == 'release' + uses: ./.github/actions/win-sign-action with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A - description: Cryptomator - timestampUrl: 'http://timestamp.digicert.com' - folder: appdir + base-dir: 'appdir' + file-extensions: 'dll,exe,ps1' recursive: true + username: ${{ secrets.WIN_CODESIGN_USERNAME }} + password: ${{ secrets.WIN_CODESIGN_PW }} - name: Replace DLLs inside jars with signed ones shell: pwsh run: | @@ -263,15 +267,15 @@ jobs: env: JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir - - name: Codesign MSI - uses: skymatic/code-sign-action@v3 + - name: Sign msi with Actalis CodeSigner + if: inputs.sign || github.event_name == 'release' + uses: ./.github/actions/win-sign-action with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: installer + base-dir: 'installer' + file-extensions: 'msi' + sign-description: 'Cryptomator Installer' + username: ${{ secrets.WIN_CODESIGN_USERNAME }} + password: ${{ secrets.WIN_CODESIGN_PW }} - name: Add possible alpha/beta tags and architecture to installer name run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi - name: Create detached GPG signature with key 615D449FE6E6A235 @@ -374,27 +378,26 @@ jobs: - name: Detach burn engine in preparation to sign run: > wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe - - name: Codesign burn engine - uses: skymatic/code-sign-action@v3 + - name: Sign burn engine with Actalis CodeSigner + if: inputs.sign || github.event_name == 'release' + uses: ./.github/actions/win-sign-action with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: tmp + base-dir: 'tmp' + file-extensions: 'exe' + username: ${{ secrets.WIN_CODESIGN_USERNAME }} + password: ${{ secrets.WIN_CODESIGN_PW }} - name: Reattach signed burn engine to installer run: > wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe - - name: Codesign EXE - uses: skymatic/code-sign-action@v3 + - name: Sign installer with Actalis CodeSigner + if: inputs.sign || github.event_name == 'release' + uses: ./.github/actions/win-sign-action with: - certificate: ${{ secrets.WIN_CODESIGN_P12_BASE64 }} - password: ${{ secrets.WIN_CODESIGN_P12_PW }} - certificatesha1: 5FC94CE149E5B511E621F53A060AC67CBD446B3A - description: Cryptomator Installer - timestampUrl: 'http://timestamp.digicert.com' - folder: installer + base-dir: 'installer' + file-extensions: 'exe' + sign-description: 'Cryptomator Bundle Installer' + username: ${{ secrets.WIN_CODESIGN_USERNAME }} + password: ${{ secrets.WIN_CODESIGN_PW }} - name: Add possible alpha/beta tags to installer name run: mv installer/Cryptomator-Installer.exe Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.executable-suffix }}.exe - name: Create detached GPG signature with key 615D449FE6E6A235