41 lines
2.9 KiB
JavaScript
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};
|