Skip to content
Closed
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
62 changes: 62 additions & 0 deletions .buildkite/commands/setup_windows_code_signing.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,68 @@
# Windows AMI as of a8c-ci-toolkit 6.0.0, so there is no separate host-preparation step.

$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

& "setup_azure_trusted_signing.ps1"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

function Assert-SigningToolIntegrity {
param (
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
[string]$Description,
[Parameter(Mandatory = $true)]
[string]$ExpectedSha256EnvVar
)

if ([string]::IsNullOrWhiteSpace($Path)) {
Write-Host "[!] $Description path is not set."
exit 1
}

if (-not (Test-Path -LiteralPath $Path)) {
Write-Host "[!] $Description was not found at $Path."
exit 1
}

$expectedSha256 = [Environment]::GetEnvironmentVariable($ExpectedSha256EnvVar)
if (-not [string]::IsNullOrWhiteSpace($expectedSha256)) {
$actualSha256 = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
if ($actualSha256 -ne $expectedSha256.Trim().ToUpperInvariant()) {
Write-Host "[!] $Description SHA256 mismatch."
Write-Host "Expected: $($expectedSha256.Trim().ToUpperInvariant())"
Write-Host "Actual: $actualSha256"
exit 1
}

Write-Host "$Description SHA256 matched $ExpectedSha256EnvVar."
return
}

$signature = Get-AuthenticodeSignature -FilePath $Path
if ($signature.Status -ne "Valid" -or $null -eq $signature.SignerCertificate) {
Write-Host "[!] $Description does not have a valid Authenticode signature."
Write-Host "Status: $($signature.Status)"
exit 1
}

$subject = $signature.SignerCertificate.Subject
if ($subject.IndexOf("Microsoft", [System.StringComparison]::OrdinalIgnoreCase) -lt 0) {
Write-Host "[!] $Description is not signed by an expected Microsoft certificate."
Write-Host "Actual signer subject: $subject"
exit 1
}

Write-Host "$Description Authenticode signature is valid: $subject"
}

Assert-SigningToolIntegrity `
-Path $env:SIGNTOOL_PATH `
-Description "signtool.exe" `
-ExpectedSha256EnvVar "WINDOWS_SIGNTOOL_SHA256"

Assert-SigningToolIntegrity `
-Path $env:AZURE_CODE_SIGNING_DLIB `
-Description "Azure Trusted Signing DLib" `
-ExpectedSha256EnvVar "AZURE_CODE_SIGNING_DLIB_SHA256"
51 changes: 51 additions & 0 deletions .buildkite/commands/verify_windows_signature.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

$artifacts = @(Get-ChildItem -Path "dist\*.exe" -File -ErrorAction SilentlyContinue)
if ($artifacts.Count -eq 0) {
Write-Host "[!] No Windows .exe artifacts found in dist."
exit 1
}

if ([string]::IsNullOrWhiteSpace($env:SIGNTOOL_PATH)) {
Write-Host "[!] SIGNTOOL_PATH is not set."
exit 1
}

if ([string]::IsNullOrWhiteSpace($env:WINDOWS_EXPECTED_SIGNER_SUBJECT)) {
Write-Host "[!] WINDOWS_EXPECTED_SIGNER_SUBJECT must be set to verify the artifact signer identity."
exit 1
}

foreach ($artifact in $artifacts) {
Write-Host "Verifying Authenticode signature for $($artifact.FullName)"
& $env:SIGNTOOL_PATH verify /pa /v $artifact.FullName
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

$signature = Get-AuthenticodeSignature -FilePath $artifact.FullName
if ($signature.Status -ne "Valid" -or $null -eq $signature.SignerCertificate) {
Write-Host "[!] $($artifact.Name) does not have a valid Authenticode signature."
Write-Host "Status: $($signature.Status)"
exit 1
}

$actualSubject = $signature.SignerCertificate.Subject
if ($actualSubject.IndexOf($env:WINDOWS_EXPECTED_SIGNER_SUBJECT.Trim(), [System.StringComparison]::OrdinalIgnoreCase) -lt 0) {
Write-Host "[!] $($artifact.Name) was not signed by the expected subject."
Write-Host "Expected subject to contain: $($env:WINDOWS_EXPECTED_SIGNER_SUBJECT.Trim())"
Write-Host "Actual signer subject: $actualSubject"
exit 1
}

if (-not [string]::IsNullOrWhiteSpace($env:WINDOWS_EXPECTED_SIGNER_ISSUER)) {
$actualIssuer = $signature.SignerCertificate.Issuer
if ($actualIssuer.IndexOf($env:WINDOWS_EXPECTED_SIGNER_ISSUER.Trim(), [System.StringComparison]::OrdinalIgnoreCase) -lt 0) {
Write-Host "[!] $($artifact.Name) was not signed by the expected issuer."
Write-Host "Expected issuer to contain: $($env:WINDOWS_EXPECTED_SIGNER_ISSUER.Trim())"
Write-Host "Actual signer issuer: $actualIssuer"
exit 1
}
}

Write-Host "$($artifact.Name) signer identity verified."
}
23 changes: 17 additions & 6 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,31 @@ steps:
agents: { queue: windows }
plugins: [$CI_TOOLKIT, $NVM_PLUGIN]
command: |
$ErrorActionPreference = "Stop"

function Invoke-NativeCommand {
param (
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(ValueFromRemainingArguments = $true)]
[string[]]$ArgumentList
)

& $FilePath @ArgumentList
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

echo "~~~ Setup code signing"
.buildkite/commands/setup_windows_code_signing.ps1

echo "~~~ Install dependencies"
bash .buildkite/commands/install_node_dependencies.sh
Invoke-NativeCommand bash .buildkite/commands/install_node_dependencies.sh

echo "~~~ Build Windows artifact"
npm run dist:win
Invoke-NativeCommand npm run dist:win

echo "~~~ Verify Azure Trusted Signing signature"
Get-ChildItem dist\*.exe | ForEach-Object {
& $env:SIGNTOOL_PATH verify /pa /v $_.FullName
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}
.buildkite/commands/verify_windows_signature.ps1
artifact_paths:
- dist\*.exe
notify:
Expand Down
Loading