From 17c004a66ba2323473596860f66f5c4df666b02a Mon Sep 17 00:00:00 2001 From: J8k3 Date: Sat, 16 May 2026 10:38:09 -0400 Subject: [PATCH] Add Futurex Excrypt command parser --- PAYMENT_RECIPES.md | 25 ++- src/core/config/Categories.json | 1 + .../operations/ParseFuturexExcryptCommand.mjs | 177 ++++++++++++++++++ tests/operations/tests/Payment.mjs | 88 +++++++++ 4 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 src/core/operations/ParseFuturexExcryptCommand.mjs diff --git a/PAYMENT_RECIPES.md b/PAYMENT_RECIPES.md index a20720ec..5ea0af5c 100644 --- a/PAYMENT_RECIPES.md +++ b/PAYMENT_RECIPES.md @@ -200,16 +200,25 @@ Important assumptions: - `Derive DUKPT Key` is TDES DUKPT, not AES DUKPT - `Generate AS2805 KEK Validation` is an emulation-oriented helper and explicitly documents its simplifications in the operation comments -## 10) Key Container Inspection +## 10) Key Container And HSM Command Inspection Operations: +- `Parse Thales payShield command` +- `Parse Futurex Excrypt command` - `Parse TR-31 key block` - `Parse TR-34 B9 envelope` Use this when: -- you need to inspect inbound wrapped-key material or transport frames during testing +- you need to inspect vendor HSM command syntax, wrapped-key material, or transport frames during testing Input: -- full TR-31 or TR-34 payload as text or hex, depending on the operation comment +- `Parse Thales payShield command`: raw legacy host command or response text +- `Parse Futurex Excrypt command`: raw bracketed Excrypt command or response text +- `Parse TR-31 key block` / `Parse TR-34 B9 envelope`: full payload as text or hex, depending on the operation comment + +Important assumptions: +- the Thales and Futurex parsers currently focus on visible message syntax, delimiters, command identification, and field splitting rather than deep per-command semantic decoding +- `Parse Thales payShield command` expects the configured message-header length to be supplied in the op args +- `Parse Futurex Excrypt command` treats Excrypt messages as delimiter-based tag/value fields and commonly uses the `AO` field as the command code ## Chaining Patterns @@ -309,3 +318,13 @@ Operations: Flow: - inspect the KEK with `Calculate Payment KCV` - generate request or response RandomKeySend / RandomKeyReceive values with the AS2805 helper + +## J) Vendor Command Triage +Operations: +- `Parse Thales payShield command` +- `Parse Futurex Excrypt command` + +Flow: +- paste the raw host message first before trying to interpret the business meaning +- use the parsed command code, delimiters, header, trailer, or tag/value split to confirm what family of command you are looking at +- follow with lower-level payment, EMV, PIN, or key-container recipes only after the transport syntax is understood diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index cffaf11e..98a88240 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -615,6 +615,7 @@ "Calculate Payment KCV", "Generate AS2805 KEK Validation", "Parse Thales payShield command", + "Parse Futurex Excrypt command", "Parse TR-31 key block", "Parse TR-34 B9 envelope", "HMAC", diff --git a/src/core/operations/ParseFuturexExcryptCommand.mjs b/src/core/operations/ParseFuturexExcryptCommand.mjs new file mode 100644 index 00000000..d6d1c228 --- /dev/null +++ b/src/core/operations/ParseFuturexExcryptCommand.mjs @@ -0,0 +1,177 @@ +/** + * @license Apache-2.0 + * @author Jacob Marks [https://jacobmarks.com] + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +const COMMANDS = { + CAAV: "Calculate Account holder Authentication Value", + DAPT: "Decrypt Apple Pay Token", + DCDK: "Decrypt Cardholder Data Using DUKPT", + DGPT: "Decrypt Google Pay Token", + DRKI: "Identification Request", + DRKK: "Key Request", + DRKV: "Key Verification Request", + DSPT: "Decrypt Samsung Pay Token", + ECDK: "Encrypt Cardholder Data Using DUKPT", + EMPT: "Translate PIN Block for EMV Personalization", + EMVA: "Verify ARQC and optionally generate ARPC", + EMVG: "Generate Master Key", + EMVK: "Derive Key from Vendor Master Key and Derivation Data", + EMVM: "Generate or Verify MAC", + EMVP: "PIN Change", + EMVR: "EMV RSA Private Key or Component Translation to Encryption Under a Personalization Key", + EMVS: "Translate an ICC Master Key to Encryption Under a Personalization Key", + EMVT: "EMV Translate Sensitive Data", + GCAV: "Generate CAVV", + GCIV: "Generate a CVC3 IV", + GCSC: "Generate American Express CSC Value", + GCVC: "Generate CVC and CVC2", + GCVV: "Generate CVV or CVC Value", + GDAC: "Generate a Data Authentication Code", + GDCV: "Generate dCVV/CVC3", + GDDC: "Generate Discover dynamic CVV", + GEMC: "Generate EMV ICC Certificate", + GEMQ: "Generate EMV Issuer CSR", + GHMC: "Generate HCE Mobile Cryptogram", + GHMD: "Generate HCE Magstripe Verification Value", + GHMK: "Generate HCE Mobile Keys", + GHPB: "Generate HMAC and PBKDF2 Obfuscated Value", + GIDN: "Generate an ICC dynamic number", + GMAC: "Generate Message Authentication Code", + GNOF: "Generate New Offset", + GOFC: "Generate Offset of Clear PIN", + GOFF: "Generate PIN offset value", + GOPC: "Generate Offset and EMV PIN Change", + GPIN: "Generate PIN", + GPMC: "General Purpose Symmetric MAC", + GVDC: "Generate dynamic CVV", + HMAC: "Generate MAC Hash", + OFPC: "Perform EMV PIN Change Using Offset", + ONGQ: "Translate PAN Encrypted Under an Asymmetric Key Pair to a Different Trusted Public Key", + PEDK: "Key Request", + RKHM: "Generate or Verify HMAC", + RPIN: "PIN Change and Optional PIN Verification", + SSAD: "Sign Static Authentication Data with Issuer Private Key", + TCDK: "Translate Cardholder Data Using DUKPT", + TDKD: "Translate Cardholder Data Using DUKPT and Symmetric Keys", + TKDR: "Translate DUKPT Data to RSA with Specific Output Data", + TPCP: "Translate Encrypted PIN Coordinates to a PEK for Generate New Map Collection", + TPDD: "Allow an encrypted ANSI PIN block to be translated", + TPIN: "Translate PIN blocks", + TRPN: "Translate PIN from RSA to Symmetric PIN Block", + TSPN: "Translate PIN from PIN block to RSA encryption", + VAAV: "Verify Account Holder Authentication Value", + VCAC: "Verify EMV Mastercard CAP Token", + VCAV: "Verify Cardholder Authentication Verification Value", + VCSC: "Verify American Express CSC Value", + VCVC: "Verify CVC and CVC2", + VCVV: "Verify CVV", + VDAC: "Verify a Data Authentication Code", + VDCV: "Verify CVC3", + VDDC: "Verify dynamic CVC value", + VEMI: "Verify an EMV Issuer Certificate", + VHMC: "Verify HCE Mobile Cryptogram", + VHMD: "Verify HCE Magstripe Verification Value", + VIDN: "Verify an ICC dynamic number", + VMAC: "Verify Message Authentication Code", + VMAP: "Verify MAC and PIN", + VPIN: "Verify PIN", + VVDC: "Verify a dynamic CVV", + WPIN: "Weak PIN checking", + XPIN: "PIN translation" +}; + +/** + * Parses an Excrypt field into tag/value components. + * + * @param {string} field + * @returns {{raw: string, tag: string, value: string}} + */ +function parseField(field) { + const tag = field.substring(0, Math.min(2, field.length)).toUpperCase(); + return { + raw: field, + tag, + value: field.substring(tag.length) + }; +} + +/** + * Parse Futurex Excrypt command operation. + */ +class ParseFuturexExcryptCommand extends Operation { + + /** + * ParseFuturexExcryptCommand constructor + */ + constructor() { + super(); + + this.name = "Parse Futurex Excrypt command"; + this.module = "Payment"; + this.description = "Paste a Futurex Excrypt command or response into the input field as text.

