Skip to content

Commit 3b6dd81

Browse files
committed
Sign exe files
1 parent aa5371c commit 3b6dd81

5 files changed

Lines changed: 299 additions & 5 deletions

File tree

.github/workflows/build.yml

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,51 @@ jobs:
295295
set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH%
296296
nmake check TESTARGS="-maxwarnings 100000"
297297
298+
- name: Upload executables for signing
299+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
300+
uses: actions/upload-artifact@v4
301+
id: upload_executables
302+
with:
303+
name: windows-x64-executables-unsigned-${{ needs.initialization.outputs.build_number }}
304+
path: |
305+
release64/YACReader.exe
306+
release64/YACReaderLibrary.exe
307+
release64/YACReaderLibraryServer.exe
308+
309+
- name: Sign executables with SignPath
310+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
311+
uses: signpath/github-action-submit-signing-request@v1
312+
with:
313+
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
314+
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
315+
project-slug: 'yacreader'
316+
signing-policy-slug: 'release-signing'
317+
artifact-configuration-slug: 'zipped-files'
318+
github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }}
319+
wait-for-completion: true
320+
wait-for-completion-timeout-in-seconds: "3600"
321+
output-artifact-directory: release64/signed
322+
323+
- name: Replace with signed executables
324+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
325+
shell: pwsh
326+
run: |
327+
Write-Host "=== Replacing executables with signed versions ==="
328+
Get-ChildItem -Path "release64/signed" -Filter "*.exe" | ForEach-Object {
329+
$destPath = "release64/$($_.Name)"
330+
Write-Host "Moving signed: $($_.Name) -> $destPath"
331+
Move-Item -Path $_.FullName -Destination $destPath -Force
332+
Write-Host " Moved successfully"
333+
}
334+
Remove-Item -Path "release64/signed" -Recurse -Force -ErrorAction SilentlyContinue
335+
Write-Host "Signed executables are ready for installer creation"
336+
298337
- name: Create installer
299338
shell: cmd
300339
working-directory: ci/win
340+
env:
341+
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
342+
SIGNPATH_ORGANIZATION_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
301343
run: |
302344
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat"
303345
set PATH=C:\Qt\5.15.2\msvc2019_64\bin;%PATH%
@@ -328,9 +370,10 @@ jobs:
328370
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
329371
project-slug: 'yacreader'
330372
signing-policy-slug: 'release-signing'
331-
artifact-configuration-slug: 'windows-installer'
373+
artifact-configuration-slug: 'zipped-files'
332374
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
333375
wait-for-completion: true
376+
wait-for-completion-timeout-in-seconds: "3600"
334377
output-artifact-directory: ci/win/Output/signed
335378

336379
- name: Replace with signed installer
@@ -421,9 +464,51 @@ jobs:
421464
set PATH=C:\Qt\6.3.1\msvc2019_64\bin;%PATH%
422465
nmake check TESTARGS="-maxwarnings 100000"
423466
467+
- name: Upload executables for signing
468+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
469+
uses: actions/upload-artifact@v4
470+
id: upload_executables
471+
with:
472+
name: windows-x64-qt6-executables-unsigned-${{ needs.initialization.outputs.build_number }}
473+
path: |
474+
release64/YACReader.exe
475+
release64/YACReaderLibrary.exe
476+
release64/YACReaderLibraryServer.exe
477+
478+
- name: Sign executables with SignPath
479+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
480+
uses: signpath/github-action-submit-signing-request@v1
481+
with:
482+
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
483+
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
484+
project-slug: 'yacreader'
485+
signing-policy-slug: 'release-signing'
486+
artifact-configuration-slug: 'zipped-files'
487+
github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }}
488+
wait-for-completion: true
489+
wait-for-completion-timeout-in-seconds: "3600"
490+
output-artifact-directory: release64/signed
491+
492+
- name: Replace with signed executables
493+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
494+
shell: pwsh
495+
run: |
496+
Write-Host "=== Replacing executables with signed versions ==="
497+
Get-ChildItem -Path "release64/signed" -Filter "*.exe" | ForEach-Object {
498+
$destPath = "release64/$($_.Name)"
499+
Write-Host "Moving signed: $($_.Name) -> $destPath"
500+
Move-Item -Path $_.FullName -Destination $destPath -Force
501+
Write-Host " Moved successfully"
502+
}
503+
Remove-Item -Path "release64/signed" -Recurse -Force -ErrorAction SilentlyContinue
504+
Write-Host "Signed executables are ready for installer creation"
505+
424506
- name: Create installer
425507
shell: cmd
426508
working-directory: ci/win
509+
env:
510+
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
511+
SIGNPATH_ORGANIZATION_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
427512
run: |
428513
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" -vcvars_ver=14.29
429514
set PATH=C:\Qt\6.3.1\msvc2019_64\bin;%PATH%
@@ -454,9 +539,10 @@ jobs:
454539
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
455540
project-slug: 'yacreader'
456541
signing-policy-slug: 'release-signing'
457-
artifact-configuration-slug: 'windows-installer-qt6'
542+
artifact-configuration-slug: 'zipped-files'
458543
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
459544
wait-for-completion: true
545+
wait-for-completion-timeout-in-seconds: "3600"
460546
output-artifact-directory: ci/win/Output/signed
461547

