Skip to content

Commit 8ec2142

Browse files
enhance: add code signing steps and documentation for Windows artifacts
- Introduced optional code signing steps in the GitHub Actions workflow for the engine, dashboard, and installer. - Updated building.md to include details on obtaining a code signing certificate and the importance of signing for Windows SmartScreen. - Enhanced the installer script with additional metadata fields for better user experience and support.
1 parent dd069d8 commit 8ec2142

4 files changed

Lines changed: 202 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ jobs:
173173
Copy-Item $cfg "dist\engine-config.json" -Force
174174
Write-Host "engine-config.json -> Release + dist"
175175
176+
- name: Code sign engine + dashboard (optional)
177+
shell: pwsh
178+
env:
179+
WINDOWS_CODESIGN_PFX_BASE64: ${{ secrets.WINDOWS_CODESIGN_PFX_BASE64 }}
180+
WINDOWS_CODESIGN_PFX_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PFX_PASSWORD }}
181+
run: |
182+
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CODESIGN_PFX_BASE64)) {
183+
Write-Host "Skipping Authenticode signing (set repo secret WINDOWS_CODESIGN_PFX_BASE64 + WINDOWS_CODESIGN_PFX_PASSWORD; see docs/architecture/building.md)."
184+
exit 0
185+
}
186+
$pfxPath = Join-Path $env:RUNNER_TEMP "sentracore-codesign.pfx"
187+
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_CODESIGN_PFX_BASE64))
188+
& ".\scripts\sign-windows-artifacts.ps1" -PfxPath $pfxPath -RepoRoot (Get-Location).Path
189+
Remove-Item -LiteralPath $pfxPath -Force
190+
176191
- name: Install Inno Setup
177192
shell: pwsh
178193
run: |
@@ -186,6 +201,21 @@ jobs:
186201
& $iscc "installer\\sentracore.iss"
187202
Get-ChildItem "dist" -Filter "SentraCore_Setup_v*.exe" | Select-Object -First 1 | ForEach-Object { $_.FullName }
188203
204+
- name: Code sign installer (optional)
205+
shell: pwsh
206+
env:
207+
WINDOWS_CODESIGN_PFX_BASE64: ${{ secrets.WINDOWS_CODESIGN_PFX_BASE64 }}
208+
WINDOWS_CODESIGN_PFX_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PFX_PASSWORD }}
209+
run: |
210+
if ([string]::IsNullOrWhiteSpace($env:WINDOWS_CODESIGN_PFX_BASE64)) {
211+
Write-Host "Skipping installer signing (same secrets as engine/dashboard step)."
212+
exit 0
213+
}
214+
$pfxPath = Join-Path $env:RUNNER_TEMP "sentracore-codesign-installer.pfx"
215+
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_CODESIGN_PFX_BASE64))
216+
& ".\scripts\sign-windows-artifacts.ps1" -PfxPath $pfxPath -RepoRoot (Get-Location).Path -InstallerOnly
217+
Remove-Item -LiteralPath $pfxPath -Force
218+
189219
- name: Upload installer artifact
190220
uses: actions/upload-artifact@v4
191221
with:

docs/architecture/building.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ Install:
224224

225225
Official website:
226226

