Files
safetynet/tests/verified-installer.test.mjs

39 lines
3.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {createWindowsPackagePlan} from '../scripts/windows-package-plan.mjs';
import {buildVerifiedInstaller} from '../scripts/verified-installer.mjs';
const identity={sourceSha256:'a'.repeat(64),runtimeSha256:'b'.repeat(64),packageSha256:'c'.repeat(64)};
async function fixture(t){
const root=await mkdtemp(path.join(os.tmpdir(),'safetynet-installer-verifier-'));t.after(()=>rm(root,{force:true,recursive:true}));
const output=path.join(root,'dist');await mkdir(output);const plan=createWindowsPackagePlan({workspace:root,outputDirectory:output,electronDirectory:path.join(root,'electron'),platform:'win32'});
return {plan,expectedIdentity:identity,readIdentity:async()=>identity,verify:async()=>({identity,fuses:true,resources:true,broker:true,runtimeAcl:true}),installerPath:path.join(output,'fixture.exe')};
}
test('actual invoked installer arguments require before and after package verification',async t=>{
const f=await fixture(t);let calls=0,verifications=0;
const result=await buildVerifiedInstaller({...f,verify:async()=>{verifications++;return f.verify();},run:async args=>{calls++;assert.ok(args.includes('--prepackaged'));const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake);return {status:0};}});
assert.equal(calls,1);assert.equal(verifications,2);assert.equal(result.bytes,1024);assert.match(result.sha256,/^[a-f0-9]{64}$/);
// Synthetic PE prefix exercises orchestration only, never installer validity.
});
test('source mutation during successful CLI run invalidates the installer result',async t=>{
const f=await fixture(t);let changed=false;
await assert.rejects(buildVerifiedInstaller({...f,readIdentity:async()=>changed?{...identity,sourceSha256:'d'.repeat(64)}:identity,run:async()=>{changed=true;return {status:0};}}),{code:'installer_identity_changed'});
});
test('failed CLI and missing or stale artifact cannot pass based on a good plan',async t=>{
const f=await fixture(t);
await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:1})}),{code:'installer_build_failed'});
await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:0})}),{code:'installer_artifact_invalid'});
const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake);
await assert.rejects(buildVerifiedInstaller({...f,run:async()=>({status:0})}),{code:'installer_artifact_invalid'});
});
test('post-invocation mandatory security verification cannot be skipped',async t=>{
const f=await fixture(t);let calls=0;
await assert.rejects(buildVerifiedInstaller({...f,verify:async()=>({...await f.verify(),broker:++calls===1}),run:async()=>({status:0})}),{code:'installer_verification_failed'});
});
test('identity stays pinned through final artifact hashing and result delivery',async t=>{
const f=await fixture(t);let reads=0;
await assert.rejects(buildVerifiedInstaller({...f,readIdentity:async()=>++reads>=6?{...identity,sourceSha256:'d'.repeat(64)}:identity,run:async()=>{const fake=Buffer.alloc(1024);fake.write('MZ');await writeFile(f.installerPath,fake);return {status:0};}}),{code:'installer_identity_changed'});
});