mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
314 lines
10 KiB
JavaScript
314 lines
10 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { writeFileSync } from 'node:fs'
|
|
import { lstat, mkdir, mkdtemp, readFile, rm, symlink, 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 {
|
|
assessSshRelayRuntimeWindowsAuthenticode,
|
|
classifySshRelayRuntimeWindowsAuthenticode,
|
|
getSshRelayRuntimeWindowsAuthenticodeJson,
|
|
parseSshRelayRuntimeWindowsAuthenticodeJson
|
|
} from './ssh-relay-runtime-windows-authenticode-assessment.mjs'
|
|
|
|
function digest(bytes) {
|
|
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
|
}
|
|
|
|
async function windowsFixture(tupleId = 'win32-x64') {
|
|
const root = await mkdtemp(join(tmpdir(), 'ssh-relay-authenticode-assessment-'))
|
|
const runtimeRoot = join(root, 'runtime')
|
|
await mkdir(runtimeRoot)
|
|
const entries = []
|
|
for (const entry of expectedSshRelayRuntimeClosureEntries(tupleId)) {
|
|
if (entry.type === 'directory') {
|
|
await mkdir(join(runtimeRoot, ...entry.path.split('/')), { recursive: true })
|
|
entries.push(entry)
|
|
continue
|
|
}
|
|
const bytes = Buffer.from(`fixture:${tupleId}:${entry.path}`)
|
|
const filePath = join(runtimeRoot, ...entry.path.split('/'))
|
|
await mkdir(dirname(filePath), { recursive: true })
|
|
await writeFile(filePath, bytes)
|
|
entries.push({ ...entry, size: bytes.length, sha256: digest(bytes) })
|
|
}
|
|
return {
|
|
root,
|
|
runtimeRoot,
|
|
identity: {
|
|
tupleId,
|
|
os: 'win32',
|
|
nodeVersion: '24.18.0',
|
|
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
|
|
entries
|
|
}
|
|
}
|
|
}
|
|
|
|
function signatureJson({
|
|
status = 'NotSigned',
|
|
signerSubject = null,
|
|
signerThumbprint = null
|
|
} = {}) {
|
|
return JSON.stringify({ status, signerSubject, signerThumbprint })
|
|
}
|
|
|
|
describe('SSH relay runtime Windows Authenticode assessment', () => {
|
|
it('classifies only exact unsigned and valid-upstream signature states', () => {
|
|
expect(
|
|
classifySshRelayRuntimeWindowsAuthenticode({
|
|
status: 'NotSigned',
|
|
signerSubject: null,
|
|
signerThumbprint: null
|
|
})
|
|
).toEqual({ status: 'unsigned' })
|
|
expect(
|
|
classifySshRelayRuntimeWindowsAuthenticode({
|
|
status: 'Valid',
|
|
signerSubject: 'CN=Microsoft Corporation',
|
|
signerThumbprint: 'a'.repeat(40)
|
|
})
|
|
).toEqual({
|
|
status: 'valid-upstream',
|
|
signerSubject: 'CN=Microsoft Corporation',
|
|
signerThumbprint: 'A'.repeat(40)
|
|
})
|
|
|
|
for (const status of ['HashMismatch', 'NotTrusted', 'UnknownError', 'PublisherMismatch']) {
|
|
expect(() =>
|
|
classifySshRelayRuntimeWindowsAuthenticode({
|
|
status,
|
|
signerSubject: null,
|
|
signerThumbprint: null
|
|
})
|
|
).toThrow(/rejects signature status/i)
|
|
}
|
|
})
|
|
|
|
it('rejects malformed JSON, fields, and status-qualified certificate metadata', () => {
|
|
expect(() => parseSshRelayRuntimeWindowsAuthenticodeJson('')).toThrow(/did not return/i)
|
|
expect(() => parseSshRelayRuntimeWindowsAuthenticodeJson('{')).toThrow(/malformed/i)
|
|
expect(() =>
|
|
parseSshRelayRuntimeWindowsAuthenticodeJson(
|
|
JSON.stringify({
|
|
status: 'NotSigned',
|
|
signerSubject: null,
|
|
signerThumbprint: null,
|
|
extra: true
|
|
})
|
|
)
|
|
).toThrow(/unexpected fields/i)
|
|
expect(() =>
|
|
classifySshRelayRuntimeWindowsAuthenticode({
|
|
status: 'NotSigned',
|
|
signerSubject: 'CN=Unexpected',
|
|
signerThumbprint: null
|
|
})
|
|
).toThrow(/unsigned.*certificate/i)
|
|
expect(() =>
|
|
classifySshRelayRuntimeWindowsAuthenticode({
|
|
status: 'Valid',
|
|
signerSubject: '',
|
|
signerThumbprint: 'bad'
|
|
})
|
|
).toThrow(/valid.*certificate/i)
|
|
})
|
|
|
|
it('uses a bounded noninteractive PowerShell probe with the path only in the environment', () => {
|
|
const calls = []
|
|
const stdout = signatureJson()
|
|
const result = getSshRelayRuntimeWindowsAuthenticodeJson(
|
|
'C:\\Path With Spaces\\candidate.node',
|
|
(command, args, options) => {
|
|
calls.push({ command, args, options })
|
|
return { status: 0, stdout, stderr: '' }
|
|
}
|
|
)
|
|
|
|
expect(result).toBe(stdout)
|
|
expect(calls).toHaveLength(1)
|
|
expect(calls[0].command).toBe('pwsh')
|
|
expect(calls[0].args).toEqual(
|
|
expect.arrayContaining(['-NoLogo', '-NoProfile', '-NonInteractive', '-Command'])
|
|
)
|
|
expect(calls[0].args.join(' ')).not.toContain('Path With Spaces')
|
|
expect(calls[0].options).toEqual(
|
|
expect.objectContaining({
|
|
encoding: 'utf8',
|
|
timeout: 30_000,
|
|
maxBuffer: 64 * 1024,
|
|
windowsHide: true,
|
|
env: expect.objectContaining({
|
|
ORCA_SSH_RELAY_AUTHENTICODE_FILE: 'C:\\Path With Spaces\\candidate.node'
|
|
})
|
|
})
|
|
)
|
|
})
|
|
|
|
it('fails on probe errors, stderr, nonzero exit, or empty output', () => {
|
|
expect(() =>
|
|
getSshRelayRuntimeWindowsAuthenticodeJson('candidate.node', () => ({
|
|
error: new Error('timed out'),
|
|
status: null,
|
|
stdout: '',
|
|
stderr: ''
|
|
}))
|
|
).toThrow(/timed out/i)
|
|
expect(() =>
|
|
getSshRelayRuntimeWindowsAuthenticodeJson('candidate.node', () => ({
|
|
status: 0,
|
|
stdout: signatureJson(),
|
|
stderr: 'warning'
|
|
}))
|
|
).toThrow(/stderr/i)
|
|
expect(() =>
|
|
getSshRelayRuntimeWindowsAuthenticodeJson('candidate.node', () => ({
|
|
status: 7,
|
|
stdout: '',
|
|
stderr: ''
|
|
}))
|
|
).toThrow(/exit code 7/i)
|
|
expect(() =>
|
|
getSshRelayRuntimeWindowsAuthenticodeJson('candidate.node', () => ({
|
|
status: 0,
|
|
stdout: '',
|
|
stderr: ''
|
|
}))
|
|
).toThrow(/did not return/i)
|
|
})
|
|
|
|
it('hash-binds and assesses every exact Windows signing candidate', async () => {
|
|
const fixture = await windowsFixture()
|
|
const calls = []
|
|
try {
|
|
const assessments = await assessSshRelayRuntimeWindowsAuthenticode({
|
|
identity: fixture.identity,
|
|
runtimeRoot: fixture.runtimeRoot,
|
|
platform: 'win32',
|
|
spawnSyncImpl: (_command, _args, options) => {
|
|
calls.push(options.env.ORCA_SSH_RELAY_AUTHENTICODE_FILE)
|
|
const isUpstream =
|
|
options.env.ORCA_SSH_RELAY_AUTHENTICODE_FILE.endsWith('OpenConsole.exe')
|
|
return {
|
|
status: 0,
|
|
stdout: isUpstream
|
|
? signatureJson({
|
|
status: 'Valid',
|
|
signerSubject: 'CN=Microsoft Corporation',
|
|
signerThumbprint: 'b'.repeat(40)
|
|
})
|
|
: signatureJson(),
|
|
stderr: ''
|
|
}
|
|
}
|
|
})
|
|
|
|
expect(assessments).toHaveLength(5)
|
|
expect(calls).toHaveLength(5)
|
|
expect(assessments.find((entry) => entry.status === 'valid-upstream')).toEqual(
|
|
expect.objectContaining({
|
|
path: 'node_modules/node-pty/build/Release/conpty/OpenConsole.exe',
|
|
signerSubject: 'CN=Microsoft Corporation',
|
|
signerThumbprint: 'B'.repeat(40)
|
|
})
|
|
)
|
|
expect(assessments.filter((entry) => entry.status === 'unsigned')).toHaveLength(4)
|
|
for (const assessment of assessments) {
|
|
expect(assessment.sourceSha256).toMatch(/^sha256:[0-9a-f]{64}$/)
|
|
}
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('rejects host mismatch and source mutation before the first probe', async () => {
|
|
const fixture = await windowsFixture('win32-arm64')
|
|
let calls = 0
|
|
const spawnSyncImpl = () => {
|
|
calls += 1
|
|
return { status: 0, stdout: signatureJson(), stderr: '' }
|
|
}
|
|
try {
|
|
await expect(
|
|
assessSshRelayRuntimeWindowsAuthenticode({
|
|
identity: fixture.identity,
|
|
runtimeRoot: fixture.runtimeRoot,
|
|
platform: 'darwin',
|
|
spawnSyncImpl
|
|
})
|
|
).rejects.toThrow(/requires Windows/i)
|
|
|
|
const candidate = fixture.identity.entries.find(
|
|
(entry) => entry.type === 'file' && entry.role === 'node-pty-native'
|
|
)
|
|
await writeFile(join(fixture.runtimeRoot, ...candidate.path.split('/')), 'mutated')
|
|
await expect(
|
|
assessSshRelayRuntimeWindowsAuthenticode({
|
|
identity: fixture.identity,
|
|
runtimeRoot: fixture.runtimeRoot,
|
|
platform: 'win32',
|
|
spawnSyncImpl
|
|
})
|
|
).rejects.toThrow(/authenticated (size|hash)/i)
|
|
expect(calls).toBe(0)
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('rejects a candidate changed during its PowerShell assessment', async () => {
|
|
const fixture = await windowsFixture()
|
|
let calls = 0
|
|
try {
|
|
await expect(
|
|
assessSshRelayRuntimeWindowsAuthenticode({
|
|
identity: fixture.identity,
|
|
runtimeRoot: fixture.runtimeRoot,
|
|
platform: 'win32',
|
|
spawnSyncImpl: (_command, _args, options) => {
|
|
calls += 1
|
|
writeFileSync(options.env.ORCA_SSH_RELAY_AUTHENTICODE_FILE, 'changed-during-probe')
|
|
return { status: 0, stdout: signatureJson(), stderr: '' }
|
|
}
|
|
})
|
|
).rejects.toThrow(/changed during assessment/i)
|
|
expect(calls).toBe(1)
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'rejects a symlinked candidate before PowerShell',
|
|
async () => {
|
|
const fixture = await windowsFixture()
|
|
try {
|
|
const candidate = fixture.identity.entries.find(
|
|
(entry) => entry.type === 'file' && entry.role === 'node-pty-native'
|
|
)
|
|
const candidatePath = join(fixture.runtimeRoot, ...candidate.path.split('/'))
|
|
const target = join(fixture.root, 'candidate-target')
|
|
await writeFile(target, await readFile(candidatePath))
|
|
await rm(candidatePath)
|
|
await symlink(target, candidatePath)
|
|
await expect(
|
|
assessSshRelayRuntimeWindowsAuthenticode({
|
|
identity: fixture.identity,
|
|
runtimeRoot: fixture.runtimeRoot,
|
|
platform: 'win32',
|
|
spawnSyncImpl: () => {
|
|
throw new Error('should not probe')
|
|
}
|
|
})
|
|
).rejects.toThrow(/symbolic link/i)
|
|
expect((await lstat(candidatePath)).isSymbolicLink()).toBe(true)
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true })
|
|
}
|
|
}
|
|
)
|
|
})
|