ci(signing): bound certificate store mutation

This commit is contained in:
rcourtman
2026-08-06 16:00:36 +01:00
parent 737bde7362
commit 5ed518403f
2 changed files with 61 additions and 25 deletions
+55 -23
View File
@@ -158,23 +158,59 @@ jobs:
$certificate.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert)
)
function Invoke-BoundedCertUtil {
param(
[Parameter(Mandatory)] [string[]] $Arguments,
[Parameter(Mandatory)] [string] $Description
)
$startInfo = [Diagnostics.ProcessStartInfo]::new()
$startInfo.FileName = Join-Path $env:SystemRoot 'System32\certutil.exe'
$startInfo.UseShellExecute = $false
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
foreach ($argument in $Arguments) {
[void] $startInfo.ArgumentList.Add($argument)
}
$process = [Diagnostics.Process]::new()
$process.StartInfo = $startInfo
try {
if (-not $process.Start()) {
throw "Failed to start certutil for $Description."
}
$stdout = $process.StandardOutput.ReadToEndAsync()
$stderr = $process.StandardError.ReadToEndAsync()
if (-not $process.WaitForExit(30000)) {
$process.Kill($true)
$process.WaitForExit()
throw "certutil timed out after 30 seconds while $Description."
}
$stdoutText = $stdout.GetAwaiter().GetResult()
$stderrText = $stderr.GetAwaiter().GetResult()
if (-not [string]::IsNullOrWhiteSpace($stdoutText)) { Write-Host $stdoutText.TrimEnd() }
if (-not [string]::IsNullOrWhiteSpace($stderrText)) { Write-Warning $stderrText.TrimEnd() }
if ($process.ExitCode -ne 0) {
throw "certutil failed while $Description with exit code $($process.ExitCode)."
}
}
finally {
$process.Dispose()
}
}
$trustedStoreNames = @()
try {
foreach ($storeName in @('Root', 'TrustedPublisher')) {
Write-Host "Adding test signer $thumbprint to CurrentUser/$storeName on the ephemeral runner."
$store = [Security.Cryptography.X509Certificates.X509Store]::new(
Invoke-BoundedCertUtil -Arguments @(
'-user',
'-f',
'-addstore',
$storeName,
[Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
)
try {
$store.Open([Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($certificate)
$trustedStoreNames += $storeName
}
finally {
$store.Close()
$store.Dispose()
}
$certificatePath
) -Description "adding the test signer to CurrentUser/$storeName"
$trustedStoreNames += $storeName
}
Write-Host 'Locating signtool.exe.'
@@ -210,6 +246,7 @@ jobs:
$stderr = $process.StandardError.ReadToEndAsync()
if (-not $process.WaitForExit(90000)) {
$process.Kill($true)
$process.WaitForExit()
throw "SignTool verification timed out after 90 seconds for $ArtifactPath."
}
$stdoutText = $stdout.GetAwaiter().GetResult()
@@ -270,18 +307,13 @@ jobs:
}
finally {
foreach ($storeName in $trustedStoreNames) {
$store = [Security.Cryptography.X509Certificates.X509Store]::new(
Write-Host "Removing test signer $thumbprint from CurrentUser/$storeName."
Invoke-BoundedCertUtil -Arguments @(
'-user',
'-delstore',
$storeName,
[Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
)
try {
$store.Open([Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Remove($certificate)
}
finally {
$store.Close()
$store.Dispose()
}
$thumbprint
) -Description "removing the test signer from CurrentUser/$storeName"
}
}
@@ -529,8 +529,12 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn("signedArtifactsUploadedAsGitHubArtifact = $false", workflow)
self.assertIn("nonProduction = $true", workflow)
self.assertIn("WaitForExit(90000)", workflow)
self.assertIn("X509Store]::new", workflow)
self.assertIn("OpenFlags]::ReadWrite", workflow)
self.assertIn("System32\\certutil.exe", workflow)
self.assertIn("WaitForExit(30000)", workflow)
self.assertIn("certutil timed out after 30 seconds", workflow)
self.assertIn("'-addstore'", workflow)
self.assertIn("'-delstore'", workflow)
self.assertNotIn("X509Store]::new", workflow)
self.assertNotIn("Import-Certificate", workflow)
self.assertIn("path: signpath-test-signing-evidence.json", workflow)
self.assertEqual(