| 12345678910111213141516171819202122232425262728293031323334 |
- import crypto from 'node:crypto';
- export const PURPOSE = 'KSF-credential-receive-20261031';
- export const sha256 = value => crypto.createHash('sha256').update(value).digest('hex');
- const must = (value, code) => { if (!value) throw Object.assign(new Error(code), { code }); };
- export function sealCredential({ publicKey, host, directory, credentialId, expiresAt, env, now = Date.now() }) {
- const recipient = crypto.createPublicKey(publicKey);
- must(recipient.asymmetricKeyType === 'rsa' && recipient.asymmetricKeyDetails.modulusLength >= 3072, 'BAD_PUBLIC_KEY');
- must(typeof host === 'string' && /^[a-f0-9]{12,64}$/.test(host), 'BAD_RECIPIENT_HOST');
- must(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(directory), 'BAD_KEY_DIRECTORY');
- must(Number.isFinite(Date.parse(expiresAt)) && Date.parse(expiresAt) > now, 'EXPIRED_CREDENTIAL');
- must(typeof credentialId === 'string' && credentialId.length > 0, 'MISSING_CREDENTIAL_ID');
- must(env && Object.keys(env).sort().join(',') === 'KSF_FUNCTIONS_URL,KSF_PARSE_APP_ID,KSF_SERVICE_TOKEN', 'ENV_KEYS_NOT_ALLOWED');
- must(env.KSF_FUNCTIONS_URL === 'https://kangshifu.fmode.cn/api/functions'
- && env.KSF_PARSE_APP_ID === 'FKieRbIoGt_main' && /^[A-Za-z0-9_-]{43}$/.test(env.KSF_SERVICE_TOKEN), 'BAD_SERVICE_CONFIG');
- const header = {
- version: 'ksf.credential-envelope.v1', purpose: PURPOSE, recipientHost: host, recipientDirectory: directory,
- publicKeySha256: sha256(recipient.export({ type: 'spki', format: 'der' })),
- encryptedAt: new Date(now).toISOString(), expiresAt, keyAlgorithm: 'RSA-OAEP-SHA256', contentAlgorithm: 'AES-256-GCM',
- };
- const aad = Buffer.from(JSON.stringify(header));
- const plaintext = Buffer.from(JSON.stringify({ purpose: PURPOSE, credentialId, expiresAt, env }));
- const key = crypto.randomBytes(32), iv = crypto.randomBytes(12);
- try {
- const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
- cipher.setAAD(aad);
- const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
- const wrapped = crypto.publicEncrypt({ key: recipient, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
- oaepHash: 'sha256', oaepLabel: Buffer.from(PURPOSE) }, key);
- return { header, aadBase64: aad.toString('base64'), wrappedKeyBase64: wrapped.toString('base64'),
- ivBase64: iv.toString('base64'), tagBase64: cipher.getAuthTag().toString('base64'), ciphertextBase64: ciphertext.toString('base64') };
- } finally { key.fill(0); plaintext.fill(0); }
- }
|