Files
safetynet/tests/broker-service-bootstrap.test.mjs
T

70 lines
7.4 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {generateKeyPairSync,createHash} from 'node:crypto';
import {defaultPolicy,signPolicy} from '../packages/core/policy.mjs';
const {createServiceFactory}=createRequire(import.meta.url)('../apps/network-broker/service-bootstrap.cjs');
function fixture(){
const keys=generateKeyPairSync('ed25519'),publicKey=keys.publicKey.export({type:'spki',format:'pem'}),policy=defaultPolicy();policy.version=4;policy.assignment={deviceId:'managed-device',groups:[]};policy.proxy={mode:'direct'};
const config={publicKey,deviceId:'managed-device',minimumVersion:4,envelope:signPolicy(policy,keys.privateKey.export({type:'pkcs8',format:'pem'})),brokerPort:43120,network:{mode:'direct',dns:'system'}};
return {config,binding:{publicKey,deviceId:'managed-device',minimumVersion:4}};
}
const trusted=f=>({bootstrap:f.binding,deployment:{manifest:{expiresAt:Date.now()+60000}},policyVersion:4,policyDigest:createHash('sha256').update(f.config.envelope.payload).digest('hex')});
const broker=()=>({listen:async()=>43120,close:async()=>{}});
test('caller-selected receiver port fails before trust loading or construction',async()=>{
const f=fixture();let loads=0,launches=0;
const factory=createServiceFactory({loadTrust:async()=>{loads++;return trusted(f);},factory:async()=>{launches++;return broker();}});
for(const port of [1,43100,43121,65535])await assert.rejects(factory({...f.config,brokerPort:port},{expectedPolicyVersion:4}),{message:'broker-bootstrap-invalid'});
assert.equal(loads,0);assert.equal(launches,0);
});
test('service bootstrap rejects caller-selected key/device or lower protected floor before factory',async()=>{
const f=fixture();let launches=0;const factory=createServiceFactory({loadTrust:async()=>trusted(f),factory:async()=>{launches++;return broker();}});
const forged=generateKeyPairSync('ed25519').publicKey.export({type:'spki',format:'pem'});
for(const changed of [{publicKey:forged},{deviceId:'other-user-device'},{minimumVersion:3},{minimumVersion:0},{resourceRoot:'C:/caller-selected'}])await assert.rejects(factory({...f.config,...changed},{expectedPolicyVersion:4}),{message:'broker-bootstrap-invalid'});
assert.equal(launches,0);
});
test('trusted SID-derived mapping keeps stricter caller floor and exact callbacks without mutation',async()=>{
const f=fixture(),callbacks={expectedPolicyVersion:4};let observed;
const factory=createServiceFactory({loadTrust:async()=>trusted(f),factory:async(config,value)=>{observed={config,value};return broker();}});
const result=await factory({...f.config,minimumVersion:5},callbacks);assert.equal(await result.listen(),43120);await result.close();assert.equal(observed.config.minimumVersion,5);assert.equal(observed.config.publicKey,f.binding.publicKey);assert.equal(observed.value,callbacks);assert.equal(f.config.minimumVersion,4);
});
test('missing assignment, invalid trusted floor and failed trust load expose only fixed error',async()=>{
const f=fixture();let launches=0;
for(const loadTrust of [async()=>({}),async()=>({...trusted(f),bootstrap:{...f.binding,minimumVersion:0}}),async()=>{throw Error('PRIVATE_PATH_OR_KEY');}]){
const factory=createServiceFactory({loadTrust,factory:async()=>{launches++;}});
await assert.rejects(factory(f.config,{}),error=>error.message==='broker-bootstrap-invalid'&&!String(error).includes('PRIVATE'));
}
assert.equal(launches,0);
});
test('real vendor/customer signature chain binds bootstrap to actual supplied peer SID',async()=>{
const {verifyReleaseEnvelope,verifyDeploymentEnvelope,signManifest}=createRequire(import.meta.url)('../packages/core/windows-trust.cjs');
const f=fixture(),vendor=generateKeyPairSync('ec',{namedCurve:'prime256v1'}),customer=generateKeyPairSync('ec',{namedCurve:'prime256v1'});
const pub=key=>key.publicKey.export({type:'spki',format:'pem'}),priv=key=>key.privateKey.export({type:'pkcs8',format:'pem'});
const release={format:'safetynet-windows-release-v1',releaseId:'12345678-1234-1234-1234-123456789abc',releaseSequence:1,serviceName:'SafetyNetNetworkBroker',deploymentTenant:'22345678-1234-1234-1234-123456789abc',deploymentPublicKey:pub(customer),files:['SafetyNet.exe','resources/app.asar','resources/broker/node.exe','resources/inspector/server.py','resources/python/python.exe','resources/semantic/model.bin','safetynet-service-host.exe'].map(path=>({path,bytes:1,sha256:'a'.repeat(64)}))};
const signedRelease=signManifest(release,priv(vendor),'release'),verifiedRelease=verifyReleaseEnvelope(signedRelease,{publicKey:pub(vendor),floor:{minimumSequence:1}});
const sid='S-1-12-1-1-2-3-4',deployment={format:'safetynet-windows-deployment-v1',releaseId:release.releaseId,deploymentTenant:release.deploymentTenant,deploymentSequence:1,policyPublicKey:f.binding.publicKey,expiresAt:Date.now()+60000,users:[{sid,deviceId:f.binding.deviceId,minimumPolicyVersion:4}]};
let envelope=signManifest(deployment,priv(customer),'deployment'),peer=sid,launches=0;
const factory=createServiceFactory({loadTrust:async()=>{const deployment=verifyDeploymentEnvelope(envelope,{release:verifiedRelease,authenticatedSid:peer,floor:{minimumSequence:1}});return {...trusted(f),bootstrap:deployment.bootstrap,deployment};},factory:async()=>{launches++;return broker();}});
const result=await factory(f.config,{expectedPolicyVersion:4});assert.equal(await result.listen(),43120);await result.close();
peer='S-1-12-1-5-6-7-8';await assert.rejects(factory(f.config,{}),{message:'broker-bootstrap-invalid'});
peer=sid;envelope=signManifest(deployment,priv(vendor),'deployment');await assert.rejects(factory(f.config,{}),{message:'broker-bootstrap-invalid'});
assert.equal(launches,1);
});
test('deployment expiration rejects startup and closes an otherwise valid running broker',async()=>{
const f=fixture();let closed=0,expired;
const stopped=new Promise(resolve=>expired=resolve);
const factory=createServiceFactory({loadTrust:async()=>({...trusted(f),deployment:{manifest:{expiresAt:Date.now()+60}}}),factory:async()=>({listen:async()=>43120,close:async()=>{closed++;expired();}})});
const result=await factory(f.config,{expectedPolicyVersion:4});assert.equal(await result.listen(),43120);
await Promise.race([stopped,new Promise((_,reject)=>{const limit=setTimeout(()=>reject(Error('expiry not enforced')),1000);limit.unref();})]);
assert.equal(closed,1);await assert.rejects(result.listen(),{message:'broker-bootstrap-invalid'});await result.close();assert.equal(closed,1);
let launches=0;const rejected=createServiceFactory({loadTrust:async()=>({...trusted(f),deployment:{manifest:{expiresAt:Date.now()-1}}}),factory:async()=>{launches++;return broker();}});
await assert.rejects(rejected(f.config,{}),{message:'broker-bootstrap-invalid'});assert.equal(launches,0);
});
test('repeated close waits the original cleanup and preserves its failure',async()=>{
const f=fixture();let finish,closes=0;
const cleanup=new Promise((_,reject)=>finish=reject);
const factory=createServiceFactory({loadTrust:async()=>trusted(f),factory:async()=>({listen:async()=>43120,close:()=>{closes++;return cleanup;}})});
const result=await factory(f.config,{expectedPolicyVersion:4}),first=result.close(),second=result.close();assert.equal(first,second);
const failure=Error('synthetic cleanup failure'),rejected=assert.rejects(first,error=>error===failure);await new Promise(resolve=>setImmediate(resolve));assert.equal(closes,1);finish(failure);await rejected;await assert.rejects(result.close(),error=>error===failure);
});