common.mjs 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. export const COMPANY='FKieRbIoGt', APP='FKieRbIoGt_main';
  5. export const AGENT='agent-node-kangshifu', HOST='6f03725e5dc8';
  6. export const SKILL='/opt/data/skills/ksf-workshop-runtime';
  7. export const PURPOSE='KSF-credential-receive-20261031';
  8. export const WRAPPER_SHA='10abc8fcb4c0dd459ff15c086668608b404cc0d61efaf559213a90d0a573ce75';
  9. export const DECRYPTOR_SHA='43e462c21053c2829402e218b05aaa4438aed9552f561d54582675e687a63f79';
  10. export const DELIVERY_FILES=['RELEASE.json','credential-binding.json','credential-envelope.json','decrypt-envelope.mjs'];
  11. export const SCOPES=['data:read','source:write','ingest:write'];
  12. export const sha=raw=>crypto.createHash('sha256').update(raw).digest('hex');
  13. export const encode=value=>Buffer.from(JSON.stringify(value,null,2)+'\n');
  14. export const fail=code=>{throw Object.assign(Error(code),{code});};
  15. export const need=(value,code)=>{if(!value)fail(code);};
  16. export function plain(file){
  17. need(path.isAbsolute(file),'ABSOLUTE_PATH_REQUIRED');
  18. for(let p=path.resolve(file);;p=path.dirname(p)){
  19. if(fs.existsSync(p))need(!fs.lstatSync(p).isSymbolicLink(),'SYMLINK_PATH');
  20. if(path.dirname(p)===p)break;
  21. }
  22. return path.resolve(file);
  23. }
  24. export function writeNew(file,bytes){
  25. plain(file);fs.mkdirSync(path.dirname(file),{recursive:true,mode:0o700});
  26. const fd=fs.openSync(file,'wx',0o600);
  27. try{fs.writeFileSync(fd,bytes);fs.fsyncSync(fd);}finally{fs.closeSync(fd);}
  28. }
  29. export function readPrivate(file){
  30. plain(file);const stat=fs.lstatSync(file);
  31. need(stat.isFile(),'NOT_PLAIN_FILE');
  32. if(process.platform==='linux')need(stat.uid===process.geteuid()&&(stat.mode&0o777)===0o600,'PRIVATE_FILE_PERMISSIONS');
  33. return fs.readFileSync(file);
  34. }
  35. export function privateDirectory(file){
  36. plain(file);const s=fs.lstatSync(file);need(s.isDirectory(),'NOT_DIRECTORY');
  37. if(process.platform==='linux')need(s.uid===process.geteuid()&&(s.mode&0o777)===0o700,'PRIVATE_DIRECTORY_PERMISSIONS');
  38. }
  39. export function readDelivery(directory,expectedSha){
  40. plain(directory);need(/^[a-f0-9]{64}$/.test(expectedSha||''),'BUNDLE_MANIFEST_SHA_REQUIRED');
  41. need(JSON.stringify(fs.readdirSync(directory).sort())===JSON.stringify([...DELIVERY_FILES,'FILES.sha256'].sort()),'UNEXPECTED_DELIVERY_FILES');
  42. const manifest=readPrivate(path.join(directory,'FILES.sha256'));
  43. need(sha(manifest)===expectedSha,'DELIVERY_MANIFEST_MISMATCH');
  44. const entries=manifest.toString('utf8').trim().split('\n').map(line=>line.match(/^([a-f0-9]{64}) ([A-Za-z.-]+)$/));
  45. need(entries.every(Boolean)&&JSON.stringify(entries.map(m=>m[2]).sort())===JSON.stringify(DELIVERY_FILES),'INVALID_DELIVERY_MANIFEST');
  46. const files={};for(const[,digest,name]of entries){files[name]=readPrivate(path.join(directory,name));need(sha(files[name])===digest,'DELIVERY_FILE_MISMATCH');}
  47. return{files,manifest};
  48. }
  49. export function recipientInfo(value){
  50. need(value?.schema==='ksf.enterprise-workshop-recipient.v1'&&value.publicOnly===true,'INVALID_RECIPIENT');
  51. need(value.companyObjectId===COMPANY&&value.appId===APP&&value.agentId===AGENT
  52. &&value.host===HOST,'RECIPIENT_IDENTITY_MISMATCH');
  53. need(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(value.keyDirectory),'BAD_KEY_DIRECTORY');
  54. need(typeof value.publicKeyPem==='string'&&value.publicKeyPem.startsWith('-----BEGIN PUBLIC KEY-----'),'INVALID_PUBLIC_KEY');
  55. const key=crypto.createPublicKey(value.publicKeyPem);
  56. need(key.asymmetricKeyType==='rsa'&&key.asymmetricKeyDetails.modulusLength>=3072,'WEAK_PUBLIC_KEY');
  57. need(sha(key.export({type:'spki',format:'der'}))===value.publicKeySha256,'PUBLIC_KEY_FINGERPRINT_MISMATCH');
  58. need(value.privateKeyExported===false&&value.tokenExported===false,'PRIVATE_RECIPIENT_EXPORT_REJECTED');
  59. return value;
  60. }
  61. export function argsOf(argv,allowed){
  62. const values={};for(let i=0;i<argv.length;i+=2){const[k,v]=argv.slice(i,i+2);
  63. need(allowed.includes(k)&&v&&!values[k]&&!v.startsWith('--'),'USAGE');values[k]=v;}
  64. return values;
  65. }
  66. export function errorReceipt(error){return{ok:false,error:{code:/^[A-Z][A-Z0-9_]+$/.test(error.code||'')?error.code:'AUTHORIZATION_STOPPED'},businessRecordWrites:0};}