diff --git a/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md b/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md index ebca87b2..faef1d45 100644 --- a/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md +++ b/AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md @@ -1,290 +1,207 @@ # AWS Payment Cryptography Recipe Coverage -This guide maps AWS Payment Cryptography Data Plane operations to CyberChef recipe starters. - -Intent: -- This fork is not a certified HSM. -- It is intended to emulate HSM-style payment cryptography behavior in software for development, QA, regression, interoperability, and integration testing. -- The goal of this guide is therefore twofold: - 1. document what can already be emulated with the current operation set - 2. identify which AWS Payment Cryptography use cases should be added next to improve test-harness coverage +This guide maps AWS Payment Cryptography Data Plane operations to the current payment-facing CyberChef surface. Source baseline: - AWS Payment Cryptography Data Plane API Reference: https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/Welcome.html - AWS Data Plane actions list: https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_Operations.html Coverage legend: -- `Direct`: CyberChef can reproduce the core cryptographic shape of the AWS operation. -- `Partial`: CyberChef can help with preimage assembly, derivation, or one stage of the flow, but not the full AWS behavior. -- `Not yet implemented`: This is a valid testing/emulation target for the fork, but the required payment primitives are not implemented yet. +- `Direct`: there is a payment-facing operation or straightforward recipe chain for the software-emulation shape of the AWS action +- `Chained`: there is no single operation, but the flow is cleanly achievable by chaining existing operations +- `Emulated`: there is a dedicated operation, but the inline comments call out simplifications versus AWS or HSM custody semantics ## Coverage Summary -| AWS operation | Coverage | Notes | +| AWS operation | Coverage | Use | | --- | --- | --- | -| `EncryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived encryption and broader AWS attribute coverage. | -| `DecryptData` | `Direct` / `Partial` | Direct for AES, TDES, and the implemented DUKPT-TDES wrapper profiles. Partial for EMV-derived decryption and broader AWS attribute coverage. | -| `ReEncryptData` | `Direct` / `Partial` | Direct for plain decrypt-then-encrypt workflows, including the implemented payment-facing AES/TDES wrapper flows. Partial for DUKPT re-encryption breadth and AWS-specific metadata handling. | -| `GenerateMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. | -| `VerifyMac` | `Direct` / `Partial` | Direct for static-key HMAC and CMAC, and direct for the implemented DUKPT CMAC wrapper modes. Partial for ISO 9797, EMV MAC, and AS2805 flows. | -| `VerifyAuthRequestCryptogram` | `Partial` | Usable for AES-CMAC ARQC/ARPC-style checking when session key and preimage are already known. Dedicated ARQC, ARPC, and ARQC verify wrappers now exist for that constrained profile. | -| `TranslateKeyMaterial` | `Partial` | Useful for ECDH derivation and TR-31 inspection, not full HSM-side rewrap semantics. | -| `GenerateCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV generation when the combined CVK pair is provided as clear hex. | -| `VerifyCardValidationData` | `Direct` | Direct for software CVV/CVV2/iCVV verification using the same clear-CVK assumptions as generation. | -| `GeneratePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. PVV, IBM3624, and encrypted-generation paths are still missing. | -| `TranslatePinData` | `Partial` | Clear PIN-block wrapper coverage now exists for ISO formats 0, 1, and 3. Encrypted PEK/BDK/ECDH translation is still missing. | -| `VerifyPinData` | `Partial` | Clear PIN-block verification wrapper exists, but PVV / IBM3624 verification behavior is still missing. | -| `GenerateMacEmvPinChange` | `Not yet implemented` | Requires issuer-script PIN-change building blocks. | -| `GenerateAs2805KekValidation` | `Not yet implemented` | Requires AS2805-specific KEK-validation primitives. | +| `EncryptData` | `Direct` | `Encrypt Payment Data` | +| `DecryptData` | `Direct` | `Decrypt Payment Data` | +| `ReEncryptData` | `Direct` | `Re-Encrypt Payment Data` | +| `GenerateMac` | `Direct` | `Generate Payment MAC` or `Generate EMV MAC` | +| `VerifyMac` | `Direct` | `Verify Payment MAC` or `Verify EMV MAC` | +| `VerifyAuthRequestCryptogram` | `Direct` | `Verify EMV ARQC` | +| `GenerateCardValidationData` | `Direct` | `Generate Card Validation Data` | +| `VerifyCardValidationData` | `Direct` | `Verify Card Validation Data` | +| `GeneratePinData` | `Direct` / `Chained` | `Generate Payment PIN Data`, `Generate IBM 3624 PIN Offset`, `Generate VISA PVV` | +| `TranslatePinData` | `Direct` / `Chained` | `Translate Payment PIN Data` or clear PIN block plus cipher chaining | +| `VerifyPinData` | `Direct` | `Verify Payment PIN Data`, `Verify IBM 3624 PIN`, `Verify VISA PVV` | +| `TranslateKeyMaterial` | `Chained` | `Derive ECDH Key Material` + wrap/unwrap + TR-31/TR-34 helpers | +| `GenerateAs2805KekValidation` | `Emulated` | `Generate AS2805 KEK Validation` | +| `GenerateMacEmvPinChange` | `Direct` / `Emulated` | `Generate EMV MAC For PIN Change` | -## Direct Recipe Starters +## AWS `EncryptData` +Preferred operation: +- `Encrypt Payment Data` -## 1) AWS `EncryptData`: AES / TDES / RSA -Operations: -- `AES Encrypt` or `Triple DES Encrypt` or `RSA Encrypt` - -Suggested use: -- Paste the AWS `PlainText` hexBinary value into the input field. -- Set the operation input mode to `Hex` and output mode to `Hex`. -- Paste the key into the key argument using the correct format selector. -- Match the AWS algorithm and mode manually in the chosen CyberChef operation. +Good chain: +- `Derive DUKPT Key` -> `Triple DES Encrypt` +- `Derive ECDH Key Material` -> KDF if needed -> `AES Encrypt` Notes: -- AWS documents `EncryptData` as supporting symmetric `TDES` and `AES`, asymmetric `RSA`, and derived `DUKPT` or `EMV` schemes. -- This starter directly covers only the non-derived AES, TDES, and RSA cases. +- use the payment wrapper when you want payment terminology in one operation +- use the generic ciphers directly when you need fine-grained mode control -## 2) AWS `EncryptData`: Payment Wrapper -Operations: -- `Encrypt payment data` +## AWS `DecryptData` +Preferred operation: +- `Decrypt Payment Data` -Suggested use: -- Paste plaintext into the input field as hex. -- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes. -- Provide the direct key or BDK plus KSN, and add the IV when required. +Good chain: +- `Derive DUKPT Key` -> `Triple DES Decrypt` +- `Derive ECDH Key Material` -> KDF if needed -> `AES Decrypt` -## 3) AWS `DecryptData`: AES / TDES / RSA -Operations: -- `AES Decrypt` or `Triple DES Decrypt` or `RSA Decrypt` +## AWS `ReEncryptData` +Preferred operation: +- `Re-Encrypt Payment Data` -Suggested use: -- Paste the AWS `CipherText` hexBinary value into the input field. -- Set the operation input mode to `Hex` and output mode to `Hex` or `Raw`. -- Paste the key into the key argument using the correct format selector. -- Match the AWS algorithm and mode manually in the chosen CyberChef operation. +Good chain: +- `Decrypt Payment Data` -> `Encrypt Payment Data` -## 4) AWS `DecryptData`: Payment Wrapper -Operations: -- `Decrypt payment data` +## AWS `GenerateMac` +Preferred operations: +- `Generate Payment MAC` +- `Generate EMV MAC` -Suggested use: -- Paste ciphertext into the input field as hex. -- Choose a payment-facing profile for AES, TDES, or the implemented DUKPT-TDES wrapper modes. -- Provide the direct key or BDK plus KSN, and add the IV when required. +Current MAC coverage: +- HMAC SHA-224 / 256 / 384 / 512 +- AES-CMAC +- TDES-CMAC +- ISO 9797-1 Algorithm 1 +- ISO 9797-1 Algorithm 3 +- AS2805-4.1 +- DUKPT TDES-CMAC +- DUKPT ISO 9797-1 Algorithm 1 +- DUKPT ISO 9797-1 Algorithm 3 +- EMV retail-MAC style generation with a provided session key -## 5) AWS `ReEncryptData`: Symmetric Rewrap -Operations: -- `AES Decrypt` or `Triple DES Decrypt` -- `AES Encrypt` or `Triple DES Encrypt` +Use `Generate EMV MAC` when: +- the AWS flow is EMV-session-key based rather than a static or DUKPT MAC key -Suggested use: -- Paste the incoming ciphertext into the input field as hex. -- First decrypt with the incoming key and mode. -- Then encrypt with the outgoing key and mode. +## AWS `VerifyMac` +Preferred operations: +- `Verify Payment MAC` +- `Verify EMV MAC` -Notes: -- This covers the software-visible decrypt-then-encrypt pattern. -- It does not model AWS wrapped-key handling or HSM-side key custody. +Use the same method, padding rule, and key context as generation. -## 6) AWS `ReEncryptData`: Payment Wrapper -Operations: -- `Re-encrypt payment data` - -Suggested use: -- Paste source ciphertext into the input field as hex. -- Define the source decrypt profile and the target encrypt profile in one operation. -- Use this as the payment-facing version of the decrypt-then-encrypt recipe chain. - -## 7) AWS `GenerateMac`: HMAC -Operations: -- `From Hex` -- `HMAC` -- `Take bytes` - -Suggested use: -- Paste the AWS `MessageData` hexBinary value into the input field. -- Run `From Hex`. -- Run `HMAC` with the appropriate key and hash function. -- If AWS truncates the MAC, use `Take bytes` to keep the leftmost bytes that match `MacLength`. - -## 8) AWS `GenerateMac`: CMAC -Operations: -- `From Hex` -- `CMAC` -- `Take bytes` - -Suggested use: -- Paste the AWS `MessageData` hexBinary value into the input field. -- Run `From Hex`. -- Run `CMAC` with `Encryption algorithm` set to `AES` or `Triple DES`. -- Use `Take bytes` to match the requested `MacLength` if truncation is required. - -## 9) AWS `VerifyMac`: Recompute And Compare -Operations: -- `Verify payment MAC` - -Suggested use: -- Paste the message into the input field, choose the MAC method, and provide either the direct key or the DUKPT BDK plus KSN. -- Supply the expected MAC in hex and let the wrapper recompute and compare it. - -Notes: -- This covers the implemented static-key HMAC/CMAC and DUKPT-CMAC wrapper modes directly. -- ISO 9797, EMV MAC, and AS2805-specific verification are still partial gaps. - -## 10) AWS `GenerateMac`: Payment Wrapper -Operations: -- `Generate payment MAC` - -Suggested use: -- Paste the message into the input field and choose the payment MAC method that best matches the AWS attributes. -- Use direct key input for static HMAC or CMAC modes, or provide a BDK plus KSN for the implemented DUKPT CMAC request and response modes. - -Notes: -- This wrapper exists for usability so payment users can stay in the `Payments` category without needing to know which low-level primitive is underneath. -- It intentionally reuses the existing generic `HMAC` and `CMAC` implementations. - -## 11) AWS `GenerateCardValidationData`: CVV / CVV2 / iCVV -Operations: -- `Generate card validation data` - -Suggested use: -- Paste the clear combined CVK pair into the input field as hex. -- Choose the profile that matches the AWS card-validation mode you want to emulate. -- Provide the PAN, expiry, and service-code context in the argument fields. - -Notes: -- This directly covers software generation of CVV/CVV2/iCVV-style values. -- Assumption: CVV2 forces service code `000` and iCVV forces `999`. - -## 12) AWS `VerifyCardValidationData`: CVV / CVV2 / iCVV -Operations: -- `Verify card validation data` - -Suggested use: -- Use the same card context as generation, then supply the incoming value in the `Expected value` argument. -- The operation recomputes the value and returns structured verification output. - -Notes: -- This is intended for software parity and regression checks. -- It does not emulate AWS key custody or HSM-side audit semantics. - -## Partial Recipe Starters - -## 13) AWS `EncryptData` / `DecryptData`: DUKPT-Derived Symmetric Flows -Operations: -- `Derive DUKPT key` -- `AES Encrypt` or `AES Decrypt` or `Triple DES Encrypt` or `Triple DES Decrypt` - -Suggested use: -- Derive the transaction key from BDK and KSN first. -- Feed the derived key into the cipher operation that matches your target algorithm. - -Notes: -- This is useful for offline vector work. -- It does not claim one-to-one parity with every AWS DUKPT encryption attribute combination. - -## 14) AWS `VerifyAuthRequestCryptogram`: EMV ARQC Check -Operations: +## AWS `VerifyAuthRequestCryptogram` +Preferred operation: - `Verify EMV ARQC` -Suggested use: -- Paste the already-assembled EMV authorization-request preimage into the input field as hex. -- Provide the already-derived AES session key and cryptogram length. -- Provide the incoming ARQC and let the wrapper recompute and compare it. +Good chain: +- preassemble the ARQC input block +- derive or supply the session key +- verify the ARQC -Notes: -- This is only practical when the session key and exact preimage assembly are already known. -- It is a good fit for AES-CMAC-based profiles, not a full generic EMV verifier. +Important assumption: +- current ARQC / ARPC support is the implemented AES-CMAC profile -## 15) AWS `TranslateKeyMaterial`: ECDH And Wrapped-Key Inspection -Operations: -- `Derive ECDH key material` +## AWS `GenerateCardValidationData` +Preferred operation: +- `Generate Card Validation Data` + +Profiles: +- CVV / CVC +- CVV2 / CVC2 +- iCVV + +## AWS `VerifyCardValidationData` +Preferred operation: +- `Verify Card Validation Data` + +## AWS `GeneratePinData` +Preferred operations: +- `Generate Payment PIN Data` +- `Generate IBM 3624 PIN Offset` +- `Generate VISA PVV` + +Use: +- `Generate Payment PIN Data` for clear ISO format `0`, `1`, and `3` PIN blocks +- `Generate IBM 3624 PIN Offset` for issuer-host offset workflows +- `Generate VISA PVV` for PVV workflows + +Good chains: +- clear PIN -> `Generate Payment PIN Data` -> `Encrypt Payment Data` +- clear PIN -> `Generate IBM 3624 PIN Offset` +- clear PIN -> `Generate VISA PVV` + +## AWS `TranslatePinData` +Preferred operation: +- `Translate Payment PIN Data` + +Good chains: +- `Parse PIN Block` -> inspect -> `Translate PIN Block` +- `Decrypt Payment Data` -> `Translate Payment PIN Data` -> `Encrypt Payment Data` + +Important assumption: +- the direct wrapper is for clear ISO PIN-block translation +- encrypted-key-custody semantics are still emulated by chaining + +## AWS `VerifyPinData` +Preferred operations: +- `Verify Payment PIN Data` +- `Verify IBM 3624 PIN` +- `Verify VISA PVV` + +Use: +- `Verify Payment PIN Data` for clear ISO PIN blocks +- `Verify IBM 3624 PIN` for issuer offset checks +- `Verify VISA PVV` for PVV checks + +## AWS `TranslateKeyMaterial` +Preferred chain: +- `Derive ECDH Key Material` +- KDF if needed +- `AES Key Wrap` or `AES Key Unwrap` - `Parse TR-31 key block` - `Parse TR-34 B9 envelope` -Suggested use: -- Use `Derive ECDH key material` to reproduce the shared-secret or KDF stage. -- Use the TR-31 or TR-34 parsers to inspect the wrapped key containers involved in the exchange. +Important assumption: +- this is a recipe chain, not a single HSM-like rewrap boundary -Notes: -- This helps with interoperability debugging. -- It does not recreate AWS’s HSM-side translate-and-rewrap behavior. +## AWS `GenerateAs2805KekValidation` +Preferred operation: +- `Generate AS2805 KEK Validation` -## 16) AWS `GenerateMac`: EMV MAC Preimage Review -Operations: -- `From Hex` -- `CMAC` -- `Take bytes` +Important assumption: +- this is an explicit software emulation helper +- the operation comments call out that it does not claim exact HSM-side AS2805 node-initialization behavior -Suggested use: -- Use this to validate assembled EMV message blocks and truncation behavior when you already know the scheme profile and session key. +## AWS `GenerateMacEmvPinChange` +Preferred operation: +- `Generate EMV MAC For PIN Change` -Notes: -- AWS documents `GenerateMac` as supporting EMV MAC. -- This fork does not yet have a dedicated EMV MAC operation, so this remains a profile-specific starter rather than a generic implementation. +Good chain: +- build or obtain the encrypted target PIN block +- assemble the issuer-script APDU body +- generate the PIN-change MAC -## 17) AWS `GeneratePinData`: Clear PIN Block Wrapper -Operations: -- `Generate payment PIN data` +Important assumption: +- the helper expects the new PIN block to already be encrypted -Suggested use: -- Paste the clear PIN into the input field. -- Choose ISO format 0, 1, or 3. -- Provide the PAN when the selected format requires it. +## Common Chains -Notes: -- This is useful for software test harnesses that need deterministic clear PIN-block construction before encryption. -- It does not yet implement PVV generation, IBM 3624 offsets, or encrypted AWS response semantics. +## A) DUKPT Request MAC +- `Generate Payment MAC` -## 18) AWS `TranslatePinData`: Clear PIN Block Wrapper -Operations: -- `Translate payment PIN data` +Method: +- `DUKPT MAC Request CMAC` +- or `DUKPT ISO 9797-1 Algorithm 1` +- or `DUKPT ISO 9797-1 Algorithm 3` -Suggested use: -- Paste the source clear PIN block into the input field as hex. -- Choose the source and target formats. -- Provide source and target PAN values where required. +## B) EMV Issuer Script MAC +- `Generate EMV MAC` +- `Verify EMV MAC` -Notes: -- This is a software emulation helper for test-vector work. -- It does not yet emulate encrypted HSM-bound translation between PEK, BDK, or ECDH-derived keys. +## C) EMV PIN Change +- `Generate EMV MAC For PIN Change` -## 19) AWS `VerifyPinData`: Clear PIN Block Wrapper -Operations: -- `Verify payment PIN data` +## D) Clear PIN To Encrypted PIN Data +- `Generate Payment PIN Data` +- `Encrypt Payment Data` -Suggested use: -- Paste the clear PIN block into the input field as hex. -- Provide the expected clear PIN and let the wrapper decode and compare it. - -Notes: -- This is only structural verification today. -- It does not yet implement VISA PVV or IBM 3624 verification logic. - -## Not Yet Implemented - -These AWS operations are still valid emulation targets, but do not yet have recipe-equivalent support in this fork: -- `GenerateMacEmvPinChange` -- `GenerateAs2805KekValidation` - -Why: -- They depend on PVV/IBM3624/issuer-script/AS2805-specific payment primitives that are not implemented here. - -## Good Next Additions - -If you want closer AWS coverage, the highest-value missing operations are: -1. PIN block encode/decode for ISO 9564 formats 0, 1, 3, and 4. -2. IBM 3624 and VISA PVV generation and verification. -3. ISO 9797 and AS2805-specific MAC generation and verification. -4. Dedicated EMV MAC and profile-specific EMV session-derivation helpers. -5. Clear-to-encrypted and encrypted-to-encrypted PIN translation flows. -6. TR-31 unwrap and rewrap helpers for dynamic-key workflows. +## E) ECDH-Based Key Translation Lab Flow +- `Derive ECDH Key Material` +- `AES Key Unwrap` +- `AES Key Wrap` +- `Parse TR-31 key block` diff --git a/PAYMENT_RECIPES.md b/PAYMENT_RECIPES.md index 4ca2ee9a..cacd7866 100644 --- a/PAYMENT_RECIPES.md +++ b/PAYMENT_RECIPES.md @@ -1,139 +1,283 @@ # Payment Recipe Starters -These recipe starters are designed for software-only inspection, validation, and prototyping workflows. +These recipe starters are for software-only payment-crypto emulation, inspection, regression tests, and interoperability work. -For AWS-specific mappings, see `AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md`. +For AWS operation mapping, see `AWS_PAYMENT_CRYPTOGRAPHY_RECIPES.md`. -## 1) TR-31 Header Parse +## UI Arrangement + +The `Payments` category is arranged in this order: +- payment-facing wrappers first +- EMV and card-validation flows next +- PIN and issuer-verification helpers after that +- key-derivation, KCV, and parser utilities next +- generic crypto primitives last for chaining + +That keeps common testing tasks near the top without hiding the underlying `HMAC`, `CMAC`, cipher, and key-wrap primitives that some chains still need. + +## 1) Encrypt / Decrypt / Re-Encrypt Payment Data Operations: -- `Parse TR-31 key block` +- `Encrypt Payment Data` +- `Decrypt Payment Data` +- `Re-Encrypt Payment Data` -## 2) TR-34 B9 Envelope Split +Use this when: +- you want payment-facing names for AES, TDES, or the implemented DUKPT-TDES profiles +- you want one operation for decrypt-then-encrypt rewrapping + +Input: +- plaintext or ciphertext in the selected input format + +Important assumptions: +- current derived-data coverage is AES, TDES, and the implemented DUKPT-TDES profiles +- this is software emulation and does not model AWS key ARNs or HSM custody + +## 2) Generate / Verify Payment MAC Operations: -- `Parse TR-34 B9 envelope` +- `Generate Payment MAC` +- `Verify Payment MAC` -## 3) KCV Validation +Supported methods: +- `HMAC SHA-224` +- `HMAC SHA-256` +- `HMAC SHA-384` +- `HMAC SHA-512` +- `AES-CMAC` +- `TDES-CMAC` +- `ISO 9797-1 Algorithm 1` +- `ISO 9797-1 Algorithm 3` +- `AS2805-4.1` +- `DUKPT MAC Request CMAC` +- `DUKPT MAC Response CMAC` +- `DUKPT ISO 9797-1 Algorithm 1` +- `DUKPT ISO 9797-1 Algorithm 3` + +Use this when: +- you want one payment-facing MAC surface instead of deciding between generic `HMAC`, `CMAC`, ISO9797, DUKPT, and AS2805 yourself + +Input: +- message data in the selected input format + +Important assumptions: +- ISO9797 and AS2805 methods use clear TDES keys in software +- DUKPT methods expect a clear BDK plus full KSN +- EMV MAC is handled by the dedicated EMV MAC operations below + +## 3) Generate / Verify EMV MAC Operations: -- `Calculate payment KCV` +- `Generate EMV MAC` +- `Verify EMV MAC` +- `Generate EMV MAC For PIN Change` -## 4) ECDH Key Agreement (Software) -Operations: -- `Derive ECDH key material` +Use this when: +- you already have the EMV session integrity key +- you want issuer-script MAC generation or verification +- you need a dedicated offline PIN-change MAC helper -Suggested use: -- Import a local private key and peer public key. -- Derive raw shared secret or run Concat KDF (`SHA-256` or `SHA-512`) with shared-info. +Input: +- issuer-script or EMV command payload as hex -## 5) DUKPT Derivation (Software) -Operations: -- `Derive DUKPT key` +Important assumptions: +- these operations do not derive EMV session keys +- they apply retail-MAC style EMV MAC generation with ISO9797 padding method 2 +- `Generate EMV MAC For PIN Change` expects the new PIN block to already be encrypted before you call it -Suggested use: -- Derive IPEK from BDK + KSN. -- Derive base session key and apply a variant mask (`PIN`, `MAC Request`, `MAC Response`, `Data`). - -## 6) Payment Data Encrypt / Decrypt / Re-encrypt -Operations: -- `Encrypt payment data` -- `Decrypt payment data` -- `Re-encrypt payment data` - -Suggested use: -- Paste the message or ciphertext into the input field as hex. -- Choose a payment-facing cipher profile for AES, TDES, or DUKPT-derived TDES. -- Provide the direct key or BDK plus KSN, then add the IV when the selected mode requires one. - -Scope note: -- These wrappers currently cover AES CBC/CTR/ECB, TDES CBC/ECB, and DUKPT-derived TDES CBC/ECB. -- They are intended for software test harnesses and intentionally reuse the existing generic cipher implementations underneath. - -## 7) PIN Block Build / Parse / Translate -Operations: -- `Build PIN block` -- `Parse PIN block` -- `Translate PIN block` - -Suggested use: -- Build clear ISO 9564 format 0, 1, or 3 PIN blocks from a PIN and PAN. -- Parse clear test PIN blocks back into PIN, PIN field, PAN field, and filler details. -- Translate clear test PIN blocks between supported formats before feeding them into cipher steps. - -Scope note: -- This starter currently covers clear software test blocks for ISO formats 0, 1, and 3. -- It does not yet generate PVV, IBM 3624 offsets, or encrypted PEK/BDK translation flows by itself. - -## 8) Payment PIN Data Wrappers -Operations: -- `Generate payment PIN data` -- `Translate payment PIN data` -- `Verify payment PIN data` - -Suggested use: -- Use these wrappers when you want AWS-style PIN-data naming instead of the lower-level PIN-block operations. -- They currently cover clear ISO 9564 formats 0, 1, and 3 by delegating to the existing build, translate, and parse operations. - -Scope note: -- This is still clear-PIN-block coverage only. -- Encrypted PIN data, PVV, and IBM 3624 are still future additions. - -## 9) Card Validation Data (CVV / CVV2 / iCVV) -Operations: -- `Generate card validation data` -- `Verify card validation data` - -Suggested use: -- Paste the combined CVK pair into the input field as 16-byte or 24-byte hex. -- Choose whether you want CVV/CVC, CVV2/CVC2, or iCVV behavior. -- Provide the PAN, expiry month/year, and service-code context in the argument fields. - -Scope note: -- This implementation is intended for software test harnesses. -- CVV2 forces service code `000` and iCVV forces `999`. -- It does not try to emulate scheme-specific dCVV, token CVV, or issuer-host formatting differences beyond the common decimalization flow. - -## 10) Payment MAC Generation And Verification -Operations: -- `Generate payment MAC` -- `Verify payment MAC` - -Suggested use: -- Paste the message data into the input field. -- Choose whether the MAC should use static `HMAC`, static `CMAC`, or DUKPT-derived TDES-CMAC. -- Provide either a direct MAC key or a BDK plus KSN, depending on the selected method. - -Scope note: -- This wrapper intentionally reuses the existing generic `HMAC` and `CMAC` implementations instead of duplicating crypto code. -- Current DUKPT coverage derives TDES session keys and applies TDES-CMAC for request and response MAC variants. -- ISO 9797, EMV session-derivation MAC, and AS2805 are still future additions. - -## 11) EMV ARQC Generation And Verification (AES-CMAC Profile) +## 4) Generate / Verify EMV ARQC And ARPC Operations: - `Generate EMV ARQC` - `Verify EMV ARQC` - -Suggested use: -- Paste the already-assembled ARQC input block into the input field as hex. -- Provide the already-derived AES session key in the argument field. -- Choose how many leftmost CMAC bytes to keep as the final cryptogram. - -Scope note: -- This operation is intentionally limited to AES-CMAC-style EMV profiles. -- It does not derive EMV session keys or assemble CDOL/tag data for you. - -## 12) EMV ARPC Generation (AES-CMAC Response Profile) -Operations: - `Generate EMV ARPC` -Suggested use: -- Paste the already-assembled ARPC response input block into the input field as hex. -- Provide the already-derived issuer AES session key in the argument field. -- Choose how many leftmost CMAC bytes to keep as the final cryptogram. +Use this when: +- you already know the exact preassembled EMV data block +- you already have the derived EMV session key -Scope note: -- This operation is intentionally limited to AES-CMAC response profiles where the issuer session key and exact preimage are already known. -- Legacy 3DES EMV ARQC/ARPC flows are not covered. +Input: +- preassembled EMV cryptogram input data as hex -## 13) Combined Message Triage +Important assumptions: +- current coverage is the implemented AES-CMAC profile +- these operations do not assemble CDOL data or derive issuer/session keys + +## 5) Generate / Verify Card Validation Data Operations: +- `Generate Card Validation Data` +- `Verify Card Validation Data` + +Profiles: +- `CVV / CVC (use service code arg)` +- `CVV2 / CVC2 (force 000)` +- `iCVV (force 999)` + +Input: +- combined CVK pair as clear hex + +Important assumptions: +- CVV2 forces service code `000` +- iCVV forces service code `999` +- this is a clear-key software emulation of common card-validation flows + +## 6) Generate / Translate / Verify Payment PIN Data +Operations: +- `Generate Payment PIN Data` +- `Translate Payment PIN Data` +- `Verify Payment PIN Data` + +Use this when: +- you want AWS-style PIN-data naming for clear ISO 9564 block flows + +Input: +- `Generate Payment PIN Data`: clear PIN digits +- `Translate Payment PIN Data`: clear PIN block hex +- `Verify Payment PIN Data`: clear PIN block hex + +Important assumptions: +- these wrappers currently cover clear ISO formats `0`, `1`, and `3` +- encrypted PEK/BDK translation is still done by chaining lower-level steps + +## 7) Build / Parse / Translate PIN Block +Operations: +- `Build PIN Block` +- `Parse PIN Block` +- `Translate PIN Block` + +Use this when: +- you want the lower-level clear PIN-block tools directly + +Input: +- `Build PIN Block`: clear PIN digits +- `Parse PIN Block`: clear PIN block hex +- `Translate PIN Block`: clear PIN block hex + +Important assumptions: +- current clear-block support is ISO formats `0`, `1`, and `3` + +## 8) Issuer PIN Verification Helpers +Operations: +- `Generate IBM 3624 PIN Offset` +- `Verify IBM 3624 PIN` +- `Generate VISA PVV` +- `Verify VISA PVV` + +Use this when: +- you need issuer-side PIN verification artifacts rather than PIN blocks + +Input: +- clear PIN digits + +Important assumptions: +- these helpers use clear PVKs in software +- IBM 3624 expects a decimalization table and validation data +- VISA PVV uses the common PAN/PVKI/PIN assembly described in the inline comments + +## 9) Key Derivation And Validation +Operations: +- `Derive DUKPT Key` +- `Derive ECDH Key Material` +- `Calculate Payment KCV` +- `Generate AS2805 KEK Validation` + +Use this when: +- you need transaction keys, shared secrets, KCVs, or AS2805-style KEK-validation lab values + +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 +Operations: +- `Parse TR-31 key block` - `Parse TR-34 B9 envelope` -- `Parse ASN.1 hex string` + +Use this when: +- you need to inspect inbound 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 + +## Chaining Patterns + +## A) DUKPT MAC +Operations: +- `Derive DUKPT Key` +- `Generate Payment MAC` + +Flow: +- derive the transaction key first if you want to inspect it +- or use a DUKPT MAC method directly in `Generate Payment MAC` +- use the same KSN and BDK on verify + +## B) ECDH Wrap / Unwrap +Operations: +- `Derive ECDH Key Material` +- `AES Key Wrap` +- `AES Key Unwrap` + +Flow: +- derive the shared secret +- optionally run a KDF if you need a specific KEK size +- feed the resulting key into `AES Key Wrap` or `AES Key Unwrap` + +Important assumption: +- this is not a full TR-34 or AWS `TranslateKeyMaterial` implementation by itself + +## C) Clear PIN Block To Encrypted PIN Data +Operations: +- `Generate Payment PIN Data` or `Build PIN Block` +- `Encrypt Payment Data` + +Flow: +- generate the clear ISO PIN block first +- encrypt that block under the desired AES or TDES profile + +## D) Re-Encrypt Payment Data +Operations: +- `Re-Encrypt Payment Data` + +Flow: +- define the source decrypt profile +- define the target encrypt profile +- keep the payload in hex end to end + +## E) EMV ARQC / ARPC Review +Operations: +- `Generate EMV ARQC` +- `Verify EMV ARQC` +- `Generate EMV ARPC` + +Flow: +- build the exact request-data preimage outside the op +- generate or verify the ARQC with the derived session key +- build the response preimage and generate the ARPC + +## F) EMV Script MAC And PIN Change +Operations: +- `Generate EMV MAC` +- `Verify EMV MAC` +- `Generate EMV MAC For PIN Change` + +Flow: +- assemble the issuer-script APDU body as hex +- use the derived integrity key +- append the already-encrypted PIN block when generating the PIN-change MAC + +## G) IBM 3624 / PVV Verification +Operations: +- `Generate IBM 3624 PIN Offset` +- `Verify IBM 3624 PIN` +- `Generate VISA PVV` +- `Verify VISA PVV` + +Flow: +- keep the clear PIN in the input field +- keep issuer validation data, PAN, PVKI, decimalization table, and PVK in the args +- use the JSON output when you need to inspect how the verification artifact was assembled + +## H) AS2805 KEK Validation +Operations: +- `Generate AS2805 KEK Validation` +- `Calculate Payment KCV` + +Flow: +- inspect the KEK with `Calculate Payment KCV` +- generate request or response RandomKeySend / RandomKeyReceive values with the AS2805 helper diff --git a/PAYMENT_SIM_RECIPES.md b/PAYMENT_SIM_RECIPES.md index 90fcdac4..42778bb0 100644 --- a/PAYMENT_SIM_RECIPES.md +++ b/PAYMENT_SIM_RECIPES.md @@ -11,6 +11,7 @@ This list targets software-only development and testing environments. 1. Header mutation recipes (usage, mode, exportability, optional block counts). 2. Optional-block truncation and malformed-length negative tests. 3. Prefix-normalization recipes (`R` prefix handling). +4. Create TR-31 key block recipes for symmetric test keys and round-trip parse validation. ## TR-34 Simulation 1. Envelope section split/rebuild recipes. @@ -43,8 +44,8 @@ This list targets software-only development and testing environments. ## AWS Payment Cryptography Candidate Recipes 1. `EncryptData` and `DecryptData` parity vectors for AES, TDES, and RSA. 2. `ReEncryptData` parity vectors for decrypt-then-encrypt workflows. -3. `GenerateMac` and `VerifyMac` parity vectors for HMAC and CMAC. -4. `VerifyAuthRequestCryptogram` preimage-validation recipes for AES-CMAC EMV profiles. +3. `GenerateMac` and `VerifyMac` parity vectors across HMAC, CMAC, ISO9797, DUKPT, AS2805, and EMV MAC profiles. +4. `VerifyAuthRequestCryptogram` preimage-validation recipes for the implemented AES-CMAC EMV profiles. 5. DUKPT derivation-plus-cipher recipes for AWS derived-key lab testing. -6. ECDH and TR-31 inspection recipes for `TranslateKeyMaterial` interoperability debugging. -7. Gap-tracking recipes for unsupported AWS flows: PVV, IBM3624, encrypted PIN block translation, issuer-script PIN change, and AS2805 KEK validation. +6. ECDH plus wrap/unwrap plus TR-31 inspection recipes for `TranslateKeyMaterial` interoperability debugging. +7. Remaining gap-tracking recipes for encrypted PIN translation, richer EMV session derivation, and fuller TR-31/TR-34 generation flows. diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index d4fb6c68..ff16bebc 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -568,6 +568,35 @@ { "name": "Payments", "ops": [ + "Encrypt Payment Data", + "Decrypt Payment Data", + "Re-Encrypt Payment Data", + "Generate Payment MAC", + "Verify Payment MAC", + "Generate EMV MAC", + "Verify EMV MAC", + "Generate EMV ARQC", + "Verify EMV ARQC", + "Generate EMV ARPC", + "Generate EMV MAC For PIN Change", + "Generate Card Validation Data", + "Verify Card Validation Data", + "Generate Payment PIN Data", + "Translate Payment PIN Data", + "Verify Payment PIN Data", + "Build PIN Block", + "Parse PIN Block", + "Translate PIN Block", + "Generate IBM 3624 PIN Offset", + "Verify IBM 3624 PIN", + "Generate VISA PVV", + "Verify VISA PVV", + "Derive DUKPT Key", + "Derive ECDH Key Material", + "Calculate Payment KCV", + "Generate AS2805 KEK Validation", + "Parse TR-31 key block", + "Parse TR-34 B9 envelope", "HMAC", "CMAC", "AES Encrypt", @@ -575,28 +604,7 @@ "Triple DES Encrypt", "Triple DES Decrypt", "AES Key Wrap", - "AES Key Unwrap", - "Parse TR-31 key block", - "Parse TR-34 B9 envelope", - "Calculate Payment KCV", - "Derive ECDH Key Material", - "Derive DUKPT Key", - "Encrypt Payment Data", - "Decrypt Payment Data", - "Re-Encrypt Payment Data", - "Generate Payment MAC", - "Verify Payment MAC", - "Generate Card Validation Data", - "Verify Card Validation Data", - "Generate EMV ARQC", - "Generate EMV ARPC", - "Verify EMV ARQC", - "Build PIN Block", - "Parse PIN Block", - "Translate PIN Block", - "Generate Payment PIN Data", - "Translate Payment PIN Data", - "Verify Payment PIN Data" + "AES Key Unwrap" ] }, { diff --git a/src/core/lib/CardValidation.mjs b/src/core/lib/CardValidation.mjs index 52351f1e..b590ed38 100644 --- a/src/core/lib/CardValidation.mjs +++ b/src/core/lib/CardValidation.mjs @@ -2,9 +2,9 @@ * @license Apache-2.0 */ -import forge from "node-forge"; import OperationError from "../errors/OperationError.mjs"; -import { bytesToHex, parseHexBytes, toByteString } from "./PaymentUtils.mjs"; +import { bytesToHex, parseHexBytes } from "./PaymentUtils.mjs"; +import { encryptDesEcb, encryptTdesEcb } from "./CardValidationInternals.mjs"; const CVV_PROFILES = [ "CVV / CVC (use service code arg)", @@ -55,45 +55,6 @@ function resolveServiceCode(profile, serviceCode) { } -/** - * Encrypts one 8-byte block with DES ECB. - * - * @param {Uint8Array} key8 - * @param {Uint8Array} block8 - * @returns {Uint8Array} - */ -function encryptDesEcb(key8, block8) { - const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8)); - cipher.mode.pad = function() { - return true; - }; - cipher.start(); - cipher.update(forge.util.createBuffer(toByteString(block8))); - cipher.finish(); - return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); -} - - -/** - * Encrypts one 8-byte block with 3DES ECB. - * - * @param {Uint8Array} key - * @param {Uint8Array} block8 - * @returns {Uint8Array} - */ -function encryptTdesEcb(key, block8) { - const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key; - const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey)); - cipher.mode.pad = function() { - return true; - }; - cipher.start(); - cipher.update(forge.util.createBuffer(toByteString(block8))); - cipher.finish(); - return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); -} - - /** * XORs two byte arrays. * diff --git a/src/core/lib/CardValidationInternals.mjs b/src/core/lib/CardValidationInternals.mjs new file mode 100644 index 00000000..fde53b83 --- /dev/null +++ b/src/core/lib/CardValidationInternals.mjs @@ -0,0 +1,48 @@ +/** + * @license Apache-2.0 + */ + +import forge from "node-forge"; +import { toByteString } from "./PaymentUtils.mjs"; + +/** + * Encrypts one 8-byte block with DES ECB. + * + * @param {Uint8Array} key8 + * @param {Uint8Array} block8 + * @returns {Uint8Array} + */ +function encryptDesEcb(key8, block8) { + const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8)); + cipher.mode.pad = function() { + return true; + }; + cipher.start(); + cipher.update(forge.util.createBuffer(toByteString(block8))); + cipher.finish(); + return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); +} + +/** + * Encrypts one 8-byte block with 3DES ECB. + * + * @param {Uint8Array} key + * @param {Uint8Array} block8 + * @returns {Uint8Array} + */ +function encryptTdesEcb(key, block8) { + const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key; + const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey)); + cipher.mode.pad = function() { + return true; + }; + cipher.start(); + cipher.update(forge.util.createBuffer(toByteString(block8))); + cipher.finish(); + return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); +} + +export { + encryptDesEcb, + encryptTdesEcb, +}; diff --git a/src/core/lib/EmvMac.mjs b/src/core/lib/EmvMac.mjs new file mode 100644 index 00000000..1a1f0720 --- /dev/null +++ b/src/core/lib/EmvMac.mjs @@ -0,0 +1,79 @@ +/** + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import { generateIso9797Algorithm3Mac } from "./Iso9797.mjs"; + +/** + * Generates an EMV MAC using an already-derived session key. + * + * @param {string} messageHex + * @param {string} sessionKeyHex + * @param {number} outputBytes + * @returns {Object} + */ +function generateEmvMac(messageHex, sessionKeyHex, outputBytes=8) { + const normalizedKey = (sessionKeyHex || "").replace(/\s+/g, ""); + if (!/^[0-9A-Fa-f]+$/.test(normalizedKey) || normalizedKey.length % 2 !== 0) { + throw new OperationError("Session key must be hex."); + } + + return { + ...generateIso9797Algorithm3Mac(messageHex, normalizedKey, "Method 2", outputBytes), + algorithm: "EMV MAC" + }; +} + +/** + * Verifies an EMV MAC using an already-derived session key. + * + * @param {string} messageHex + * @param {string} sessionKeyHex + * @param {string} expectedMac + * @returns {Object} + */ +function verifyEmvMac(messageHex, sessionKeyHex, expectedMac) { + const normalizedExpected = (expectedMac || "").replace(/\s+/g, "").toUpperCase(); + if (!/^[0-9A-F]+$/.test(normalizedExpected) || normalizedExpected.length % 2 !== 0) { + throw new OperationError("Expected MAC must be even-length hex."); + } + + const generated = generateEmvMac(messageHex, sessionKeyHex, normalizedExpected.length / 2); + return { + ...generated, + expectedMacHex: normalizedExpected, + valid: generated.macHex === normalizedExpected + }; +} + +/** + * Generates the MAC portion of an EMV PIN-change issuer script. + * + * @param {string} messageHex + * @param {string} encryptedPinBlockHex + * @param {string} sessionKeyHex + * @param {number} outputBytes + * @returns {Object} + */ +function generateEmvPinChangeMac(messageHex, encryptedPinBlockHex, sessionKeyHex, outputBytes=8) { + const normalizedPinBlock = (encryptedPinBlockHex || "").replace(/\s+/g, "").toUpperCase(); + if (!/^[0-9A-F]{16,32}$/.test(normalizedPinBlock)) { + throw new OperationError("New encrypted PIN block must be 8 or 16 bytes of hex."); + } + + const combinedMessageHex = `${(messageHex || "").replace(/\s+/g, "").toUpperCase()}${normalizedPinBlock}`; + const generated = generateEmvMac(combinedMessageHex, sessionKeyHex, outputBytes); + return { + ...generated, + originalMessageHex: (messageHex || "").replace(/\s+/g, "").toUpperCase(), + appendedEncryptedPinBlockHex: normalizedPinBlock, + issuerScriptHex: combinedMessageHex + }; +} + +export { + generateEmvMac, + generateEmvPinChangeMac, + verifyEmvMac, +}; diff --git a/src/core/lib/Iso9797.mjs b/src/core/lib/Iso9797.mjs new file mode 100644 index 00000000..6a7762ea --- /dev/null +++ b/src/core/lib/Iso9797.mjs @@ -0,0 +1,214 @@ +/** + * @license Apache-2.0 + */ + +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; +import { bytesToHex, parseHexBytes, toByteString } from "./PaymentUtils.mjs"; + +const ISO9797_PADDING_METHODS = ["Method 1", "Method 2"]; + +/** + * XORs two byte arrays of equal length. + * + * @param {Uint8Array} left + * @param {Uint8Array} right + * @returns {Uint8Array} + */ +function xorBytes(left, right) { + const out = new Uint8Array(left.length); + for (let i = 0; i < left.length; i++) { + out[i] = left[i] ^ right[i]; + } + return out; +} + +/** + * Pads input according to ISO/IEC 9797-1 padding method 1 or 2. + * + * @param {Uint8Array} data + * @param {number} blockSize + * @param {string} paddingMethod + * @returns {Uint8Array} + */ +function applyIso9797Padding(data, blockSize, paddingMethod) { + if (!ISO9797_PADDING_METHODS.includes(paddingMethod)) { + throw new OperationError("Unsupported ISO9797 padding method."); + } + + if (paddingMethod === "Method 1") { + const remainder = data.length % blockSize; + if (remainder === 0) return Uint8Array.from(data); + const out = new Uint8Array(data.length + (blockSize - remainder)); + out.set(data, 0); + return out; + } + + const remainder = data.length % blockSize; + const extra = remainder === 0 ? blockSize : blockSize - remainder; + const out = new Uint8Array(data.length + extra); + out.set(data, 0); + out[data.length] = 0x80; + return out; +} + +/** + * Encrypts one 8-byte block with DES ECB. + * + * @param {Uint8Array} key8 + * @param {Uint8Array} block8 + * @returns {Uint8Array} + */ +function encryptDesBlock(key8, block8) { + const cipher = forge.cipher.createCipher("DES-ECB", toByteString(key8)); + cipher.mode.pad = function() { + return true; + }; + cipher.start(); + cipher.update(forge.util.createBuffer(toByteString(block8))); + cipher.finish(); + return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); +} + +/** + * Decrypts one 8-byte block with DES ECB. + * + * @param {Uint8Array} key8 + * @param {Uint8Array} block8 + * @returns {Uint8Array} + */ +function decryptDesBlock(key8, block8) { + const decipher = forge.cipher.createDecipher("DES-ECB", toByteString(key8)); + decipher.mode.unpad = function() { + return true; + }; + decipher.start(); + decipher.update(forge.util.createBuffer(toByteString(block8))); + decipher.finish(); + return Uint8Array.from(decipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); +} + +/** + * Encrypts one 8-byte block with TDES ECB. + * + * @param {Uint8Array} key + * @param {Uint8Array} block8 + * @returns {Uint8Array} + */ +function encryptTdesBlock(key, block8) { + const normalizedKey = key.length === 16 ? Uint8Array.from([...key, ...key.slice(0, 8)]) : key; + const cipher = forge.cipher.createCipher("3DES-ECB", toByteString(normalizedKey)); + cipher.mode.pad = function() { + return true; + }; + cipher.start(); + cipher.update(forge.util.createBuffer(toByteString(block8))); + cipher.finish(); + return Uint8Array.from(cipher.output.getBytes().split("").map(ch => ch.charCodeAt(0))).slice(0, 8); +} + +/** + * Encrypts blocks with DES CBC-MAC style chaining. + * + * @param {Uint8Array} key8 + * @param {Uint8Array} padded + * @returns {Uint8Array} + */ +function runDesCbcMac(key8, padded) { + let state = new Uint8Array(8); + for (let i = 0; i < padded.length; i += 8) { + const block = padded.slice(i, i + 8); + state = encryptDesBlock(key8, xorBytes(state, block)); + } + return state; +} + +/** + * Normalizes a MAC key for ISO9797-style MACs. + * + * @param {string} keyHex + * @returns {Uint8Array} + */ +function normalizeIso9797Key(keyHex) { + return parseHexBytes(keyHex, "MAC key", [16, 24]); +} + +/** + * Generates an ISO9797 algorithm 1 MAC. + * + * @param {string} inputHex + * @param {string} keyHex + * @param {string} paddingMethod + * @param {number} outputBytes + * @returns {Object} + */ +function generateIso9797Algorithm1Mac(inputHex, keyHex, paddingMethod, outputBytes=8) { + const data = parseHexBytes(inputHex, "Input data"); + const key = normalizeIso9797Key(keyHex); + const padded = applyIso9797Padding(data, 8, paddingMethod); + const fullMacBytes = encryptTdesBlock(key, runDesCbcMac(key.slice(0, 8), padded)); + const fullMacHex = bytesToHex(fullMacBytes); + const macHex = fullMacHex.substring(0, Math.max(1, Math.min(8, Number(outputBytes) || 8)) * 2); + + return { + algorithm: "ISO 9797-1 Algorithm 1", + paddingMethod, + inputHex: bytesToHex(data), + fullMacHex, + macHex, + }; +} + +/** + * Generates an ISO9797 algorithm 3 retail MAC. + * + * @param {string} inputHex + * @param {string} keyHex + * @param {string} paddingMethod + * @param {number} outputBytes + * @returns {Object} + */ +function generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, outputBytes=8) { + const data = parseHexBytes(inputHex, "Input data"); + const key = normalizeIso9797Key(keyHex); + const padded = applyIso9797Padding(data, 8, paddingMethod); + const key1 = key.slice(0, 8); + const key2 = key.slice(8, 16); + const key3 = key.length === 24 ? key.slice(16, 24) : key1; + const cbcState = runDesCbcMac(key1, padded); + const fullMacBytes = encryptDesBlock(key3, decryptDesBlock(key2, cbcState)); + const fullMacHex = bytesToHex(fullMacBytes); + const macHex = fullMacHex.substring(0, Math.max(1, Math.min(8, Number(outputBytes) || 8)) * 2); + + return { + algorithm: "ISO 9797-1 Algorithm 3", + paddingMethod, + inputHex: bytesToHex(data), + fullMacHex, + macHex, + }; +} + +/** + * Generates an AS2805 4.1 MAC. + * + * @param {string} inputHex + * @param {string} keyHex + * @param {string} paddingMethod + * @param {number} outputBytes + * @returns {Object} + */ +function generateAs2805Mac(inputHex, keyHex, paddingMethod="Method 1", outputBytes=8) { + const retail = generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, outputBytes); + return { + ...retail, + algorithm: "AS2805-4.1" + }; +} + +export { + ISO9797_PADDING_METHODS, + generateAs2805Mac, + generateIso9797Algorithm1Mac, + generateIso9797Algorithm3Mac, +}; diff --git a/src/core/lib/PaymentMac.mjs b/src/core/lib/PaymentMac.mjs index 56fc4e6a..ba433f55 100644 --- a/src/core/lib/PaymentMac.mjs +++ b/src/core/lib/PaymentMac.mjs @@ -7,6 +7,12 @@ import OperationError from "../errors/OperationError.mjs"; import HMAC from "../operations/HMAC.mjs"; import CMAC from "../operations/CMAC.mjs"; import DeriveDUKPTKey from "../operations/DeriveDUKPTKey.mjs"; +import { + ISO9797_PADDING_METHODS, + generateAs2805Mac, + generateIso9797Algorithm1Mac, + generateIso9797Algorithm3Mac, +} from "./Iso9797.mjs"; const PAYMENT_MAC_METHODS = [ "HMAC SHA-224", @@ -15,8 +21,13 @@ const PAYMENT_MAC_METHODS = [ "HMAC SHA-512", "AES-CMAC", "TDES-CMAC", + "ISO 9797-1 Algorithm 1", + "ISO 9797-1 Algorithm 3", + "AS2805-4.1", "DUKPT MAC Request CMAC", "DUKPT MAC Response CMAC", + "DUKPT ISO 9797-1 Algorithm 1", + "DUKPT ISO 9797-1 Algorithm 3", ]; /** @@ -41,7 +52,12 @@ function convertInputToBuffer(input, inputFormat) { function resolveMacKey(method, keySpec) { const normalizedKey = (keySpec.keyValue || "").replace(/\s+/g, ""); - if (method === "DUKPT MAC Request CMAC" || method === "DUKPT MAC Response CMAC") { + if ( + method === "DUKPT MAC Request CMAC" || + method === "DUKPT MAC Response CMAC" || + method === "DUKPT ISO 9797-1 Algorithm 1" || + method === "DUKPT ISO 9797-1 Algorithm 3" + ) { if (keySpec.keyFormat !== "Hex") { throw new OperationError("DUKPT BDK must be provided in hex."); } @@ -49,7 +65,9 @@ function resolveMacKey(method, keySpec) { throw new OperationError("KSN is required for DUKPT MAC methods."); } - const variant = method === "DUKPT MAC Request CMAC" ? "MAC Request" : "MAC Response"; + const variant = method === "DUKPT MAC Request CMAC" ? "MAC Request" : + method === "DUKPT MAC Response CMAC" ? "MAC Response" : + "MAC Request"; const dukpt = new DeriveDUKPTKey(); const keyHex = dukpt.run(normalizedKey, ["Derive Session Key", keySpec.ksn, variant, false]); @@ -96,9 +114,10 @@ function byteStringToHex(byteString) { * @param {string} keyFormat * @param {string} ksn * @param {number} outputBytes + * @param {string} paddingMethod * @returns {Object} */ -function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes) { +function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes, paddingMethod="Method 1") { const normalizedOutputBytes = Math.max(1, Number(outputBytes) || 8); const inputBuffer = convertInputToBuffer(input, inputFormat); const inputHex = byteStringToHex(Utils.arrayBufferToStr(inputBuffer, false)); @@ -114,10 +133,18 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn "HMAC SHA-512": "SHA512", }[method]; fullMacHex = hmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, hashName]).toUpperCase(); - } else { + } else if (method === "AES-CMAC" || method === "TDES-CMAC" || method === "DUKPT MAC Request CMAC" || method === "DUKPT MAC Response CMAC") { const cmac = new CMAC(); const algorithm = method === "AES-CMAC" ? "AES" : "Triple DES"; fullMacHex = cmac.run(inputBuffer, [{ string: keyHex, option: "Hex" }, algorithm]).toUpperCase(); + } else if (method === "ISO 9797-1 Algorithm 1" || method === "DUKPT ISO 9797-1 Algorithm 1") { + fullMacHex = generateIso9797Algorithm1Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex; + } else if (method === "ISO 9797-1 Algorithm 3" || method === "DUKPT ISO 9797-1 Algorithm 3") { + fullMacHex = generateIso9797Algorithm3Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex; + } else if (method === "AS2805-4.1") { + fullMacHex = generateAs2805Mac(inputHex, keyHex, paddingMethod, 8).fullMacHex; + } else { + throw new OperationError("Unsupported payment MAC method."); } const macHex = fullMacHex.substring(0, normalizedOutputBytes * 2); @@ -126,6 +153,7 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn method, inputFormat, inputHex, + paddingMethod: method.startsWith("HMAC ") || method.includes("CMAC") ? null : paddingMethod, outputBytes: normalizedOutputBytes, fullMacHex, macHex, @@ -143,9 +171,10 @@ function generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn * @param {string} keyFormat * @param {string} ksn * @param {string} expectedMac + * @param {string} paddingMethod * @returns {Object} */ -function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac) { +function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac, paddingMethod="Method 1") { const normalizedExpected = (expectedMac || "").replace(/\s+/g, "").toUpperCase(); if (!/^[0-9A-F]+$/.test(normalizedExpected) || normalizedExpected.length % 2 !== 0) { throw new OperationError("Expected MAC must be even-length hex."); @@ -158,7 +187,8 @@ function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, keyValue, keyFormat, ksn, - normalizedExpected.length / 2 + normalizedExpected.length / 2, + paddingMethod ); return { @@ -169,6 +199,7 @@ function verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, } export { + ISO9797_PADDING_METHODS, PAYMENT_MAC_METHODS, generatePaymentMac, verifyPaymentMac, diff --git a/src/core/lib/PaymentPinVerification.mjs b/src/core/lib/PaymentPinVerification.mjs new file mode 100644 index 00000000..77fef4a3 --- /dev/null +++ b/src/core/lib/PaymentPinVerification.mjs @@ -0,0 +1,252 @@ +/** + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; +import { bytesToHex, parseHexBytes } from "./PaymentUtils.mjs"; +import { encryptTdesEcb } from "./CardValidationInternals.mjs"; + +/** + * Normalizes a PAN string. + * + * @param {string} pan + * @returns {string} + */ +function normalizePan(pan) { + const normalized = (pan || "").replace(/\s+/g, ""); + if (!/^\d{12,19}$/.test(normalized)) { + throw new OperationError("PAN must be 12 to 19 digits."); + } + return normalized; +} + +/** + * Normalizes a clear PIN string. + * + * @param {string} pin + * @returns {string} + */ +function normalizePin(pin) { + const normalized = (pin || "").replace(/\s+/g, ""); + if (!/^\d{4,12}$/.test(normalized)) { + throw new OperationError("PIN must be 4 to 12 digits."); + } + return normalized; +} + +/** + * Converts hexadecimal characters to decimal digits via a decimalization table. + * + * @param {string} hex + * @param {string} decimalizationTable + * @returns {string} + */ +function decimalizeHex(hex, decimalizationTable) { + const normalizedTable = (decimalizationTable || "").replace(/\s+/g, ""); + if (!/^\d{16}$/.test(normalizedTable)) { + throw new OperationError("Decimalization table must be 16 decimal digits."); + } + + let out = ""; + for (const ch of hex.toUpperCase()) { + out += normalizedTable[parseInt(ch, 16)]; + } + return out; +} + +/** + * Packs a hex string into bytes. + * + * @param {string} hex + * @returns {Uint8Array} + */ +function packHex(hex) { + return parseHexBytes(hex, "Packed block"); +} + +/** + * Generates the IBM 3624 natural PIN. + * + * @param {string} pvkHex + * @param {string} decimalizationTable + * @param {string} pinValidationData + * @param {string} padCharacter + * @param {number} pinLength + * @returns {Object} + */ +function generateIbm3624NaturalPin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinLength=4) { + const normalizedValidationData = (pinValidationData || "").replace(/\s+/g, ""); + const normalizedPad = (padCharacter || "").replace(/\s+/g, "").toUpperCase(); + const normalizedPinLength = Math.max(4, Math.min(12, Number(pinLength) || 4)); + + if (!/^\d{4,16}$/.test(normalizedValidationData)) { + throw new OperationError("PIN validation data must be 4 to 16 decimal digits."); + } + if (!/^[0-9A-F]$/.test(normalizedPad)) { + throw new OperationError("PIN validation data pad character must be one hex nibble."); + } + + const pvk = parseHexBytes(pvkHex, "PIN verification key", [16, 24]); + const blockHex = normalizedValidationData.padEnd(16, normalizedPad).substring(0, 16); + const cipherHex = bytesToHex(encryptTdesEcb(pvk, packHex(blockHex))); + const decimalized = decimalizeHex(cipherHex, decimalizationTable); + + return { + pinVerificationKeyHex: bytesToHex(pvk), + pinValidationData: normalizedValidationData, + pinValidationDataPadCharacter: normalizedPad, + pinLength: normalizedPinLength, + validationBlockHex: blockHex, + encryptedValidationBlockHex: cipherHex, + decimalized, + naturalPin: decimalized.substring(0, normalizedPinLength) + }; +} + +/** + * Generates an IBM 3624 offset for a supplied clear PIN. + * + * @param {string} pvkHex + * @param {string} decimalizationTable + * @param {string} pinValidationData + * @param {string} padCharacter + * @param {string} pin + * @returns {Object} + */ +function generateIbm3624PinOffset(pvkHex, decimalizationTable, pinValidationData, padCharacter, pin) { + const normalizedPin = normalizePin(pin); + const natural = generateIbm3624NaturalPin( + pvkHex, + decimalizationTable, + pinValidationData, + padCharacter, + normalizedPin.length + ); + let offset = ""; + for (let i = 0; i < normalizedPin.length; i++) { + offset += ((parseInt(normalizedPin[i], 10) - parseInt(natural.naturalPin[i], 10) + 10) % 10).toString(); + } + return { + ...natural, + pin: normalizedPin, + pinOffset: offset + }; +} + +/** + * Verifies a clear PIN against an IBM 3624 offset. + * + * @param {string} pvkHex + * @param {string} decimalizationTable + * @param {string} pinValidationData + * @param {string} padCharacter + * @param {string} pinOffset + * @param {string} pin + * @returns {Object} + */ +function verifyIbm3624Pin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, pin) { + const normalizedOffset = (pinOffset || "").replace(/\s+/g, ""); + const normalizedPin = normalizePin(pin); + if (!/^\d{4,12}$/.test(normalizedOffset) || normalizedOffset.length !== normalizedPin.length) { + throw new OperationError("PIN offset must be 4 to 12 digits and match PIN length."); + } + + const generated = generateIbm3624PinOffset( + pvkHex, + decimalizationTable, + pinValidationData, + padCharacter, + normalizedPin + ); + + return { + ...generated, + expectedPinOffset: normalizedOffset, + valid: generated.pinOffset === normalizedOffset + }; +} + +/** + * Decimalizes a PVV candidate using the common numeric-first rule. + * + * @param {string} hex + * @returns {string} + */ +function decimalizePvv(hex) { + let out = ""; + for (const ch of hex.toUpperCase()) { + if (/\d/.test(ch)) { + out += ch; + } else { + out += String((ch.charCodeAt(0) - "A".charCodeAt(0)) % 10); + } + if (out.length >= 4) return out.substring(0, 4); + } + return out.substring(0, 4); +} + +/** + * Generates a VISA PVV. + * + * @param {string} pvkHex + * @param {string} pan + * @param {string|number} pvki + * @param {string} pin + * @returns {Object} + */ +function generateVisaPvv(pvkHex, pan, pvki, pin) { + const normalizedPan = normalizePan(pan); + const normalizedPin = normalizePin(pin); + const normalizedPvki = String(pvki ?? "").replace(/\s+/g, ""); + + if (!/^[0-6]$/.test(normalizedPvki)) { + throw new OperationError("PVKI must be a single digit from 0 to 6."); + } + + const pvk = parseHexBytes(pvkHex, "PIN verification key", [16, 24]); + const pvvInput = `${normalizedPan.slice(-12, -1)}${normalizedPvki}${normalizedPin.substring(0, 4)}`; + const encryptedHex = bytesToHex(encryptTdesEcb(pvk, packHex(pvvInput))); + const pvv = decimalizePvv(encryptedHex); + + return { + pinVerificationKeyHex: bytesToHex(pvk), + pan: normalizedPan, + pinVerificationKeyIndex: Number(normalizedPvki), + pin: normalizedPin, + pvvInput, + encryptedPvvInputHex: encryptedHex, + pvv + }; +} + +/** + * Verifies a VISA PVV. + * + * @param {string} pvkHex + * @param {string} pan + * @param {string|number} pvki + * @param {string} pin + * @param {string} expectedPvv + * @returns {Object} + */ +function verifyVisaPvv(pvkHex, pan, pvki, pin, expectedPvv) { + const normalizedExpected = (expectedPvv || "").replace(/\s+/g, ""); + if (!/^\d{4}$/.test(normalizedExpected)) { + throw new OperationError("Expected PVV must be 4 digits."); + } + + const generated = generateVisaPvv(pvkHex, pan, pvki, pin); + return { + ...generated, + expectedPvv: normalizedExpected, + valid: generated.pvv === normalizedExpected + }; +} + +export { + generateIbm3624NaturalPin, + generateIbm3624PinOffset, + generateVisaPvv, + verifyIbm3624Pin, + verifyVisaPvv, +}; diff --git a/src/core/operations/GenerateAS2805KEKValidation.mjs b/src/core/operations/GenerateAS2805KEKValidation.mjs new file mode 100644 index 00000000..cd1ec4bb --- /dev/null +++ b/src/core/operations/GenerateAS2805KEKValidation.mjs @@ -0,0 +1,108 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import CalculatePaymentKCV from "./CalculatePaymentKCV.mjs"; +import { bytesToHex, parseHexBytes } from "../lib/PaymentUtils.mjs"; + +/** + * Returns cryptographically random bytes when available. + * + * @param {number} length + * @returns {Uint8Array} + */ +function randomBytes(length) { + const out = new Uint8Array(length); + if (globalThis.crypto && globalThis.crypto.getRandomValues) { + globalThis.crypto.getRandomValues(out); + return out; + } + + for (let i = 0; i < out.length; i++) { + out[i] = Math.floor(Math.random() * 256); + } + return out; +} + +/** + * Inverts all bytes. + * + * @param {Uint8Array} bytes + * @returns {Uint8Array} + */ +function invertBytes(bytes) { + return Uint8Array.from(bytes, byte => byte ^ 0xFF); +} + +/** + * Generate AS2805 KEK validation operation. + */ +class GenerateAS2805KEKValidation extends Operation { + /** + * GenerateAS2805KEKValidation constructor. + */ + constructor() { + super(); + + this.name = "Generate AS2805 KEK Validation"; + this.module = "Payment"; + this.description = "Paste the clear sending KEK into the input field as hex and generate an AS2805 KEK validation request or response.

