prepare-installed-update-network.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. /** Prepare one operator-owned firewall fault for an installed, independently verified qualification executable. */
  2. import { lstat, mkdir, readFile, realpath, writeFile } from 'node:fs/promises'
  3. import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path'
  4. import { readInstalledUpdateRun } from './installed-update-qualification.ts'
  5. import { installedUpdateFileHash } from './installed-update-signature.mjs'
  6. /**
  7. * Bind a local recovery plan to the version 1 executable hash from retained package verification.
  8. * @param manifest Original qualification run.json.
  9. * @param executable Absolute installed test application path, not the extracted verification payload.
  10. * @param receipt Successful version 1 package verification result.json.
  11. * @returns Exclusively created plan path; no credentials, subprocess, or firewall operation is involved.
  12. */
  13. export async function prepareInstalledUpdateNetwork(manifest: string, executable: string, receipt: string): Promise<string> {
  14. const run = await readInstalledUpdateRun(manifest)
  15. const receiptRelative = relative(join(run.root, run.versions[0], 'verification'), resolve(receipt)).replaceAll('\\', '/')
  16. if (!/^check-[^/]+\/result\.json$/u.test(receiptRelative)) throw new Error('installed update: version 1 verification receipt is required')
  17. const result = JSON.parse(await readFile(receipt, 'utf8')) as {
  18. schemaVersion?: unknown
  19. runId?: unknown
  20. version?: unknown
  21. passed?: unknown
  22. applicationSignature?: { sha512?: unknown; valid?: unknown; timestamped?: unknown; updaterVerificationInvoked?: unknown }
  23. contents?: { appId?: unknown; version?: unknown }
  24. }
  25. const signature = result.applicationSignature
  26. if (result.schemaVersion !== 1 || result.runId !== run.id || result.version !== run.versions[0] || result.passed !== true
  27. || result.contents?.appId !== run.appId || result.contents.version !== run.versions[0]
  28. || signature?.valid !== true || signature.timestamped !== true || signature.updaterVerificationInvoked !== true
  29. || typeof signature.sha512 !== 'string' || !/^[A-Za-z0-9+/]{86}==$/u.test(signature.sha512)) {
  30. throw new Error('installed update: successful identity and signature verification is required')
  31. }
  32. if (!isAbsolute(executable) || basename(executable) !== `${run.productName}.exe`
  33. || !(await lstat(executable)).isFile()) {
  34. throw new Error('installed update: installed test executable path or bytes do not match verification')
  35. }
  36. const installed = await realpath(executable)
  37. const insideRun = relative(await realpath(run.root), installed)
  38. if ((!insideRun.startsWith(`..${sep}`) && !isAbsolute(insideRun))
  39. || await installedUpdateFileHash(installed) !== signature.sha512) {
  40. throw new Error('installed update: installed test executable path or bytes do not match verification')
  41. }
  42. const directory = join(run.root, 'network-fault')
  43. await mkdir(directory)
  44. const path = join(directory, 'plan.json')
  45. await writeFile(path, `${JSON.stringify({ schemaVersion: 1, runId: run.id, executable: installed,
  46. sha512Hex: Buffer.from(signature.sha512, 'base64').toString('hex').toUpperCase(),
  47. ruleName: `DSH-Update-Qualification-${run.id}`, manifest: resolve(manifest), receipt: resolve(receipt),
  48. receiptSha512: await installedUpdateFileHash(receipt), networkChanged: false }, null, 2)}\n`, { flag: 'wx', flush: true })
  49. return path
  50. }
  51. if (process.argv[1] !== undefined && resolve(process.argv[1]) === resolve(import.meta.filename)) {
  52. const [manifest, executable, receipt, ...extra] = process.argv.slice(2)
  53. if (!manifest || !executable || !receipt || extra.length) {
  54. console.error('usage: prepare-installed-update-network.ts <run.json> <installed-test.exe> <verification/result.json>')
  55. process.exitCode = 1
  56. } else prepareInstalledUpdateNetwork(manifest, executable, receipt).then(path => console.log(path)).catch(() => {
  57. console.error('installed update: network plan preparation failed; no network changes were made')
  58. process.exitCode = 1
  59. })
  60. }