2
0

feat(ci): add automated code signing to release workflow
All checks were successful
Build / build (push) Successful in 9h0m8s
Build and Release / build (push) Successful in 8h0m23s

Implement automated code signing using Certum SimplySign with TOTP authentication. Add steps to generate TOTP from otpauth URI, authenticate SimplySign Desktop, verify certificate availability, and sign both application exe and installer using signtool.
This commit is contained in:
2026-01-30 23:11:41 -05:00
parent 13d44de9f9
commit 9220023898

View File

@@ -37,25 +37,176 @@ jobs:
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
[System.IO.File]::WriteAllText($issPath, $issContent, [System.Text.UTF8Encoding]::new($true))
- name: Connect SimplySign (automated TOTP)
if: ${{ secrets.CERTUM_OTP_URI != '' }}
shell: pwsh
run: |
# Generate TOTP from otpauth:// URI and authenticate SimplySign Desktop
$otpUri = $env:CERTUM_OTP_URI
$userId = $env:CERTUM_USERID
# Parse the otpauth URI to extract the secret
if ($otpUri -match 'secret=([A-Z2-7=]+)') {
$base32Secret = $Matches[1]
} else {
Write-Error "Failed to parse OTP secret from URI"
exit 1
}
# Base32 decode and generate TOTP
Add-Type -TypeDefinition @"
using System;
using System.Security.Cryptography;
public static class TOTP {
public static string Generate(string base32Secret) {
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = "";
foreach (var c in base32Secret.TrimEnd('=').ToUpper()) {
bits += Convert.ToString(Array.IndexOf(alphabet.ToCharArray(), c), 2).PadLeft(5, '0');
}
var key = new byte[bits.Length / 8];
for (int i = 0; i < key.Length; i++) {
key[i] = Convert.ToByte(bits.Substring(i * 8, 8), 2);
}
long counter = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds / 30;
var counterBytes = BitConverter.GetBytes(counter);
if (BitConverter.IsLittleEndian) Array.Reverse(counterBytes);
using (var hmac = new HMACSHA1(key)) {
var hash = hmac.ComputeHash(counterBytes);
int offset = hash[hash.Length - 1] & 0x0F;
int code = ((hash[offset] & 0x7F) << 24)
| ((hash[offset + 1] & 0xFF) << 16)
| ((hash[offset + 2] & 0xFF) << 8)
| (hash[offset + 3] & 0xFF);
return (code % 1000000).ToString("D6");
}
}
}
"@
$totp = [TOTP]::Generate($base32Secret)
Write-Host "TOTP generated successfully"
# Find and launch SimplySign
$exePath = $env:CERTUM_EXE_PATH
if (-not $exePath) {
$exePath = "$env:LOCALAPPDATA\Programs\SimplySign Desktop\SimplySign Desktop.exe"
}
if (-not (Test-Path $exePath)) {
$exePath = "C:\Program Files\SimplySign Desktop\SimplySign Desktop.exe"
}
if (-not (Test-Path $exePath)) {
Write-Error "SimplySign Desktop not found"
exit 1
}
# Start SimplySign if not running
$process = Get-Process -Name "SimplySign Desktop" -ErrorAction SilentlyContinue
if (-not $process) {
Start-Process $exePath
Start-Sleep -Seconds 5
}
# Send credentials via SendKeys
Add-Type -AssemblyName System.Windows.Forms
$wshell = New-Object -ComObject WScript.Shell
# Activate SimplySign window
Start-Sleep -Seconds 2
$wshell.AppActivate("SimplySign") | Out-Null
Start-Sleep -Seconds 1
# Enter user ID
[System.Windows.Forms.SendKeys]::SendWait($userId)
[System.Windows.Forms.SendKeys]::SendWait("{TAB}")
Start-Sleep -Milliseconds 500
# Enter TOTP
[System.Windows.Forms.SendKeys]::SendWait($totp)
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")
# Wait for authentication
Write-Host "Waiting for SimplySign authentication..."
Start-Sleep -Seconds 10
Write-Host "SimplySign authentication completed"
env:
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_USERID: ${{ secrets.CERTUM_USERID }}
CERTUM_EXE_PATH: ${{ secrets.CERTUM_EXE_PATH }}
- name: Verify code signing certificate
shell: pwsh
run: |
$certName = "${{ secrets.CERTUM_CERT_NAME }}"
if (-not $certName) {
Write-Host "CERTUM_CERT_NAME not set, skipping signing verification"
exit 0
}
Write-Host "Looking for certificate: $certName"
Write-Host ""
Write-Host "=== CurrentUser\My code signing certs ==="
Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host " Subject: $($_.Subject)"
Write-Host " Issuer: $($_.Issuer)"
Write-Host " Thumbprint: $($_.Thumbprint)"
Write-Host ""
}
Write-Host "=== Checking via certutil (includes smart card / CSP certs) ==="
certutil -user -store My 2>&1 | Select-String -Pattern "Subject:|Cert Hash|Serial Number|Provider" | ForEach-Object { Write-Host " $_" }
Write-Host ""
Write-Host "=== SimplySign process check ==="
$ss = Get-Process -Name "SimplySign*" -ErrorAction SilentlyContinue
if ($ss) {
$ss | ForEach-Object { Write-Host " Running: $($_.ProcessName) (PID: $($_.Id))" }
} else {
Write-Host " WARNING: SimplySign Desktop is not running"
}
Write-Host ""
Write-Host "Certificate verification complete."
- name: Restore dependencies
run: dotnet restore
- name: Build Release
run: dotnet build --configuration Release --no-restore
- name: List build output
shell: cmd
- name: Sign application exe
if: ${{ secrets.CERTUM_CERT_NAME != '' }}
shell: pwsh
run: |
echo "Current directory:"
cd
echo "MonitorControl contents:"
dir MonitorControl
echo "MonitorControl\bin contents:"
dir MonitorControl\bin
echo "MonitorControl\bin\Release contents:"
dir MonitorControl\bin\Release
echo "MonitorControl\bin\Release\net9.0-windows contents:"
dir MonitorControl\bin\Release\net9.0-windows
$certName = "${{ secrets.CERTUM_CERT_NAME }}"
$exe = "MonitorControl\bin\Release\net9.0-windows\MonitorControl.exe"
if (-not (Test-Path $exe)) {
Write-Error "Build output not found: $exe"
exit 1
}
# Find signtool.exe from Windows SDK
$signtool = "signtool"
$sdkBase = "C:\Program Files (x86)\Windows Kits\10\bin"
if (Test-Path $sdkBase) {
$versions = Get-ChildItem $sdkBase -Directory | Where-Object { $_.Name -match '^\d+\.' } | Sort-Object Name -Descending
foreach ($v in $versions) {
$candidate = Join-Path $v.FullName "x64\signtool.exe"
if (Test-Path $candidate) {
$signtool = $candidate
break
}
}
}
Write-Host "Signing $exe with signtool..."
& $signtool sign /debug /n "$certName" /t http://time.certum.pl /fd SHA256 "$exe"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Host "Application exe signed successfully"
- name: Build Installer
shell: cmd
@@ -82,6 +233,38 @@ jobs:
echo Installer directory contents:
dir installer
- name: Sign installer exe
if: ${{ secrets.CERTUM_CERT_NAME != '' }}
shell: pwsh
run: |
$certName = "${{ secrets.CERTUM_CERT_NAME }}"
$version = "${{ gitea.ref_name }}".TrimStart("v")
$installer = "C:\build\app\installer\MonitorControl-Setup-$version.exe"
if (-not (Test-Path $installer)) {
Write-Error "Installer not found: $installer"
exit 1
}
# Find signtool.exe from Windows SDK
$signtool = "signtool"
$sdkBase = "C:\Program Files (x86)\Windows Kits\10\bin"
if (Test-Path $sdkBase) {
$versions = Get-ChildItem $sdkBase -Directory | Where-Object { $_.Name -match '^\d+\.' } | Sort-Object Name -Descending
foreach ($v in $versions) {
$candidate = Join-Path $v.FullName "x64\signtool.exe"
if (Test-Path $candidate) {
$signtool = $candidate
break
}
}
}
Write-Host "Signing $installer with signtool..."
& $signtool sign /debug /n "$certName" /t http://time.certum.pl /fd SHA256 "$installer"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Host "Installer signed successfully"
- name: Create Portable Zip
shell: powershell
run: |