mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Prevent unsigned Windows app releases (#6806)
* Verify Windows app executable signing * Isolate Windows signing verifier tests * Handle direct Windows installer extraction --------- Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -1062,6 +1062,51 @@ jobs:
|
||||
}
|
||||
$signature.SignerCertificate | Format-List Subject,Issuer,NotBefore,NotAfter,Thumbprint
|
||||
|
||||
- name: Verify signed Windows inner executable
|
||||
if: matrix.platform == 'win'
|
||||
shell: pwsh
|
||||
env:
|
||||
ORCA_WINDOWS_EXPECTED_SIGNERS: CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US;CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, ST=Delaware, C=US
|
||||
run: |
|
||||
# Why: SignPath must recursively sign nested PE files in the dashboard
|
||||
# artifact configuration; this gate keeps unsigned app payloads out of releases.
|
||||
$installer = Join-Path -Path (Get-Location) -ChildPath 'dist/orca-windows-setup.exe'
|
||||
$setupExtractDir = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'orca-signed-setup'
|
||||
$appExtractDir = Join-Path -Path $env:RUNNER_TEMP -ChildPath 'orca-signed-app'
|
||||
|
||||
Remove-Item -LiteralPath $setupExtractDir, $appExtractDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Path $setupExtractDir, $appExtractDir -Force | Out-Null
|
||||
|
||||
& 7z x -y "-o$setupExtractDir" $installer
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to extract signed Windows installer with 7z. Exit code: $LASTEXITCODE"
|
||||
}
|
||||
|
||||
# Why: 7-Zip can expose electron-builder NSIS payloads either as an
|
||||
# app-root tree directly or as a nested app-*.7z archive.
|
||||
$innerExecutables = @(Get-ChildItem -LiteralPath $setupExtractDir -File -Filter 'Orca.exe')
|
||||
if ($innerExecutables.Count -eq 0) {
|
||||
$appArchives = @(Get-ChildItem -LiteralPath $setupExtractDir -Recurse -File -Filter 'app-*.7z')
|
||||
if ($appArchives.Count -ne 1) {
|
||||
$matches = ($appArchives | ForEach-Object { $_.FullName }) -join [Environment]::NewLine
|
||||
throw "Expected app-root Orca.exe or exactly one app-*.7z payload in signed Windows installer; found $($appArchives.Count) app archive(s).$([Environment]::NewLine)$matches"
|
||||
}
|
||||
|
||||
& 7z x -y "-o$appExtractDir" $($appArchives[0].FullName)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to extract signed Windows app payload with 7z. Exit code: $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$innerExecutables = @(Get-ChildItem -LiteralPath $appExtractDir -File -Filter 'Orca.exe')
|
||||
}
|
||||
|
||||
if ($innerExecutables.Count -ne 1) {
|
||||
$matches = ($innerExecutables | ForEach-Object { $_.FullName }) -join [Environment]::NewLine
|
||||
throw "Expected exactly one app-root Orca.exe in signed Windows app payload; found $($innerExecutables.Count).$([Environment]::NewLine)$matches"
|
||||
}
|
||||
|
||||
node config/scripts/verify-windows-inner-signature.mjs $($innerExecutables[0].FullName)
|
||||
|
||||
- name: Publish signed Windows release artifacts
|
||||
if: matrix.platform == 'win'
|
||||
uses: nick-fields/retry@v4
|
||||
|
||||
@@ -217,6 +217,53 @@ describe('Electron runtime package contract', () => {
|
||||
expect(installRun).not.toMatch(/throw\s+\$_/)
|
||||
})
|
||||
|
||||
it('verifies the SignPath-signed Windows inner executable before publishing', () => {
|
||||
const releaseWorkflow = readFileSync(
|
||||
join(projectDir, '.github/workflows/release-cut.yml'),
|
||||
'utf8'
|
||||
)
|
||||
const parsedWorkflow = parse(releaseWorkflow)
|
||||
const steps = parsedWorkflow.jobs.build.steps
|
||||
const stepNames = steps.map((step) => step.name)
|
||||
const outerVerifyIndex = stepNames.indexOf('Verify signed Windows installer')
|
||||
const innerVerifyIndex = stepNames.indexOf('Verify signed Windows inner executable')
|
||||
const publishIndex = stepNames.indexOf('Publish signed Windows release artifacts')
|
||||
|
||||
expect(outerVerifyIndex).toBeGreaterThan(-1)
|
||||
expect(innerVerifyIndex).toBe(outerVerifyIndex + 1)
|
||||
expect(innerVerifyIndex).toBeLessThan(publishIndex)
|
||||
|
||||
const innerVerifyStep = steps[innerVerifyIndex]
|
||||
const innerVerifyRun = innerVerifyStep.run
|
||||
|
||||
expect(innerVerifyStep.if).toBe("matrix.platform == 'win'")
|
||||
expect(innerVerifyStep.shell).toBe('pwsh')
|
||||
expect(innerVerifyStep.env.ORCA_WINDOWS_EXPECTED_SIGNERS).toBe(
|
||||
'CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US;CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, ST=Delaware, C=US'
|
||||
)
|
||||
expect(innerVerifyRun).toContain('& 7z x -y "-o$setupExtractDir" $installer')
|
||||
expect(innerVerifyRun).toContain(
|
||||
"$innerExecutables = @(Get-ChildItem -LiteralPath $setupExtractDir -File -Filter 'Orca.exe')"
|
||||
)
|
||||
expect(innerVerifyRun).toContain('if ($innerExecutables.Count -eq 0)')
|
||||
expect(innerVerifyRun).toContain(
|
||||
"$appArchives = @(Get-ChildItem -LiteralPath $setupExtractDir -Recurse -File -Filter 'app-*.7z')"
|
||||
)
|
||||
expect(innerVerifyRun).toContain('if ($appArchives.Count -ne 1)')
|
||||
expect(innerVerifyRun).toContain('Expected app-root Orca.exe or exactly one app-*.7z')
|
||||
expect(innerVerifyRun).toContain('& 7z x -y "-o$appExtractDir" $($appArchives[0].FullName)')
|
||||
expect(innerVerifyRun).toContain(
|
||||
"$innerExecutables = @(Get-ChildItem -LiteralPath $appExtractDir -File -Filter 'Orca.exe')"
|
||||
)
|
||||
expect(innerVerifyRun.indexOf("-Filter 'Orca.exe'")).toBeLessThan(
|
||||
innerVerifyRun.indexOf("-Filter 'app-*.7z'")
|
||||
)
|
||||
expect(innerVerifyRun).toContain('if ($innerExecutables.Count -ne 1)')
|
||||
expect(innerVerifyRun).toContain(
|
||||
'node config/scripts/verify-windows-inner-signature.mjs $($innerExecutables[0].FullName)'
|
||||
)
|
||||
})
|
||||
|
||||
it('publishes both Linux release matrix entries', () => {
|
||||
const releaseWorkflow = readFileSync(
|
||||
join(projectDir, '.github/workflows/release-cut.yml'),
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { existsSync, statSync } from 'node:fs'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
|
||||
export const DEFAULT_EXPECTED_SIGNER =
|
||||
'CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US'
|
||||
|
||||
const POWERSHELL_SIGNATURE_SCRIPT = String.raw`
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$signature = Get-AuthenticodeSignature -FilePath $args[0]
|
||||
$certificate = $signature.SignerCertificate
|
||||
[pscustomobject]@{
|
||||
status = $signature.Status.ToString()
|
||||
statusMessage = $signature.StatusMessage
|
||||
signerSubject = if ($null -eq $certificate) { $null } else { $certificate.Subject }
|
||||
signerIssuer = if ($null -eq $certificate) { $null } else { $certificate.Issuer }
|
||||
signerThumbprint = if ($null -eq $certificate) { $null } else { $certificate.Thumbprint }
|
||||
notBefore = if ($null -eq $certificate) { $null } else { $certificate.NotBefore.ToString('o') }
|
||||
notAfter = if ($null -eq $certificate) { $null } else { $certificate.NotAfter.ToString('o') }
|
||||
} | ConvertTo-Json -Compress
|
||||
`
|
||||
|
||||
export function normalizeSignerSubject(subject) {
|
||||
if (typeof subject !== 'string') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return subject
|
||||
.split(',')
|
||||
.map((part) => part.trim().replace(/\s*=\s*/u, '='))
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
export function normalizeThumbprint(thumbprint) {
|
||||
if (typeof thumbprint !== 'string') {
|
||||
return ''
|
||||
}
|
||||
|
||||
return thumbprint.replace(/[^0-9a-f]/giu, '').toUpperCase()
|
||||
}
|
||||
|
||||
export function parseExpectedSigners(value = process.env.ORCA_WINDOWS_EXPECTED_SIGNERS) {
|
||||
const source = typeof value === 'string' && value.trim() !== '' ? value : DEFAULT_EXPECTED_SIGNER
|
||||
|
||||
return source
|
||||
.split(/[\r\n;]+/u)
|
||||
.map(normalizeSignerSubject)
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function parseExpectedThumbprints(value = process.env.ORCA_WINDOWS_EXPECTED_THUMBPRINTS) {
|
||||
if (typeof value !== 'string' || value.trim() === '') {
|
||||
return []
|
||||
}
|
||||
|
||||
return value
|
||||
.split(/[\r\n,;]+/u)
|
||||
.map(normalizeThumbprint)
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function parseSignatureJson(stdout) {
|
||||
const trimmed = typeof stdout === 'string' ? stdout.trim() : ''
|
||||
if (trimmed === '') {
|
||||
throw new Error('PowerShell did not return signature JSON.')
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(trimmed)
|
||||
} catch (error) {
|
||||
throw new Error(`PowerShell returned malformed signature JSON: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function classifySignature(signature, options = {}) {
|
||||
const expectedSigners = options.expectedSigners ?? parseExpectedSigners()
|
||||
const expectedThumbprints = options.expectedThumbprints ?? parseExpectedThumbprints()
|
||||
const status = typeof signature?.status === 'string' ? signature.status : ''
|
||||
const signerSubject = normalizeSignerSubject(signature?.signerSubject)
|
||||
const signerThumbprint = normalizeThumbprint(signature?.signerThumbprint)
|
||||
const subjectAllowed = expectedSigners.includes(signerSubject)
|
||||
const thumbprintAllowed =
|
||||
expectedThumbprints.length > 0 &&
|
||||
signerThumbprint !== '' &&
|
||||
expectedThumbprints.includes(signerThumbprint)
|
||||
|
||||
if (status !== 'Valid') {
|
||||
return {
|
||||
ok: false,
|
||||
message: `Windows inner executable signature status is ${status || '<missing>'}.`,
|
||||
signature
|
||||
}
|
||||
}
|
||||
|
||||
if (!subjectAllowed && !thumbprintAllowed) {
|
||||
return {
|
||||
ok: false,
|
||||
message: `Unexpected Windows inner executable signer: ${signerSubject || '<missing>'}.`,
|
||||
signature
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true, signature }
|
||||
}
|
||||
|
||||
export function formatSignatureSummary(signature) {
|
||||
return [
|
||||
`Status: ${signature.status ?? '<missing>'}`,
|
||||
`Subject: ${normalizeSignerSubject(signature.signerSubject) || '<missing>'}`,
|
||||
`Issuer: ${signature.signerIssuer ?? '<missing>'}`,
|
||||
`Thumbprint: ${normalizeThumbprint(signature.signerThumbprint) || '<missing>'}`,
|
||||
`NotBefore: ${signature.notBefore ?? '<missing>'}`,
|
||||
`NotAfter: ${signature.notAfter ?? '<missing>'}`
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
export function validateExecutablePath(executablePath) {
|
||||
if (typeof executablePath !== 'string' || executablePath.trim() === '') {
|
||||
throw new Error('Usage: node config/scripts/verify-windows-inner-signature.mjs <Orca.exe>')
|
||||
}
|
||||
|
||||
if (!existsSync(executablePath)) {
|
||||
throw new Error(`Windows inner executable does not exist: ${executablePath}`)
|
||||
}
|
||||
|
||||
if (!statSync(executablePath).isFile()) {
|
||||
throw new Error(`Windows inner executable path is not a file: ${executablePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getPowerShellSignatureJson(executablePath, spawnSyncImpl = spawnSync) {
|
||||
const result = spawnSyncImpl(
|
||||
'pwsh',
|
||||
[
|
||||
'-NoLogo',
|
||||
'-NoProfile',
|
||||
'-NonInteractive',
|
||||
'-ExecutionPolicy',
|
||||
'Bypass',
|
||||
'-Command',
|
||||
POWERSHELL_SIGNATURE_SCRIPT,
|
||||
executablePath
|
||||
],
|
||||
{ encoding: 'utf8' }
|
||||
)
|
||||
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
|
||||
if (result.stderr?.trim()) {
|
||||
throw new Error(`PowerShell wrote to stderr while checking signature:\n${result.stderr.trim()}`)
|
||||
}
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`PowerShell signature check failed with exit code ${result.status ?? '<unknown>'}.`
|
||||
)
|
||||
}
|
||||
|
||||
return result.stdout
|
||||
}
|
||||
|
||||
export function verifyWindowsInnerSignature({
|
||||
executablePath,
|
||||
platform = process.platform,
|
||||
spawnSyncImpl = spawnSync,
|
||||
expectedSigners = parseExpectedSigners(),
|
||||
expectedThumbprints = parseExpectedThumbprints()
|
||||
}) {
|
||||
validateExecutablePath(executablePath)
|
||||
|
||||
if (platform !== 'win32') {
|
||||
throw new Error('Windows inner executable signature verification requires Windows.')
|
||||
}
|
||||
|
||||
const signature = parseSignatureJson(getPowerShellSignatureJson(executablePath, spawnSyncImpl))
|
||||
const classification = classifySignature(signature, { expectedSigners, expectedThumbprints })
|
||||
if (!classification.ok) {
|
||||
throw new Error(`${classification.message}\n${formatSignatureSummary(signature)}`)
|
||||
}
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
export function main(argv = process.argv.slice(2)) {
|
||||
try {
|
||||
const signature = verifyWindowsInnerSignature({ executablePath: argv[0] })
|
||||
console.log('Verified Windows inner executable signature.')
|
||||
console.log(formatSignatureSummary(signature))
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
process.exitCode = 1
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
main()
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
DEFAULT_EXPECTED_SIGNER,
|
||||
classifySignature,
|
||||
getPowerShellSignatureJson,
|
||||
normalizeSignerSubject,
|
||||
normalizeThumbprint,
|
||||
parseExpectedSigners,
|
||||
parseExpectedThumbprints,
|
||||
parseSignatureJson,
|
||||
validateExecutablePath,
|
||||
verifyWindowsInnerSignature
|
||||
} from './verify-windows-inner-signature.mjs'
|
||||
|
||||
const validSignature = {
|
||||
status: 'Valid',
|
||||
statusMessage: 'Signature verified.',
|
||||
signerSubject: DEFAULT_EXPECTED_SIGNER,
|
||||
signerIssuer: 'CN=SignPath Foundation Root',
|
||||
signerThumbprint: 'aa bb cc dd',
|
||||
notBefore: '2026-01-01T00:00:00.0000000Z',
|
||||
notAfter: '2027-01-01T00:00:00.0000000Z'
|
||||
}
|
||||
|
||||
function withTempFile(callback) {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'orca-inner-signature-'))
|
||||
const filePath = join(dir, 'Orca.exe')
|
||||
writeFileSync(filePath, 'placeholder executable')
|
||||
|
||||
try {
|
||||
return callback(filePath, dir)
|
||||
} finally {
|
||||
rmSync(dir, { force: true, recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
describe('verify-windows-inner-signature', () => {
|
||||
const originalExpectedSigners = process.env.ORCA_WINDOWS_EXPECTED_SIGNERS
|
||||
const originalExpectedThumbprints = process.env.ORCA_WINDOWS_EXPECTED_THUMBPRINTS
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.ORCA_WINDOWS_EXPECTED_SIGNERS
|
||||
delete process.env.ORCA_WINDOWS_EXPECTED_THUMBPRINTS
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (originalExpectedSigners === undefined) {
|
||||
delete process.env.ORCA_WINDOWS_EXPECTED_SIGNERS
|
||||
} else {
|
||||
process.env.ORCA_WINDOWS_EXPECTED_SIGNERS = originalExpectedSigners
|
||||
}
|
||||
|
||||
if (originalExpectedThumbprints === undefined) {
|
||||
delete process.env.ORCA_WINDOWS_EXPECTED_THUMBPRINTS
|
||||
} else {
|
||||
process.env.ORCA_WINDOWS_EXPECTED_THUMBPRINTS = originalExpectedThumbprints
|
||||
}
|
||||
})
|
||||
|
||||
it('normalizes signer subjects without widening exact matching', () => {
|
||||
expect(
|
||||
normalizeSignerSubject(
|
||||
' CN = SignPath Foundation , O=SignPath Foundation,L=Lewes,S=Delaware,C=US '
|
||||
)
|
||||
).toBe(DEFAULT_EXPECTED_SIGNER)
|
||||
expect(
|
||||
normalizeSignerSubject(
|
||||
'CN=Different Signer, O=SignPath Foundation, L=Lewes, S=Delaware, C=US'
|
||||
)
|
||||
).not.toBe(DEFAULT_EXPECTED_SIGNER)
|
||||
})
|
||||
|
||||
it('defaults the expected signer allowlist to SignPath Foundation', () => {
|
||||
expect(parseExpectedSigners('')).toEqual([DEFAULT_EXPECTED_SIGNER])
|
||||
expect(parseExpectedSigners(' ')).toEqual([DEFAULT_EXPECTED_SIGNER])
|
||||
})
|
||||
|
||||
it('parses semicolon and newline separated signer allowlists', () => {
|
||||
expect(parseExpectedSigners(`${DEFAULT_EXPECTED_SIGNER};\nCN=Backup, O=Backup`)).toEqual([
|
||||
DEFAULT_EXPECTED_SIGNER,
|
||||
'CN=Backup, O=Backup'
|
||||
])
|
||||
})
|
||||
|
||||
it('normalizes optional thumbprint allowlists', () => {
|
||||
expect(normalizeThumbprint('aa bb:cc')).toBe('AABBCC')
|
||||
expect(parseExpectedThumbprints('aa bb cc,11:22:33')).toEqual(['AABBCC', '112233'])
|
||||
})
|
||||
|
||||
it('rejects missing, nonexistent, and directory executable paths before PowerShell', () => {
|
||||
expect(() => validateExecutablePath('')).toThrow(/Usage:/)
|
||||
expect(() => validateExecutablePath(join(tmpdir(), 'missing-Orca.exe'))).toThrow(
|
||||
/does not exist/
|
||||
)
|
||||
|
||||
withTempFile((filePath, dir) => {
|
||||
expect(() => validateExecutablePath(dir)).toThrow(/not a file/)
|
||||
expect(() => validateExecutablePath(filePath)).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
it('parses the exact JSON emitted by PowerShell', () => {
|
||||
expect(parseSignatureJson(JSON.stringify(validSignature))).toEqual(validSignature)
|
||||
expect(() => parseSignatureJson('')).toThrow(/did not return/)
|
||||
expect(() => parseSignatureJson(`${JSON.stringify(validSignature)}\nextra`)).toThrow(
|
||||
/malformed/
|
||||
)
|
||||
})
|
||||
|
||||
it('accepts a valid signature with an exact normalized signer subject', () => {
|
||||
const result = classifySignature({
|
||||
...validSignature,
|
||||
signerSubject: ' CN=SignPath Foundation, O=SignPath Foundation, L=Lewes, S=Delaware, C=US '
|
||||
})
|
||||
|
||||
expect(result.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects invalid status and unexpected signer subjects', () => {
|
||||
expect(classifySignature({ ...validSignature, status: 'NotSigned' }).message).toMatch(
|
||||
/status is NotSigned/
|
||||
)
|
||||
expect(
|
||||
classifySignature({
|
||||
...validSignature,
|
||||
signerSubject: 'CN=SignPath Foundation Test, O=SignPath Foundation, L=Vienna, C=AT'
|
||||
}).message
|
||||
).toMatch(/Unexpected Windows inner executable signer/)
|
||||
})
|
||||
|
||||
it('accepts an expected thumbprint as an alternate explicit allowlist', () => {
|
||||
expect(classifySignature({ ...validSignature, signerThumbprint: '00' }).ok).toBe(true)
|
||||
expect(
|
||||
classifySignature(
|
||||
{ ...validSignature, signerSubject: 'CN=Rotated Signer, O=Rotated' },
|
||||
{
|
||||
expectedSigners: [DEFAULT_EXPECTED_SIGNER],
|
||||
expectedThumbprints: ['AABBCCDD']
|
||||
}
|
||||
).ok
|
||||
).toBe(true)
|
||||
expect(
|
||||
classifySignature(
|
||||
{ ...validSignature, signerSubject: 'CN=Rotated Signer, O=Rotated' },
|
||||
{
|
||||
expectedSigners: [DEFAULT_EXPECTED_SIGNER],
|
||||
expectedThumbprints: ['001122']
|
||||
}
|
||||
).message
|
||||
).toMatch(/Unexpected Windows inner executable signer/)
|
||||
expect(
|
||||
classifySignature(validSignature, {
|
||||
expectedSigners: [DEFAULT_EXPECTED_SIGNER],
|
||||
expectedThumbprints: ['001122']
|
||||
}).ok
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('runs PowerShell with an argument array and fails on stderr or nonzero exit', () => {
|
||||
const calls = []
|
||||
const spawnSyncImpl = (command, args, options) => {
|
||||
calls.push({ command, args, options })
|
||||
return { status: 0, stdout: JSON.stringify(validSignature), stderr: '' }
|
||||
}
|
||||
|
||||
expect(getPowerShellSignatureJson('C:\\Path With Spaces\\Orca.exe', spawnSyncImpl)).toBe(
|
||||
JSON.stringify(validSignature)
|
||||
)
|
||||
expect(calls[0].command).toBe('pwsh')
|
||||
expect(calls[0].args).toContain('-Command')
|
||||
expect(calls[0].args.at(-1)).toBe('C:\\Path With Spaces\\Orca.exe')
|
||||
expect(calls[0].options).toEqual({ encoding: 'utf8' })
|
||||
|
||||
expect(() =>
|
||||
getPowerShellSignatureJson('Orca.exe', () => ({ status: 0, stdout: '{}', stderr: 'warning' }))
|
||||
).toThrow(/stderr/)
|
||||
expect(() =>
|
||||
getPowerShellSignatureJson('Orca.exe', () => ({ status: 7, stdout: '', stderr: '' }))
|
||||
).toThrow(/exit code 7/)
|
||||
})
|
||||
|
||||
it('verifies with injected Windows platform and spawn implementation', () => {
|
||||
withTempFile((filePath) => {
|
||||
const signature = verifyWindowsInnerSignature({
|
||||
executablePath: filePath,
|
||||
platform: 'win32',
|
||||
spawnSyncImpl: () => ({ status: 0, stdout: JSON.stringify(validSignature), stderr: '' })
|
||||
})
|
||||
|
||||
expect(signature).toEqual(validSignature)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not attempt real Authenticode verification outside Windows', () => {
|
||||
withTempFile((filePath) => {
|
||||
expect(() =>
|
||||
verifyWindowsInnerSignature({
|
||||
executablePath: filePath,
|
||||
platform: 'linux',
|
||||
spawnSyncImpl: () => {
|
||||
throw new Error('should not spawn')
|
||||
}
|
||||
})
|
||||
).toThrow(/requires Windows/)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user