2026-10-13 10:33:31 +02:00
|
|
|
'use strict';
|
|
|
|
|
const channel=require('../../packages/core/policy-channel.cjs');
|
|
|
|
|
const fs=require('node:fs');
|
|
|
|
|
function deploymentPin(){if(process.env.SAFETYNET_REQUIRE_CHANNEL_PIN==='true'&&!process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE)throw Error('Deployment requires a protected management signing pin');return process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE?fs.readFileSync(process.env.SAFETYNET_CHANNEL_SIGNATURE_KEY_FILE,'utf8').trim():undefined;}
|
2026-10-13 16:58:15 +02:00
|
|
|
async function initializeSigningPin({device,request,save}){
|
2026-10-13 10:33:31 +02:00
|
|
|
const pin=deploymentPin();if(pin&&device.channelSignatureKey&&device.channelSignatureKey.trim()!==pin)throw Error('Protected management signing pin mismatch');
|
|
|
|
|
if(!device.channelSignatureKey){
|
|
|
|
|
// One-time legacy migration is authenticated by the enrollment signing pin and HTTPS.
|
|
|
|
|
const identity=await request(device.server,'/api/device/channel/identity',null,device.token);
|
|
|
|
|
channel.verifyIdentity(identity,device.publicKey);if(pin&&identity.signatureKey.trim()!==pin)throw Error('Protected management signing pin mismatch');
|
|
|
|
|
device.channelSignatureKey=identity.signatureKey;save(device);
|
|
|
|
|
}
|
2026-10-13 16:58:15 +02:00
|
|
|
}
|
|
|
|
|
async function protectedPolicyRequest({device,request,save}){
|
|
|
|
|
await initializeSigningPin({device,request,save});
|
2026-10-13 10:33:31 +02:00
|
|
|
const challenge=await request(device.server,'/api/device/channel/challenge',{},device.token);
|
|
|
|
|
channel.verifyChallenge(challenge,device.id,device.publicKey,device.channelSignatureKey);
|
|
|
|
|
const context=channel.prepare(challenge);
|
|
|
|
|
try{const response=await request(device.server,'/api/device/channel/policy',context.request,device.token);return channel.finish(response,challenge,context);}
|
|
|
|
|
finally{context.responseKey.fill(0);}
|
|
|
|
|
}
|
2026-10-13 16:58:15 +02:00
|
|
|
async function protectedEvidenceRequest({device,request,save,payload}){
|
|
|
|
|
await initializeSigningPin({device,request,save});
|
|
|
|
|
const crypto=channel.evidenceChannel;
|
|
|
|
|
const challenge=await request(device.server,'/api/device/channel/evidence/challenge',{},device.token);
|
|
|
|
|
crypto.verifyChallenge(challenge,device.id,device.publicKey,device.channelSignatureKey);
|
|
|
|
|
const context=crypto.prepare(challenge,payload);
|
|
|
|
|
try{
|
|
|
|
|
const response=await request(device.server,'/api/device/channel/evidence',context.request,device.token);
|
|
|
|
|
// Status is trusted only inside the authenticated encrypted acknowledgement.
|
|
|
|
|
const result=crypto.finish(response,challenge,context);
|
|
|
|
|
if(!result||Object.keys(result).sort().join(',')!=='body,status'||!Number.isInteger(result.status)||result.status<200||result.status>599||!result.body||typeof result.body!=='object'||Array.isArray(result.body))throw Error('Protected evidence acknowledgement invalid');
|
|
|
|
|
if(result.status>=300){const error=Error('Protected evidence submission refused');error.status=result.status;throw error;}
|
|
|
|
|
return result.body;
|
|
|
|
|
}finally{context.responseKey.fill(0);}
|
|
|
|
|
}
|
|
|
|
|
module.exports={protectedPolicyRequest,protectedEvidenceRequest,deploymentPin};
|