mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 08:02:44 +00:00
build(ssh): verify preserved Windows signatures
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -141,6 +141,8 @@ jobs:
|
||||
node --check config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-windows-signature-verification.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-windows-signature-verification.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-windows-source-signature-verification.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-windows-source-signature-verification.test.mjs
|
||||
pnpm exec vitest run --config config/vitest.config.ts \
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs \
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs \
|
||||
@@ -161,6 +163,7 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-windows-signature-verification.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-windows-source-signature-verification.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
|
||||
@@ -472,6 +475,10 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime Windows signature verification script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-windows-signature-verification.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime Windows signature verification test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-windows-source-signature-verification.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime Windows source signature verification script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-windows-source-signature-verification.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime Windows source signature verification test syntax check failed' }
|
||||
pnpm exec vitest run --config config/vitest.config.ts `
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs `
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs `
|
||||
@@ -492,6 +499,7 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-native-signing-apply.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-macos-signature-verification.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-signature-verification.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-source-signature-verification.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
|
||||
@@ -639,6 +647,28 @@ jobs:
|
||||
[System.Text.UTF8Encoding]::new($false)
|
||||
)
|
||||
$reportJson
|
||||
$sourceSignatureReport = Join-Path $outputRoot 'orca-ssh-relay-runtime-${{ matrix.tuple }}.source-signatures.json'
|
||||
$sourceSignatureLines = & node config/scripts/ssh-relay-runtime-windows-source-signature-verification.mjs `
|
||||
--identity (Join-Path $firstOutput 'orca-ssh-relay-runtime-${{ matrix.tuple }}.identity.json') `
|
||||
--runtime-directory (Join-Path $firstOutput 'runtime') `
|
||||
--signing-report $signingReport
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime Windows source signature verification failed' }
|
||||
$sourceSignatureJson = $sourceSignatureLines -join [Environment]::NewLine
|
||||
$sourceSignature = $sourceSignatureJson | ConvertFrom-Json
|
||||
if (
|
||||
$sourceSignature.tupleId -ne '${{ matrix.tuple }}' -or
|
||||
@($sourceSignature.verifiedFiles).Count -ne 3 -or
|
||||
@($sourceSignature.verifiedFiles | Where-Object { $_.signerKind -eq 'official-node' }).Count -ne 1 -or
|
||||
@($sourceSignature.verifiedFiles | Where-Object { $_.signerKind -eq 'preserved-upstream' }).Count -ne 2
|
||||
) {
|
||||
throw 'Windows source signature report violates the immutable/preserved trust contract'
|
||||
}
|
||||
[System.IO.File]::WriteAllText(
|
||||
$sourceSignatureReport,
|
||||
"$sourceSignatureJson`n",
|
||||
[System.Text.UTF8Encoding]::new($false)
|
||||
)
|
||||
$sourceSignatureJson
|
||||
Remove-Item -LiteralPath $signingStage -Recurse -Force
|
||||
$evidence = Join-Path $env:GITHUB_WORKSPACE 'runtime-evidence/${{ matrix.tuple }}'
|
||||
New-Item -ItemType Directory -Path $evidence -Force | Out-Null
|
||||
@@ -648,6 +678,7 @@ jobs:
|
||||
Where-Object { $_.Name -match '\.(?:spdx|provenance|identity)\.json$' } |
|
||||
Copy-Item -Destination $evidence
|
||||
Copy-Item -LiteralPath $signingReport -Destination $evidence
|
||||
Copy-Item -LiteralPath $sourceSignatureReport -Destination $evidence
|
||||
|
||||
- name: Upload unpublished artifact evidence
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
|
||||
@@ -114,7 +114,13 @@ function assertSignerPolicy(signature, signerKind, expectedSigner) {
|
||||
return classified
|
||||
}
|
||||
|
||||
async function verifySignedFile({ path, entry, signerKind, expectedSigner, spawnSyncImpl }) {
|
||||
export async function verifySshRelayRuntimeWindowsSignatureTarget({
|
||||
path,
|
||||
entry,
|
||||
signerKind,
|
||||
expectedSigner,
|
||||
spawnSyncImpl = spawnSync
|
||||
}) {
|
||||
const metadata = await lstat(path)
|
||||
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
||||
throw new Error(`Runtime Windows signature target is not a regular file: ${entry.path}`)
|
||||
@@ -191,7 +197,7 @@ export async function verifySshRelayRuntimeWindowsSignatures({
|
||||
const verifiedFiles = []
|
||||
for (const target of verificationTargets(finalIdentity, selection)) {
|
||||
verifiedFiles.push(
|
||||
await verifySignedFile({
|
||||
await verifySshRelayRuntimeWindowsSignatureTarget({
|
||||
path: localPath(physicalRoot, target.entry.path),
|
||||
...target,
|
||||
spawnSyncImpl
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { lstat, readFile, realpath } from 'node:fs/promises'
|
||||
import { resolve } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
|
||||
import { readSshRelayRuntimeNativeSigningIdentity } from './ssh-relay-runtime-native-signing-plan.mjs'
|
||||
import {
|
||||
assertSshRelayRuntimeNativeSigningSelection,
|
||||
buildSshRelayRuntimeNativeSigningSelection
|
||||
} from './ssh-relay-runtime-native-signing-selection.mjs'
|
||||
import { verifySshRelayRuntimeWindowsSignatureTarget } from './ssh-relay-runtime-windows-signature-verification.mjs'
|
||||
import { verifyRuntimeTree } from './verify-ssh-relay-runtime.mjs'
|
||||
|
||||
const MAX_SIGNING_REPORT_BYTES = 4 * 1024 * 1024
|
||||
const REPORT_FIELDS = [
|
||||
'assessments',
|
||||
'immutableVendorFiles',
|
||||
'payload',
|
||||
'platform',
|
||||
'policy',
|
||||
'preservedUpstreamFiles',
|
||||
'signingFiles',
|
||||
'tupleId'
|
||||
]
|
||||
const ARGUMENT_FIELDS = new Map([
|
||||
['--identity', 'identityPath'],
|
||||
['--runtime-directory', 'runtimeRoot'],
|
||||
['--signing-report', 'signingReportPath']
|
||||
])
|
||||
|
||||
function assertExactFields(value, expected, label) {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
throw new Error(`Runtime Windows source signature ${label} must be an object`)
|
||||
}
|
||||
const actual = Object.keys(value).sort()
|
||||
if (
|
||||
actual.length !== expected.length ||
|
||||
actual.some((field, index) => field !== expected[index])
|
||||
) {
|
||||
throw new Error(`Runtime Windows source signature ${label} has unexpected fields`)
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSshRelayRuntimeWindowsSourceSignatureArguments(argv) {
|
||||
const result = {}
|
||||
for (let index = 0; index < argv.length; index += 2) {
|
||||
const flag = argv[index]
|
||||
const field = ARGUMENT_FIELDS.get(flag)
|
||||
const value = argv[index + 1]
|
||||
if (!field) {
|
||||
throw new Error(`Unknown runtime Windows source signature argument: ${flag}`)
|
||||
}
|
||||
if (result[field]) {
|
||||
throw new Error(`Duplicate runtime Windows source signature argument: ${flag}`)
|
||||
}
|
||||
if (!value || value.startsWith('--')) {
|
||||
throw new Error(`Runtime Windows source signature ${flag} requires a value`)
|
||||
}
|
||||
result[field] = resolve(value)
|
||||
}
|
||||
for (const field of ARGUMENT_FIELDS.values()) {
|
||||
if (!result[field]) {
|
||||
throw new Error(`Missing required runtime Windows source signature argument: ${field}`)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function readSshRelayRuntimeWindowsSigningStageReport(path) {
|
||||
const metadata = await lstat(path)
|
||||
if (metadata.isSymbolicLink() || !metadata.isFile() || metadata.size > MAX_SIGNING_REPORT_BYTES) {
|
||||
throw new Error('Runtime Windows signing-stage report must be one bounded regular file')
|
||||
}
|
||||
const source = await readFile(path, 'utf8')
|
||||
try {
|
||||
return JSON.parse(source)
|
||||
} catch (error) {
|
||||
throw new Error(`Runtime Windows signing-stage report is not valid JSON: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function selectionFromSshRelayRuntimeWindowsSigningStageReport(identity, report) {
|
||||
assertExactFields(report, REPORT_FIELDS, 'signing-stage report')
|
||||
if (!report.payload || typeof report.payload !== 'object' || Array.isArray(report.payload)) {
|
||||
throw new Error('Runtime Windows signing-stage report has malformed payload evidence')
|
||||
}
|
||||
const selection = buildSshRelayRuntimeNativeSigningSelection(identity, report.assessments)
|
||||
const reportSelection = {
|
||||
tupleId: report.tupleId,
|
||||
platform: report.platform,
|
||||
policy: report.policy,
|
||||
immutableVendorFiles: report.immutableVendorFiles,
|
||||
signingFiles: report.signingFiles,
|
||||
preservedUpstreamFiles: report.preservedUpstreamFiles
|
||||
}
|
||||
const expected = {
|
||||
tupleId: selection.tupleId,
|
||||
platform: selection.platform,
|
||||
policy: selection.policy,
|
||||
immutableVendorFiles: selection.immutableVendorFiles,
|
||||
signingFiles: selection.signingFiles,
|
||||
preservedUpstreamFiles: selection.preservedUpstreamFiles
|
||||
}
|
||||
if (!isDeepStrictEqual(reportSelection, expected)) {
|
||||
// Why: retained signature evidence must describe the same hash-bound selection used for staging.
|
||||
throw new Error('Runtime Windows signing-stage report and authenticated selection disagree')
|
||||
}
|
||||
return selection
|
||||
}
|
||||
|
||||
function localPath(root, portablePath) {
|
||||
return resolve(root, ...portablePath.split('/'))
|
||||
}
|
||||
|
||||
export async function verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot,
|
||||
identity,
|
||||
selection,
|
||||
platform = process.platform,
|
||||
spawnSyncImpl = spawnSync
|
||||
}) {
|
||||
if (platform !== 'win32') {
|
||||
throw new Error('Runtime Windows source signature verification requires Windows')
|
||||
}
|
||||
assertSshRelayRuntimeNativeSigningSelection(identity, selection)
|
||||
const rootMetadata = await lstat(resolve(runtimeRoot))
|
||||
if (rootMetadata.isSymbolicLink() || !rootMetadata.isDirectory()) {
|
||||
throw new Error('Runtime Windows source signature verification requires a real runtime root')
|
||||
}
|
||||
const physicalRoot = await realpath(resolve(runtimeRoot))
|
||||
// Why: native trust probes run only after the entire unsigned source tree matches its identity.
|
||||
await verifyRuntimeTree(physicalRoot, identity)
|
||||
|
||||
const sourceFiles = new Map(
|
||||
identity.entries.filter((entry) => entry.type === 'file').map((entry) => [entry.path, entry])
|
||||
)
|
||||
const targets = [
|
||||
...selection.immutableVendorFiles.map((expectedSigner) => ({
|
||||
expectedSigner,
|
||||
signerKind: 'official-node'
|
||||
})),
|
||||
...selection.preservedUpstreamFiles.map((expectedSigner) => ({
|
||||
expectedSigner,
|
||||
signerKind: 'preserved-upstream'
|
||||
}))
|
||||
]
|
||||
const verifiedFiles = []
|
||||
for (const target of targets) {
|
||||
const entry = sourceFiles.get(target.expectedSigner.path)
|
||||
if (!entry) {
|
||||
throw new Error('Runtime Windows source signature target is missing from its identity')
|
||||
}
|
||||
verifiedFiles.push(
|
||||
await verifySshRelayRuntimeWindowsSignatureTarget({
|
||||
path: localPath(physicalRoot, entry.path),
|
||||
entry,
|
||||
...target,
|
||||
spawnSyncImpl
|
||||
})
|
||||
)
|
||||
}
|
||||
return {
|
||||
tupleId: identity.tupleId,
|
||||
sourceContentId: identity.contentId,
|
||||
verifiedFiles
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseSshRelayRuntimeWindowsSourceSignatureArguments(process.argv.slice(2))
|
||||
const [identity, report] = await Promise.all([
|
||||
readSshRelayRuntimeNativeSigningIdentity(options.identityPath),
|
||||
readSshRelayRuntimeWindowsSigningStageReport(options.signingReportPath)
|
||||
])
|
||||
const selection = selectionFromSshRelayRuntimeWindowsSigningStageReport(identity, report)
|
||||
const result = await verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: options.runtimeRoot,
|
||||
identity,
|
||||
selection
|
||||
})
|
||||
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
||||
}
|
||||
|
||||
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(
|
||||
`SSH relay runtime Windows source signature verification failed: ${error.stack ?? error}\n`
|
||||
)
|
||||
process.exitCode = 1
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { appendFileSync } from 'node:fs'
|
||||
import { appendFile, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { expectedSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs'
|
||||
import { sshRelayRuntimeCompatibility } from './ssh-relay-runtime-compatibility.mjs'
|
||||
import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs'
|
||||
import { buildSshRelayRuntimeNativeSigningPlan } from './ssh-relay-runtime-native-signing-plan.mjs'
|
||||
import { buildSshRelayRuntimeNativeSigningSelection } from './ssh-relay-runtime-native-signing-selection.mjs'
|
||||
import {
|
||||
parseSshRelayRuntimeWindowsSourceSignatureArguments,
|
||||
readSshRelayRuntimeWindowsSigningStageReport,
|
||||
selectionFromSshRelayRuntimeWindowsSigningStageReport,
|
||||
verifySshRelayRuntimeWindowsSourceSignatures
|
||||
} from './ssh-relay-runtime-windows-source-signature-verification.mjs'
|
||||
import { OFFICIAL_NODE_WINDOWS_SIGNER_SUBJECT } from './ssh-relay-runtime-windows-signature-verification.mjs'
|
||||
|
||||
const MICROSOFT_SUBJECT =
|
||||
'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
|
||||
|
||||
function digest(bytes) {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
async function sourceFixture() {
|
||||
const root = await mkdtemp(join(tmpdir(), 'ssh-relay-windows-source-signature-'))
|
||||
const runtimeRoot = join(root, 'runtime')
|
||||
await mkdir(runtimeRoot)
|
||||
const entries = []
|
||||
for (const entry of expectedSshRelayRuntimeClosureEntries('win32-x64')) {
|
||||
if (entry.type === 'directory') {
|
||||
await mkdir(join(runtimeRoot, ...entry.path.split('/')), {
|
||||
recursive: true,
|
||||
mode: entry.mode
|
||||
})
|
||||
entries.push(entry)
|
||||
continue
|
||||
}
|
||||
const bytes = Buffer.from(`fixture:${entry.path}`)
|
||||
const path = join(runtimeRoot, ...entry.path.split('/'))
|
||||
await mkdir(dirname(path), { recursive: true })
|
||||
await writeFile(path, bytes, { mode: entry.mode })
|
||||
entries.push({ ...entry, size: bytes.length, sha256: digest(bytes) })
|
||||
}
|
||||
const base = {
|
||||
identitySchemaVersion: 1,
|
||||
tupleId: 'win32-x64',
|
||||
os: 'win32',
|
||||
architecture: 'x64',
|
||||
compatibility: sshRelayRuntimeCompatibility['win32-x64'],
|
||||
nodeVersion: '24.18.0',
|
||||
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
|
||||
entries
|
||||
}
|
||||
const files = entries.filter((entry) => entry.type === 'file')
|
||||
const identity = {
|
||||
...base,
|
||||
contentId: computeSshRelayRuntimeContentId(base),
|
||||
fileCount: files.length,
|
||||
expandedSize: files.reduce((total, entry) => total + entry.size, 0)
|
||||
}
|
||||
const assessments = buildSshRelayRuntimeNativeSigningPlan(identity).signingCandidates.map(
|
||||
(entry, index) => {
|
||||
const preserved =
|
||||
entry.path.endsWith('/OpenConsole.exe') || entry.path.endsWith('/conpty.dll')
|
||||
return preserved
|
||||
? {
|
||||
path: entry.path,
|
||||
sourceSha256: entry.sourceSha256,
|
||||
status: 'valid-upstream',
|
||||
signerSubject: MICROSOFT_SUBJECT,
|
||||
signerThumbprint: `${index + 1}`.repeat(40)
|
||||
}
|
||||
: { path: entry.path, sourceSha256: entry.sourceSha256, status: 'unsigned' }
|
||||
}
|
||||
)
|
||||
const selection = buildSshRelayRuntimeNativeSigningSelection(identity, assessments)
|
||||
const report = {
|
||||
tupleId: selection.tupleId,
|
||||
platform: selection.platform,
|
||||
policy: selection.policy,
|
||||
assessments,
|
||||
immutableVendorFiles: selection.immutableVendorFiles,
|
||||
signingFiles: selection.signingFiles,
|
||||
preservedUpstreamFiles: selection.preservedUpstreamFiles,
|
||||
payload: { stagingRequired: true }
|
||||
}
|
||||
const reportPath = join(root, 'signing-stage.json')
|
||||
await writeFile(reportPath, JSON.stringify(report))
|
||||
return { root, runtimeRoot, identity, assessments, selection, report, reportPath }
|
||||
}
|
||||
|
||||
function entryForPath(selection, physicalPath) {
|
||||
return [...selection.immutableVendorFiles, ...selection.preservedUpstreamFiles].find((entry) =>
|
||||
physicalPath.endsWith(join(...entry.path.split('/')))
|
||||
)
|
||||
}
|
||||
|
||||
function signatureJson(subject, thumbprint) {
|
||||
return JSON.stringify({ status: 'Valid', signerSubject: subject, signerThumbprint: thumbprint })
|
||||
}
|
||||
|
||||
function successfulAuthenticode(selection, calls, override = () => undefined) {
|
||||
return (command, args, options) => {
|
||||
calls.push({ command, args, options })
|
||||
const entry = entryForPath(selection, options.env.ORCA_SSH_RELAY_AUTHENTICODE_FILE)
|
||||
const overridden = override(entry)
|
||||
if (overridden) {
|
||||
return { status: 0, stdout: JSON.stringify(overridden), stderr: '' }
|
||||
}
|
||||
return {
|
||||
status: 0,
|
||||
stdout:
|
||||
entry.action === 'preserve-exact-bytes'
|
||||
? signatureJson(OFFICIAL_NODE_WINDOWS_SIGNER_SUBJECT, 'A'.repeat(40))
|
||||
: signatureJson(entry.signerSubject, entry.signerThumbprint),
|
||||
stderr: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('SSH relay runtime Windows source signature verification', () => {
|
||||
it('parses exact CLI arguments and authenticates the signing-stage report', async () => {
|
||||
expect(
|
||||
parseSshRelayRuntimeWindowsSourceSignatureArguments([
|
||||
'--identity',
|
||||
'identity.json',
|
||||
'--runtime-directory',
|
||||
'runtime',
|
||||
'--signing-report',
|
||||
'report.json'
|
||||
])
|
||||
).toEqual({
|
||||
identityPath: expect.stringMatching(/identity\.json$/u),
|
||||
runtimeRoot: expect.stringMatching(/runtime$/u),
|
||||
signingReportPath: expect.stringMatching(/report\.json$/u)
|
||||
})
|
||||
expect(() =>
|
||||
parseSshRelayRuntimeWindowsSourceSignatureArguments(['--identity', 'identity.json'])
|
||||
).toThrow(/missing required/i)
|
||||
|
||||
const fixture = await sourceFixture()
|
||||
try {
|
||||
const report = await readSshRelayRuntimeWindowsSigningStageReport(fixture.reportPath)
|
||||
expect(
|
||||
selectionFromSshRelayRuntimeWindowsSigningStageReport(fixture.identity, report)
|
||||
).toEqual(fixture.selection)
|
||||
|
||||
report.preservedUpstreamFiles[0].signerThumbprint = '0'.repeat(40)
|
||||
expect(() =>
|
||||
selectionFromSshRelayRuntimeWindowsSigningStageReport(fixture.identity, report)
|
||||
).toThrow(/report and authenticated selection disagree/i)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('verifies the complete source tree before exact Node and preserved signer policy', async () => {
|
||||
const fixture = await sourceFixture()
|
||||
const calls = []
|
||||
try {
|
||||
const result = await verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: fixture.runtimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: fixture.selection,
|
||||
platform: 'win32',
|
||||
spawnSyncImpl: successfulAuthenticode(fixture.selection, calls)
|
||||
})
|
||||
|
||||
expect(result.tupleId).toBe('win32-x64')
|
||||
expect(result.verifiedFiles).toHaveLength(3)
|
||||
expect(result.verifiedFiles.map((entry) => entry.signerKind).sort()).toEqual([
|
||||
'official-node',
|
||||
'preserved-upstream',
|
||||
'preserved-upstream'
|
||||
])
|
||||
expect(calls).toHaveLength(3)
|
||||
expect(calls.every((call) => call.command === 'pwsh')).toBe(true)
|
||||
expect(calls.every((call) => call.options.timeout === 30_000)).toBe(true)
|
||||
expect(calls.every((call) => call.options.maxBuffer === 64 * 1024)).toBe(true)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects wrong Node or preserved signer policy and invalid status', async () => {
|
||||
const fixture = await sourceFixture()
|
||||
const common = {
|
||||
runtimeRoot: fixture.runtimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: fixture.selection,
|
||||
platform: 'win32'
|
||||
}
|
||||
try {
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
...common,
|
||||
spawnSyncImpl: successfulAuthenticode(fixture.selection, [], (entry) =>
|
||||
entry.action === 'preserve-exact-bytes'
|
||||
? {
|
||||
status: 'Valid',
|
||||
signerSubject: 'CN=Unexpected Node',
|
||||
signerThumbprint: 'A'.repeat(40)
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
})
|
||||
).rejects.toThrow(/official Node signer policy/i)
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
...common,
|
||||
spawnSyncImpl: successfulAuthenticode(fixture.selection, [], (entry) =>
|
||||
entry.action === 'preserve-valid-upstream'
|
||||
? {
|
||||
status: 'Valid',
|
||||
signerSubject: entry.signerSubject,
|
||||
signerThumbprint: '0'.repeat(40)
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
})
|
||||
).rejects.toThrow(/preserved upstream signer policy/i)
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
...common,
|
||||
spawnSyncImpl: () => ({
|
||||
status: 0,
|
||||
stdout: JSON.stringify({
|
||||
status: 'NotSigned',
|
||||
signerSubject: null,
|
||||
signerThumbprint: null
|
||||
}),
|
||||
stderr: ''
|
||||
})
|
||||
})
|
||||
).rejects.toThrow(/requires a valid Authenticode signature/i)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects tree mutation before probing and source mutation during a probe', async () => {
|
||||
const fixture = await sourceFixture()
|
||||
let calls = 0
|
||||
try {
|
||||
await appendFile(join(fixture.runtimeRoot, 'relay.js'), ':mutated')
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: fixture.runtimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: fixture.selection,
|
||||
platform: 'win32',
|
||||
spawnSyncImpl: () => {
|
||||
calls += 1
|
||||
throw new Error('must not spawn')
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/integrity mismatch/i)
|
||||
expect(calls).toBe(0)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
const raced = await sourceFixture()
|
||||
try {
|
||||
const spawn = successfulAuthenticode(raced.selection, [])
|
||||
let mutated = false
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: raced.runtimeRoot,
|
||||
identity: raced.identity,
|
||||
selection: raced.selection,
|
||||
platform: 'win32',
|
||||
spawnSyncImpl: (command, args, options) => {
|
||||
if (!mutated) {
|
||||
mutated = true
|
||||
appendFileSync(options.env.ORCA_SSH_RELAY_AUTHENTICODE_FILE, ':raced')
|
||||
}
|
||||
return spawn(command, args, options)
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/changed during signature verification/i)
|
||||
} finally {
|
||||
await rm(raced.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects cross-platform execution and stale preserved-signer evidence', async () => {
|
||||
const fixture = await sourceFixture()
|
||||
let calls = 0
|
||||
try {
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: fixture.runtimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: fixture.selection,
|
||||
platform: 'linux',
|
||||
spawnSyncImpl: () => {
|
||||
calls += 1
|
||||
throw new Error('must not spawn')
|
||||
}
|
||||
})
|
||||
).rejects.toThrow(/requires Windows/i)
|
||||
expect(calls).toBe(0)
|
||||
|
||||
const stale = structuredClone(fixture.selection)
|
||||
stale.preservedUpstreamFiles[0].signerThumbprint = '0'.repeat(40)
|
||||
await expect(
|
||||
verifySshRelayRuntimeWindowsSourceSignatures({
|
||||
runtimeRoot: fixture.runtimeRoot,
|
||||
identity: fixture.identity,
|
||||
selection: stale,
|
||||
platform: 'win32',
|
||||
spawnSyncImpl: successfulAuthenticode(fixture.selection, [])
|
||||
})
|
||||
).rejects.toThrow(/preserved upstream signer policy/i)
|
||||
expect(calls).toBe(0)
|
||||
} finally {
|
||||
await rm(fixture.root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -95,6 +95,9 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(
|
||||
source.match(/ssh-relay-runtime-windows-signature-verification\.test\.mjs/g)
|
||||
).toHaveLength(4)
|
||||
expect(
|
||||
source.match(/ssh-relay-runtime-windows-source-signature-verification\.test\.mjs/g)
|
||||
).toHaveLength(4)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-plan\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
@@ -125,6 +128,11 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
/node --check config\/scripts\/ssh-relay-runtime-windows-signature-verification\.mjs/g
|
||||
)
|
||||
).toHaveLength(2)
|
||||
expect(
|
||||
source.match(
|
||||
/node --check config\/scripts\/ssh-relay-runtime-windows-source-signature-verification\.mjs/g
|
||||
)
|
||||
).toHaveLength(2)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-closure\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
@@ -219,7 +227,20 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(windowsRun).toContain('@($report.preservedUpstreamFiles).Count -ne 2')
|
||||
expect(windowsRun).toContain('Required upstream signature was not preserved')
|
||||
expect(windowsRun).toContain("Join-Path $signingStage 'bin/node.exe'")
|
||||
expect(windowsRun).toContain('ssh-relay-runtime-windows-source-signature-verification.mjs')
|
||||
expect(
|
||||
windowsRun.indexOf('ssh-relay-runtime-windows-source-signature-verification.mjs')
|
||||
).toBeGreaterThan(windowsRun.indexOf('ssh-relay-runtime-native-signing-stage.mjs'))
|
||||
expect(
|
||||
windowsRun.indexOf('ssh-relay-runtime-windows-source-signature-verification.mjs')
|
||||
).toBeLessThan(windowsRun.indexOf('Remove-Item -LiteralPath $signingStage'))
|
||||
expect(windowsRun).toContain(
|
||||
'Windows source signature report violates the immutable/preserved trust contract'
|
||||
)
|
||||
expect(windowsRun).toContain("$_.signerKind -eq 'official-node'")
|
||||
expect(windowsRun).toContain("$_.signerKind -eq 'preserved-upstream'")
|
||||
expect(source.match(/\.signing-stage\.json/g)).toHaveLength(3)
|
||||
expect(source.match(/\.source-signatures\.json/g)).toHaveLength(1)
|
||||
expect(source).not.toMatch(/SIGNPATH_|APPLE_(?:ID|KEY)|Developer ID Application/)
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user