Files
orca/config/scripts/ssh-relay-node-release-file-verification.mjs
T
2026-07-14 09:58:21 -04:00

99 lines
3.1 KiB
JavaScript

import { createHash } from 'node:crypto'
import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { validateSshRelayNodeReleaseContract } from './ssh-relay-node-release-contract.mjs'
async function assertBoundedRegularFile(filePath, maximumBytes, label) {
const metadata = await stat(filePath)
if (!metadata.isFile()) {
throw new Error(`${label} must be a regular file`)
}
if (metadata.size === 0 || metadata.size > maximumBytes) {
throw new Error(`${label} exceeds its size limit`)
}
return metadata
}
export async function readBoundedFile(filePath, maximumBytes, label) {
await assertBoundedRegularFile(filePath, maximumBytes, label)
const chunks = []
let bytes = 0
const stream = createReadStream(filePath)
try {
for await (const chunk of stream) {
bytes += chunk.length
if (bytes > maximumBytes) {
throw new Error(`${label} exceeds its size limit`)
}
chunks.push(chunk)
}
} catch (error) {
stream.destroy()
throw error
}
if (bytes === 0) {
throw new Error(`${label} must not be empty`)
}
return Buffer.concat(chunks, bytes)
}
export async function hashBoundedFile(filePath, maximumBytes, label) {
await assertBoundedRegularFile(filePath, maximumBytes, label)
const digest = createHash('sha256')
let bytes = 0
const stream = createReadStream(filePath)
try {
for await (const chunk of stream) {
bytes += chunk.length
if (bytes > maximumBytes) {
throw new Error(`${label} exceeds its size limit`)
}
digest.update(chunk)
}
} catch (error) {
stream.destroy()
throw error
}
if (bytes === 0) {
throw new Error(`${label} must not be empty`)
}
return { bytes, sha256: digest.digest('hex') }
}
export async function verifySshRelayNodeArchive(releaseInput, tuple, archivePath) {
const release = validateSshRelayNodeReleaseContract(releaseInput)
const archive = release.archives[tuple]
if (archive === undefined) {
throw new Error(`Unknown Node archive tuple: ${String(tuple)}`)
}
const result = await hashBoundedFile(archivePath, release.maximumArchiveBytes, 'Node archive')
if (result.sha256 !== archive.sha256) {
throw new Error(`Node archive SHA-256 mismatch for ${tuple}`)
}
return { tuple, name: archive.name, ...result }
}
export async function verifySshRelayNodeWindowsBuildInput(releaseInput, kind, tuple, inputPath) {
const release = validateSshRelayNodeReleaseContract(releaseInput)
const input =
kind === 'headers'
? release.windowsBuildInputs.headersArchive
: kind === 'import-library'
? release.windowsBuildInputs.importLibraries[tuple]
: undefined
if (!input) {
throw new Error(`Unknown Node Windows build input: ${kind}/${String(tuple)}`)
}
const result = await hashBoundedFile(
inputPath,
release.maximumArchiveBytes,
`Node Windows ${kind}`
)
if (result.sha256 !== input.sha256) {
throw new Error(`Node Windows ${kind} SHA-256 mismatch for ${String(tuple)}`)
}
return { kind, tuple: kind === 'headers' ? undefined : tuple, name: input.name, ...result }
}