| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- import fs from 'node:fs';
- import path from 'node:path';
- import os from 'node:os';
- import crypto from 'node:crypto';
- import {spawnSync} from 'node:child_process';
- import {fileURLToPath} from 'node:url';
- import {decryptEnvelope,runReadControls} from './decrypt-envelope.mjs';
- import {COMPANY,APP,AGENT,HOST,SKILL,PURPOSE,WRAPPER_SHA,DECRYPTOR_SHA,SCOPES,DELIVERY_FILES,
- sha,encode,need,plain,privateDirectory,readPrivate,readDelivery,writeNew,argsOf,errorReceipt} from './common.mjs';
- const self=fileURLToPath(import.meta.url);
- export function validateDelivery(bundle,manifestSha){
- const {files,manifest}=readDelivery(bundle,manifestSha);
- const release=JSON.parse(files['RELEASE.json']),binding=JSON.parse(files['credential-binding.json']);
- need(release.schema==='ksf.enterprise-workshop-authorization.v1'&&release.companyObjectId===COMPANY
- &&release.appId===APP&&release.agentId===AGENT&&release.host===HOST,'DELIVERY_IDENTITY_MISMATCH');
- need(/^[A-Za-z0-9_-]{1,80}$/.test(release.credentialId||'')
- &&release.installDirectory==='/opt/data/ksf-enterprise-auth-'+release.credentialId
- &&release.skillDirectory===SKILL&&release.wrapperSha256===WRAPPER_SHA,'INVALID_INSTALL_TARGET');
- need(JSON.stringify(release.scopes)===JSON.stringify(SCOPES)&&JSON.stringify(release.projectIds)==='["*"]'
- &&release.formalReportPublicationGranted===false&&release.credentialPlaintextIncluded===false
- &&release.privateKeyIncluded===false,'DELIVERY_PERMISSION_MISMATCH');
- need(binding.version==='ksf.encrypted-entry.v1'&&binding.purpose===PURPOSE&&binding.host===HOST
- &&/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(binding.keyDirectory)
- &&binding.keyDirectory===release.keyDirectory&&binding.publicKeySha256===release.publicKeySha256,'INVALID_BINDING');
- need(binding.envelopeFile===release.installDirectory+'/credential-envelope.json'
- &&binding.decryptModule===release.installDirectory+'/decrypt-envelope.mjs'
- &&binding.envelopeSha256===sha(files['credential-envelope.json'])
- &&binding.decryptModuleSha256===DECRYPTOR_SHA&&sha(files['decrypt-envelope.mjs'])===DECRYPTOR_SHA,'BINDING_HASH_MISMATCH');
- const envelope=JSON.parse(files['credential-envelope.json']);
- need(envelope.header?.recipientHost===HOST&&envelope.header?.recipientDirectory===binding.keyDirectory
- &&envelope.header?.publicKeySha256===binding.publicKeySha256&&envelope.header?.expiresAt===release.expiresAt,'ENVELOPE_BINDING_MISMATCH');
- return{files,manifest,release,binding,envelope};
- }
- export async function install({bundle,manifestSha,apply=false},{dataDir='/opt/data',hostname=os.hostname(),
- platform=process.platform,spawn=spawnSync,controls=runReadControls}={}){
- need(platform==='linux','LINUX_REQUIRED');need(hostname===HOST,'WRONG_CONTAINER');
- dataDir=plain(dataDir);const local=target=>path.join(dataDir,...target.slice('/opt/data/'.length).split('/'));
- const current=JSON.parse(readPrivate(path.join(dataDir,'.fmode/config/enterprise/current.json')));
- need(current.appId===APP&¤t.companyObjectId===COMPANY&¤t.provisionStatus==='success','ENTERPRISE_MISMATCH');
- const {files,manifest,release,binding,envelope}=validateDelivery(bundle,manifestSha);
- const skill=local(SKILL),entry=path.join(skill,'scripts/ksf-secure.mjs');
- need(sha(fs.readFileSync(entry))===WRAPPER_SHA,'WRAPPER_CHANGED');
- need(fs.readFileSync(path.join(skill,'SKILL.md'),'utf8').includes('version: r13'),'SKILL_VERSION_MISMATCH');
- const keyDirectory=local(binding.keyDirectory);privateDirectory(keyDirectory);
- const privateBytes=readPrivate(path.join(keyDirectory,'recipient-private.pem'));
- let key;try{key=crypto.createPrivateKey(privateBytes);}finally{privateBytes.fill(0);}
- const payload=decryptEnvelope(envelope,key,{host:HOST,directory:binding.keyDirectory,publicKeySha256:binding.publicKeySha256});
- try{
- need(payload.credentialId===release.credentialId&&payload.expiresAt===release.expiresAt,'CREDENTIAL_IDENTITY_MISMATCH');
- const bindingTarget=path.join(skill,'credential-binding.json'),destination=local(release.installDirectory);
- const alreadyBound=fs.existsSync(bindingTarget);
- if(alreadyBound)need(readPrivate(bindingTarget).equals(files['credential-binding.json']),'EXISTING_BINDING_CHANGED');
- if(fs.existsSync(destination)){
- privateDirectory(destination);
- need(fs.readdirSync(destination).every(n=>[...DELIVERY_FILES,'FILES.sha256'].includes(n)),'UNKNOWN_INSTALL_FILES');
- for(const name of fs.readdirSync(destination))need(readPrivate(path.join(destination,name)).equals(
- name==='FILES.sha256'?manifest:files[name]),'EXISTING_AUTH_FILE_CHANGED');
- }
- const base={ok:true,planOnly:!apply,credentialId:release.credentialId,host:HOST,
- scopes:release.scopes,alreadyBound,bundleManifestSha256:manifestSha,
- privateKeyExported:false,credentialPlaintextWrittenToDisk:false,protectedRuntimeFilesModified:0,businessRecordWrites:0};
- if(!apply)return{...base,bindingInstalled:alreadyBound};
- fs.mkdirSync(destination,{recursive:true,mode:0o700});privateDirectory(destination);
- for(const[name,body]of [...Object.entries(files),['FILES.sha256',manifest]]){
- const target=path.join(destination,name);if(!fs.existsSync(target))writeNew(target,body);
- }
- if(!alreadyBound)writeNew(bindingTarget,files['credential-binding.json']);
- // Exercise the frozen production entry; do not replace it with a direct API success claim.
- // Keep verification input outside the sealed authorization directory.
- const input=path.join(dataDir,'ksf-enterprise-auth-checks',crypto.randomUUID()+'.json');
- writeNew(input,Buffer.from('{}\n'));
- const child=spawn(process.execPath,[entry,'workshop-contract','--input',input,'--request-id','enterprise-auth-'+crypto.randomUUID()],
- {encoding:'utf8',timeout:120000,maxBuffer:4*1024*1024,windowsHide:true});
- need(!String(child.stdout).includes(payload.env.KSF_SERVICE_TOKEN)
- &&!String(child.stderr).includes(payload.env.KSF_SERVICE_TOKEN),'OUTPUT_CONTAINS_SECRET');
- need(!child.error&&child.status===0,'FROZEN_ENTRY_CONTRACT_FAILED');
- const result=JSON.parse(child.stdout);
- need(result.ok===true&&result.wrapperSha256===WRAPPER_SHA&&result.credentialLoadedForChild===true
- &&result.credentialWrittenToDisk===false&&result.entryResult?.readOnly===true
- &&result.entryResult?.result?.workshop?.version==='ksf.workshop.v1','FROZEN_ENTRY_CONTRACT_MISMATCH');
- const authControls=await controls(payload.env,'enterprise-auth-'+crypto.randomUUID());
- need(sha(fs.readFileSync(entry))===WRAPPER_SHA,'WRAPPER_CHANGED');
- return{...base,bindingInstalled:true,workshopContractVerified:true,authorizationControls:authControls,
- researchEnvironmentActivated:false,studentContextVerified:false,saveReadbackVerified:false};
- }finally{delete payload.env.KSF_SERVICE_TOKEN;}
- }
- if(process.argv[1]&&path.resolve(process.argv[1])===self){
- try{
- const a=argsOf(process.argv.slice(2),['--bundle','--sha256','--mode']);
- need(a['--bundle']&&a['--sha256']&&['plan','apply'].includes(a['--mode']),'USAGE');
- console.log(JSON.stringify(await install({bundle:a['--bundle'],manifestSha:a['--sha256'],apply:a['--mode']==='apply'}),null,2));
- }catch(error){console.error(JSON.stringify(errorReceipt(error)));process.exitCode=1;}
- }
|