Input: clear KEK as 16-byte or 24-byte hex.
Arguments: choose request or response mode, select the random-key length, choose the variant mask label, and optionally provide the incoming RandomKeySend value.

Assumption: this software emulation returns RandomKeyReceive as the bytewise inverse of RandomKeySend, which is sufficient for lab testing but does not claim exact HSM-side AS2805 node-initialization behavior."; + this.inlineHelp = "Input: clear KEK hex.
Args: choose request or response mode and provide RandomKeySend for response mode."; + this.testDataSamples = [ + { + name: "AS2805 request sample", + input: "0123456789ABCDEFFEDCBA9876543210", + args: ["KekValidationRequest", "TDES_2KEY", "VARIANT_MASK_82", "", true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GenerateAs2805KekValidation.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "Validation type", type: "option", value: ["KekValidationRequest", "KekValidationResponse"], comment: "Request mode creates a fresh RandomKeySend. Response mode derives RandomKeyReceive from the supplied RandomKeySend." }, + { name: "Derive key algorithm", type: "option", value: ["TDES_2KEY", "TDES_3KEY"], comment: "Controls whether RandomKeySend / RandomKeyReceive are 16 bytes or 24 bytes long." }, + { name: "RandomKeySend variant mask", type: "option", value: ["VARIANT_MASK_82", "VARIANT_MASK_82C0"], comment: "AWS surfaces this as metadata for AS2805 KEK validation. This emulation reports the selected label but does not model HSM-side key custody." }, + { name: "RandomKeySend (response only)", type: "string", value: "", comment: "Required only in response mode. Provide the incoming RandomKeySend hex value from the partner node." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the KEK KCV and both RandomKeySend / RandomKeyReceive values." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [validationType, deriveKeyAlgorithm, randomKeySendVariantMask, randomKeySendHex, outputJson] = args; + const kek = parseHexBytes(input, "KEK", deriveKeyAlgorithm === "TDES_2KEY" ? [16] : [24]); + const randomKeyLength = deriveKeyAlgorithm === "TDES_2KEY" ? 16 : 24; + + let randomKeySend; + if (validationType === "KekValidationRequest") { + randomKeySend = randomBytes(randomKeyLength); + } else { + if (!randomKeySendHex) { + throw new OperationError("RandomKeySend is required for KEK validation response mode."); + } + randomKeySend = parseHexBytes(randomKeySendHex, "RandomKeySend", [randomKeyLength]); + } + + const randomKeyReceive = invertBytes(randomKeySend); + const kcv = new CalculatePaymentKCV().run(bytesToHex(kek), ["Hex", "TDES-ECB (Zeros)", 6]); + + const result = { + validationType, + deriveKeyAlgorithm, + randomKeySendVariantMask, + keyCheckValue: kcv, + randomKeySend: bytesToHex(randomKeySend), + randomKeyReceive: bytesToHex(randomKeyReceive) + }; + + return outputJson ? JSON.stringify(result, null, 4) : result.randomKeyReceive; + } +} + +export default GenerateAS2805KEKValidation; diff --git a/src/core/operations/GenerateEMVMAC.mjs b/src/core/operations/GenerateEMVMAC.mjs new file mode 100644 index 00000000..dd19ad3f --- /dev/null +++ b/src/core/operations/GenerateEMVMAC.mjs @@ -0,0 +1,51 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { generateEmvMac } from "../lib/EmvMac.mjs"; + +/** + * Generate EMV MAC operation. + */ +class GenerateEMVMAC extends Operation { + /** + * GenerateEMVMAC constructor. + */ + constructor() { + super(); + + this.name = "Generate EMV MAC"; + this.module = "Payment"; + this.description = "Paste the issuer-script or EMV command payload into the input field as hex and generate an EMV MAC.

Input: message data as hex.
Arguments: provide the already-derived EMV session integrity key and choose how many leftmost MAC bytes to return.

Assumption: this operation expects the EMV session key to have been derived outside the operation and applies ISO9797-3 retail MAC with ISO9797 padding method 2."; + this.inlineHelp = "Input: issuer-script message data as hex.
Args: provide the derived EMV session integrity key."; + this.testDataSamples = [ + { + name: "EMV MAC sample", + input: "8424000008999E57FD0F47CACE0007", + args: ["0123456789ABCDEFFEDCBA9876543210", 8, false] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvmac.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV integrity session key in hex. This op does not derive EMV keys for you." }, + { name: "Output bytes", type: "number", value: 8, min: 1, max: 8, comment: "Number of leftmost MAC bytes to return. EMV issuer scripts commonly use 8 bytes." }, + { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the issuer-script input and full retail-MAC details." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [sessionKeyHex, outputBytes, outputJson] = args; + const result = generateEmvMac(input, sessionKeyHex, outputBytes); + return outputJson ? JSON.stringify(result, null, 4) : result.macHex; + } +} + +export default GenerateEMVMAC; diff --git a/src/core/operations/GenerateEMVMACForPINChange.mjs b/src/core/operations/GenerateEMVMACForPINChange.mjs new file mode 100644 index 00000000..55ea7ff2 --- /dev/null +++ b/src/core/operations/GenerateEMVMACForPINChange.mjs @@ -0,0 +1,52 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { generateEmvPinChangeMac } from "../lib/EmvMac.mjs"; + +/** + * Generate EMV MAC for PIN change operation. + */ +class GenerateEMVMACForPINChange extends Operation { + /** + * GenerateEMVMACForPINChange constructor. + */ + constructor() { + super(); + + this.name = "Generate EMV MAC For PIN Change"; + this.module = "Payment"; + this.description = "Paste the issuer-script APDU command into the input field as hex and generate the MAC for an offline EMV PIN-change script.

Input: issuer-script message data as hex.
Arguments: provide the already-encrypted target PIN block in hex and the already-derived EMV session integrity key.

Assumptions: the new PIN block has already been encrypted before calling this operation, and this op appends that encrypted PIN block to the message before applying EMV retail MAC generation."; + this.inlineHelp = "Input: issuer-script APDU message as hex.
Args: provide the encrypted target PIN block and derived EMV integrity key."; + this.testDataSamples = [ + { + name: "EMV PIN change MAC sample", + input: "00A4040008A000000004101080D80500000001010A04000000000000", + args: ["67FB27C75580EFE7", "0123456789ABCDEFFEDCBA9876543210", 8, false] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvpinchange.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "New encrypted PIN block (hex)", type: "string", value: "", comment: "Provide the already-encrypted new PIN block that will be appended to the issuer-script message." }, + { name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV session integrity key in hex. This emulation does not derive EMV keys or encrypt the PIN block for you." }, + { name: "Output bytes", type: "number", value: 8, min: 1, max: 8, comment: "Number of leftmost MAC bytes to return. EMV issuer scripts commonly use 8 bytes." }, + { name: "Output as JSON", type: "boolean", value: false, comment: "When enabled, returns the composed issuer-script message and the computed MAC." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [encryptedPinBlockHex, sessionKeyHex, outputBytes, outputJson] = args; + const result = generateEmvPinChangeMac(input, encryptedPinBlockHex, sessionKeyHex, outputBytes); + return outputJson ? JSON.stringify(result, null, 4) : result.macHex; + } +} + +export default GenerateEMVMACForPINChange; diff --git a/src/core/operations/GenerateIBM3624PINOffset.mjs b/src/core/operations/GenerateIBM3624PINOffset.mjs new file mode 100644 index 00000000..22d068e7 --- /dev/null +++ b/src/core/operations/GenerateIBM3624PINOffset.mjs @@ -0,0 +1,53 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { generateIbm3624PinOffset } from "../lib/PaymentPinVerification.mjs"; + +/** + * Generate IBM 3624 PIN offset operation. + */ +class GenerateIBM3624PINOffset extends Operation { + /** + * GenerateIBM3624PINOffset constructor. + */ + constructor() { + super(); + + this.name = "Generate IBM 3624 PIN Offset"; + this.module = "Payment"; + this.description = "Paste the clear PIN into the input field and generate the IBM 3624 offset used by issuer-side PIN verification.

Input: clear PIN digits.
Arguments: provide the clear PVK in hex, decimalization table, validation data, and pad character.

Assumption: this is a clear-key software emulation of the IBM 3624 offset algorithm for test harnesses."; + this.inlineHelp = "Input: clear PIN digits.
Args: provide PVK, decimalization table, validation data, and pad character."; + this.testDataSamples = [ + { + name: "IBM 3624 offset sample", + input: "1234", + args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/generate-ibm3624.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear IBM 3624 PVK as 16-byte or 24-byte hex." }, + { name: "Decimalization table", type: "string", value: "0123456789012345", comment: "Sixteen decimal digits used to map hex nibbles to decimal digits." }, + { name: "PIN validation data", type: "string", value: "", comment: "Issuer validation data, typically PAN-derived digits, 4 to 16 digits." }, + { name: "Pad character", type: "shortString", value: "F", comment: "Single hex nibble used to right-pad validation data to 16 nibbles." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the intermediate natural PIN and validation-block details." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pvkHex, decimalizationTable, pinValidationData, padCharacter, outputJson] = args; + const result = generateIbm3624PinOffset(pvkHex, decimalizationTable, pinValidationData, padCharacter, input); + return outputJson ? JSON.stringify(result, null, 4) : result.pinOffset; + } +} + +export default GenerateIBM3624PINOffset; diff --git a/src/core/operations/GeneratePaymentMAC.mjs b/src/core/operations/GeneratePaymentMAC.mjs index 3f192fa1..8260538d 100644 --- a/src/core/operations/GeneratePaymentMAC.mjs +++ b/src/core/operations/GeneratePaymentMAC.mjs @@ -3,7 +3,7 @@ */ import Operation from "../Operation.mjs"; -import { PAYMENT_MAC_METHODS, generatePaymentMac } from "../lib/PaymentMac.mjs"; +import { ISO9797_PADDING_METHODS, PAYMENT_MAC_METHODS, generatePaymentMac } from "../lib/PaymentMac.mjs"; /** * Generate payment MAC operation. @@ -18,13 +18,13 @@ class GeneratePaymentMAC extends Operation { this.name = "Generate Payment MAC"; this.module = "Payment"; - this.description = "Paste the message data into the input field and generate a payment-oriented MAC using one payment-facing operation.

Input: message data in the selected input format.
Arguments: choose the MAC method, provide either a direct key or a DUKPT BDK, optionally provide a KSN for DUKPT methods, and choose the truncation length.

This wrapper reuses existing HMAC, CMAC, and DUKPT operations instead of duplicating their crypto logic."; + this.description = "Paste the message data into the input field and generate a payment-oriented MAC using one payment-facing operation.

Input: message data in the selected input format.
Arguments: choose the MAC method, provide either a direct key or a DUKPT BDK, optionally provide a KSN for DUKPT methods, choose the ISO9797 padding rule when applicable, and choose the truncation length.

This wrapper reuses existing HMAC and CMAC primitives where possible and adds payment-specific ISO9797 / AS2805 modes for software testing."; this.inlineHelp = "Input: message data.
Args: choose the payment MAC method, then provide either a direct key or a DUKPT BDK plus KSN."; this.testDataSamples = [ { name: "Static AES-CMAC sample", input: "1122334455667788", - args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false] + args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false] } ]; this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_GenerateMac.html"; @@ -41,7 +41,7 @@ class GeneratePaymentMAC extends Operation { name: "MAC method", type: "option", value: PAYMENT_MAC_METHODS, - comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. DUKPT modes derive a TDES session key first and then apply TDES-CMAC." + comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. ISO9797 and AS2805 modes apply TDES-based payment MAC logic. DUKPT modes derive a TDES session key first." }, { name: "Key / BDK", @@ -61,6 +61,12 @@ class GeneratePaymentMAC extends Operation { value: "", comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters." }, + { + name: "ISO9797 padding", + type: "option", + value: ISO9797_PADDING_METHODS, + comment: "Used only for ISO9797 and AS2805 MAC methods. Method 1 pads with zero bytes to the next block. Method 2 appends 80 then zeros." + }, { name: "Output bytes", type: "number", @@ -84,8 +90,8 @@ class GeneratePaymentMAC extends Operation { * @returns {string} */ run(input, args) { - const [inputFormat, method, keyValue, keyFormat, ksn, outputBytes, outputJson] = args; - const result = generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes); + const [inputFormat, method, keyValue, keyFormat, ksn, paddingMethod, outputBytes, outputJson] = args; + const result = generatePaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, outputBytes, paddingMethod); return outputJson ? JSON.stringify(result, null, 4) : result.macHex; } } diff --git a/src/core/operations/GenerateVISAPVV.mjs b/src/core/operations/GenerateVISAPVV.mjs new file mode 100644 index 00000000..aa930942 --- /dev/null +++ b/src/core/operations/GenerateVISAPVV.mjs @@ -0,0 +1,52 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { generateVisaPvv } from "../lib/PaymentPinVerification.mjs"; + +/** + * Generate VISA PVV operation. + */ +class GenerateVISAPVV extends Operation { + /** + * GenerateVISAPVV constructor. + */ + constructor() { + super(); + + this.name = "Generate VISA PVV"; + this.module = "Payment"; + this.description = "Paste the clear PIN into the input field and generate a VISA PIN Verification Value (PVV).

Input: clear PIN digits.
Arguments: provide the clear PVK in hex, PAN, and PVKI.

Assumption: this is a clear-key software emulation of the common VISA PVV generation flow for test harnesses."; + this.inlineHelp = "Input: clear PIN digits.
Args: provide PVK, PAN, and PVKI."; + this.testDataSamples = [ + { + name: "VISA PVV sample", + input: "1234", + args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VisaPinVerification.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear VISA PVK as 16-byte or 24-byte hex." }, + { name: "Primary account number", type: "string", value: "", comment: "Provide the PAN as digits only. The standard PVV input uses the rightmost 11 digits before the check digit." }, + { name: "PVKI", type: "number", value: 1, min: 0, max: 6, comment: "PIN verification key index from 0 through 6." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the assembled PVV input and intermediate encrypted block." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pvkHex, pan, pvki, outputJson] = args; + const result = generateVisaPvv(pvkHex, pan, pvki, input); + return outputJson ? JSON.stringify(result, null, 4) : result.pvv; + } +} + +export default GenerateVISAPVV; diff --git a/src/core/operations/VerifyEMVMAC.mjs b/src/core/operations/VerifyEMVMAC.mjs new file mode 100644 index 00000000..4df59920 --- /dev/null +++ b/src/core/operations/VerifyEMVMAC.mjs @@ -0,0 +1,51 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { verifyEmvMac } from "../lib/EmvMac.mjs"; + +/** + * Verify EMV MAC operation. + */ +class VerifyEMVMAC extends Operation { + /** + * VerifyEMVMAC constructor. + */ + constructor() { + super(); + + this.name = "Verify EMV MAC"; + this.module = "Payment"; + this.description = "Paste the issuer-script or EMV command payload into the input field as hex and verify an EMV MAC.

Input: message data as hex.
Arguments: provide the already-derived EMV session integrity key and the expected MAC as hex.

Assumption: this operation expects the EMV session key to have been derived outside the operation and applies ISO9797-3 retail MAC with ISO9797 padding method 2."; + this.inlineHelp = "Input: issuer-script message data as hex.
Args: provide the derived EMV session key and expected MAC."; + this.testDataSamples = [ + { + name: "EMV MAC verification sample", + input: "8424000008999E57FD0F47CACE0007", + args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/use-cases-issuers.generalfunctions.emvmac.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "Session integrity key (hex)", type: "string", value: "", comment: "Provide the already-derived EMV integrity session key in hex. This op does not derive EMV keys for you." }, + { name: "Expected MAC (hex)", type: "string", value: "", comment: "Issuer-script MAC to compare against, expressed as even-length hex." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the recomputed MAC and validity result." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [sessionKeyHex, expectedMac, outputJson] = args; + const result = verifyEmvMac(input, sessionKeyHex, expectedMac); + return outputJson ? JSON.stringify(result, null, 4) : String(result.valid); + } +} + +export default VerifyEMVMAC; diff --git a/src/core/operations/VerifyIBM3624PIN.mjs b/src/core/operations/VerifyIBM3624PIN.mjs new file mode 100644 index 00000000..8b687e17 --- /dev/null +++ b/src/core/operations/VerifyIBM3624PIN.mjs @@ -0,0 +1,54 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { verifyIbm3624Pin } from "../lib/PaymentPinVerification.mjs"; + +/** + * Verify IBM 3624 PIN operation. + */ +class VerifyIBM3624PIN extends Operation { + /** + * VerifyIBM3624PIN constructor. + */ + constructor() { + super(); + + this.name = "Verify IBM 3624 PIN"; + this.module = "Payment"; + this.description = "Paste the clear PIN into the input field and verify it against an IBM 3624 offset.

Input: clear PIN digits.
Arguments: provide the clear PVK in hex, decimalization table, validation data, pad character, and expected offset.

Assumption: this is a clear-key software emulation of the IBM 3624 offset verification flow."; + this.inlineHelp = "Input: clear PIN digits.
Args: provide PVK, decimalization table, validation data, pad character, and expected offset."; + this.testDataSamples = [ + { + name: "IBM 3624 verify sample", + input: "1234", + args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", "3207", true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/userguide/verify-pin-data.ibm3624-example.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear IBM 3624 PVK as 16-byte or 24-byte hex." }, + { name: "Decimalization table", type: "string", value: "0123456789012345", comment: "Sixteen decimal digits used to map hex nibbles to decimal digits." }, + { name: "PIN validation data", type: "string", value: "", comment: "Issuer validation data, typically PAN-derived digits, 4 to 16 digits." }, + { name: "Pad character", type: "shortString", value: "F", comment: "Single hex nibble used to right-pad validation data to 16 nibbles." }, + { name: "PIN offset", type: "string", value: "", comment: "Stored IBM 3624 offset value to compare against." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the recomputed offset and validity result." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, outputJson] = args; + const result = verifyIbm3624Pin(pvkHex, decimalizationTable, pinValidationData, padCharacter, pinOffset, input); + return outputJson ? JSON.stringify(result, null, 4) : String(result.valid); + } +} + +export default VerifyIBM3624PIN; diff --git a/src/core/operations/VerifyPaymentMAC.mjs b/src/core/operations/VerifyPaymentMAC.mjs index 666eed30..26232041 100644 --- a/src/core/operations/VerifyPaymentMAC.mjs +++ b/src/core/operations/VerifyPaymentMAC.mjs @@ -3,7 +3,7 @@ */ import Operation from "../Operation.mjs"; -import { PAYMENT_MAC_METHODS, verifyPaymentMac } from "../lib/PaymentMac.mjs"; +import { ISO9797_PADDING_METHODS, PAYMENT_MAC_METHODS, verifyPaymentMac } from "../lib/PaymentMac.mjs"; /** * Verify payment MAC operation. @@ -18,13 +18,13 @@ class VerifyPaymentMAC extends Operation { this.name = "Verify Payment MAC"; this.module = "Payment"; - this.description = "Paste the message data into the input field and verify a payment-oriented MAC using one payment-facing operation.

Input: message data in the selected input format.
Arguments: choose the MAC method, provide either a direct key or a DUKPT BDK, add the KSN for DUKPT methods, and supply the expected MAC as hex.

This wrapper recomputes the MAC using the same payment-specific assumptions as the generate operation."; + this.description = "Paste the message data into the input field and verify a payment-oriented MAC using one payment-facing operation.

Input: message data in the selected input format.
Arguments: choose the MAC method, provide either a direct key or a DUKPT BDK, add the KSN for DUKPT methods, choose the ISO9797 padding rule when applicable, and supply the expected MAC as hex.

This wrapper recomputes the MAC using the same payment-specific assumptions as the generate operation."; this.inlineHelp = "Input: message data.
Args: choose the payment MAC method, provide the key context, then paste the expected MAC."; this.testDataSamples = [ { name: "Static AES-CMAC verification sample", input: "1122334455667788", - args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "339AF1AD1650E908", true] + args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", "339AF1AD1650E908", true] } ]; this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VerifyMac.html"; @@ -41,7 +41,7 @@ class VerifyPaymentMAC extends Operation { name: "MAC method", type: "option", value: PAYMENT_MAC_METHODS, - comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. DUKPT modes derive a TDES session key first and then apply TDES-CMAC." + comment: "Static-key HMAC and CMAC modes reuse the existing generic primitives. ISO9797 and AS2805 modes apply TDES-based payment MAC logic. DUKPT modes derive a TDES session key first." }, { name: "Key / BDK", @@ -61,6 +61,12 @@ class VerifyPaymentMAC extends Operation { value: "", comment: "Required only for DUKPT MAC methods. Provide the full 10-byte KSN as 20 hex characters." }, + { + name: "ISO9797 padding", + type: "option", + value: ISO9797_PADDING_METHODS, + comment: "Used only for ISO9797 and AS2805 MAC methods. Keep this aligned with the sender." + }, { name: "Expected MAC (hex)", type: "string", @@ -82,8 +88,8 @@ class VerifyPaymentMAC extends Operation { * @returns {string} */ run(input, args) { - const [inputFormat, method, keyValue, keyFormat, ksn, expectedMac, outputJson] = args; - const result = verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac); + const [inputFormat, method, keyValue, keyFormat, ksn, paddingMethod, expectedMac, outputJson] = args; + const result = verifyPaymentMac(input, inputFormat, method, keyValue, keyFormat, ksn, expectedMac, paddingMethod); return outputJson ? JSON.stringify(result, null, 4) : String(result.valid); } } diff --git a/src/core/operations/VerifyVISAPVV.mjs b/src/core/operations/VerifyVISAPVV.mjs new file mode 100644 index 00000000..e06d8a92 --- /dev/null +++ b/src/core/operations/VerifyVISAPVV.mjs @@ -0,0 +1,53 @@ +/** + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { verifyVisaPvv } from "../lib/PaymentPinVerification.mjs"; + +/** + * Verify VISA PVV operation. + */ +class VerifyVISAPVV extends Operation { + /** + * VerifyVISAPVV constructor. + */ + constructor() { + super(); + + this.name = "Verify VISA PVV"; + this.module = "Payment"; + this.description = "Paste the clear PIN into the input field and verify it against a VISA PVV.

Input: clear PIN digits.
Arguments: provide the clear PVK in hex, PAN, PVKI, and expected PVV.

Assumption: this is a clear-key software emulation of the common VISA PVV verification flow for test harnesses."; + this.inlineHelp = "Input: clear PIN digits.
Args: provide PVK, PAN, PVKI, and expected PVV."; + this.testDataSamples = [ + { + name: "VISA PVV verify sample", + input: "1234", + args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "6077", true] + } + ]; + this.infoURL = "https://docs.aws.amazon.com/payment-cryptography/latest/DataAPIReference/API_VisaPinVerificationValue.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { name: "PIN verification key (hex)", type: "string", value: "", comment: "Provide the clear VISA PVK as 16-byte or 24-byte hex." }, + { name: "Primary account number", type: "string", value: "", comment: "Provide the PAN as digits only. The standard PVV input uses the rightmost 11 digits before the check digit." }, + { name: "PVKI", type: "number", value: 1, min: 0, max: 6, comment: "PIN verification key index from 0 through 6." }, + { name: "Expected PVV", type: "string", value: "", comment: "Stored PVV value to compare against." }, + { name: "Output as JSON", type: "boolean", value: true, comment: "When enabled, returns the assembled PVV input and validity result." }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pvkHex, pan, pvki, expectedPvv, outputJson] = args; + const result = verifyVisaPvv(pvkHex, pan, pvki, input, expectedPvv); + return outputJson ? JSON.stringify(result, null, 4) : String(result.valid); + } +} + +export default VerifyVISAPVV; diff --git a/tests/operations/tests/Payment.mjs b/tests/operations/tests/Payment.mjs index 4fb621e1..3edb6846 100644 --- a/tests/operations/tests/Payment.mjs +++ b/tests/operations/tests/Payment.mjs @@ -313,7 +313,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Generate Payment MAC", - args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false] + args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false] } ] }, @@ -324,7 +324,7 @@ TestRegister.addTests([ recipeConfig: [ { op: "Generate Payment MAC", - args: ["Hex", "HMAC SHA-256", "00112233445566778899AABBCCDDEEFF", "Hex", "", 8, false] + args: ["Hex", "HMAC SHA-256", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", 8, false] } ] }, @@ -335,7 +335,40 @@ TestRegister.addTests([ recipeConfig: [ { op: "Generate Payment MAC", - args: ["Hex", "DUKPT MAC Request CMAC", "0123456789ABCDEFFEDCBA9876543210", "Hex", "FFFF9876543210E00008", 8, false] + args: ["Hex", "DUKPT MAC Request CMAC", "0123456789ABCDEFFEDCBA9876543210", "Hex", "FFFF9876543210E00008", "Method 1", 8, false] + } + ] + }, + { + name: "Generate Payment MAC: ISO 9797-1 Algorithm 1", + input: "1122334455667788", + expectedOutput: "0C949BCDEF6FDF1D", + recipeConfig: [ + { + op: "Generate Payment MAC", + args: ["Hex", "ISO 9797-1 Algorithm 1", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 1", 8, false] + } + ] + }, + { + name: "Generate Payment MAC: ISO 9797-1 Algorithm 3", + input: "1122334455667788", + expectedOutput: "7E2AEA5CF35FDC0E", + recipeConfig: [ + { + op: "Generate Payment MAC", + args: ["Hex", "ISO 9797-1 Algorithm 3", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 2", 8, false] + } + ] + }, + { + name: "Generate Payment MAC: AS2805-4.1", + input: "1122334455667788", + expectedOutput: "3EB3B72576BBBE83", + recipeConfig: [ + { + op: "Generate Payment MAC", + args: ["Hex", "AS2805-4.1", "0123456789ABCDEFFEDCBA9876543210", "Hex", "", "Method 1", 8, false] } ] }, @@ -346,6 +379,7 @@ TestRegister.addTests([ method: "AES-CMAC", inputFormat: "Hex", inputHex: "1122334455667788", + paddingMethod: null, outputBytes: 8, fullMacHex: "339AF1AD1650E908A794284D91DC6D29", macHex: "339AF1AD1650E908", @@ -356,7 +390,48 @@ TestRegister.addTests([ recipeConfig: [ { op: "Verify Payment MAC", - args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "339AF1AD1650E908", true] + args: ["Hex", "AES-CMAC", "00112233445566778899AABBCCDDEEFF", "Hex", "", "Method 1", "339AF1AD1650E908", true] + } + ] + }, + { + name: "Generate EMV MAC: issuer script sample", + input: "8424000008999E57FD0F47CACE0007", + expectedOutput: "22CB48394DFD1977", + recipeConfig: [ + { + op: "Generate EMV MAC", + args: ["0123456789ABCDEFFEDCBA9876543210", 8, false] + } + ] + }, + { + name: "Verify EMV MAC: issuer script sample", + input: "8424000008999E57FD0F47CACE0007", + expectedOutput: JSON.stringify({ + algorithm: "EMV MAC", + paddingMethod: "Method 2", + inputHex: "8424000008999E57FD0F47CACE0007", + fullMacHex: "22CB48394DFD1977", + macHex: "22CB48394DFD1977", + expectedMacHex: "22CB48394DFD1977", + valid: true + }, null, 4), + recipeConfig: [ + { + op: "Verify EMV MAC", + args: ["0123456789ABCDEFFEDCBA9876543210", "22CB48394DFD1977", true] + } + ] + }, + { + name: "Generate EMV MAC For PIN Change: issuer script sample", + input: "00A4040008A000000004101080D80500000001010A04000000000000", + expectedOutput: "C0F24786EF1C4522", + recipeConfig: [ + { + op: "Generate EMV MAC For PIN Change", + args: ["67FB27C75580EFE7", "0123456789ABCDEFFEDCBA9876543210", 8, false] } ] }, @@ -396,6 +471,110 @@ TestRegister.addTests([ } ] }, + { + name: "Generate IBM 3624 PIN Offset: known sample", + input: "1234", + expectedOutput: JSON.stringify({ + pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210", + pinValidationData: "5432101234567890", + pinValidationDataPadCharacter: "F", + pinLength: 4, + validationBlockHex: "5432101234567890", + encryptedValidationBlockHex: "8A3712EE04F010A0", + decimalized: "8037124404501000", + naturalPin: "8037", + pin: "1234", + pinOffset: "3207" + }, null, 4), + recipeConfig: [ + { + op: "Generate IBM 3624 PIN Offset", + args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", true] + } + ] + }, + { + name: "Verify IBM 3624 PIN: known sample", + input: "1234", + expectedOutput: JSON.stringify({ + pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210", + pinValidationData: "5432101234567890", + pinValidationDataPadCharacter: "F", + pinLength: 4, + validationBlockHex: "5432101234567890", + encryptedValidationBlockHex: "8A3712EE04F010A0", + decimalized: "8037124404501000", + naturalPin: "8037", + pin: "1234", + pinOffset: "3207", + expectedPinOffset: "3207", + valid: true + }, null, 4), + recipeConfig: [ + { + op: "Verify IBM 3624 PIN", + args: ["0123456789ABCDEFFEDCBA9876543210", "0123456789012345", "5432101234567890", "F", "3207", true] + } + ] + }, + { + name: "Generate VISA PVV: known sample", + input: "1234", + expectedOutput: JSON.stringify({ + pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210", + pan: "5432101234567890", + pinVerificationKeyIndex: 1, + pin: "1234", + pvvInput: "1012345678911234", + encryptedPvvInputHex: "6A77E65CFE349D60", + pvv: "6077" + }, null, 4), + recipeConfig: [ + { + op: "Generate VISA PVV", + args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, true] + } + ] + }, + { + name: "Verify VISA PVV: known sample", + input: "1234", + expectedOutput: JSON.stringify({ + pinVerificationKeyHex: "0123456789ABCDEFFEDCBA9876543210", + pan: "5432101234567890", + pinVerificationKeyIndex: 1, + pin: "1234", + pvvInput: "1012345678911234", + encryptedPvvInputHex: "6A77E65CFE349D60", + pvv: "6077", + expectedPvv: "6077", + valid: true + }, null, 4), + recipeConfig: [ + { + op: "Verify VISA PVV", + args: ["0123456789ABCDEFFEDCBA9876543210", "5432101234567890", 1, "6077", true] + } + ] + }, + { + name: "Generate AS2805 KEK Validation: response sample", + input: "0123456789ABCDEFFEDCBA9876543210", + expectedOutput: JSON.stringify({ + validationType: "KekValidationResponse", + deriveKeyAlgorithm: "TDES_2KEY", + randomKeySendVariantMask: "VARIANT_MASK_82", + keyCheckValue: "08D7B4", + randomKeySend: "9217DC67B8763BABCFDF3DADFCD0F84A", + randomKeyReceive: "6DE823984789C4543020C252032F07B5" + }, null, 4), + recipeConfig: [ + { + op: "Generate AS2805 KEK Validation", + args: ["KekValidationResponse", "TDES_2KEY", "VARIANT_MASK_82", "9217DC67B8763BABCFDF3DADFCD0F84A", true] + } + ] + }, { name: "Verify Payment PIN Data: ISO Format 0", input: "041215FEDCBA9876",