Skip to content

Commit 3a25af4

Browse files
Add Windows release signing workflow (#569)
* Add Windows release signing workflow * Fix Windows signing workflow and remove docs file Skip signing gracefully when certificate secrets are not configured instead of failing the entire release. Add certutil exit code check to catch malformed base64 certificates early. Remove unsupported tsp field from Tauri config injection. Remove docs/contributing.mdx since documentation now lives in the www repository. --------- Co-authored-by: Mehmet Özgül <mehmetozguldev@gmail.com>
1 parent a3bb85a commit 3a25af4

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,57 @@ jobs:
198198
fi
199199
done < <(find src/extensions/bundled -type f -perm -111 -print0)
200200
201+
- name: Import Windows Certificate (Windows only)
202+
if: matrix.os == 'windows'
203+
shell: pwsh
204+
env:
205+
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
206+
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
207+
WINDOWS_TIMESTAMP_URL: ${{ secrets.WINDOWS_TIMESTAMP_URL }}
208+
run: |
209+
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE) -or
210+
[string]::IsNullOrWhiteSpace($env:WINDOWS_CERTIFICATE_PASSWORD)) {
211+
Write-Host "Windows signing secrets not configured, skipping code signing."
212+
"WINDOWS_SIGNING_SKIP=true" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
213+
exit 0
214+
}
215+
216+
New-Item -ItemType Directory -Force -Path certificate | Out-Null
217+
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
218+
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
219+
if ($LASTEXITCODE -ne 0) {
220+
throw "Failed to decode Windows certificate from base64."
221+
}
222+
223+
$securePassword = ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force
224+
$importedCertificates = Import-PfxCertificate `
225+
-FilePath certificate/certificate.pfx `
226+
-CertStoreLocation Cert:\CurrentUser\My `
227+
-Password $securePassword
228+
229+
$signingCertificate = $importedCertificates |
230+
Where-Object { $_.HasPrivateKey } |
231+
Select-Object -First 1
232+
233+
if (-not $signingCertificate) {
234+
throw "Failed to import a Windows signing certificate with a private key."
235+
}
236+
237+
"WINDOWS_CERTIFICATE_THUMBPRINT=$($signingCertificate.Thumbprint)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
238+
"WINDOWS_DIGEST_ALGORITHM=sha256" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
239+
240+
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_TIMESTAMP_URL)) {
241+
"WINDOWS_TIMESTAMP_URL=http://time.certum.pl/" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
242+
} else {
243+
"WINDOWS_TIMESTAMP_URL=$($env:WINDOWS_TIMESTAMP_URL)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
244+
}
245+
246+
Remove-Item -Recurse -Force certificate
247+
248+
- name: Prepare Tauri Windows signing (Windows only)
249+
if: matrix.os == 'windows' && env.WINDOWS_SIGNING_SKIP != 'true'
250+
run: node scripts/windows/prepare-tauri-signing.mjs
251+
201252
- uses: tauri-apps/tauri-action@v0
202253
env:
203254
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { readFileSync, writeFileSync } from "node:fs";
2+
import { resolve } from "node:path";
3+
4+
const tauriConfigPath = resolve(process.cwd(), "src-tauri/tauri.conf.json");
5+
6+
const certificateThumbprint = process.env.WINDOWS_CERTIFICATE_THUMBPRINT?.trim();
7+
const timestampUrl = process.env.WINDOWS_TIMESTAMP_URL?.trim() || "http://time.certum.pl/";
8+
const digestAlgorithm = process.env.WINDOWS_DIGEST_ALGORITHM?.trim() || "sha256";
9+
10+
if (!certificateThumbprint) {
11+
throw new Error("WINDOWS_CERTIFICATE_THUMBPRINT is required to prepare Tauri Windows signing.");
12+
}
13+
14+
const tauriConfig = JSON.parse(readFileSync(tauriConfigPath, "utf8"));
15+
tauriConfig.bundle ??= {};
16+
tauriConfig.bundle.windows ??= {};
17+
tauriConfig.bundle.windows.certificateThumbprint = certificateThumbprint;
18+
tauriConfig.bundle.windows.digestAlgorithm = digestAlgorithm;
19+
tauriConfig.bundle.windows.timestampUrl = timestampUrl;
20+
21+
writeFileSync(tauriConfigPath, `${JSON.stringify(tauriConfig, null, 2)}\n`);
22+
23+
const maskedThumbprint = `${certificateThumbprint.slice(0, 8)}...${certificateThumbprint.slice(-6)}`;
24+
console.log(`Prepared Windows signing for thumbprint ${maskedThumbprint}`);
25+
console.log(`Timestamp URL: ${timestampUrl}`);

0 commit comments

Comments
 (0)