Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ jobs:
- name: Build, test, and test-sign package
shell: powershell
run: .\build.ps1 -Version "${{ github.ref_name }}" -TestSign
- name: Verify signing material cleanup
if: always()
shell: powershell
run: |
$certificates = Get-ChildItem `
Cert:\CurrentUser\My, `
Cert:\LocalMachine\Root, `
Cert:\LocalMachine\TrustedPublisher `
-ErrorAction SilentlyContinue |
Where-Object Subject -eq "CN=Roxy Kernel Tools Test Signing"
if ($certificates) {
$certificates | Format-List PSParentPath, Thumbprint, HasPrivateKey
throw "Test-signing certificate or private-key material remains on the runner."
}
- uses: actions/upload-artifact@v4
with:
name: kernel-tools-windows-x64-${{ github.sha }}
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ certificate, and `roxy-kernel-bridge.exe`. Roxy downloads a pinned
release, verifies its SHA-256 digest, and installs it only after explicit user
confirmation and a UAC prompt.

The release workflow creates a one-build, non-exportable private key, embeds a
SHA-256 signature in the driver, verifies it with SignTool's Authenticode
policy, matches the embedded signer to the generated certificate, and deletes
the private key before uploading artifacts. CI temporarily trusts the public
certificate in the runner's machine stores so SignTool must report success, then
removes that trust. A final workflow guard fails if the certificate or private
key remains on the runner. Non-elevated local builds accept only SignTool's
expected self-signed-root error; any other verification failure stops the build.
Only the public certificate is published. During installation, Roxy adds that
certificate to the target machine's Trusted Root and Trusted Publishers stores and removes
only trust entries that Roxy added when Kernel Tools is uninstalled.

These development releases require Windows test-signing mode and trusting the
included public certificate. Normal Secure Boot production deployment requires
Microsoft attestation or WHQL signing; a GitHub-built test certificate is not a
Expand Down
58 changes: 46 additions & 12 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,56 @@ if ($TestSign) {
-KeyAlgorithm RSA `
-KeyLength 3072 `
-HashAlgorithm SHA256 `
-KeyExportPolicy Exportable `
-KeyExportPolicy NonExportable `
-NotAfter (Get-Date).AddYears(3)

$signTool = Find-WdkTool "signtool.exe"
& $signTool sign /v /fd SHA256 /s My /sha1 $certificate.Thumbprint (Join-Path $package "aibridge.sys")
if ($LASTEXITCODE -ne 0) { throw "Driver signing failed." }
Copy-Item -Force (Join-Path $package "aibridge.sys") $driver
Export-Certificate -Cert $certificate -FilePath (Join-Path $package "aibridge-test.cer") | Out-Null
}
$rootTrustedForVerification = $false
$publisherTrustedForVerification = $false
try {
$signTool = Find-WdkTool "signtool.exe"
& $signTool sign /v /fd SHA256 /s My /sha1 $certificate.Thumbprint (Join-Path $package "aibridge.sys")
if ($LASTEXITCODE -ne 0) { throw "Driver signing failed." }
Copy-Item -Force (Join-Path $package "aibridge.sys") $driver
$certificatePath = Join-Path $package "aibridge-test.cer"
Export-Certificate -Cert $certificate -FilePath $certificatePath | Out-Null
if ($env:GITHUB_ACTIONS -eq "true") {
Import-Certificate -FilePath $certificatePath -CertStoreLocation "Cert:\LocalMachine\Root" | Out-Null
$rootTrustedForVerification = $true
Import-Certificate -FilePath $certificatePath -CertStoreLocation "Cert:\LocalMachine\TrustedPublisher" | Out-Null
$publisherTrustedForVerification = $true
}

if ($TestSign) {
$signature = Get-AuthenticodeSignature (Join-Path $package "aibridge.sys")
if (-not $signature.SignerCertificate -or $signature.SignerCertificate.Thumbprint -ne $certificate.Thumbprint) {
throw "The packaged driver is not signed by the generated test certificate."
$verifyOutputLog = Join-Path $dist "signtool-verify.stdout.log"
$verifyErrorLog = Join-Path $dist "signtool-verify.stderr.log"
$verifyProcess = Start-Process `
-FilePath $signTool `
-ArgumentList @("verify", "/v", "/pa", (Join-Path $package "aibridge.sys")) `
-RedirectStandardOutput $verifyOutputLog `
-RedirectStandardError $verifyErrorLog `
-Wait `
-PassThru
$verifyOutput = (Get-Content -Raw $verifyOutputLog), (Get-Content -Raw $verifyErrorLog) -join "`n"
Remove-Item $verifyOutputLog, $verifyErrorLog -Force
if ($verifyProcess.ExitCode -ne 0) {
if (($rootTrustedForVerification -and $publisherTrustedForVerification) -or $verifyOutput -notmatch "terminated in a root\s+certificate which is not trusted") {
throw "Driver signature verification failed.`n$verifyOutput"
}
}
Write-Host $verifyOutput

$signature = Get-AuthenticodeSignature (Join-Path $package "aibridge.sys")
if (-not $signature.SignerCertificate -or $signature.SignerCertificate.Thumbprint -ne $certificate.Thumbprint) {
throw "The packaged driver is not signed by the generated test certificate."
}
} finally {
if ($rootTrustedForVerification) {
Remove-Item "Cert:\LocalMachine\Root\$($certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue
}
if ($publisherTrustedForVerification) {
Remove-Item "Cert:\LocalMachine\TrustedPublisher\$($certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue
}
Remove-Item "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -DeleteKey -Force -ErrorAction SilentlyContinue
}
Remove-Item "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -Force -ErrorAction SilentlyContinue
}

$sourceCommit = (& git -C $root rev-parse HEAD).Trim()
Expand Down
Loading