Add protected interaction evidence and measured debugging phases
This commit is contained in:
1 parent
ea448de2d4
commit
244f01630b
42 files changed
+640
-87
No files matched your search
@@ -2,10 +2,10 @@ import test from 'node:test';import assert from 'node:assert/strict';import {mkd
|
||||
import channel from '../packages/core/policy-channel.cjs';import browser from '../apps/browser/management-channel.cjs';import {openStore,digest} from '../apps/server/store.mjs';import {createApp} from '../apps/server/app.mjs';
|
||||
async function fixture(t){
|
||||
const dir=mkdtempSync(join(tmpdir(),'safetynet-pq-evidence-')),keyFile=join(dir,'evidence.key');writeFileSync(keyFile,randomBytes(32),{mode:0o600});if(process.platform==='win32')execFileSync(join(process.env.SystemRoot,'System32/WindowsPowerShell/v1.0/powershell.exe'),['-NoProfile','-NonInteractive','-Command',"$ErrorActionPreference='Stop';$sid=[System.Security.Principal.WindowsIdentity]::GetCurrent().User;$acl=New-Object System.Security.AccessControl.FileSecurity;$acl.SetOwner($sid);$acl.SetAccessRuleProtection($true,$false);$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($sid,'FullControl','Allow')));Set-Acl -LiteralPath $env:TEST_PQ_EVIDENCE_KEY -AclObject $acl"],{windowsHide:true,env:{SystemRoot:process.env.SystemRoot,TEST_PQ_EVIDENCE_KEY:keyFile},stdio:'pipe'});
|
||||
const store=openStore(dir),p=store.get('policy');p.evidence={enabled:true,captureDownloads:true};store.put('policy',p);const token='synthetic-pq-evidence-device-token-123456789',otherToken='synthetic-pq-evidence-other-token-123456789',id='device-a';store.db.prepare('INSERT INTO devices(id,token_hash,revoked) VALUES(?,?,0)').run(id,digest(token));store.db.prepare('INSERT INTO devices(id,token_hash,revoked) VALUES(?,?,0)').run('device-b',digest(otherToken));
|
||||
const store=openStore(dir),p=store.get('policy');p.evidence={enabled:true,captureDownloads:true,captureInteractions:true};store.put('policy',p);const token='synthetic-pq-evidence-device-token-123456789',otherToken='synthetic-pq-evidence-other-token-123456789',id='device-a';store.db.prepare('INSERT INTO devices(id,token_hash,revoked) VALUES(?,?,0)').run(id,digest(token));store.db.prepare('INSERT INTO devices(id,token_hash,revoked) VALUES(?,?,0)').run('device-b',digest(otherToken));
|
||||
const config={publicUrl:'http://127.0.0.1',secure:false,evidenceKeyFile:keyFile,requirePQManagement:true},server=createApp(store,config).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));t.after(async()=>{server.closeAllConnections();await new Promise(r=>server.close(r));store.db.close();assert.ok(dir.startsWith(join(tmpdir(),'safetynet-pq-evidence-')));rmSync(dir,{recursive:true,force:true});});
|
||||
const base=`http://127.0.0.1:${server.address().port}`,wire=[];const api=async(route,body,credential=token)=>{wire.push({route,body});const r=await fetch(base+route,{method:body?'POST':'GET',headers:{authorization:`Bearer ${credential}`,...(body?{'content-type':'application/json'}:{})},body:body?JSON.stringify(body):undefined});return {status:r.status,body:await r.json()};};
|
||||
const eventId=randomUUID();store.event(id,'clipboard.copy',{eventId,policyVersion:1});const payload={eventId,type:'interaction',payload:{pageUrl:'https://intranet.example/file',action:'copy',content:'CONFIDENTIAL_SYNTHETIC_PQ_EVIDENCE',sensitiveField:false}};
|
||||
const eventId=randomUUID();store.event(id,'clipboard.copy',{eventId,policyVersion:1,interaction:{action:'copy',outcome:'completed',sessionId:'00000000-0000-4000-8000-000000000001'}});const payload={eventId,type:'interaction',payload:{pageUrl:'https://intranet.example/file',action:'copy',sessionId:'00000000-0000-4000-8000-000000000001',outcome:'completed',captureSource:'guarded-clipboard',sensitiveField:false}};
|
||||
const device={id,token,server:base,publicKey:store.publicKey(),channelSignatureKey:store.get('channelSigningKeys').publicKey};const make=async()=>{const challenge=await api('/api/device/channel/evidence/challenge',{});assert.equal(challenge.status,200);channel.evidenceChannel.verifyChallenge(challenge.body,id,device.publicKey,device.channelSignatureKey);return {bundle:challenge.body,context:channel.evidenceChannel.prepare(challenge.body,payload)};};return {store,api,wire,payload,device,make};
|
||||
}
|
||||
test('real central PQ evidence storage rejects plaintext and supports authenticated idempotent retry',async t=>{
|
||||
@@ -13,9 +13,9 @@ test('real central PQ evidence storage rejects plaintext and supports authentica
|
||||
const request=async(_base,route,body,credential)=>{const response=await f.api(route,body,credential);if(response.status>=400)throw Object.assign(Error('Transport refused'),{status:response.status});return response.body;};
|
||||
const created=await browser.protectedEvidenceRequest({device:f.device,request,save:()=>{},payload:f.payload});assert.match(created.id,/^[0-9a-f-]{36}$/);const again=await browser.protectedEvidenceRequest({device:f.device,request,save:()=>{},payload:f.payload});assert.equal(again.id,created.id);assert.equal(again.replayed,true);
|
||||
assert.equal(f.store.db.prepare('SELECT count(*) n FROM protected_evidence').get().n,1);assert.equal(f.store.db.prepare("SELECT count(*) n FROM events WHERE kind='evidence.recorded'").get().n,1);
|
||||
assert.equal(JSON.stringify(f.wire.filter(r=>r.route.includes('/channel/'))).includes(f.payload.payload.content),false);assert.equal(f.store.db.prepare('SELECT metadata FROM events').all().some(r=>r.metadata.includes(f.payload.payload.content)),false);
|
||||
const row=f.store.db.prepare('SELECT ciphertext FROM protected_evidence').get();assert.equal(Buffer.from(row.ciphertext).includes(Buffer.from(f.payload.payload.content)),false);
|
||||
await assert.rejects(browser.protectedEvidenceRequest({device:f.device,request,save:()=>{},payload:{...f.payload,payload:{...f.payload.payload,content:'Different synthetic payload'}}}),e=>e.status===409);
|
||||
assert.equal(JSON.stringify(f.wire.filter(r=>r.route.includes('/channel/'))).includes(f.payload.payload.pageUrl),false);assert.equal(f.store.db.prepare('SELECT metadata FROM events').all().some(r=>r.metadata.includes(f.payload.payload.pageUrl)),false);
|
||||
const row=f.store.db.prepare('SELECT ciphertext FROM protected_evidence').get();assert.equal(Buffer.from(row.ciphertext).includes(Buffer.from(f.payload.payload.pageUrl)),false);
|
||||
await assert.rejects(browser.protectedEvidenceRequest({device:f.device,request,save:()=>{},payload:{...f.payload,payload:{...f.payload.payload,bytes:17}}}),e=>e.status===409);
|
||||
});
|
||||
test('challenge consumption covers successful replay, invalid ciphertext and cross-device substitution',async t=>{
|
||||
const f=await fixture(t);let a=await f.make();assert.equal((await f.api('/api/device/channel/evidence',a.context.request,'synthetic-pq-evidence-other-token-123456789')).status,403);
|
||||
|
||||
Reference in new issue
Block a user