| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- import test from 'node:test';
- import assert from 'node:assert/strict';
- import crypto from 'node:crypto';
- import { sealCredential, sha256 } from '../scripts/lib/workshop-credential-envelope.mjs';
- import { bindingUpdate } from '../scripts/restore-workshop-credential-binding.mjs';
- import { decryptEnvelope } from '../docs/workshop-auth-recovery-20261108/receipts/r1-return/files/public-recipient/decrypt-module.mjs';
- // Test-only keys and token. Production recipient private material is never needed here.
- const pair = crypto.generateKeyPairSync('rsa', { modulusLength: 3072 });
- const publicKey = pair.publicKey.export({ type: 'spki', format: 'pem' });
- const env = { KSF_FUNCTIONS_URL: 'https://kangshifu.fmode.cn/api/functions', KSF_PARSE_APP_ID: 'FKieRbIoGt_main', KSF_SERVICE_TOKEN: 'T'.repeat(43) };
- const parameters = { publicKey, host: '0123456789ab', directory: '/opt/data/ksf-key-receive-20261031-test',
- credentialId: 'test-credential', expiresAt: '2030-01-02T00:00:00.000Z', env, now: Date.parse('2030-01-01T00:00:00Z') };
- const context = { host: parameters.host, directory: parameters.directory,
- publicKeySha256: sha256(pair.publicKey.export({ type: 'spki', format: 'der' })), now: parameters.now };
- test('original returned decryptor accepts the renewed envelope and preserves exact identity/config', () => {
- const envelope = sealCredential(parameters);
- const result = decryptEnvelope(envelope, pair.privateKey, context);
- assert.equal(result.credentialId, parameters.credentialId);
- assert.equal(result.expiresAt, parameters.expiresAt);
- assert.deepEqual(result.env, env);
- assert.equal(JSON.stringify(envelope).includes(env.KSF_SERVICE_TOKEN), false);
- });
- test('original decryptor rejects expired credentials even if encryption itself is intact', () => {
- const envelope = sealCredential(parameters);
- assert.throws(() => decryptEnvelope(envelope, pair.privateKey, { ...context, now: Date.parse(parameters.expiresAt) }), { code: 'CREDENTIAL_EXPIRED_OR_MISMATCH' });
- });
- test('recipient host and key binding are enforced by the unchanged decryptor', () => {
- const envelope = sealCredential(parameters);
- assert.throws(() => decryptEnvelope(envelope, pair.privateKey, { ...context, host: 'different-host' }), { code: 'WRONG_RECIPIENT' });
- assert.throws(() => decryptEnvelope(envelope, pair.privateKey, { ...context, publicKeySha256: '0'.repeat(64) }), { code: 'KEY_FINGERPRINT_MISMATCH' });
- });
- test('expiry cannot be extended by editing only the public header', () => {
- const envelope = sealCredential(parameters);
- envelope.header.expiresAt = '2040-01-01T00:00:00Z';
- assert.throws(() => decryptEnvelope(envelope, pair.privateKey, context), { code: 'HEADER_INCONSISTENT' });
- envelope.aadBase64 = Buffer.from(JSON.stringify(envelope.header)).toString('base64');
- assert.throws(() => decryptEnvelope(envelope, pair.privateKey, context));
- });
- test('ciphertext tampering is rejected and encryption uses fresh random material', () => {
- const first = sealCredential(parameters), second = sealCredential(parameters);
- assert.notEqual(first.ciphertextBase64, second.ciphertextBase64);
- const bytes = Buffer.from(first.ciphertextBase64, 'base64'); bytes[0] ^= 1;
- first.ciphertextBase64 = bytes.toString('base64');
- assert.throws(() => decryptEnvelope(first, pair.privateKey, context));
- });
- test('sealer rejects wrong endpoint, extra credential keys, weak public keys and expired input', () => {
- assert.throws(() => sealCredential({ ...parameters, env: { ...env, MASTER_KEY: 'not-allowed' } }), { code: 'ENV_KEYS_NOT_ALLOWED' });
- assert.throws(() => sealCredential({ ...parameters, env: { ...env, KSF_FUNCTIONS_URL: 'https://other.example/api/functions' } }), { code: 'BAD_SERVICE_CONFIG' });
- assert.throws(() => sealCredential({ ...parameters, now: Date.parse(parameters.expiresAt) }), { code: 'EXPIRED_CREDENTIAL' });
- const weak = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }).publicKey.export({ type: 'spki', format: 'pem' });
- assert.throws(() => sealCredential({ ...parameters, publicKey: weak }), { code: 'BAD_PUBLIC_KEY' });
- });
- const binding = { version: 'ksf.encrypted-entry.v1', purpose: 'KSF-credential-receive-20261031', host: parameters.host,
- keyDirectory: parameters.directory, publicKeySha256: context.publicKeySha256, decryptModule: '/opt/data/receiver.mjs',
- decryptModuleSha256: 'a'.repeat(64), envelopeFile: '/opt/data/old.json', envelopeSha256: 'b'.repeat(64), preservedExtra: 'keep' };
- const bindingBytes = Buffer.from(JSON.stringify(binding));
- const release = { ...binding, expectedBindingSha256: sha256(bindingBytes), oldEnvelopeFile: binding.envelopeFile,
- oldEnvelopeSha256: binding.envelopeSha256, installEnvelope: '/opt/data/new.json', envelopeSha256: 'c'.repeat(64) };
- test('binding update changes only the two envelope fields and supports an identical replay', () => {
- const plan = bindingUpdate(bindingBytes, release, parameters.host);
- assert.equal(plan.alreadyApplied, false);
- assert.deepEqual(plan.after, { ...binding, envelopeFile: release.installEnvelope, envelopeSha256: release.envelopeSha256 });
- const replay = bindingUpdate(plan.afterBytes, release, parameters.host);
- assert.equal(replay.alreadyApplied, true);
- assert.deepEqual(replay.afterBytes, plan.afterBytes);
- });
- test('binding changes, other recipient hosts and key substitutions are blocked', () => {
- assert.throws(() => bindingUpdate(Buffer.from(JSON.stringify({ ...binding, preservedExtra: 'concurrent' })), release, parameters.host), { code: 'UNKNOWN_BINDING_VERSION' });
- assert.throws(() => bindingUpdate(bindingBytes, release, 'other-container'), { code: 'WRONG_CONTAINER' });
- assert.throws(() => bindingUpdate(bindingBytes, { ...release, publicKeySha256: 'f'.repeat(64) }, parameters.host), { code: 'RECIPIENT_BINDING_CHANGED' });
- });
|