462548
- name: Replace with signed installer
@@ -531,9 +617,51 @@ jobs:
531617
set PATH=C:\Qt\5.15.2\msvc2019\bin;%PATH%
532618
nmake check TESTARGS="-maxwarnings 100000"
533619
620+
- name: Upload executables for signing
621+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
622+
uses: actions/upload-artifact@v4
623+
id: upload_executables
624+
with:
625+
name: windows-x86-executables-unsigned-${{ needs.initialization.outputs.build_number }}
626+
path: |
627+
release/YACReader.exe
628+
release/YACReaderLibrary.exe
629+
release/YACReaderLibraryServer.exe
630+
631+
- name: Sign executables with SignPath
632+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
633+
uses: signpath/github-action-submit-signing-request@v1
634+
with:
635+
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
636+
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
637+
project-slug: 'yacreader'
638+
signing-policy-slug: 'release-signing'
639+
artifact-configuration-slug: 'zipped-files'
640+
github-artifact-id: ${{ steps.upload_executables.outputs.artifact-id }}
641+
wait-for-completion: true
642+
wait-for-completion-timeout-in-seconds: "3600"
643+
output-artifact-directory: release/signed
644+
645+
- name: Replace with signed executables
646+
if: github.repository == 'YACReader/yacreader' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
647+
shell: pwsh
648+
run: |
649+
Write-Host "=== Replacing executables with signed versions ==="
650+
Get-ChildItem -Path "release/signed" -Filter "*.exe" | ForEach-Object {
651+
$destPath = "release/$($_.Name)"
652+
Write-Host "Moving signed: $($_.Name) -> $destPath"
653+
Move-Item -Path $_.FullName -Destination $destPath -Force
654+
Write-Host " Moved successfully"
655+
}
656+
Remove-Item -Path "release/signed" -Recurse -Force -ErrorAction SilentlyContinue
657+
Write-Host "Signed executables are ready for installer creation"
658+
534659
- name: Create installer
535660
shell: cmd
536661
working-directory: ci/win
662+
env:
663+
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
664+
SIGNPATH_ORGANIZATION_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
537665
run: |
538666
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars32.bat"
539667
set PATH=C:\Qt\5.15.2\msvc2019\bin;%PATH%
@@ -564,9 +692,10 @@ jobs:
564692
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
565693
project-slug: 'yacreader'
566694
signing-policy-slug: 'release-signing'
567-
artifact-configuration-slug: 'windows-installer-x86'
695+
artifact-configuration-slug: 'zipped-files'
568696
github-artifact-id: ${{ steps.upload_unsigned.outputs.artifact-id }}
569697
wait-for-completion: true
698+
wait-for-completion-timeout-in-seconds: "3600"
570699
output-artifact-directory: ci/win/Output/signed
571700

572701
- name: Replace with signed installer

ci/win/build_installer.iss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SetupIconFile=setup.ico
1515
UninstallDisplayIcon=uninstall.ico
1616
ArchitecturesInstallIn64BitMode=x64
1717
ArchitecturesAllowed=x64
18+
SignTool=custom_signtool
1819

1920
[Registry]
2021
Root: HKCR; SubKey: .cbz; ValueType: string; ValueData: Comic Book (zip); Flags: uninsdeletekey; Tasks: File_association