227-
[Inno Setup](https://jrsoftware.org/isinfo.php?utm_source=chatgpt.com)
227+
[Inno Setup](https://jrsoftware.org/isinfo.php)
228228

229229
---
230230

@@ -240,6 +240,32 @@ The installer may:
240240

241241
---
242242

243+
## Windows SmartScreen and "Unknown publisher"
244+
245+
Windows Defender SmartScreen and the UAC prompt show **Unknown publisher** when the installer (or the executables inside it) are **not signed** with a **publicly trusted** Authenticode (code signing) certificate.
246+
`AppPublisher` and related Inno Setup fields improve **Add/Remove Programs** and installer metadata, but they **do not** replace a digital signature.
247+
248+
What actually fixes the warning:
249+
250+
1. Obtain a **code signing certificate** from a CA that chains to the Microsoft Trusted Root Program (for example standard **OV** or **EV** code signing). **EV** certificates are often associated with faster SmartScreen reputation for brand-new binaries.
251+
2. Sign **`SentraCoreEngine.exe`**, **`sentracore_dashboard.exe`**, and the **setup `.exe`** with `signtool` (Windows SDK), including an **RFC 3161 timestamp** so signatures stay valid after the cert expires.
252+
3. For releases built in GitHub Actions, add repository secrets (see below) so the workflow can sign before uploading artifacts.
253+
254+
Local signing (PFX on your machine):
255+
256+
```powershell
257+
$env:WINDOWS_CODESIGN_PFX_PASSWORD = '<your-pfx-password>'
258+
.\scripts\sign-windows-artifacts.ps1 -PfxPath C:\path\to\codesign.pfx
259+
# After Inno Setup produces dist\SentraCore_Setup_v*.exe:
260+
.\scripts\sign-windows-artifacts.ps1 -PfxPath C:\path\to\codesign.pfx -InstallerOnly
261+
```
262+
263+
GitHub Actions (optional): create secrets **`WINDOWS_CODESIGN_PFX_BASE64`** (base64-encoded `.pfx` file) and **`WINDOWS_CODESIGN_PFX_PASSWORD`**. When `WINDOWS_CODESIGN_PFX_BASE64` is set, the release workflow signs the engine and dashboard before packaging, then signs the installer afterward. Protect these secrets with restricted **environments** / branch policies where possible.
264+
265+
References: [Microsoft: Sign Windows apps](https://learn.microsoft.com/en-us/windows/win32/appxpkg/how-to-sign-a-package-using-signtool), [Introduction to code signing](https://learn.microsoft.com/en-us/windows/win32/seccrypto/cryptography-tools).
266+
267+
---
268+
243269
# Recommended Packaging Workflow
244270

245271
```text
@@ -288,6 +314,7 @@ Before publishing a release, verify:
288314
- notifications operate correctly
289315
- installer shortcuts work
290316
- uninstall removes runtime artifacts cleanly
317+
- Windows release: setup and shipped `.exe` files show a trusted publisher after code signing (see **Windows SmartScreen and "Unknown publisher"** above)
291318

292319
---
293320

installer/sentracore.iss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
AppName=SentraCore
33
AppVersion=0.0.1
44
AppPublisher=SentraCore Development Team
5+
AppPublisherURL=https://github.com/AsieduDevelopmentHub/SentraCore
6+
AppSupportURL=https://github.com/AsieduDevelopmentHub/SentraCore/issues
7+
AppUpdatesURL=https://github.com/AsieduDevelopmentHub/SentraCore/releases
8+
AppCopyright=Copyright (C) SentraCore contributors
9+
; "Unknown publisher" in Windows SmartScreen is resolved by Authenticode signing
10+
; (see docs/architecture/building.md), not by metadata alone.
511
DefaultDirName={autopf}\SentraCore
612
DefaultGroupName=SentraCore
713
OutputDir=..\dist

scripts/sign-windows-artifacts.ps1

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#Requires -Version 5.1
2+
<#
3+
.SYNOPSIS
4+
Authenticode-sign SentraCore Windows binaries and the Inno Setup installer.
5+
6+
.DESCRIPTION
7+
Use after building the engine (dist\SentraCoreEngine.exe), the Flutter Windows
8+
release (dashboard\build\windows\x64\runner\Release\sentracore_dashboard.exe),
9+
and optionally after ISCC produces dist\SentraCore_Setup_v*.exe.
10+
11+
Signing requires a code-signing certificate trusted by Windows (public CA).
12+
Self-signed certificates will NOT remove SmartScreen warnings for end users.
13+
14+
.PARAMETER PfxPath
15+
Path to a .pfx (PKCS#12) file containing the signing certificate and private key.
16+
17+
.PARAMETER PfxPassword
18+
Password for the PFX file. Avoid passing on the command line in shared logs;
19+
prefer environment variable WINDOWS_CODESIGN_PFX_PASSWORD.
20+
21+
.PARAMETER TimestampServer
22+
RFC3161 timestamp authority (SHA-256). Default: DigiCert public TSA.
23+
24+
.EXAMPLE
25+
.\scripts\sign-windows-artifacts.ps1 -PfxPath C:\certs\codesign.pfx
26+
27+
.EXAMPLE
28+
$env:WINDOWS_CODESIGN_PFX_PASSWORD = '***'
29+
.\scripts\sign-windows-artifacts.ps1 -PfxPath C:\certs\codesign.pfx -SignInstaller
30+
31+
.EXAMPLE
32+
.\scripts\sign-windows-artifacts.ps1 -PfxPath C:\certs\codesign.pfx -InstallerOnly
33+
#>
34+
param(
35+
[Parameter(Mandatory = $true)]
36+
[string] $PfxPath,
37+
38+
[string] $PfxPassword = "",
39+
40+
[string] $TimestampServer = "http://timestamp.digicert.com",
41+
42+
[string] $Description = "SentraCore",
43+
44+
[string] $RepoRoot = "",
45+
46+
[switch] $SignInstaller,
47+
48+
[switch] $InstallerOnly
49+
)
50+
51+
$ErrorActionPreference = "Stop"
52+
53+
if (-not $RepoRoot) {
54+
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
55+
}
56+
57+
$pfxPasswordEffective = $PfxPassword
58+
if (-not $pfxPasswordEffective -and $env:WINDOWS_CODESIGN_PFX_PASSWORD) {
59+
$pfxPasswordEffective = $env:WINDOWS_CODESIGN_PFX_PASSWORD
60+
}
61+
if (-not (Test-Path -LiteralPath $PfxPath)) {
62+
throw "PFX not found: $PfxPath"
63+
}
64+
65+
function Find-SignTool {
66+
$roots = @(
67+
"${env:ProgramFiles(x86)}\Windows Kits\10\bin",
68+
"${env:ProgramFiles}\Windows Kits\10\bin"
69+
)
70+
foreach ($root in $roots) {
71+
if (-not (Test-Path $root)) { continue }
72+
$candidates = Get-ChildItem -Path $root -Recurse -Filter "signtool.exe" -ErrorAction SilentlyContinue |
73+
Where-Object { $_.FullName -match '\\x64\\signtool\.exe$' } |
74+
Sort-Object { $_.DirectoryName } -Descending
75+
if ($candidates) {
76+
return $candidates[0].FullName
77+
}
78+
}
79+
throw "signtool.exe not found. Install the Windows SDK (Desktop development with C++ / Windows SDK)."
80+
}
81+
82+
function Invoke-SignFile {
83+
param(
84+
[string] $SignTool,
85+
[string] $FilePath,
86+
[string] $Pfx,
87+
[string] $Pass,
88+
[string] $Tsa,
89+
[string] $Desc
90+
)
91+
if (-not (Test-Path -LiteralPath $FilePath)) {
92+
Write-Warning "Skip (missing): $FilePath"
93+
return
94+
}
95+
$full = (Resolve-Path -LiteralPath $FilePath).Path
96+
Write-Host "Signing: $full"
97+
$argList = @(
98+
"sign",
99+
"/f", $Pfx,
100+
"/tr", $Tsa,
101+
"/td", "sha256",
102+
"/fd", "sha256",
103+
"/d", $Desc
104+
)
105+
if ($Pass) {
106+
$argList += @("/p", $Pass)
107+
}
108+
$argList += $full
109+
& $SignTool @argList
110+
if ($LASTEXITCODE -ne 0) {
111+
throw "signtool failed with exit $LASTEXITCODE for $full"
112+
}
113+
}
114+
115+
$signTool = Find-SignTool
116+
Write-Host "Using: $signTool"
117+
118+
$engine = Join-Path $RepoRoot "dist\SentraCoreEngine.exe"
119+
$dashboard = Join-Path $RepoRoot "dashboard\build\windows\x64\runner\Release\sentracore_dashboard.exe"
120+
121+
if (-not $InstallerOnly) {
122+
Invoke-SignFile -SignTool $signTool -FilePath $engine -Pfx $PfxPath -Pass $pfxPasswordEffective -Tsa $TimestampServer -Desc $Description
123+
Invoke-SignFile -SignTool $signTool -FilePath $dashboard -Pfx $PfxPath -Pass $pfxPasswordEffective -Tsa $TimestampServer -Desc $Description
124+
}
125+
126+
if ($SignInstaller -or $InstallerOnly) {
127+
$setup = Get-ChildItem -Path (Join-Path $RepoRoot "dist") -Filter "SentraCore_Setup_v*.exe" -ErrorAction SilentlyContinue |
128+
Sort-Object LastWriteTime -Descending |
129+
Select-Object -First 1
130+
if ($setup) {
131+
Invoke-SignFile -SignTool $signTool -FilePath $setup.FullName -Pfx $PfxPath -Pass $pfxPasswordEffective -Tsa $TimestampServer -Desc "$Description Installer"
132+
}
133+
else {
134+
Write-Warning "No dist\SentraCore_Setup_v*.exe found to sign."
135+
}
136+
}
137+
138+
Write-Host "Done."

0 commit comments

Comments
 (0)