import { Buffer } from "node:buffer"; import { register } from "../engine/registry"; import { RuntimeError, type CapabilityHandler } from "../engine/types"; const SM4_KEY = "INPUT_PARAM_KEY4"; const SM4_KEY_HEX = Buffer.from(SM4_KEY, "utf8").toString("hex"); function looksLikeBase64(value: string): boolean { const text = value.trim().replace(/\s+/g, ""); if (!text || text.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(text)) return false; return Buffer.from(text, "base64").toString("base64").replace(/=+$/, "") === text.replace(/=+$/, ""); } function readableUtf8(value: string): boolean { if (!value) return false; let controls = 0; for (const char of value) { const code = char.codePointAt(0)!; if (code < 32 && !["\n", "\r", "\t"].includes(char)) controls += 1; if (code === 0xfffd) controls += 10; } return controls === 0; } async function sm4Decrypt(value: string): Promise { const { sm4 } = await import("sm-crypto"); const errors: string[] = []; for (const padding of ["pkcs#7", "none"] as const) { try { const output = sm4.decrypt(value.toLowerCase(), SM4_KEY_HEX, { padding }); if (output && readableUtf8(output)) return output; errors.push(`${padding}: empty or non-text output`); } catch (error) { errors.push(`${padding}: ${error instanceof Error ? error.message : String(error)}`); } } throw new RuntimeError("decryption_failed", "SM4 密文解密失败", { detail: errors.join("; ") }); } const dodecryption: CapabilityHandler = async (_ctx, node, args) => { const words = String(args.words ?? args.value ?? args.text ?? ""); if (!words.trim()) throw new RuntimeError("validation_error", "密文内容为空", { nodeId: node.id, componentId: node.componentId }); if (looksLikeBase64(words)) { const decoded = Buffer.from(words, "base64").toString("utf8"); if (readableUtf8(decoded)) return decoded; } return sm4Decrypt(words.trim()); }; register("Basic_Dodecryption", dodecryption);