Files
Recordly/.github/workflows/signpath-test.yml

162 lines
7.2 KiB
YAML

name: SignPath Test Signing
on:
workflow_dispatch:
permissions:
actions: read # SignPath reads the uploaded workflow artifact and job metadata.
contents: read # Checkout and SignPath origin metadata need repository access.
concurrency:
group: signpath-test-signing
cancel-in-progress: false
jobs:
sign-cursor-monitor:
name: Sign cursor monitor with the test policy
if: github.repository == 'webadderallorg/Recordly' && github.ref == 'refs/heads/main'
runs-on: windows-latest
timeout-minutes: 20
steps:
- name: Require the SignPath CI token
shell: pwsh
env:
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
run: |
if ([string]::IsNullOrWhiteSpace($env:SIGNPATH_API_TOKEN)) {
throw 'SIGNPATH_API_TOKEN is not configured. Refusing to submit a signing request.'
}
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
- name: Build an unsigned cursor monitor
id: build
shell: pwsh
run: |
node scripts/build-cursor-monitor.mjs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$source = Join-Path $env:GITHUB_WORKSPACE 'electron/native/bin/win32-x64/cursor-monitor.exe'
$inputDirectory = Join-Path $env:RUNNER_TEMP 'recordly-signpath-input'
$input = Join-Path $inputDirectory 'cursor-monitor.exe'
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) {
throw "Expected cursor monitor was not built: $source"
}
$signature = Get-AuthenticodeSignature -LiteralPath $source
if ($signature.Status -ne [System.Management.Automation.SignatureStatus]::NotSigned) {
throw "Test input must be unsigned, but status was $($signature.Status)."
}
New-Item -ItemType Directory -Path $inputDirectory -Force | Out-Null
Copy-Item -LiteralPath $source -Destination $input -Force
"unsigned-sha256=$((Get-FileHash -LiteralPath $input -Algorithm SHA256).Hash)" |
Add-Content -LiteralPath $env:GITHUB_OUTPUT
- name: Upload the unsigned PE
id: upload-unsigned
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: ${{ runner.temp }}/recordly-signpath-input/cursor-monitor.exe
archive: false
if-no-files-found: error
retention-days: 1
- name: Submit the test-signing request
id: signpath
uses: SignPath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2.2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: d10f26b9-667f-46fb-9ecf-17c19d024c1a
project-slug: Recordly
signing-policy-slug: test-signing
artifact-configuration-slug: initial
github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: ${{ runner.temp }}/recordly-signpath-signed
skip-decompress: true
- name: Verify the test-signed PE
id: verify
shell: pwsh
run: |
$signedDirectory = Join-Path $env:RUNNER_TEMP 'recordly-signpath-signed'
$signedFiles = @(Get-ChildItem -LiteralPath $signedDirectory -Filter 'cursor-monitor.exe' -File -Recurse)
if ($signedFiles.Count -ne 1) {
throw "Expected exactly one signed cursor-monitor.exe, found $($signedFiles.Count)."
}
$signed = $signedFiles[0].FullName
$unsigned = Join-Path $env:RUNNER_TEMP 'recordly-signpath-input/cursor-monitor.exe'
$unsignedHash = (Get-FileHash -LiteralPath $unsigned -Algorithm SHA256).Hash
$signedHash = (Get-FileHash -LiteralPath $signed -Algorithm SHA256).Hash
if ($signedHash -eq $unsignedHash) {
throw 'Signed and unsigned artifacts have the same SHA-256 digest.'
}
$signature = Get-AuthenticodeSignature -LiteralPath $signed
if ($signature.SignatureType -ne [System.Management.Automation.SignatureType]::Authenticode) {
throw "Expected Authenticode, found $($signature.SignatureType)."
}
if ($signature.Status -eq [System.Management.Automation.SignatureStatus]::HashMismatch -or
$signature.Status -eq [System.Management.Automation.SignatureStatus]::NotSigned) {
throw "Invalid Authenticode status: $($signature.Status) — $($signature.StatusMessage)"
}
if ($signature.Status -notin @(
[System.Management.Automation.SignatureStatus]::Valid,
[System.Management.Automation.SignatureStatus]::UnknownError
)) {
throw "Unexpected Authenticode status: $($signature.Status) — $($signature.StatusMessage)"
}
if ($signature.Status -eq [System.Management.Automation.SignatureStatus]::UnknownError -and
$signature.StatusMessage -notmatch '(?i)root certificate.*not trusted|not trusted.*root certificate') {
throw "The test signature failed for an unexpected reason: $($signature.StatusMessage)"
}
$expectedSubject = "CN=Test certificate for 'Recordly [OSS]'"
if ($null -eq $signature.SignerCertificate -or
$signature.SignerCertificate.Subject -ne $expectedSubject) {
throw "Unexpected signer: $($signature.SignerCertificate.Subject)"
}
$hasCodeSigningEku = @($signature.SignerCertificate.EnhancedKeyUsageList) |
Where-Object { $_.ObjectId -eq '1.3.6.1.5.5.7.3.3' }
if (-not $hasCodeSigningEku) {
throw 'The signer certificate does not contain the Code Signing EKU.'
}
if ($null -eq $signature.TimeStamperCertificate) {
throw 'The test-signed artifact has no timestamp certificate.'
}
"signed-sha256=$signedHash" | Add-Content -LiteralPath $env:GITHUB_OUTPUT
"signer-thumbprint=$($signature.SignerCertificate.Thumbprint)" |
Add-Content -LiteralPath $env:GITHUB_OUTPUT
- name: Record the verification summary
shell: pwsh
env:
SIGNING_REQUEST_URL: ${{ steps.signpath.outputs.signing-request-web-url }}
UNSIGNED_SHA256: ${{ steps.build.outputs.unsigned-sha256 }}
SIGNED_SHA256: ${{ steps.verify.outputs.signed-sha256 }}
SIGNER_THUMBPRINT: ${{ steps.verify.outputs.signer-thumbprint }}
run: |
@(
'## SignPath test-signing verification'
''
'- Policy: `test-signing` (self-signed test certificate; not for release)'
('- Signing request: {0}' -f $env:SIGNING_REQUEST_URL)
('- Unsigned SHA-256: `{0}`' -f $env:UNSIGNED_SHA256)
('- Signed SHA-256: `{0}`' -f $env:SIGNED_SHA256)
('- Signer thumbprint: `{0}`' -f $env:SIGNER_THUMBPRINT)
'- Public release/upload: none'
) | Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY