import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; export const COMPANY='FKieRbIoGt', APP='FKieRbIoGt_main'; export const AGENT='agent-node-kangshifu', HOST='6f03725e5dc8'; export const SKILL='/opt/data/skills/ksf-workshop-runtime'; export const PURPOSE='KSF-credential-receive-20261031'; export const WRAPPER_SHA='10abc8fcb4c0dd459ff15c086668608b404cc0d61efaf559213a90d0a573ce75'; export const DECRYPTOR_SHA='43e462c21053c2829402e218b05aaa4438aed9552f561d54582675e687a63f79'; export const DELIVERY_FILES=['RELEASE.json','credential-binding.json','credential-envelope.json','decrypt-envelope.mjs']; export const SCOPES=['data:read','source:write','ingest:write']; export const sha=raw=>crypto.createHash('sha256').update(raw).digest('hex'); export const encode=value=>Buffer.from(JSON.stringify(value,null,2)+'\n'); export const fail=code=>{throw Object.assign(Error(code),{code});}; export const need=(value,code)=>{if(!value)fail(code);}; export function plain(file){ need(path.isAbsolute(file),'ABSOLUTE_PATH_REQUIRED'); for(let p=path.resolve(file);;p=path.dirname(p)){ if(fs.existsSync(p))need(!fs.lstatSync(p).isSymbolicLink(),'SYMLINK_PATH'); if(path.dirname(p)===p)break; } return path.resolve(file); } export function writeNew(file,bytes){ plain(file);fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o700}); const fd=fs.openSync(file,'wx',0o600); try{fs.writeFileSync(fd,bytes);fs.fsyncSync(fd);}finally{fs.closeSync(fd);} } export function readPrivate(file){ plain(file);const stat=fs.lstatSync(file); need(stat.isFile(),'NOT_PLAIN_FILE'); if(process.platform==='linux')need(stat.uid===process.geteuid()&&(stat.mode&0o777)===0o600,'PRIVATE_FILE_PERMISSIONS'); return fs.readFileSync(file); } export function privateDirectory(file){ plain(file);const s=fs.lstatSync(file);need(s.isDirectory(),'NOT_DIRECTORY'); if(process.platform==='linux')need(s.uid===process.geteuid()&&(s.mode&0o777)===0o700,'PRIVATE_DIRECTORY_PERMISSIONS'); } export function readDelivery(directory,expectedSha){ plain(directory);need(/^[a-f0-9]{64}$/.test(expectedSha||''),'BUNDLE_MANIFEST_SHA_REQUIRED'); need(JSON.stringify(fs.readdirSync(directory).sort())===JSON.stringify([...DELIVERY_FILES,'FILES.sha256'].sort()),'UNEXPECTED_DELIVERY_FILES'); const manifest=readPrivate(path.join(directory,'FILES.sha256')); need(sha(manifest)===expectedSha,'DELIVERY_MANIFEST_MISMATCH'); const entries=manifest.toString('utf8').trim().split('\n').map(line=>line.match(/^([a-f0-9]{64}) ([A-Za-z.-]+)$/)); need(entries.every(Boolean)&&JSON.stringify(entries.map(m=>m[2]).sort())===JSON.stringify(DELIVERY_FILES),'INVALID_DELIVERY_MANIFEST'); const files={};for(const[,digest,name]of entries){files[name]=readPrivate(path.join(directory,name));need(sha(files[name])===digest,'DELIVERY_FILE_MISMATCH');} return{files,manifest}; } export function recipientInfo(value){ need(value?.schema==='ksf.enterprise-workshop-recipient.v1'&&value.publicOnly===true,'INVALID_RECIPIENT'); need(value.companyObjectId===COMPANY&&value.appId===APP&&value.agentId===AGENT &&value.host===HOST,'RECIPIENT_IDENTITY_MISMATCH'); need(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(value.keyDirectory),'BAD_KEY_DIRECTORY'); need(typeof value.publicKeyPem==='string'&&value.publicKeyPem.startsWith('-----BEGIN PUBLIC KEY-----'),'INVALID_PUBLIC_KEY'); const key=crypto.createPublicKey(value.publicKeyPem); need(key.asymmetricKeyType==='rsa'&&key.asymmetricKeyDetails.modulusLength>=3072,'WEAK_PUBLIC_KEY'); need(sha(key.export({type:'spki',format:'der'}))===value.publicKeySha256,'PUBLIC_KEY_FINGERPRINT_MISMATCH'); need(value.privateKeyExported===false&&value.tokenExported===false,'PRIVATE_RECIPIENT_EXPORT_REJECTED'); return value; } export function argsOf(argv,allowed){ const values={};for(let i=0;i