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); } }