Implement managed browser security, individual agents and observability

This commit is contained in:
Karl committed 2026-10-13 16:58:15 +02:00
1 parent ff482ebe23
commit ea448de2d4
172 files changed
+5404 -347

No files matched your search

+16 -10
View File
@@ -1,8 +1,10 @@
'use strict';
// Application-layer policy encryption over authenticated HTTPS. No TLS replacement.
// Fixed, domain-separated application-layer channels over authenticated HTTPS.
// The server owns one-use challenge consumption; this cryptographic module is stateless.
const c=require('node:crypto');
const VERSION='safetynet-policy-mlkem1024-mldsa87-v1';
const MAX=3*1024*1024;
function createChannel(VERSION, payloadProfile=false){
const MAX=payloadProfile?65536+1024:3*1024*1024;
const validatePayload=value=>{record(value);const encoded=bytes(value);try{if(encoded.length>65536)fail();return value;}finally{encoded.fill(0);}};
const fail=()=>{throw Error('Protected management channel refused');};
const record=value=>{if(!value||typeof value!=='object'||Array.isArray(value))fail();return value;};
function exact(value,keys){record(value);if(Object.keys(value).sort().join(',')!==keys.slice().sort().join(','))fail();}
@@ -26,10 +28,14 @@ function verifyChallenge(value,deviceId,edPublic,pqPublic,now=Date.now()){
}
function derive(shared,bundle,direction){const salt=c.createHash('sha384').update(bytes(bundle)).digest();return Buffer.from(c.hkdfSync('sha384',shared,salt,Buffer.from(VERSION+':'+direction),32));}
const aad=(bundle,direction)=>bytes({version:VERSION,id:bundle.id,deviceId:bundle.deviceId,direction});
function seal(key,value,bundle,direction){const input=bytes(value);if(input.length>MAX)fail();const iv=c.randomBytes(12),cipher=c.createCipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));try{return {iv:encode(iv),ciphertext:encode(Buffer.concat([cipher.update(input),cipher.final()])),tag:encode(cipher.getAuthTag())};}finally{input.fill(0);}}
function open(key,value,bundle,direction){exact(value,['iv','ciphertext','tag']);const iv=decode(value.iv,12),tag=decode(value.tag,16);if(iv.length!==12||tag.length!==16)fail();const cipher=c.createDecipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));cipher.setAuthTag(tag);let plaintext;try{plaintext=Buffer.concat([cipher.update(decode(value.ciphertext)),cipher.final()]);return JSON.parse(plaintext.toString('utf8'));}finally{plaintext?.fill(0);}}
function prepare(bundle){const {sharedKey,ciphertext}=c.encapsulate(publicKey(bundle.kemKey,'ml-kem-1024'));const requestKey=derive(sharedKey,bundle,'request'),responseKey=derive(sharedKey,bundle,'response');sharedKey.fill(0);const requestId=encode(c.randomBytes(32));try{return {request:{version:VERSION,id:bundle.id,deviceId:bundle.deviceId,kemCiphertext:encode(ciphertext),encrypted:seal(requestKey,{purpose:'policy',requestId},bundle,'request')},responseKey,requestId};}finally{requestKey.fill(0);}}
function receive(request,bundle,privateKey){exact(request,['version','id','deviceId','kemCiphertext','encrypted']);if(request.version!==VERSION||request.id!==bundle.id||request.deviceId!==bundle.deviceId||bundle.expiresAt<=Date.now())fail();const ciphertext=decode(request.kemCiphertext,1568);if(ciphertext.length!==1568)fail();const shared=c.decapsulate(privateKey,ciphertext),requestKey=derive(shared,bundle,'request'),responseKey=derive(shared,bundle,'response');shared.fill(0);try{const body=open(requestKey,request.encrypted,bundle,'request');exact(body,['purpose','requestId']);if(body.purpose!=='policy'||decode(body.requestId,32).length!==32)fail();return {responseKey,requestId:body.requestId};}catch(error){responseKey.fill(0);throw error;}finally{requestKey.fill(0);}}
function response(policy,bundle,context){return {version:VERSION,id:bundle.id,encrypted:seal(context.responseKey,{requestId:context.requestId,policy},bundle,'response')};}
function finish(value,bundle,context){exact(value,['version','id','encrypted']);if(value.version!==VERSION||value.id!==bundle.id)fail();const body=open(context.responseKey,value.encrypted,bundle,'response');exact(body,['requestId','policy']);if(body.requestId!==context.requestId)fail();return body.policy;}
module.exports={VERSION,identity,verifyIdentity,challenge,verifyChallenge,prepare,receive,response,finish};
function seal(key,value,bundle,direction){const input=bytes(value);try{if(input.length>MAX)fail();const iv=c.randomBytes(12),cipher=c.createCipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));return {iv:encode(iv),ciphertext:encode(Buffer.concat([cipher.update(input),cipher.final()])),tag:encode(cipher.getAuthTag())};}finally{input.fill(0);}}
function open(key,value,bundle,direction){exact(value,['iv','ciphertext','tag']);const iv=decode(value.iv,12),tag=decode(value.tag,16);if(iv.length!==12||tag.length!==16)fail();const cipher=c.createDecipheriv('aes-256-gcm',key,iv);cipher.setAAD(aad(bundle,direction));cipher.setAuthTag(tag);let plaintext,partial,final;try{partial=cipher.update(decode(value.ciphertext));final=cipher.final();plaintext=Buffer.concat([partial,final]);return JSON.parse(plaintext.toString('utf8'));}finally{partial?.fill(0);final?.fill(0);plaintext?.fill(0);}}
function prepare(bundle,payload){if(payloadProfile){if(bundle.version!==VERSION)fail();validatePayload(payload);}const {sharedKey,ciphertext}=c.encapsulate(publicKey(bundle.kemKey,'ml-kem-1024'));const requestKey=derive(sharedKey,bundle,'request'),responseKey=derive(sharedKey,bundle,'response');sharedKey.fill(0);const requestId=encode(c.randomBytes(32));try{return {request:{version:VERSION,id:bundle.id,deviceId:bundle.deviceId,kemCiphertext:encode(ciphertext),encrypted:seal(requestKey,payloadProfile?{purpose:'evidence',requestId,payload}:{purpose:'policy',requestId},bundle,'request')},responseKey,requestId};}catch(error){responseKey.fill(0);throw error;}finally{requestKey.fill(0);}}
function receive(request,bundle,privateKey){exact(request,['version','id','deviceId','kemCiphertext','encrypted']);if(request.version!==VERSION||request.id!==bundle.id||request.deviceId!==bundle.deviceId||bundle.expiresAt<=Date.now())fail();const ciphertext=decode(request.kemCiphertext,1568);if(ciphertext.length!==1568)fail();const shared=c.decapsulate(privateKey,ciphertext),requestKey=derive(shared,bundle,'request'),responseKey=derive(shared,bundle,'response');shared.fill(0);try{const body=open(requestKey,request.encrypted,bundle,'request');exact(body,payloadProfile?['purpose','requestId','payload']:['purpose','requestId']);if(body.purpose!==(payloadProfile?'evidence':'policy')||decode(body.requestId,32).length!==32)fail();if(payloadProfile)validatePayload(body.payload);return {responseKey,requestId:body.requestId,...(payloadProfile?{payload:body.payload}:{})};}catch(error){responseKey.fill(0);throw error;}finally{requestKey.fill(0);}}
function response(policy,bundle,context){return {version:VERSION,id:bundle.id,encrypted:seal(context.responseKey,payloadProfile?{requestId:context.requestId,result:validatePayload(policy)}:{requestId:context.requestId,policy},bundle,'response')};}
function finish(value,bundle,context){exact(value,['version','id','encrypted']);if(value.version!==VERSION||value.id!==bundle.id)fail();const body=open(context.responseKey,value.encrypted,bundle,'response');exact(body,payloadProfile?['requestId','result']:['requestId','policy']);if(body.requestId!==context.requestId)fail();return payloadProfile?validatePayload(body.result):body.policy;}
return Object.freeze({VERSION,identity,verifyIdentity,challenge,verifyChallenge,prepare,receive,response,finish});
}
const policyChannel=createChannel('safetynet-policy-mlkem1024-mldsa87-v1');
const evidenceChannel=createChannel('safetynet-evidence-mlkem1024-mldsa87-v1',true);
module.exports={...policyChannel,evidenceChannel};