General syntax: Excrypt messages are enclosed by opening and closing delimiters, typically [ and ]. Inside the message, fields are semicolon-delimited. Each field is a tag/value pair, for example AOECHO where AO is the tag and ECHO is the value. The command code is commonly carried in the AO field.

Input: raw Excrypt message text.

This operation parses the visible Excrypt message syntax, extracts semicolon-delimited fields, splits fields into tag/value pairs, and resolves the AO command code to a known payment command name when available from the Futurex payment integration guide."; + this.inlineHelp = "Syntax: [tagvalue;tagvalue;...] where fields are separated by semicolons and tags are typically two characters such as AO.
Input: raw Futurex Excrypt message text."; + this.testDataSamples = [ + { + name: "Excrypt command sample", + input: "[AOGMAC;FS6;RV0011223344556677;]" + } + ]; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {string} input + * @returns {string} + */ + run(input) { + const rawInput = (input || "").replace(/\r?\n/g, ""); + if (!rawInput.length) { + throw new OperationError("No input."); + } + + const openingDelimiterPresent = rawInput.startsWith("["); + const closingDelimiterPresent = rawInput.endsWith("]"); + const body = rawInput.replace(/^\[/, "").replace(/\]$/, ""); + const rawFields = body.split(";").filter(field => field.length > 0); + + if (!rawFields.length) { + throw new OperationError("No Excrypt fields found."); + } + + const fields = rawFields.map(parseField); + const commandField = fields.find(field => field.tag === "AO") || fields[0]; + const commandCode = commandField.value.toUpperCase(); + const commandName = COMMANDS[commandCode] || null; + const notes = []; + + if (!openingDelimiterPresent || !closingDelimiterPresent) { + notes.push("Message is missing one or both expected Excrypt outer delimiters."); + } + + if (!commandName) { + notes.push("Command code was not found in the Futurex payment integration guide lookup."); + } + + return JSON.stringify({ + rawInput, + openingDelimiterPresent, + closingDelimiterPresent, + body, + rawFields, + fields, + commandFieldTag: commandField.tag, + commandCode, + commandName, + fieldCount: fields.length, + notes + }, null, 4); + } +} + +export default ParseFuturexExcryptCommand; diff --git a/tests/operations/tests/Payment.mjs b/tests/operations/tests/Payment.mjs index f303a3f6..b8447c4e 100644 --- a/tests/operations/tests/Payment.mjs +++ b/tests/operations/tests/Payment.mjs @@ -92,6 +92,94 @@ TestRegister.addTests([ } ] }, + { + name: "Parse Futurex Excrypt command: bracketed fields", + input: "[AOGMAC;FS6;RV0011223344556677;]", + expectedOutput: JSON.stringify({ + rawInput: "[AOGMAC;FS6;RV0011223344556677;]", + openingDelimiterPresent: true, + closingDelimiterPresent: true, + body: "AOGMAC;FS6;RV0011223344556677;", + rawFields: [ + "AOGMAC", + "FS6", + "RV0011223344556677" + ], + fields: [ + { + raw: "AOGMAC", + tag: "AO", + value: "GMAC" + }, + { + raw: "FS6", + tag: "FS", + value: "6" + }, + { + raw: "RV0011223344556677", + tag: "RV", + value: "0011223344556677" + } + ], + commandFieldTag: "AO", + commandCode: "GMAC", + commandName: "Generate Message Authentication Code", + fieldCount: 3, + notes: [] + }, null, 4), + recipeConfig: [ + { + op: "Parse Futurex Excrypt command", + args: [] + } + ] + }, + { + name: "Parse Futurex Excrypt command: missing closing delimiter", + input: "[AOVMAC;FS6;RV89ABCDEF", + expectedOutput: JSON.stringify({ + rawInput: "[AOVMAC;FS6;RV89ABCDEF", + openingDelimiterPresent: true, + closingDelimiterPresent: false, + body: "AOVMAC;FS6;RV89ABCDEF", + rawFields: [ + "AOVMAC", + "FS6", + "RV89ABCDEF" + ], + fields: [ + { + raw: "AOVMAC", + tag: "AO", + value: "VMAC" + }, + { + raw: "FS6", + tag: "FS", + value: "6" + }, + { + raw: "RV89ABCDEF", + tag: "RV", + value: "89ABCDEF" + } + ], + commandFieldTag: "AO", + commandCode: "VMAC", + commandName: "Verify Message Authentication Code", + fieldCount: 3, + notes: [ + "Message is missing one or both expected Excrypt outer delimiters." + ] + }, null, 4), + recipeConfig: [ + { + op: "Parse Futurex Excrypt command", + args: [] + } + ] + }, { name: "Parse TR-31 key block: fixed header only", input: "D0016D0AB00E0000",