Files
safetynet/tests/evidence-channel-browser.test.mjs
T

47 lines
4.3 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import {generateKeyPairSync} from 'node:crypto';
import channel from '../packages/core/policy-channel.cjs';
import browser from '../apps/browser/management-channel.cjs';
const ed=generateKeyPairSync('ed25519'),pq=generateKeyPairSync('ml-dsa-87');
const publicKey=ed.publicKey.export({type:'spki',format:'pem'}),signatureKey=pq.publicKey.export({type:'spki',format:'pem'});
const payload={eventId:'4d113278-cabd-4e6d-b4be-022220f7a505',type:'download',payload:{name:'confidential-synthetic-only.xlsx'}};
function fixture({legacy=false,result={status:201,body:{id:'synthetic-ack'}},mutateResponse,mutateChallenge}={}){
const device={server:'https://synthetic.invalid',id:'device-a',token:'synthetic-token',publicKey,...(!legacy?{channelSignatureKey:signatureKey}:{}),privateEnrollmentFlag:true};
const calls=[];let item,saved=0;
const request=async(server,route,body,token)=>{
assert.equal(server,device.server);assert.equal(token,device.token);calls.push({route,body});
if(route==='/api/device/channel/identity')return channel.identity(ed.privateKey,pq.privateKey,signatureKey);
if(route==='/api/device/channel/evidence/challenge'){item=channel.evidenceChannel.challenge(device.id,ed.privateKey,pq.privateKey);return mutateChallenge?mutateChallenge(item.bundle):item.bundle;}
assert.equal(route,'/api/device/channel/evidence');assert.equal(JSON.stringify(body).includes(payload.payload.name),false);
const context=channel.evidenceChannel.receive(body,item.bundle,item.privateKey);
try{assert.deepEqual(context.payload,payload);const response=channel.evidenceChannel.response(result,item.bundle,context);return mutateResponse?mutateResponse(response):response;}finally{context.responseKey.fill(0);}
};
return {device,calls,request,save:actual=>{assert.equal(actual,device);assert.equal(actual.privateEnrollmentFlag,true);saved++;},saved:()=>saved};
}
test('browser evidence uses real PQ encryption and authenticated acknowledgement; legacy pin migration preserves enrollment state',async()=>{
const f=fixture({legacy:true});assert.deepEqual(await browser.protectedEvidenceRequest({...f,payload}),{id:'synthetic-ack'});assert.equal(f.saved(),1);assert.equal(f.device.channelSignatureKey,signatureKey);
assert.deepEqual(f.calls.map(c=>c.route),['/api/device/channel/identity','/api/device/channel/evidence/challenge','/api/device/channel/evidence']);
await browser.protectedEvidenceRequest({...f,payload});assert.equal(f.saved(),1);
});
test('authenticated evidence rejection preserves status without disclosing server error content',async()=>{
const f=fixture({result:{status:403,body:{error:'synthetic confidential server detail'}}});
await assert.rejects(browser.protectedEvidenceRequest({...f,payload}),error=>error.status===403&&error.message==='Protected evidence submission refused');
});
test('tampered evidence response and unauthenticated status never become a trusted acknowledgement',async()=>{
for(const mutateResponse of [r=>({...r,status:403}),r=>({...r,encrypted:{...r.encrypted,tag:Buffer.alloc(16).toString('base64url')}})]){
const f=fixture({mutateResponse});await assert.rejects(browser.protectedEvidenceRequest({...f,payload}),error=>error.status===undefined);
}
for(const result of [{status:201,body:[]},{status:200,body:null},{status:201,body:{},extra:true},{status:199,body:{}}])await assert.rejects(browser.protectedEvidenceRequest({...fixture({result}),payload}));
});
test('wrong pin and cross-purpose challenge are refused before evidence transmission',async()=>{
const wrong=fixture();wrong.device.channelSignatureKey=generateKeyPairSync('ml-dsa-87').publicKey.export({type:'spki',format:'pem'});
await assert.rejects(browser.protectedEvidenceRequest({...wrong,payload}));assert.equal(wrong.calls.length,1);
const cross=fixture({mutateChallenge:()=>channel.challenge('device-a',ed.privateKey,pq.privateKey).bundle});
await assert.rejects(browser.protectedEvidenceRequest({...cross,payload}));assert.equal(cross.calls.length,1);
});
test('transport failure has no plaintext evidence fallback',async()=>{
const f=fixture(),request=async(...args)=>{if(args[1]==='/api/device/channel/evidence')throw Error('Synthetic offline transport');return f.request(...args);};
await assert.rejects(browser.protectedEvidenceRequest({...f,request,payload}));assert.equal(f.calls.some(c=>c.route==='/api/device/evidence'),false);
});