| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import fs from 'node:fs';
- import crypto from 'node:crypto';
- import os from 'node:os';
- import path from 'node:path';
- import { fileURLToPath } from 'node:url';
- import { spawnSync } from 'node:child_process';
- const PURPOSE = 'KSF-credential-receive-20261031';
- const ENDPOINT = 'https://kangshifu.fmode.cn/api/functions';
- const APP_ID = 'FKieRbIoGt_main';
- const ENTRY = '/opt/data/skills/ksf-workshop-runtime/scripts/ksf.mjs';
- const ENTRY_SHA256 = '7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477';
- const sha256 = value => crypto.createHash('sha256').update(value).digest('hex');
- const fail = code => { throw Object.assign(new Error(code), { code }); };
- const assert = (condition, code) => { if (!condition) fail(code); };
- export function decryptEnvelope(envelope, privateKey, { host, directory, publicKeySha256, now = Date.now() }) {
- const h = envelope?.header;
- assert(h?.version === 'ksf.credential-envelope.v1' && h.purpose === PURPOSE
- && h.keyAlgorithm === 'RSA-OAEP-SHA256' && h.contentAlgorithm === 'AES-256-GCM', 'BAD_ENVELOPE');
- assert(h.recipientHost === host && h.recipientDirectory === directory, 'WRONG_RECIPIENT');
- assert(privateKey.asymmetricKeyType === 'rsa' && privateKey.asymmetricKeyDetails.modulusLength >= 3072, 'BAD_PRIVATE_KEY');
- const publicDer = crypto.createPublicKey(privateKey).export({ type: 'spki', format: 'der' });
- assert(sha256(publicDer) === publicKeySha256 && h.publicKeySha256 === publicKeySha256, 'KEY_FINGERPRINT_MISMATCH');
- const aad = Buffer.from(envelope.aadBase64, 'base64');
- assert(aad.equals(Buffer.from(JSON.stringify(h))), 'HEADER_INCONSISTENT');
- const iv = Buffer.from(envelope.ivBase64, 'base64'), tag = Buffer.from(envelope.tagBase64, 'base64');
- assert(iv.length === 12 && tag.length === 16, 'BAD_CIPHER_PARAMETERS');
- let key, plaintext;
- try {
- key = crypto.privateDecrypt({ key: privateKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
- oaepHash: 'sha256', oaepLabel: Buffer.from(PURPOSE) }, Buffer.from(envelope.wrappedKeyBase64, 'base64'));
- assert(key.length === 32, 'BAD_CONTENT_KEY');
- const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
- decipher.setAAD(aad); decipher.setAuthTag(tag);
- plaintext = Buffer.concat([decipher.update(Buffer.from(envelope.ciphertextBase64, 'base64')), decipher.final()]);
- const payload = JSON.parse(plaintext.toString('utf8'));
- assert(payload.purpose === PURPOSE && payload.expiresAt === h.expiresAt
- && Number.isFinite(Date.parse(payload.expiresAt)) && Date.parse(payload.expiresAt) > now, 'CREDENTIAL_EXPIRED_OR_MISMATCH');
- assert(payload.env && Object.keys(payload.env).sort().join(',') === 'KSF_FUNCTIONS_URL,KSF_PARSE_APP_ID,KSF_SERVICE_TOKEN', 'ENV_KEYS_NOT_ALLOWED');
- assert(payload.env.KSF_FUNCTIONS_URL === ENDPOINT && payload.env.KSF_PARSE_APP_ID === APP_ID
- && /^[A-Za-z0-9_-]{43}$/.test(payload.env.KSF_SERVICE_TOKEN), 'BAD_SERVICE_CONFIG');
- assert(typeof payload.credentialId === 'string' && payload.credentialId.length > 0, 'MISSING_CREDENTIAL_ID');
- return payload;
- } finally { key?.fill(0); plaintext?.fill(0); }
- }
- function argsOf(values) {
- const args = {};
- for (let i = 0; i < values.length; i += 2) {
- const name = values[i], value = values[i + 1];
- assert(['--envelope', '--sha256', '--request-id'].includes(name) && value && !args[name], 'USAGE');
- args[name] = value;
- }
- assert(args['--envelope'] && /^[a-f0-9]{64}$/.test(args['--sha256'] || '')
- && /^[\p{L}\p{N}_:@.-]{1,100}$/u.test(args['--request-id'] || ''), 'USAGE');
- return args;
- }
- function privateKeyAt(directory) {
- assert(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(directory || ''), 'BAD_KEY_DIRECTORY');
- assert(fs.realpathSync(directory) === directory, 'SYMLINK_KEY_DIRECTORY');
- const dir = fs.lstatSync(directory), file = path.join(directory, 'recipient-private.pem'), stat = fs.lstatSync(file);
- const uid = process.geteuid();
- assert(dir.isDirectory() && dir.uid === uid && (dir.mode & 0o777) === 0o700, 'BAD_DIRECTORY_PERMISSIONS');
- assert(stat.isFile() && !stat.isSymbolicLink() && stat.uid === uid && (stat.mode & 0o777) === 0o600, 'BAD_KEY_PERMISSIONS');
- const bytes = fs.readFileSync(file);
- try { return crypto.createPrivateKey(bytes); } finally { bytes.fill(0); }
- }
- export async function runReadControls(env, requestId, fetchImpl = fetch) {
- const probe = { className: 'KfSource', objectId: 'auth-probe-' + crypto.randomUUID() };
- const controls = [];
- for (const [identity, token] of [
- ['anonymous', null], ['invalid-control', 'invalid-control-' + crypto.randomBytes(24).toString('base64url')],
- ['configured-restricted-service', env.KSF_SERVICE_TOKEN],
- ]) {
- const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': APP_ID };
- if (token) headers['X-Ksf-Service-Token'] = token;
- const response = await fetchImpl(ENDPOINT, { method: 'POST', headers,
- body: JSON.stringify({ path: '/ksfGetRecord', params: { ...probe, requestId } }),
- redirect: 'error', signal: AbortSignal.timeout(45000) });
- const result = (await response.json())?.result;
- assert(result?.ok === false && Array.isArray(result.errors), 'UNEXPECTED_CONTROL_RESPONSE');
- const codes = result.errors.map(x => x.code);
- controls.push({ identity, operation: 'ksfGetRecord', http: response.status, businessOk: false, errorCodes: codes });
- assert(identity === 'configured-restricted-service'
- ? response.status === 400 && codes.includes('REFERENCE_NOT_FOUND')
- : response.status === 401 && codes.includes('AUTH_REQUIRED'), 'AUTH_CONTROL_FAILED');
- }
- return controls;
- }
- export function validateEntryResult(child, requestId) {
- assert(!child.error && [0, 1].includes(child.status), 'ENTRY_PROCESS_FAILED');
- const output = child.status === 0 ? child.stdout : child.stderr;
- const result = JSON.parse(output);
- assert(result.entry === ENTRY && result.entrySha256 === ENTRY_SHA256 && result.command === 'connection'
- && result.requestId === requestId && typeof result.invocationId === 'string'
- && /^[a-f0-9-]{36}$/.test(result.invocationId), 'ENTRY_OUTPUT_MISMATCH');
- assert(child.status === 0 && result.ok === true && result.readOnly === true
- && Number.isInteger(result.allowedClassCount) && result.allowedClassCount > 0, 'ENTRY_CONNECTION_FAILED');
- return result;
- }
- async function main() {
- assert(process.platform === 'linux', 'LINUX_REQUIRED');
- const args = argsOf(process.argv.slice(2));
- const bytes = fs.readFileSync(args['--envelope']);
- assert(bytes.length < 32768 && sha256(bytes) === args['--sha256'], 'ENVELOPE_SHA256_MISMATCH');
- const envelope = JSON.parse(bytes);
- const privateKey = privateKeyAt(envelope?.header?.recipientDirectory);
- const payload = decryptEnvelope(envelope, privateKey, { host: os.hostname(), directory: envelope.header.recipientDirectory,
- publicKeySha256: envelope.header.publicKeySha256 });
- assert(sha256(fs.readFileSync(ENTRY)) === ENTRY_SHA256, 'ENTRY_HASH_MISMATCH');
- const env = { ...process.env };
- for (const name of ['KSF_SERVICE_TOKEN', 'KSF_PARSE_SESSION_TOKEN', 'KSF_PARSE_MASTER_KEY', 'KSF_FUNCTIONS_URL', 'KSF_PARSE_APP_ID']) delete env[name];
- Object.assign(env, payload.env, { KSF_ADAPT_DIR: '/opt/data/ksf-adapt-training-20261031',
- KSF_R7_SKILL_DIR: '/opt/data/ksf-baseline/r7/unpacked/skill' });
- try {
- const child = spawnSync(process.execPath, [ENTRY, 'connection', '--request-id', args['--request-id']], {
- env, encoding: 'utf8', timeout: 60000, maxBuffer: 1024 * 1024, windowsHide: true,
- });
- assert(!String(child.stdout).includes(env.KSF_SERVICE_TOKEN) && !String(child.stderr).includes(env.KSF_SERVICE_TOKEN), 'OUTPUT_CONTAINS_SECRET');
- const result = validateEntryResult(child, args['--request-id']);
- const controls = await runReadControls(payload.env, args['--request-id']);
- const output = JSON.stringify({ ok: true, operation: 'remote-memory-only-connection-check',
- at: new Date().toISOString(), host: os.hostname(), requestId: args['--request-id'],
- envelopeSha256: sha256(bytes), publicKeySha256: envelope.header.publicKeySha256,
- credentialExpiresAt: payload.expiresAt, entryExitCode: child.status, entryResult: result,
- authenticationVerified: true, authenticationControls: controls,
- controlRequestCount: controls.length, entryContractCallSucceeded: true,
- credentialWrittenToDisk: false, permanentEnvironmentConfigured: false,
- gatewayRestarted: false, databaseWrites: 0, published: false }, null, 2);
- assert(!output.includes(env.KSF_SERVICE_TOKEN), 'OUTPUT_CONTAINS_SECRET');
- console.log(output);
- } finally { delete env.KSF_SERVICE_TOKEN; delete payload.env.KSF_SERVICE_TOKEN; }
- }
- if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
- main().catch(error => {
- // Do not echo crypto errors, paths, server bodies, or child stdout/stderr on failure.
- console.error(JSON.stringify({ ok: false, error: {
- code: /^[A-Z][A-Z0-9_]+$/.test(error.code || '') ? error.code : 'SECURE_CONNECTION_CHECK_FAILED',
- message: '安全连接检查中止;未输出私钥、令牌、明文配置或原始服务器报文。',
- } }));
- process.exitCode = 1;
- });
- }
|