ci/win/build_installer_qt6.iss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SetupIconFile=setup.ico
1515
UninstallDisplayIcon=uninstall.ico
1616
ArchitecturesInstallIn64BitMode=x64
1717
ArchitecturesAllowed=x64
18+
SignTool=custom_signtool
1819

1920
[Registry]
2021
Root: HKCR; SubKey: .cbz; ValueType: string; ValueData: Comic Book (zip); Flags: uninsdeletekey; Tasks: File_association

ci/win/create_installer.cmd

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ if "%1"=="x86" (
5858
)
5959

6060
echo "iscc start"
61+
set "SCRIPT_PATH=%cd%\sign_tool.ps1"
6162
if "%4"=="qt6" (
62-
iscc /DVERSION=%VERSION% /DPLATFORM=%1 /DCOMPRESSED_ARCHIVE_BACKEND=%2 /DBUILD_NUMBER=%3 build_installer_qt6.iss || exit /b
63+
iscc /DVERSION=%VERSION% /DPLATFORM=%1 /DCOMPRESSED_ARCHIVE_BACKEND=%2 /DBUILD_NUMBER=%3 /Scustom_signtool="powershell.exe -ExecutionPolicy Bypass -File %SCRIPT_PATH% $f" build_installer_qt6.iss || exit /b
6364
) else (
64-
iscc /DVERSION=%VERSION% /DPLATFORM=%1 /DCOMPRESSED_ARCHIVE_BACKEND=%2 /DBUILD_NUMBER=%3 build_installer.iss || exit /b
65+
iscc /DVERSION=%VERSION% /DPLATFORM=%1 /DCOMPRESSED_ARCHIVE_BACKEND=%2 /DBUILD_NUMBER=%3 /Scustom_signtool="powershell.exe -ExecutionPolicy Bypass -File %SCRIPT_PATH% $f" build_installer.iss || exit /b
6566
)
6667
echo "iscc done!"
6768

