feat(ci): add automated code signing to release workflow
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:
@@ -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: |
|
||||
|
||||
Reference in New Issue
Block a user