Files
safetynet/apps/server/evidence-channel.mjs
T

27 lines
2.4 KiB
JavaScript

import channel from '../../packages/core/policy-channel.cjs';
import {managementIdentity} from './channel-identity.mjs';
import {traceOperation} from './telemetry.mjs';
function expireChallenge(pending,id){const timer=setTimeout(()=>pending.delete(id),60001);timer.unref();}
export function installEvidenceChannel(app,store,device,enabled,recordEvidence){
const profile=channel.evidenceChannel,keys=managementIdentity(store),pending=new Map();
app.post('/api/device/channel/evidence/challenge',device,enabled,async(req,res)=>{
if(!req.body||Array.isArray(req.body)||typeof req.body!=='object'||Object.keys(req.body).length)return res.status(400).json({error:'Evidence challenge requires an empty request'});
const policy=store.effectivePolicy(req.device.id);if(!policy.evidence.enabled||Date.parse(policy.expiresAt)<=Date.now())return res.status(403).json({error:'Evidence collection is disabled'});
for(const [id,item]of pending)if(item.bundle.expiresAt<=Date.now()||item.bundle.deviceId===req.device.id)pending.delete(id);
if(pending.size>=10000)return res.status(503).json({error:'Protected evidence channel unavailable'});
const item=await traceOperation('evidence.challenge',{},()=>profile.challenge(req.device.id,keys.edPrivate,keys.pqPrivate));pending.set(item.bundle.id,item);expireChallenge(pending,item.bundle.id);res.json(item.bundle);
});
app.post('/api/device/channel/evidence',device,enabled,async(req,res)=>{
const item=pending.get(req.body?.id);if(!item||item.bundle.deviceId!==req.device.id)return res.status(403).json({error:'Protected evidence channel refused'});
pending.delete(item.bundle.id);let context;
try{
context=await traceOperation('evidence.decrypt',{},()=>profile.receive(req.body,item.bundle,item.privateKey));
let status=200,body;const inner={status(value){status=value;return this;},json(value){body=value;return this;}};
try{await traceOperation('evidence.store',{},()=>recordEvidence({device:req.device,body:context.payload},inner));}
catch(error){status=error?.name==='ZodError'?400:500;body={error:status===400?'Protected evidence request invalid':'Protected evidence storage failed'};}
const response=await traceOperation('evidence.encrypt',{},()=>profile.response({status,body},item.bundle,context));res.json(response);
}catch{res.status(403).json({error:'Protected evidence channel refused'});}
finally{context?.responseKey.fill(0);if(context)context.payload=null;}
});
}