ci/win/sign_tool.ps1

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# PowerShell script for signing uninstaller during Inno Setup process
2+
param(
3+
[Parameter(Mandatory=$true)]
4+
[string]$FilePath
5+
)
6+
7+
# Get the filename from the full path
8+
$fileName = Split-Path $FilePath -Leaf
9+
Write-Host "SignTool called for file: $fileName"
10+
Write-Host "Full file path: $FilePath"
11+
12+
# Validate the file exists
13+
if (-not (Test-Path $FilePath)) {
14+
Write-Error "File not found: $FilePath"
15+
exit 1
16+
}
17+
18+
# Only sign the uninstaller - all other executables are pre-signed
19+
if ($fileName -eq "unins000.exe" -or $fileName -like "uninst*.tmp") {
20+
Write-Host "Signing uninstaller: $fileName"
21+
Write-Host "File path: $FilePath"
22+
Write-Host "File size: $((Get-Item $FilePath).Length) bytes"
23+
24+
# Get required environment variables (these should be set as GitHub secrets)
25+
$organizationId = $env:SIGNPATH_ORGANIZATION_ID
26+
$projectSlug = "yacreader" # Hard-coded project slug
27+
$signingPolicySlug = "release-signing" # Hard-coded signing policy
28+
$apiToken = $env:SIGNPATH_API_TOKEN
29+
30+
# Validate required environment variables are set
31+
if (-not $organizationId) {
32+
Write-Error "SIGNPATH_ORGANIZATION_ID environment variable not set"
33+
exit 1
34+
}
35+
if (-not $apiToken) {
36+
Write-Error "SIGNPATH_API_TOKEN environment variable not set"
37+
exit 1
38+
}
39+
40+
# Initialize temp directory variable for proper cleanup
41+
$tempDir = $null
42+
43+
try {
44+
# Create a temporary directory for the signing process
45+
$tempDir = New-Item -ItemType Directory -Path (Join-Path $env:TEMP "signpath_$(Get-Random)") -Force
46+
$tempFile = Join-Path $tempDir "unins000.exe"
47+
48+
Write-Host "Created temp directory: $tempDir"
49+
50+
# Copy file to temp location with error handling
51+
Copy-Item $FilePath $tempFile -Force
52+
Write-Host "Copied file to temp location: $tempFile"
53+
Write-Host "Copied file to temp location: $tempFile"
54+
55+
Write-Host "Uploading uninstaller to SignPath for signing..."
56+
57+
# Create the API request
58+
$uri = "https://app.signpath.io/api/v1/$organizationId/SigningRequests"
59+
60+
# Prepare the form data
61+
$form = @{
62+
'ProjectSlug' = $projectSlug
63+
'SigningPolicySlug' = $signingPolicySlug
64+
'ArtifactConfigurationSlug' = 'executable-file'
65+
'Description' = "Signing uninstaller for YACReader build $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
66+
}
67+
68+
# Prepare headers (API token should never be logged)
69+
$headers = @{
70+
'Authorization' = "Bearer $apiToken"
71+
}
72+
73+
# Upload and sign with error handling
74+
Write-Host "Submitting signing request..."
75+
$response = Invoke-RestMethod -Uri $uri -Method Post -Form $form -InFile $tempFile -Headers $headers
76+
77+
# Validate response has required fields
78+
if (-not $response.signingRequestId) {
79+
Write-Error "SignPath API response missing signingRequestId field"
80+
exit 1
81+
}
82+
83+
$signingRequestId = $response.signingRequestId
84+
Write-Host "Signing request submitted with ID: $signingRequestId"
85+
86+
# Poll for completion
87+
$statusUri = "https://app.signpath.io/api/v1/$organizationId/SigningRequests/$signingRequestId"
88+
$maxWaitTime = 3600 # 1 hour max (3600 seconds)
89+
$waitTime = 0
90+
$pollInterval = 10
91+
92+
do {
93+
Start-Sleep $pollInterval
94+
$waitTime += $pollInterval
95+
96+
Write-Host "Checking signing status... ($waitTime/$maxWaitTime seconds)"
97+
$status = Invoke-RestMethod -Uri $statusUri -Headers $headers
98+
99+
# Validate status response
100+
if (-not $status.status) {
101+
Write-Error "SignPath status response missing status field"
102+
exit 1
103+
}
104+
105+
if ($status.status -eq "Completed") {
106+
Write-Host "Signing completed successfully!"
107+
break
108+
} elseif ($status.status -eq "Failed") {
109+
$description = if ($status.description) { $status.description } else { "Unknown error" }
110+
Write-Error "Signing failed: $description"
111+
exit 1
112+
} elseif ($status.status -eq "Denied") {
113+
$description = if ($status.description) { $status.description } else { "Request denied" }
114+
Write-Error "Signing was denied: $description"
115+
exit 1
116+
}
117+
118+
} while ($waitTime -lt $maxWaitTime)
119+
120+
if ($waitTime -ge $maxWaitTime) {
121+
Write-Error "Signing timed out after $maxWaitTime seconds"
122+
exit 1
123+
}
124+
125+
# Download the signed file
126+
Write-Host "Downloading signed uninstaller..."
127+
$downloadUri = "https://app.signpath.io/api/v1/$organizationId/SigningRequests/$signingRequestId/SignedArtifact"
128+
129+
# Download to temp location first, then replace original
130+
$signedTempFile = Join-Path $tempDir "unins000_signed.exe"
131+
Invoke-RestMethod -Uri $downloadUri -Headers $headers -OutFile $signedTempFile
132+
133+
# Verify the signed file exists and has content
134+
if (-not (Test-Path $signedTempFile) -or (Get-Item $signedTempFile).Length -eq 0) {
135+
Write-Error "Downloaded signed file is missing or empty"
136+
exit 1
137+
}
138+
139+
# Replace the original file with the signed version
140+
Copy-Item $signedTempFile $FilePath -Force
141+
Write-Host "Uninstaller signed successfully and replaced at: $FilePath"
142+
143+
# Clean up temp directory
144+
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
145+
Write-Host "Cleaned up temporary files"
146+
147+
} catch {
148+
Write-Error "SignPath API call failed: $($_.Exception.Message)"
149+
Write-Host "Error details: $($_.Exception.ToString())"
150+
151+
# Clean up temp directory on error
152+
if ($tempDir -and (Test-Path $tempDir)) {
153+
Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
154+
Write-Host "Cleaned up temp directory after error"
155+
}
156+
exit 1
157+
}
158+
159+
} else {
160+
Write-Host "Skipping file: $fileName (not the uninstaller)"
161+
exit 0
162+
}

0 commit comments

Comments
 (0)