38 lines
3.2 KiB
JavaScript
38 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'});
|
||
|
|
});
|