Files
safetynet/tests/broker-policy-authority.test.mjs
T

54 lines
5.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {generateKeyPairSync,sign} from 'node:crypto';
import {createRequire} from 'node:module';
import {PassThrough} from 'node:stream';
import {defaultPolicy,signPolicy,verifyPolicy} from '../packages/core/policy.mjs';
const {createPolicyAuthority}=createRequire(import.meta.url)('../apps/network-broker/policy-authority.cjs');
const {gateRequest}=createRequire(import.meta.url)('../apps/network-broker/http-request-gate.cjs');
const keys=generateKeyPairSync('ed25519'),publicKey=keys.publicKey.export({type:'spki',format:'pem'}),privateKey=keys.privateKey.export({type:'pkcs8',format:'pem'});
const policy=(version=1)=>({...defaultPolicy(),version,assignment:{deviceId:'device-1',groups:[]}});
const create=()=>createPolicyAuthority({publicKey,deviceId:'device-1',minimumVersion:1});
test('actual core signature/schema verification produces deeply immutable device authority',async()=>{
const authority=await create(),envelope=signPolicy(policy(),privateKey),snapshot=authority.accept(envelope);
assert.deepEqual(snapshot.policy,verifyPolicy(envelope,publicKey,1));assert.equal(authority.current(snapshot.token),true);
assert.ok(Object.isFrozen(snapshot.policy.dlp.rules));assert.throws(()=>snapshot.policy.browser.allowedHosts.push('evil.example'));
envelope.payload='mutated';assert.equal(authority.current(snapshot.token),true);
});
test('expired, wrong-device, modified, malformed schema and rollback invalidate old authority',async()=>{
for(const invalid of ['expired','wrong-device','signature','schema','rollback']) {
const authority=await create(),valid=authority.accept(signPolicy(policy(2),privateKey));let envelope;
if(invalid==='expired'){const p=policy(3);p.expiresAt=new Date(0).toISOString();envelope=signPolicy(p,privateKey);}
if(invalid==='wrong-device'){const p=policy(3);p.assignment.deviceId='device-2';envelope=signPolicy(p,privateKey);}
if(invalid==='signature'){envelope=signPolicy(policy(3),privateKey);envelope.signature='AAAA';}
if(invalid==='schema'){const p=policy(3);p.dlp.maxBytes=0;const payload=Buffer.from(JSON.stringify(p)).toString('base64url');envelope={payload,signature:sign(null,Buffer.from(payload),keys.privateKey).toString('base64url')};}
if(invalid==='rollback')envelope=signPolicy(policy(1),privateKey);
assert.throws(()=>authority.accept(envelope),{code:'policy-rejected'});assert.equal(authority.current(valid.token),false);assert.equal(authority.snapshot(),null);assert.equal(authority.status().minimumVersion,2);
}
});
test('same-version changed policy rejected and revocation preserves rollback floor',async()=>{
const authority=await create();const p=policy(2),first=authority.accept(signPolicy(p,privateKey));
p.browser.blockedHosts=['example.com'];assert.throws(()=>authority.accept(signPolicy(p,privateKey)),{code:'policy-rejected'});assert.equal(authority.current(first.token),false);
authority.revoke();assert.throws(()=>authority.accept(signPolicy(policy(1),privateKey)),{code:'policy-rejected'});
});
test('policy update during asynchronous scan prevents release using old token',async()=>{
const authority=await create(),old=authority.accept(signPolicy(policy(),privateKey));let release,entered,dials=0;
const scan=new Promise(r=>release=r),started=new Promise(r=>entered=r);
const request=new PassThrough();Object.assign(request,{rawHeaders:['Host','example.com','Content-Length','6'],url:'http://example.com/',method:'POST',complete:true});
const result=gateRequest(request,{authority:old.token,current:authority.current,inspect:async()=>{entered();await scan;return {decision:'allow'};},forward:()=>{dials++;}});
const denied=assert.rejects(result,{code:'authority-expired'});request.end('public');await started;
const next=authority.accept(signPolicy(policy(2),privateKey));release();await denied;assert.equal(dials,0);assert.equal(authority.current(next.token),true);assert.ok(next.generation>old.generation);
authority.revoke();assert.equal(authority.current(next.token),false);
});
test('trusted bootstrap pins key and version; invalid bootstrap rejected',async()=>{
for(const changed of [{publicKey:'invalid'},{deviceId:'bad/id'},{minimumVersion:-1},{minimumVersion:1.5}])await assert.rejects(createPolicyAuthority({publicKey,deviceId:'device-1',minimumVersion:1,...changed}),{code:'policy-bootstrap-invalid'});
const authority=await createPolicyAuthority({publicKey,deviceId:'device-1',minimumVersion:5});assert.throws(()=>authority.accept(signPolicy(policy(4),privateKey)),{code:'policy-rejected'});
});
test('current authority expires without a policy refresh and rejects a different signing key',async()=>{
const authority=await create(),p=policy();p.expiresAt=new Date(Date.now()+150).toISOString();
const active=authority.accept(signPolicy(p,privateKey));assert.equal(authority.current(active.token),true);
await new Promise(resolve=>setTimeout(resolve,170));assert.equal(authority.current(active.token),false);assert.equal(authority.snapshot(),null);
const wrong=generateKeyPairSync('ed25519').privateKey.export({type:'pkcs8',format:'pem'});
assert.throws(()=>authority.accept(signPolicy(policy(2),wrong)),{code:'policy-rejected'});
});