From e0f468d2385d2cfc590c5b41a9c4a2346d470657 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:31:58 -0400 Subject: [PATCH 1/2] build: verify and clean test signing material Create a non-exportable per-build signing key, require SignTool kernel-policy verification in CI, and remove temporary machine trust and private key material before artifact upload. Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- README.md | 11 +++++++++++ build.ps1 | 58 +++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9e3b7df..2dd165f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,17 @@ 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, checks it with SignTool under kernel-mode +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. 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 diff --git a/build.ps1 b/build.ps1 index 1dfcdff..52f6ff3 100644 --- a/build.ps1 +++ b/build.ps1 @@ -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", "/kp", (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() From e74a3b6b82c63603ded356a102bc8b11c5d7ca09 Mon Sep 17 00:00:00 2001 From: Freddy Diaz <45578633+FreddyJD@users.noreply.github.com> Date: Mon, 14 Sep 2026 06:36:27 -0400 Subject: [PATCH 2/2] fix: verify test signature with Authenticode policy Use SignTool's Authenticode policy for self-signed research drivers and add an always-run guard that rejects leftover test certificates or private keys. Co-authored-by: Roxy <299891354+roxy-commits@users.noreply.github.com> --- .github/workflows/build.yml | 14 ++++++++++++++ README.md | 11 ++++++----- build.ps1 | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6574c88..3df8c5c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/README.md b/README.md index 2dd165f..34d0842 100644 --- a/README.md +++ b/README.md @@ -57,14 +57,15 @@ 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, checks it with SignTool under kernel-mode +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. 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 +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 diff --git a/build.ps1 b/build.ps1 index 52f6ff3..cb61966 100644 --- a/build.ps1 +++ b/build.ps1 @@ -73,7 +73,7 @@ if ($TestSign) { $verifyErrorLog = Join-Path $dist "signtool-verify.stderr.log" $verifyProcess = Start-Process ` -FilePath $signTool ` - -ArgumentList @("verify", "/v", "/kp", (Join-Path $package "aibridge.sys")) ` + -ArgumentList @("verify", "/v", "/pa", (Join-Path $package "aibridge.sys")) ` -RedirectStandardOutput $verifyOutputLog ` -RedirectStandardError $verifyErrorLog ` -Wait `