# Unsigned full elevated MSI (Product-Full.wxs + build-full-msi.ps1). # Artifact is NOT Authenticode-signed. Sign step is commented; secret names only. # See platform/windows/wix/CI-SIGN-NOTARY.md and platform/windows/wix/DUAL-SHIP.md § CI artifact. name: windows-msi on: workflow_dispatch: push: paths: - "platform/windows/**" - "cmd/**" - "internal/**" - "go.mod" - "go.sum" - ".github/workflows/windows-msi.yml" pull_request: paths: - "platform/windows/**" - "cmd/**" - "internal/**" - "go.mod" - "go.sum" - ".github/workflows/windows-msi.yml" permissions: contents: read jobs: full-msi-unsigned: runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Install WiX Toolset v3 shell: pwsh run: | choco install wixtoolset --version 3.14.1 -y --no-progress $wixBin = "${env:ProgramFiles(x86)}\WiX Toolset v3.14\bin" if (-not (Test-Path -LiteralPath (Join-Path $wixBin "candle.exe"))) { # Fallback: any WiX v3.* bin on Program Files $found = Get-ChildItem -Path "${env:ProgramFiles(x86)}" -Filter "candle.exe" -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.FullName -match 'WiX Toolset' } | Select-Object -First 1 if ($found) { $wixBin = $found.Directory.FullName } } if (-not (Test-Path -LiteralPath (Join-Path $wixBin "candle.exe"))) { Write-Error "WiX candle.exe not found after install. Full MSI job requires WiX v3." } "WIX_BIN=$wixBin" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 "PATH=$wixBin;$env:PATH" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 Write-Host "WiX bin: $wixBin" & (Join-Path $wixBin "candle.exe") -? - name: Build full MSI (unsigned) shell: pwsh run: | $candle = Get-Command candle -ErrorAction SilentlyContinue $light = Get-Command light -ErrorAction SilentlyContinue if (-not $candle -or -not $light) { Write-Error "WiX candle/light not on PATH — refusing unsigned MSI build (fail closed)." } powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\build-full-msi.ps1 -Arch amd64 $msi = Get-ChildItem -Path platform\windows\wix\out -Filter "VeraePrintStamp-windows-*.msi" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if (-not $msi) { Write-Error "build-full-msi.ps1 did not produce VeraePrintStamp-windows-*.msi under platform/windows/wix/out" } Write-Host "MSI: $($msi.FullName) ($([math]::Round($msi.Length/1MB, 2)) MB)" Get-FileHash -Algorithm SHA256 -Path $msi.FullName | Format-List "MSI_PATH=$($msi.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 "MSI_NAME=$($msi.Name)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 # --- Future Authenticode (NOT enabled) --- # Wire only after certs exist in the org secret store. Never commit PFX or passwords. # Full copy-paste example (gated if: false): .github/workflows/windows-msi-sign.yml.example # Local dry-run (no secrets): platform/windows/scripts/sign-dry-run.ps1 # After sign + CDN upload: docs/PUBLISH-DIGESTS.md + fill-manifest-sha256.ps1 # Secret / var names (placeholders only — see CI-SIGN-NOTARY.md): # secrets.WINDOWS_CERT_PFX_BASE64 # secrets.WINDOWS_CERT_PFX_PASSWORD # secrets.WINDOWS_CERT_THUMBPRINT # optional /sha1 after import # vars.WINDOWS_SIGN_TIMESTAMP_URL # RFC 3161 TSA # secrets.WINDOWS_TRUSTED_SIGNING_TENANT_ID # Path B: Azure Trusted Signing # secrets.WINDOWS_TRUSTED_SIGNING_CLIENT_ID # secrets.WINDOWS_TRUSTED_SIGNING_CLIENT_SECRET # omit when OIDC id-token: write # vars.WINDOWS_TRUSTED_SIGNING_ENDPOINT # vars.WINDOWS_TRUSTED_SIGNING_ACCOUNT # vars.WINDOWS_TRUSTED_SIGNING_CERTIFICATE_PROFILE # # --- Path A: PFX → signtool (commented; secret NAMES only) --- # - name: Import code-signing PFX # if: ${{ false }} # shell: pwsh # env: # WINDOWS_CERT_PFX_BASE64: ${{ secrets.WINDOWS_CERT_PFX_BASE64 }} # WINDOWS_CERT_PFX_PASSWORD: ${{ secrets.WINDOWS_CERT_PFX_PASSWORD }} # run: | # $pfx = Join-Path $env:RUNNER_TEMP "codesign.pfx" # [IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:WINDOWS_CERT_PFX_BASE64)) # $pwd = ConvertTo-SecureString $env:WINDOWS_CERT_PFX_PASSWORD -AsPlainText -Force # Import-PfxCertificate -FilePath $pfx -CertStoreLocation Cert:\CurrentUser\My -Password $pwd | Out-Null # Remove-Item -LiteralPath $pfx -Force -ErrorAction SilentlyContinue # # Never Upload-Artifact the PFX; never echo password or base64. # # - name: signtool Authenticode (EXEs + MSI) # if: ${{ false }} # shell: pwsh # env: # WINDOWS_SIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_SIGN_TIMESTAMP_URL }} # WINDOWS_CERT_THUMBPRINT: ${{ secrets.WINDOWS_CERT_THUMBPRINT }} # run: | # $tsa = $env:WINDOWS_SIGN_TIMESTAMP_URL # if (-not $tsa) { $tsa = "https://timestamp.digicert.com" } # $signtool = (Get-Command signtool.exe -ErrorAction Stop).Source # $stage = "platform\windows\wix\StageFull\bin" # $targets = @( # (Join-Path $stage "verae-print.exe"), # (Join-Path $stage "verae-printhelper.exe"), # $env:MSI_PATH # ) | Where-Object { $_ -and (Test-Path -LiteralPath $_) } # foreach ($t in $targets) { # if ($env:WINDOWS_CERT_THUMBPRINT) { # & $signtool sign /fd SHA256 /tr $tsa /td SHA256 /sha1 $env:WINDOWS_CERT_THUMBPRINT $t # } else { # & $signtool sign /fd SHA256 /tr $tsa /td SHA256 /a $t # } # if ($LASTEXITCODE -ne 0) { Write-Error "signtool sign failed: $t" } # & $signtool verify /pa $t # if ($LASTEXITCODE -ne 0) { Write-Error "signtool verify failed: $t" } # } # # --- Path B: Azure Trusted Signing (prefer over PFX when available) --- # - name: Azure Trusted Signing (EXEs + MSI) # if: ${{ false }} # # uses: azure/trusted-signing-action@v0 # pin a reviewed tag when enabling # # with: # # azure-tenant-id: ${{ secrets.WINDOWS_TRUSTED_SIGNING_TENANT_ID }} # # azure-client-id: ${{ secrets.WINDOWS_TRUSTED_SIGNING_CLIENT_ID }} # # azure-client-secret: ${{ secrets.WINDOWS_TRUSTED_SIGNING_CLIENT_SECRET }} # # endpoint: ${{ vars.WINDOWS_TRUSTED_SIGNING_ENDPOINT }} # # trusted-signing-account-name: ${{ vars.WINDOWS_TRUSTED_SIGNING_ACCOUNT }} # # certificate-profile-name: ${{ vars.WINDOWS_TRUSTED_SIGNING_CERTIFICATE_PROFILE }} # # files: ${{ env.MSI_PATH }} # # file-digest: SHA256 # # timestamp-rfc3161: ${{ vars.WINDOWS_SIGN_TIMESTAMP_URL }} # # timestamp-digest: SHA256 # shell: pwsh # run: | # Write-Error "Trusted Signing not enabled — unsigned artifact only. See windows-msi-sign.yml.example." - name: Upload unsigned MSI artifact uses: actions/upload-artifact@v4 with: name: VeraePrintStamp-windows-msi-unsigned path: platform/windows/wix/out/VeraePrintStamp-windows-*.msi if-no-files-found: error retention-days: 14