Files

41 lines
2.9 KiB
JavaScript

const {app}=require('electron');
const {spawn}=require('node:child_process');
const path=require('node:path');
function createCapabilityCrypto({packaged=false,resourcesPath,root=path.join(__dirname,'../..'),launch=spawn,environment=process.env}={}){
const python=packaged?path.join(resourcesPath,'python/python.exe'):path.join(root,'.runtime/windows/python/python.exe');
const script=packaged?path.join(resourcesPath,'inspector/dpapi.py'):path.join(root,'apps/inspector/dpapi.py');
const active=new Set();
return function convert(operation,input){
const inputLimit=operation==='protect'?32768:65536;
if(!['protect','unprotect'].includes(operation)||!(typeof input==='string'||Buffer.isBuffer(input))||!Buffer.byteLength(input)||Buffer.byteLength(input)>inputLimit)return Promise.reject(Error('Capability rejected'));
if(active.size>=8)return Promise.reject(Error('Windows capability protection failed'));
return new Promise((resolve,reject)=>{
// Python startup hooks and inherited secrets must not enter a capability
// helper. Isolated mode also disables user-site and Python environment paths.
const env={};for(const key of ['SystemRoot','WINDIR','TEMP','TMP','USERPROFILE','LOCALAPPDATA','APPDATA'])if(environment[key])env[key]=environment[key];
let child,timer,settled=false,terminal=false,bytes=0;
const output=Buffer.alloc(operation==='protect'?65536:32768),slot={};active.add(slot);
function fail(){
if(settled)return;settled=true;clearTimeout(timer);output.fill(0);
// A rejected operation does not free the slot until actual child close.
if(child&&!terminal){try{child.kill();}catch{}child.stdin?.destroy?.();child.stdout?.destroy?.();child.stderr?.destroy?.();}
reject(Error('Windows capability protection failed'));
}
try{
child=launch(python,['-I','-B',script,operation],{windowsHide:true,shell:false,stdio:['pipe','pipe','pipe'],env});
child.once('close',code=>{
terminal=true;active.delete(slot);
if(settled)return;if(code!==0||!bytes){fail();return;}
settled=true;clearTimeout(timer);const result=output.subarray(0,bytes).toString('utf8');output.fill(0);resolve(result);
});
child.on('error',fail);child.stdin.on('error',fail);child.stdout.on('error',fail);child.stderr.on('error',fail);child.stderr.on('data',()=>{});
child.stdout.on('data',data=>{if(settled)return;if(!Buffer.isBuffer(data)||bytes+data.length>output.length){fail();return;}data.copy(output,bytes);bytes+=data.length;});
timer=setTimeout(fail,5000);child.stdin.end(input);
}catch{if(!child)active.delete(slot);fail();}
});
};
}
let sharedConverter;
function capabilityCrypto(operation,input){sharedConverter??=createCapabilityCrypto({packaged:app.isPackaged,resourcesPath:process.resourcesPath});return sharedConverter(operation,input);}
module.exports={capabilityCrypto,createCapabilityCrypto};