diff --git a/.github/scripts/assert-authenticode.ps1 b/.github/scripts/assert-authenticode.ps1 new file mode 100644 index 0000000000..b1609091d9 --- /dev/null +++ b/.github/scripts/assert-authenticode.ps1 @@ -0,0 +1,44 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string[]]$Path, + + [string]$ExpectedCertificateSha1 = $env:VSIGN_CERT_HASH +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +if ([string]::IsNullOrWhiteSpace($ExpectedCertificateSha1)) { + throw "The expected Authenticode certificate SHA1 is not configured" +} + +$expectedSha1 = $ExpectedCertificateSha1.Replace(" ", "").ToUpperInvariant() +if ($expectedSha1 -notmatch '^[0-9A-F]{40}$') { + throw "The expected Authenticode certificate SHA1 is invalid" +} + +foreach ($candidate in $Path) { + $resolvedPath = (Resolve-Path -LiteralPath $candidate -ErrorAction Stop).Path + if (!(Test-Path -LiteralPath $resolvedPath -PathType Leaf)) { + throw "Authenticode target is not a file: $resolvedPath" + } + + $signature = Get-AuthenticodeSignature -LiteralPath $resolvedPath + if ($null -eq $signature.SignerCertificate) { + throw "No Authenticode signature was found on $resolvedPath" + } + + $actualSha1 = $signature.SignerCertificate.Thumbprint.Replace(" ", "").ToUpperInvariant() + if ($actualSha1 -ne $expectedSha1) { + throw "Signing certificate mismatch on $resolvedPath. Expected $expectedSha1, got $actualSha1" + } + if ($signature.Status -ne "Valid") { + throw "Authenticode signature status for $resolvedPath is $($signature.Status): $($signature.StatusMessage)" + } + if ($null -eq $signature.TimeStamperCertificate) { + throw "The Authenticode signature on $resolvedPath does not contain a timestamp certificate" + } + + Write-Host "Verified Authenticode signature: $resolvedPath" +} diff --git a/.github/scripts/setup-vsign.ps1 b/.github/scripts/setup-vsign.ps1 new file mode 100644 index 0000000000..f448dd3b3a --- /dev/null +++ b/.github/scripts/setup-vsign.ps1 @@ -0,0 +1,106 @@ +[CmdletBinding()] +param() + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$requiredVariables = @( + "GITHUB_ENV", + "RUNNER_TEMP", + "VSIGN_CERT_HASH", + "VSIGN_OPERATOR_PFX_B64", + "VSIGN_OPERATOR_PWD", + "VSIGN_OPERATOR_SHA1", + "VSIGN_SERVER", + "VSIGN_TOOL_SHA256" +) + +foreach ($variableName in $requiredVariables) { + $value = [Environment]::GetEnvironmentVariable($variableName) + if ([string]::IsNullOrWhiteSpace($value)) { + throw "Required VSign configuration is missing: $variableName" + } +} + +$serverUri = $null +if (![Uri]::TryCreate($env:VSIGN_SERVER, [UriKind]::Absolute, [ref]$serverUri) -or $serverUri.Scheme -ne "https") { + throw "VSIGN_SERVER must be an absolute HTTPS URL" +} + +$expectedToolSha256 = $env:VSIGN_TOOL_SHA256.Replace(" ", "").ToUpperInvariant() +$expectedOperatorSha1 = $env:VSIGN_OPERATOR_SHA1.Replace(" ", "").ToUpperInvariant() +$expectedCertificateSha1 = $env:VSIGN_CERT_HASH.Replace(" ", "").ToUpperInvariant() +if ($expectedToolSha256 -notmatch '^[0-9A-F]{64}$') { + throw "VSIGN_TOOL_SHA256 is invalid" +} +if ($expectedOperatorSha1 -notmatch '^[0-9A-F]{40}$') { + throw "VSIGN_OPERATOR_SHA1 is invalid" +} +if ($expectedCertificateSha1 -notmatch '^[0-9A-F]{40}$') { + throw "VSIGN_CERT_HASH is invalid" +} + +$runId = if ([string]::IsNullOrWhiteSpace($env:GITHUB_RUN_ID)) { "local" } else { $env:GITHUB_RUN_ID } +$runAttempt = if ([string]::IsNullOrWhiteSpace($env:GITHUB_RUN_ATTEMPT)) { "1" } else { $env:GITHUB_RUN_ATTEMPT } +$workDir = Join-Path $env:RUNNER_TEMP "vsign-$runId-$runAttempt" +$operatorDir = Join-Path $workDir "operator" +$operatorFile = Join-Path $operatorDir "dbx-github-ci@ssigncode.pfx" +$toolPath = Join-Path $workDir "ssigncode.exe" +$toolUrl = "https://github.com/g5wsg/vsign-github-test1/releases/download/vsign-cli-v1/ssigncode.exe" + +New-Item -ItemType Directory -Force -Path $operatorDir | Out-Null +Invoke-WebRequest -Uri $toolUrl -OutFile $toolPath + +$actualToolSha256 = (Get-FileHash -LiteralPath $toolPath -Algorithm SHA256).Hash +if ($actualToolSha256 -ne $expectedToolSha256) { + throw "VSign CLI SHA256 mismatch. Expected $expectedToolSha256, got $actualToolSha256" +} + +& $toolPath version +if ($LASTEXITCODE -ne 0) { + throw "VSign CLI version check failed with exit code $LASTEXITCODE" +} + +try { + $pfxBytes = [Convert]::FromBase64String($env:VSIGN_OPERATOR_PFX_B64.Trim()) + [IO.File]::WriteAllBytes($operatorFile, $pfxBytes) + $flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet + $operatorCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( + $operatorFile, + $env:VSIGN_OPERATOR_PWD, + $flags + ) +} +catch { + throw "Cannot restore or decrypt the VSign operator PFX: $($_.Exception.Message)" +} + +try { + $actualOperatorSha1 = $operatorCertificate.Thumbprint.Replace(" ", "").ToUpperInvariant() + if ($actualOperatorSha1 -ne $expectedOperatorSha1) { + throw "Operator certificate mismatch. Expected $expectedOperatorSha1, got $actualOperatorSha1" + } + if (!$operatorCertificate.HasPrivateKey) { + throw "The operator PFX does not contain a private key" + } + if ($operatorCertificate.NotAfter.ToUniversalTime() -le [DateTime]::UtcNow) { + throw "The operator certificate has expired" + } +} +finally { + $operatorCertificate.Dispose() +} + +$githubEnvironment = [ordered]@{ + VSIGN_WORK_DIR = $workDir + VSIGN_CLI = $toolPath + SSIGNCODE_OPERATOR_STORE = "file" + SSIGNCODE_OPERATOR_DIR = $operatorDir + SSIGNCODE_OPERATOR_FILE = $operatorFile +} + +foreach ($entry in $githubEnvironment.GetEnumerator()) { + "$($entry.Key)=$($entry.Value)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append +} + +Write-Host "VSign CLI and operator certificate are ready." diff --git a/.github/scripts/vsign-sign.ps1 b/.github/scripts/vsign-sign.ps1 new file mode 100644 index 0000000000..7d0f53b496 --- /dev/null +++ b/.github/scripts/vsign-sign.ps1 @@ -0,0 +1,72 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true, Position = 0)] + [string]$FilePath +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$resolvedPath = (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).Path +if (!(Test-Path -LiteralPath $resolvedPath -PathType Leaf)) { + throw "VSign target is not a file: $resolvedPath" +} + +$extension = [IO.Path]::GetExtension($resolvedPath).ToLowerInvariant() +if ($extension -eq ".dll") { + Write-Host "Skipping Authenticode signing for DLL: $resolvedPath" + return +} +if ($extension -notin @(".exe", ".msi")) { + throw "Unsupported VSign target type: $extension" +} + +$requiredVariables = @( + "SSIGNCODE_OPERATOR_DIR", + "SSIGNCODE_OPERATOR_FILE", + "SSIGNCODE_OPERATOR_PWD", + "SSIGNCODE_OPERATOR_STORE", + "VSIGN_CERT_HASH", + "VSIGN_CLI", + "VSIGN_KEY_PIN", + "VSIGN_SERVER" +) + +foreach ($variableName in $requiredVariables) { + $value = [Environment]::GetEnvironmentVariable($variableName) + if ([string]::IsNullOrWhiteSpace($value)) { + throw "Required VSign runtime configuration is missing: $variableName" + } +} + +$directory = [IO.Path]::GetDirectoryName($resolvedPath) +$fileName = [IO.Path]::GetFileNameWithoutExtension($resolvedPath) +$signedPath = Join-Path $directory "$fileName.vsign-$([Guid]::NewGuid().ToString('N'))$extension" + +try { + & $env:VSIGN_CLI vsign ` + -s $env:VSIGN_SERVER ` + --cert_from csign ` + -u dbx-github-ci ` + --cert_hash $env:VSIGN_CERT_HASH ` + -k $env:VSIGN_KEY_PIN ` + --hash sha256 ` + -i $resolvedPath ` + -o $signedPath + + if ($LASTEXITCODE -ne 0) { + throw "VSign failed for $resolvedPath with exit code $LASTEXITCODE" + } + if (!(Test-Path -LiteralPath $signedPath -PathType Leaf)) { + throw "VSign did not create the signed output for $resolvedPath" + } + + & "$PSScriptRoot/assert-authenticode.ps1" -Path $signedPath + Move-Item -LiteralPath $signedPath -Destination $resolvedPath -Force + Write-Host "Signed with TrustAsia VSign: $resolvedPath" +} +finally { + if (Test-Path -LiteralPath $signedPath) { + Remove-Item -LiteralPath $signedPath -Force + } +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6c405e50a..bc4579a0ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -130,12 +130,6 @@ jobs: target: x86_64-unknown-linux-gnu - platform: ubuntu-22.04-arm target: aarch64-unknown-linux-gnu - - platform: windows-2022 - target: x86_64-pc-windows-msvc - arch: x64 - - platform: windows-2022 - target: aarch64-pc-windows-msvc - arch: arm64 runs-on: ${{ matrix.platform }} env: @@ -263,12 +257,207 @@ jobs: shell: bash run: ${SCCACHE_PATH} --show-stats + build-windows: + needs: release-ready + environment: release-signing + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-pc-windows-msvc + arch: x64 + sign_windows: true + - target: aarch64-pc-windows-msvc + arch: arm64 + sign_windows: false + runs-on: windows-2022 + env: + CARGO_INCREMENTAL: "0" + RUSTC_WRAPPER: sccache + SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }} + VSIGN_CERT_HASH: ${{ vars.VSIGN_CERT_HASH }} + VSIGN_OPERATOR_SHA1: ${{ vars.VSIGN_OPERATOR_SHA1 }} + VSIGN_SERVER: ${{ vars.VSIGN_SERVER }} + VSIGN_TOOL_SHA256: ${{ vars.VSIGN_TOOL_SHA256 }} + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Install frontend dependencies + run: pnpm install + + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.97.1 + with: + targets: ${{ matrix.target }} + + - name: Setup sccache + uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 + with: + version: "v0.10.0" + + - name: Configure S3 sccache + if: env.SCCACHE_GHA_ENABLED != 'true' + shell: bash + env: + CACHE_BUCKET: ${{ secrets.SCCACHE_S3_BUCKET }} + CACHE_ENDPOINT: ${{ secrets.SCCACHE_S3_ENDPOINT }} + CACHE_REGION: ${{ secrets.SCCACHE_S3_REGION }} + CACHE_KEY_PREFIX: ${{ secrets.SCCACHE_S3_KEY_PREFIX }} + CACHE_ACCESS_KEY_ID: ${{ secrets.SCCACHE_S3_ACCESS_KEY_ID }} + CACHE_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_S3_SECRET_ACCESS_KEY }} + run: | + { + echo "SCCACHE_BUCKET=${CACHE_BUCKET}" + echo "SCCACHE_ENDPOINT=${CACHE_ENDPOINT}" + echo "SCCACHE_REGION=${CACHE_REGION}" + echo "SCCACHE_S3_KEY_PREFIX=${CACHE_KEY_PREFIX}" + echo "SCCACHE_S3_USE_SSL=true" + echo "AWS_ACCESS_KEY_ID=${CACHE_ACCESS_KEY_ID}" + echo "AWS_SECRET_ACCESS_KEY=${CACHE_SECRET_ACCESS_KEY}" + } >> "$GITHUB_ENV" + + - name: Compute Rust dependency hash + id: deps-hash + shell: bash + run: | + { + find . -name Cargo.toml -not -path './target/*' -print0 \ + | sort -z \ + | xargs -0 sed -E '/^version = /d' + grep -v '^version = ' Cargo.lock + } | sha256sum | cut -d' ' -f1 | { + read -r hash + echo "hash=${hash:0:20}" >> "$GITHUB_OUTPUT" + } + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "./ -> target" + shared-key: release-${{ matrix.target }}-${{ steps.deps-hash.outputs.hash }} + add-rust-environment-hash-key: false + cache-targets: false + cache-on-failure: true + + - name: Setup Tauri signing key + shell: bash + run: | + echo "${{ secrets.TAURI_SIGNING_PRIVATE_KEY_BASE64 }}" | base64 --decode > "$RUNNER_TEMP/updater.key" + KEY_B64=$(base64 < "$RUNNER_TEMP/updater.key" | tr -d '\r\n') + echo "TAURI_SIGNING_PRIVATE_KEY=$KEY_B64" >> "$GITHUB_ENV" + if [ -n "${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}" ]; then + echo "TAURI_SIGNING_PRIVATE_KEY_PASSWORD=${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}" >> "$GITHUB_ENV" + fi + + - name: Setup TrustAsia VSign + if: matrix.sign_windows + shell: pwsh + env: + VSIGN_OPERATOR_PFX_B64: ${{ secrets.VSIGN_OPERATOR_PFX_B64 }} + VSIGN_OPERATOR_PWD: ${{ secrets.VSIGN_OPERATOR_PWD }} + run: ./.github/scripts/setup-vsign.ps1 + + - name: Build and sign Tauri app + if: matrix.sign_windows + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SSIGNCODE_OPERATOR_PWD: ${{ secrets.VSIGN_OPERATOR_PWD }} + VSIGN_KEY_PIN: ${{ secrets.VSIGN_KEY_PIN }} + with: + tagName: ${{ github.ref_name }} + releaseDraft: true + args: --target ${{ matrix.target }} --config src-tauri/tauri.vsign.conf.json + + - name: Build unsigned Tauri app + if: ${{ !matrix.sign_windows }} + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tagName: ${{ github.ref_name }} + releaseDraft: true + args: --target ${{ matrix.target }} + + - name: Verify Windows bundles + shell: pwsh + run: | + $bundleRoot = "target/${{ matrix.target }}/release/bundle" + $nsisInstallers = @( + Get-ChildItem (Join-Path $bundleRoot "nsis") -Filter "*.exe" -File -ErrorAction SilentlyContinue + ) + $msiInstallers = @( + Get-ChildItem (Join-Path $bundleRoot "msi") -Filter "*.msi" -File -ErrorAction SilentlyContinue + ) + if ($nsisInstallers.Count -eq 0) { + throw "No NSIS installer was produced for ${{ matrix.target }}" + } + if ($msiInstallers.Count -eq 0) { + throw "No MSI installer was produced for ${{ matrix.target }}" + } + if ("${{ matrix.sign_windows }}" -eq "true") { + ./.github/scripts/assert-authenticode.ps1 -Path @( + $nsisInstallers.FullName + $msiInstallers.FullName + ) + } + + - name: Upload Windows WebView2 offline installer + shell: pwsh + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SSIGNCODE_OPERATOR_PWD: ${{ matrix.sign_windows && secrets.VSIGN_OPERATOR_PWD || '' }} + VSIGN_KEY_PIN: ${{ matrix.sign_windows && secrets.VSIGN_KEY_PIN || '' }} + run: | + $version = "${env:GITHUB_REF_NAME}".TrimStart("v") + $arch = "${{ matrix.arch }}" + $bundleDir = "target/${{ matrix.target }}/release/bundle/nsis" + $offlineName = "DBX_${version}_${arch}-webview2-offline-setup.exe" + $before = @{} + + if (Test-Path $bundleDir) { + Get-ChildItem $bundleDir -Filter "*.exe" | ForEach-Object { $before[$_.FullName] = $_.LastWriteTimeUtc } + } + + if ("${{ matrix.sign_windows }}" -eq "true") { + pnpm tauri bundle --bundles nsis --target ${{ matrix.target }} ` + --config src-tauri/tauri.webview2-offline.conf.json ` + --config src-tauri/tauri.vsign.conf.json + } else { + pnpm tauri bundle --bundles nsis --target ${{ matrix.target }} ` + --config src-tauri/tauri.webview2-offline.conf.json + } + + $installer = Get-ChildItem $bundleDir -Filter "*.exe" | + Where-Object { !$before.ContainsKey($_.FullName) -or $_.LastWriteTimeUtc -gt $before[$_.FullName] } | + Sort-Object LastWriteTimeUtc -Descending | + Select-Object -First 1 + + if (!$installer) { + throw "Missing WebView2 offline installer in ${bundleDir}" + } + + if ("${{ matrix.sign_windows }}" -eq "true") { + ./.github/scripts/assert-authenticode.ps1 -Path $installer.FullName + } + Copy-Item $installer.FullName $offlineName -Force + gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber + - name: Upload Windows portable ZIP - if: matrix.arch == 'x64' || matrix.arch == 'arm64' timeout-minutes: 20 shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SSIGNCODE_OPERATOR_PWD: ${{ matrix.sign_windows && secrets.VSIGN_OPERATOR_PWD || '' }} + VSIGN_KEY_PIN: ${{ matrix.sign_windows && secrets.VSIGN_KEY_PIN || '' }} run: | $version = "${env:GITHUB_REF_NAME}".TrimStart("v") $arch = "${{ matrix.arch }}" @@ -280,12 +469,15 @@ jobs: $appVersion = ($cargoMetadata.packages | Where-Object { $_.name -eq "dbx" } | Select-Object -First 1).version if (!(Test-Path $exePath)) { - Write-Error "Missing Windows executable: $exePath" - exit 1 + throw "Missing Windows executable: $exePath" } if (!$appVersion -or $appVersion -ne $version) { - Write-Error "Release tag version $version does not match the built DBX package version $appVersion" - exit 1 + throw "Release tag version $version does not match the built DBX package version $appVersion" + } + + if ("${{ matrix.sign_windows }}" -eq "true") { + ./.github/scripts/vsign-sign.ps1 -FilePath $exePath + ./.github/scripts/assert-authenticode.ps1 -Path $exePath } New-Item -ItemType Directory -Force -Path $portableDir | Out-Null @@ -315,51 +507,43 @@ jobs: $signatureName = "${zipName}.sig" if (!(Test-Path $signatureName)) { - Write-Error "Missing portable update signature: $signatureName" - exit 1 + throw "Missing portable update signature: $signatureName" } gh release upload "${env:GITHUB_REF_NAME}" $zipName $signatureName --repo "${env:GITHUB_REPOSITORY}" --clobber - - name: Upload Windows WebView2 offline installer - if: matrix.arch == 'x64' || matrix.arch == 'arm64' + - name: Show sccache stats + if: always() + continue-on-error: true + shell: bash + run: ${SCCACHE_PATH} --show-stats + + - name: Remove temporary VSign files + if: ${{ always() && matrix.sign_windows }} shell: pwsh - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - $version = "${env:GITHUB_REF_NAME}".TrimStart("v") - $arch = "${{ matrix.arch }}" - $bundleDir = "target/${{ matrix.target }}/release/bundle/nsis" - $offlineName = "DBX_${version}_${arch}-webview2-offline-setup.exe" - $before = @{} - - if (Test-Path $bundleDir) { - Get-ChildItem $bundleDir -Filter "*.exe" | ForEach-Object { $before[$_.FullName] = $_.LastWriteTimeUtc } + $workDir = if ([string]::IsNullOrWhiteSpace($env:VSIGN_WORK_DIR)) { + Join-Path $env:RUNNER_TEMP "vsign-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" + } else { + $env:VSIGN_WORK_DIR } - - pnpm tauri bundle --bundles nsis --target ${{ matrix.target }} --config src-tauri/tauri.webview2-offline.conf.json - - $installer = Get-ChildItem $bundleDir -Filter "*.exe" | - Where-Object { !$before.ContainsKey($_.FullName) -or $_.LastWriteTimeUtc -gt $before[$_.FullName] } | - Sort-Object LastWriteTimeUtc -Descending | - Select-Object -First 1 - - if (!$installer) { - Write-Error "Missing WebView2 offline installer in ${bundleDir}" - exit 1 + if (Test-Path -LiteralPath $workDir) { + Remove-Item -LiteralPath $workDir -Recurse -Force } - Copy-Item $installer.FullName $offlineName -Force - gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber - build-windows-7-offline: needs: release-ready runs-on: windows-2022 + environment: release-signing env: CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: ${{ secrets.SCCACHE_S3_BUCKET == '' && 'true' || 'false' }} RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static + VSIGN_CERT_HASH: ${{ vars.VSIGN_CERT_HASH }} + VSIGN_OPERATOR_SHA1: ${{ vars.VSIGN_OPERATOR_SHA1 }} + VSIGN_SERVER: ${{ vars.VSIGN_SERVER }} + VSIGN_TOOL_SHA256: ${{ vars.VSIGN_TOOL_SHA256 }} steps: - uses: actions/checkout@v5 @@ -443,10 +627,19 @@ jobs: shell: pwsh run: ./.github/scripts/assert-win7-pe-compat.ps1 -BinaryPath target/x86_64-win7-windows-msvc/release/dbx.exe + - name: Setup TrustAsia VSign + shell: pwsh + env: + VSIGN_OPERATOR_PFX_B64: ${{ secrets.VSIGN_OPERATOR_PFX_B64 }} + VSIGN_OPERATOR_PWD: ${{ secrets.VSIGN_OPERATOR_PWD }} + run: ./.github/scripts/setup-vsign.ps1 + - name: Bundle and upload Windows 7 fixed-runtime installer shell: pwsh env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SSIGNCODE_OPERATOR_PWD: ${{ secrets.VSIGN_OPERATOR_PWD }} + VSIGN_KEY_PIN: ${{ secrets.VSIGN_KEY_PIN }} run: | $version = "${env:GITHUB_REF_NAME}".TrimStart("v") $bundleDir = "target/x86_64-win7-windows-msvc/release/bundle/nsis" @@ -459,7 +652,9 @@ jobs: exit 1 } - pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc --config src-tauri/tauri.webview2-win7-fixed.conf.json + pnpm tauri bundle --bundles nsis --target x86_64-win7-windows-msvc ` + --config src-tauri/tauri.webview2-win7-fixed.conf.json ` + --config src-tauri/tauri.vsign.conf.json $installer = Get-ChildItem $bundleDir -Filter "*.exe" | Sort-Object LastWriteTimeUtc -Descending | @@ -470,6 +665,7 @@ jobs: } ./.github/scripts/assert-win7-installer-content.ps1 -InstallerPath $installer.FullName + ./.github/scripts/assert-authenticode.ps1 -Path $installer.FullName Copy-Item $installer.FullName $offlineName -Force gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber @@ -480,6 +676,19 @@ jobs: shell: bash run: ${SCCACHE_PATH} --show-stats + - name: Remove temporary VSign files + if: always() + shell: pwsh + run: | + $workDir = if ([string]::IsNullOrWhiteSpace($env:VSIGN_WORK_DIR)) { + Join-Path $env:RUNNER_TEMP "vsign-$env:GITHUB_RUN_ID-$env:GITHUB_RUN_ATTEMPT" + } else { + $env:VSIGN_WORK_DIR + } + if (Test-Path -LiteralPath $workDir) { + Remove-Item -LiteralPath $workDir -Recurse -Force + } + static-browser: # Fully static musl builds of the browser (dbx-web) variant. No glibc # dependency, so the tarball runs on any Linux distribution (verified in @@ -583,7 +792,7 @@ jobs: --repo "${GITHUB_REPOSITORY}" --clobber cleanup-release-signatures: - needs: [build, build-windows-7-offline] + needs: [build, build-windows, build-windows-7-offline] runs-on: ubuntu-latest steps: - name: Remove standalone updater signature assets diff --git a/src-tauri/tauri.vsign.conf.json b/src-tauri/tauri.vsign.conf.json new file mode 100644 index 0000000000..77cac71682 --- /dev/null +++ b/src-tauri/tauri.vsign.conf.json @@ -0,0 +1,19 @@ +{ + "bundle": { + "windows": { + "signCommand": { + "cmd": "pwsh", + "args": [ + "-NoLogo", + "-NoProfile", + "-NonInteractive", + "-ExecutionPolicy", + "Bypass", + "-File", + ".github/scripts/vsign-sign.ps1", + "%1" + ] + } + } + } +} diff --git a/src-tauri/windows/nsis/installer.nsi b/src-tauri/windows/nsis/installer.nsi index 18bbc01c03..fff85552cc 100644 --- a/src-tauri/windows/nsis/installer.nsi +++ b/src-tauri/windows/nsis/installer.nsi @@ -60,7 +60,6 @@ ${StrLoc} !define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}" !define MANUKEY "Software\${MANUFACTURER}" !define MANUPRODUCTKEY "${MANUKEY}\${PRODUCTNAME}" -!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}" !define ESTIMATEDSIZE "{{estimated_size}}" !define STARTMENUFOLDER "{{start_menu_folder}}" !searchreplace WEBVIEW2LOADERSRCPATH "${MAINBINARYSRCPATH}" "\${MAINBINARYNAME}.exe" "\WebView2Loader.dll" @@ -114,11 +113,6 @@ VIAddVersionKey "ProductVersion" "${VERSION}" # additional plugins !addplugindir "${ADDITIONALPLUGINSPATH}" -; Uninstaller signing command -!if "${UNINSTALLERSIGNCOMMAND}" != "" - !uninstfinalize '${UNINSTALLERSIGNCOMMAND}' -!endif - ; Handle install mode, `perUser`, `perMachine` or `both` !if "${INSTALLMODE}" == "perMachine" RequestExecutionLevel admin