'use strict'; const path=require('node:path'); const {createDnsPolicyRoute,assertSupportedDns}=require('./dns-policy-route.cjs'); const tls=require('node:tls'); const {X509Certificate}=require('node:crypto'); const {z}=require('zod'); const {createPolicyAuthority}=require('./policy-authority.cjs'); const {createManagedProxy}=require('./managed-proxy.cjs'); const {createSessionCertificates}=require('./tls-certificates.cjs'); const {createProxyForwarder,parseFixedProxy}=require('./proxy-forwarder.cjs'); const {createHttpsForwarder}=require('./https-forwarder.cjs'); const {createDownloadResponseGate}=require('./download-response-gate.cjs'); const {startInspector}=require('../browser/inspection.cjs'); const {scanEnterprise}=require('../browser/enterprise-inspection.cjs'); const networkBase={dns:z.literal('system'),ca:z.string().min(1).max(131072).optional()}; const NetworkSchema=z.discriminatedUnion('mode',[z.object({mode:z.literal('direct'),...networkBase}).strict(),z.object({mode:z.literal('fixed_servers'),...networkBase,proxyCa:z.string().min(1).max(131072).optional(),credentials:z.object({username:z.string().min(1).max(1024).refine(value=>!value.includes(':')&&![...value].some(character=>character.charCodeAt(0)<32||character.charCodeAt(0)===127)),password:z.string().min(1).max(3072).refine(value=>![...value].some(character=>character.charCodeAt(0)<32||character.charCodeAt(0)===127))}).strict().optional()}).strict()]); const BootstrapSchema=z.object({publicKey:z.string().min(1).max(16384),deviceId:z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/),minimumVersion:z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),envelope:z.object({payload:z.string().min(1).max(2097152),signature:z.string().min(1).max(16384)}).strict(),brokerPort:z.number().int().min(1).max(65535),network:NetworkSchema}).strict(); function deploymentRoots(pem){ if(pem===undefined)return undefined; const matches=pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); if(!matches||matches.length>32||pem.replace(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g,'').trim())throw Error('broker-bootstrap-invalid'); for(const item of matches){let certificate;try{certificate=new X509Certificate(item);}catch{throw Error('broker-bootstrap-invalid');}if(!certificate.ca||Date.parse(certificate.validFrom)>Date.now()||Date.parse(certificate.validTo)<=Date.now())throw Error('broker-bootstrap-invalid');} return Object.freeze([...tls.rootCertificates,...matches]); } /** Real packaged-resource factory. Parent verifies its signed package before * spawn; IPC cannot choose modules, resource paths, keys or scanner callbacks. * Direct or one signed fixed HTTP(S) proxy only; PAC/system/bypass fail closed. */ async function createProductionBroker(config,{persistAudit,acknowledgeCertificate,expectedPolicyVersion}={}){ let c;try{c=BootstrapSchema.parse(config);}catch{throw Error('broker-bootstrap-invalid');} if(typeof persistAudit!=='function'||typeof acknowledgeCertificate!=='function')throw Error('broker-bootstrap-invalid'); const ca=deploymentRoots(c.network.ca),proxyCa=deploymentRoots(c.network.proxyCa); const brokerRoot=path.resolve(__dirname,'../..'),scannerResourcesRoot=path.dirname(brokerRoot); const authority=await createPolicyAuthority({publicKey:c.publicKey,deviceId:c.deviceId,minimumVersion:c.minimumVersion}); const snapshot=authority.accept(c.envelope),policy=snapshot.policy; if(expectedPolicyVersion!==policy.version)throw Error('broker-bootstrap-invalid'); if(policy.proxy.mode!==c.network.mode)throw Error('broker-route-unsupported'); assertSupportedDns(policy,c.network,{numericOriginSupported:true}); if(c.network.mode==='fixed_servers'){try{parseFixedProxy(policy.proxy);}catch{throw Error('broker-route-unsupported');}} const core=await import('../../packages/core/policy.mjs'),securityCore=await import('../../packages/core/security.mjs'); const current=token=>authority.current(token),active=()=>current(snapshot.token); const approved=binding=>active()&&core.destinationAllowed(`https://${binding.authority}/`,policy); let inspector,issuer,proxy,closed=false,stage='inspector'; const close=async()=>{if(closed)return;closed=true;authority.revoke();try{await proxy?.close();}finally{issuer?.close();inspector?.stop();}}; try{ inspector=startInspector(scannerResourcesRoot,true);if(!inspector)throw Error('broker-inspector-unavailable');await inspector.waitReady(); stage='certificate';issuer=await createSessionCertificates({current:active,authorized:approved}); const maxResponseBytes=Math.min(policy.fileTransfers.download.maxBytes,16777216); stage='transport'; const resolve=createDnsPolicyRoute({current:active,blocked:address=>securityCore.dnsAddressBlocked(address,policy)}); const transport={authority:snapshot.token,current,ca:ca?.join('\n'),maxRequestBytes:policy.dlp.maxBytes,maxResponseBytes,deadlineMs:30000}; const resolveOriginRoute=async route=>{if(!core.destinationAllowed(`https://${route.hostname.includes(':')?'['+route.hostname+']':route.hostname}:${route.port}/`,policy))throw Error('broker-route-denied');return resolve(route);}; const rawForward=c.network.mode==='fixed_servers'?createProxyForwarder({...transport,proxyPolicy:policy.proxy,proxyCa:proxyCa?.join('\n'),credentials:c.network.credentials,resolveProxyRoute:resolve,resolveOriginRoute:policy.security?.dns?.enabled!==false&&policy.security?.dns?.blockedIPs?.length?resolveOriginRoute:undefined}):createHttpsForwarder({...transport,resolveRoute:resolveOriginRoute}); const forward=createDownloadResponseGate({forward:rawForward,policy,current:active,inspector,maxBytes:maxResponseBytes}); stage='managed';proxy=await createManagedProxy({deadlineMs:30000,policyAuthority:authority,snapshot,persistAudit,localInspector:(bytes,p)=>inspector.inspect(bytes,p),enterpriseInspector:(bytes,p)=>scanEnterprise(bytes,p,'upload'),forward,resolveCertificate:async binding=>{ if(!approved(binding))throw Error('broker-certificate-denied'); const leaf=issuer.resolve(binding);if(await acknowledgeCertificate({authority:leaf.authority,cert:leaf.cert})!==true||!active())throw Error('broker-certificate-denied');return leaf; }}); return Object.freeze({authMode:proxy.authMode,credentials:proxy.credentials,capability:proxy.capability,listen:()=>proxy.listen(c.brokerPort),close}); }catch{await close();throw Error('broker-startup-'+stage); } } module.exports={createProductionBroker,BootstrapSchema,deploymentRoots};