<# ================================================================================ Kiwy Signage Player - Code Signing Script ================================================================================ Signs the built KiwySignagePlayer.exe with an Authenticode certificate. For PRODUCTION PCs that have Smart App Control (SAC) ENABLED: - The cert MUST be issued by a reputable public CA (e.g. Sectigo, SSL.com, DigiCert, GlobalSign). Self-signed certs will NOT satisfy SAC. - You must use this script with -CertPath pointing at your .pfx/.p12. For DEV/TEST machines where you have admin rights: - A self-signed cert trusted in the local Root store + Trusted Publisher works (see create_self_signed_cert.ps1), but it does NOT satisfy SAC. Usage: .\sign_exe.ps1 -CertPath "C:\certs\mycodesign.pfx" -CertPassword "secret" .\sign_exe.ps1 -CertPath "C:\certs\mycodesign.pfx" # prompt for pwd .\sign_exe.ps1 -CertThumbprint "A1B2..." # from cert store .\sign_exe.ps1 -CertPath "C:\certs\mycodesign.pfx" -SkipTimestamp $false Optional: -TimestampUrl RFC3161 timestamp server (default DigiCert). Timestamping is REQUIRED for the signature to stay valid after the cert expires and to satisfy Smart App Control. -ExePath Path to the exe to sign (default dist\KiwySignagePlayer\KiwySignagePlayer.exe) -Force Re-sign even if already signed ================================================================================ #> [CmdletBinding()] param( [string]$CertPath, [string]$CertPassword, [string]$CertThumbprint, [string]$ExePath = (Join-Path $PSScriptRoot 'dist\KiwySignagePlayer\KiwySignagePlayer.exe'), [string]$TimestampUrl = 'http://timestamp.digicert.com', [switch]$SkipTimestamp, [switch]$Force ) $ErrorActionPreference = 'Stop' Set-Location $PSScriptRoot function Find-Signtool { $candidates = @( (Get-Command signtool.exe -ErrorAction SilentlyContinue).Source, "$env:ProgramFiles(x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe", "$env:ProgramFiles(x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe", "$env:ProgramFiles(x86)\Windows Kits\10\bin\10.0.22000.0\x64\signtool.exe", "$env:ProgramFiles(x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe" ) foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return $c } } # Fallback: newest SDK on disk $sdkBin = "$env:ProgramFiles(x86)\Windows Kits\10\bin" if (Test-Path $sdkBin) { $found = Get-ChildItem $sdkBin -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName if ($found) { return $found } } return $null } $signtool = Find-Signtool if ($signtool) { Write-Host "[INFO ] signtool: $signtool" -ForegroundColor Green } else { Write-Host "[WARN ] signtool.exe not found - will use PowerShell Set-AuthenticodeSignature fallback." -ForegroundColor Yellow Write-Host "[WARN ] NOTE: the fallback cannot apply an RFC3161 timestamp. For production (SAC)," Write-Host "[WARN ] install the Windows SDK signtool: winget install Microsoft.WindowsSDK.10.0.26100" } if (-not (Test-Path $ExePath)) { Write-Host "[ERROR] Exe not found: $ExePath" -ForegroundColor Red Write-Host "Run build_win.bat first, or pass -ExePath." exit 1 } # Already signed? $sig = Get-AuthenticodeSignature -FilePath $ExePath if ($sig.Status -eq 'Valid' -and -not $Force) { Write-Host "[INFO ] Exe is already validly signed by: $($sig.SignerCertificate.Subject)" -ForegroundColor Green exit 0 } # ── Load the certificate ──────────────────────────────────────────── $cert = $null if ($CertThumbprint) { $cert = Get-ChildItem Cert:\CurrentUser\My, Cert:\LocalMachine\My -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $CertThumbprint } | Select-Object -First 1 if (-not $cert) { Write-Host "[ERROR] No certificate with thumbprint $CertThumbprint in My store." -ForegroundColor Red exit 1 } } elseif ($CertPath) { if (-not (Test-Path $CertPath)) { Write-Host "[ERROR] Cert file not found: $CertPath" -ForegroundColor Red exit 1 } if (-not $CertPassword) { $secure = Read-Host "Certificate password for $CertPath" -AsSecureString $CertPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)) } $securePwd = ConvertTo-SecureString -String $CertPassword -Force -AsPlainText $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath, $securePwd) } else { Write-Host "[ERROR] Provide -CertPath or -CertThumbprint." -ForegroundColor Red exit 1 } # ── Sign (signtool preferred, PowerShell fallback) ────────────────── if ($signtool) { $args = @() if ($CertThumbprint) { $args = @('sign', '/sha1', $CertThumbprint, '/fd', 'SHA256') } else { $args = @('sign', '/f', $CertPath, '/p', $CertPassword, '/fd', 'SHA256') } if (-not $SkipTimestamp) { $args += @('/tr', $TimestampUrl, '/td', 'SHA256') } $args += @('"' + $ExePath + '"') $cmd = "& `"$signtool`" " + ($args -join ' ') Write-Host "[INFO ] Signing with signtool..." -ForegroundColor Cyan Write-Host "[CMD ] $cmd" Invoke-Expression $cmd if ($LASTEXITCODE -ne 0) { Write-Host "[ERROR] signtool failed with exit code $LASTEXITCODE" -ForegroundColor Red exit $LASTEXITCODE } } else { Write-Host "[INFO ] Signing with PowerShell Set-AuthenticodeSignature (no timestamp)..." -ForegroundColor Cyan if (-not $cert.HasPrivateKey) { Write-Host "[ERROR] Certificate has no private key - cannot sign." -ForegroundColor Red exit 1 } $sig = Set-AuthenticodeSignature -FilePath $ExePath -Certificate $cert -HashAlgorithm SHA256 if ($sig.Status -notin @('Valid','UnknownError')) { Write-Host "[ERROR] Signing failed: $($sig.StatusMessage)" -ForegroundColor Red exit 1 } } # Verify $sig = Get-AuthenticodeSignature -FilePath $ExePath Write-Host "" Write-Host "[INFO ] Signature status: $($sig.Status)" -ForegroundColor Green Write-Host "[INFO ] Signer: $($sig.SignerCertificate.Subject)" -ForegroundColor Green if ($sig.Status -eq 'Valid') { Write-Host "[OK ] KiwySignagePlayer.exe is now digitally signed." -ForegroundColor Green } else { Write-Host "[WARN ] Signature status is '$($sig.Status)' - inspect above." -ForegroundColor Yellow Write-Host "[WARN ] If no timestamp was applied, SAC may still block after cert expiry." -ForegroundColor Yellow }