| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 |
- import fs from 'node:fs';
- import path from 'node:path';
- import os from 'node:os';
- import crypto from 'node:crypto';
- import {fileURLToPath} from 'node:url';
- import {COMPANY,APP,AGENT,HOST,WRAPPER_SHA,encode,sha,need,plain,privateDirectory,writeNew,readPrivate,recipientInfo,argsOf,errorReceipt} from './common.mjs';
- export function exportRecipient({keyDirectory,output},{dataDir='/opt/data',hostname=os.hostname(),platform=process.platform}={}){
- need(platform==='linux','LINUX_REQUIRED');need(hostname===HOST,'WRONG_CONTAINER');
- const current=JSON.parse(readPrivate(path.join(dataDir,'.fmode/config/enterprise/current.json')));
- need(current.companyObjectId===COMPANY&¤t.appId===APP&¤t.provisionStatus==='success','ENTERPRISE_MISMATCH');
- const skill=path.join(dataDir,'skills/ksf-workshop-runtime');
- need(fs.readFileSync(path.join(skill,'SKILL.md'),'utf8').includes('version: r13'),'SKILL_VERSION_MISMATCH');
- need(sha(fs.readFileSync(path.join(skill,'scripts/ksf-secure.mjs')))===WRAPPER_SHA,'WRAPPER_CHANGED');
- output=plain(output);dataDir=plain(dataDir);
- need(output.startsWith(dataDir+path.sep)&&!output.startsWith(path.join(dataDir,'skills')+path.sep),'UNSAFE_PUBLIC_OUTPUT');
- if(!keyDirectory){
- if(fs.existsSync(output))keyDirectory=recipientInfo(JSON.parse(fs.readFileSync(output))).keyDirectory;
- else{
- const created=fs.mkdtempSync(path.join(dataDir,'ksf-key-receive-20261031-'));
- fs.chmodSync(created,0o700);keyDirectory='/opt/data/'+path.basename(created);
- }
- }
- need(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(keyDirectory),'BAD_KEY_DIRECTORY');
- const localDirectory=path.join(dataDir,path.posix.basename(keyDirectory));
- privateDirectory(localDirectory);
- need(!output.startsWith(localDirectory+path.sep),'UNSAFE_PUBLIC_OUTPUT');
- const privateFile=path.join(localDirectory,'recipient-private.pem');
- let privateKey;
- if(fs.existsSync(privateFile)){
- const raw=readPrivate(privateFile);try{privateKey=crypto.createPrivateKey(raw);}finally{raw.fill(0);}
- }else{
- need(fs.readdirSync(localDirectory).length===0,'EXISTING_KEY_DIRECTORY_NOT_EMPTY');
- const pair=crypto.generateKeyPairSync('rsa',{modulusLength:3072});privateKey=pair.privateKey;
- const bytes=Buffer.from(privateKey.export({type:'pkcs8',format:'pem'}));
- try{writeNew(privateFile,bytes);}finally{bytes.fill(0);}
- }
- const publicKey=crypto.createPublicKey(privateKey);
- const value={schema:'ksf.enterprise-workshop-recipient.v1',publicOnly:true,companyObjectId:COMPANY,appId:APP,
- agentId:AGENT,host:hostname,keyDirectory,publicKeyPem:publicKey.export({type:'spki',format:'pem'}),
- publicKeySha256:sha(publicKey.export({type:'spki',format:'der'})),privateKeyExported:false,tokenExported:false};
- recipientInfo(value);
- const bytes=encode(value);
- if(fs.existsSync(output))need(fs.readFileSync(output).equals(bytes),'PUBLIC_RECIPIENT_CHANGED');
- else writeNew(output,bytes);
- return{ok:true,publicRecipientFile:output,sha256:sha(bytes),host:hostname,
- privateKeyExported:false,tokenExported:false,businessRecordWrites:0};
- }
- if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
- try{const a=argsOf(process.argv.slice(2),['--out','--key-directory']);need(a['--out'],'USAGE');
- console.log(JSON.stringify(exportRecipient({output:a['--out'],keyDirectory:a['--key-directory']}),null,2));
- }catch(error){console.error(JSON.stringify(errorReceipt(error)));process.exitCode=1;}
- }
|