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

48 lines
4.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {generateKeyPairSync,randomBytes} from 'node:crypto';
import policy from '../packages/core/policy-channel.cjs';
const channel=policy.evidenceChannel;
const ed=generateKeyPairSync('ed25519'),pq=generateKeyPairSync('ml-dsa-87');
const edPublic=ed.publicKey.export({type:'spki',format:'pem'}),pqPublic=pq.publicKey.export({type:'spki',format:'pem'});
const make=()=>channel.challenge('synthetic-device',ed.privateKey,pq.privateKey);
function exchange(){const challenge=make(),client=channel.prepare(challenge.bundle,{content:'synthetic confidential evidence',sourceEventId:'fixture-event'}),server=channel.receive(client.request,challenge.bundle,challenge.privateKey);return {...challenge,client,server};}
test('real evidence KEM encrypts payload and authenticates signed identity and response',()=>{
const identity=channel.identity(ed.privateKey,pq.privateKey,pqPublic);assert.equal(channel.verifyIdentity(identity,edPublic,pqPublic),pqPublic);
const x=exchange();channel.verifyChallenge(x.bundle,'synthetic-device',edPublic,pqPublic);
assert.equal(JSON.stringify(x.client.request).includes('synthetic confidential'),false);assert.equal(x.server.payload.content,'synthetic confidential evidence');
const response=channel.response({accepted:true},x.bundle,x.server);assert.deepEqual(channel.finish(response,x.bundle,x.client),{accepted:true});
assert.throws(()=>channel.finish({...response,encrypted:x.client.request.encrypted},x.bundle,x.client));
x.client.responseKey.fill(0);x.server.responseKey.fill(0);
});
test('policy and evidence protocols cannot substitute identity, challenge or ciphertext',()=>{
const x=exchange(),other=policy.challenge('synthetic-device',ed.privateKey,pq.privateKey);
assert.throws(()=>policy.verifyIdentity(channel.identity(ed.privateKey,pq.privateKey,pqPublic),edPublic,pqPublic));
assert.throws(()=>policy.verifyChallenge(x.bundle,'synthetic-device',edPublic,pqPublic));
assert.throws(()=>channel.verifyChallenge(other.bundle,'synthetic-device',edPublic,pqPublic));
assert.throws(()=>channel.prepare(other.bundle,{content:'fixture'}));
assert.throws(()=>policy.receive(x.client.request,x.bundle,x.privateKey));
const p=policy.prepare(other.bundle);assert.throws(()=>channel.receive(p.request,other.bundle,other.privateKey));
p.responseKey.fill(0);x.client.responseKey.fill(0);x.server.responseKey.fill(0);
});
test('evidence refuses tampering, altered device, challenge, response key and request identity',()=>{
const x=exchange(),other=make();
assert.throws(()=>channel.verifyChallenge({...x.bundle,deviceId:'other'},'other',edPublic,pqPublic));
assert.throws(()=>channel.receive({...x.client.request,deviceId:'other'},x.bundle,x.privateKey));
assert.throws(()=>channel.receive({...x.client.request,id:other.bundle.id},x.bundle,x.privateKey));
const altered=structuredClone(x.client.request);altered.encrypted.tag=randomBytes(16).toString('base64url');assert.throws(()=>channel.receive(altered,x.bundle,x.privateKey));
const response=channel.response({accepted:true},x.bundle,x.server);
assert.throws(()=>channel.finish(response,x.bundle,{...x.client,responseKey:randomBytes(32)}));
assert.throws(()=>channel.finish(response,x.bundle,{...x.client,requestId:'other'}));
assert.throws(()=>channel.finish({...response,id:other.bundle.id},x.bundle,x.client));
x.client.responseKey.fill(0);x.server.responseKey.fill(0);
});
test('evidence payload size and strict envelope are bounded; replay consumption belongs to server',()=>{
const x=make();assert.throws(()=>channel.prepare(x.bundle,{content:'x'.repeat(65536)}));assert.throws(()=>channel.prepare(x.bundle,[]));
const client=channel.prepare(x.bundle,{content:'x'.repeat(65500)}),first=channel.receive(client.request,x.bundle,x.privateKey);
assert.equal(first.payload.content.length,65500);
assert.throws(()=>channel.receive({...client.request,extra:true},x.bundle,x.privateKey));
assert.throws(()=>channel.receive(client.request,{...x.bundle,expiresAt:Date.now()-1},x.privateKey));
client.responseKey.fill(0);first.responseKey.fill(0);
});