From 2290bd7ab6524ba6502565c6609152418e81bda6 Mon Sep 17 00:00:00 2001 From: ohernandovisa Date: Thu, 13 Mar 2025 10:09:07 -0500 Subject: [PATCH] added support for MLE --- README.md | 6 +- .../client/default/custom/flexMicroform.js | 20 ++- .../cartridge/apiClient/ApiClient.js | 71 ++++---- .../apiClient/api/PayerAuthenticationApi.js | 9 +- .../apiClient/api/PaymentInstrumentApi.js | 2 +- .../cartridge/apiClient/api/PaymentsApi.js | 6 +- .../cartridge/apiClient/constants.js | 2 +- .../cartridge/configuration/index.js | 11 +- .../preferences/customPreferences.js | 41 +++++ .../payments_credit_form_processor.js | 14 +- .../scripts/mleEncrypt/aesgcmCustom.js | 151 ++++++++++++++++++ .../scripts/mleEncrypt/jweEncrypt.js | 77 +++++++++ documentation/markdown/Configure-features.md | 34 +++- documentation/markdown/Release-notes.md | 4 + .../payments_metadata/meta/FlexMicroform.xml | 16 ++ metadata/payments_metadata/meta/MLE.xml | 34 ++++ metadata/payments_metadata/meta/merged.xml | 41 +++++ 17 files changed, 481 insertions(+), 58 deletions(-) create mode 100644 cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/aesgcmCustom.js create mode 100644 cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/jweEncrypt.js create mode 100644 metadata/payments_metadata/meta/MLE.xml diff --git a/README.md b/README.md index ec6cc36..f44f8d7 100755 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ * **Description:** Cybersource, a Visa solution, is the only global, modular payment management platform built on secure Visa infrastructure with the payment reach and fraud insights of a massive $500B+ global processing network. You can find out more about what Cybersource does [here](https://www.cybersource.com/en-gb.html) * **Categories:** Payment Processing, Fraud Detection, Address Validation, Tax Computation -* **Version:** 25.1.0 -* **Last Certification Date:** January 2025 +* **Version:** 25.2.0 +* **Last Certification Date:** March 2025 * **Supports SFRA v7.0** * **JavaScript Controllers Friendly:** **YES** ### Contact ### -* +* ---- diff --git a/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js b/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js index d39794c..a1ad9bd 100644 --- a/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js +++ b/cartridges/int_cybs_sfra/cartridge/client/default/custom/flexMicroform.js @@ -3,7 +3,7 @@ 'use strict'; $(document).ready(function () { - var captureContext = $('#flexTokenResponse').val(); + var captureContext = JSON.parse($('#flexTokenResponse').val()).keyId; var flex = new Flex(captureContext); // eslint-disable-line no-undef var customStyles = { input: { @@ -25,7 +25,7 @@ $(document).ready(function () { color: '#a94442' } }; - var microform = flex.microform("card",{ + var microform = flex.microform({ styles: customStyles }); var number = microform.createField('number'); @@ -107,7 +107,7 @@ $(document).ready(function () { var decodedJwt = parseJwt(response); document.getElementById('cardNumber').valid = true; $('#flex-response').val(response); - $('#cardNumber').val(decodedJwt.content.paymentInformation.card.number.maskedValue); + $('#cardNumber').val(decodedJwt.data.number); if ($('.submit-payment').length === 1) { $('.submit-payment').trigger('click'); @@ -137,7 +137,7 @@ $(document).ready(function () { case 'discover': correctCardType = 'Discover'; break; - case 'dinersclub': + case 'diners-club': correctCardType = 'DinersClub'; break; case 'maestro': @@ -146,6 +146,18 @@ $(document).ready(function () { case 'jcb': correctCardType = 'JCB'; break; + case "cartesbancaires": + correctCardType = "CartesBancaires"; + break; + case "elo": + correctCardType = "Elo"; + break; + case "cup": + correctCardType = "China UnionPay"; + break; + case "jcrew": + correctCardType = "JCrew"; + break; } $('#cardType').val(correctCardType); } diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/ApiClient.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/ApiClient.js index 9ef24af..cdcdb26 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/ApiClient.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/ApiClient.js @@ -3,40 +3,40 @@ var Bytes = require('dw/util/Bytes'); var Encoding = require('dw/crypto/Encoding'); var Mac = require('dw/crypto/Mac'); var MessageDigest = require('dw/crypto/MessageDigest'); - +var configObject = require('*/cartridge/configuration/index'); var MerchantConfig = require('./merchantConfig'); var Logger = require('./logger'); -var _exports = function () {} - -_exports.prototype.createService = function (){ - var PaymentsHttpService = dw.svc.LocalServiceRegistry.createService("PaymentHttpService", { - createRequest: function (svc, url, headers, method, requestBody) { - var keys = Object.keys(headers); - var StringHeaders = ""; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - svc.addHeader(key, headers[key]); - StringHeaders += key + ":" + headers[key] + "\n"; - } - svc.URL = url; - svc.setRequestMethod(method.toUpperCase()); - if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PATCH') { - if (typeof requestBody === 'string') { - return requestBody; +var _exports = function () { } + +_exports.prototype.createService = function () { + var PaymentsHttpService = dw.svc.LocalServiceRegistry.createService("PaymentHttpService", { + createRequest: function (svc, url, headers, method, requestBody) { + var keys = Object.keys(headers); + var StringHeaders = ""; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + svc.addHeader(key, headers[key]); + StringHeaders += key + ":" + headers[key] + "\n"; + } + svc.URL = url; + svc.setRequestMethod(method.toUpperCase()); + if (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PATCH') { + if (typeof requestBody === 'string') { + return requestBody; + } + return JSON.stringify(requestBody); } - return JSON.stringify(requestBody); + }, + parseResponse: function (svc, client) { + return client.text; + }, + filterLogMessage: function (msg) { + // No need to filter logs. No sensitive information. + return msg; } - }, - parseResponse: function (svc, client) { - return client.text; - }, - filterLogMessage: function (msg) { - // No need to filter logs. No sensitive information. - return msg; - } -}); - return PaymentsHttpService; + }); + return PaymentsHttpService; }; _exports.prototype.setConfiguration = function (configObject) { @@ -183,7 +183,7 @@ _exports.prototype.normalizeParams = function (params) { return newParams; } -_exports.prototype.callApi = function (path, httpMethod, pathParams, queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts, returnType, callback) { +_exports.prototype.callApi = function (path, httpMethod, pathParams, queryParams, headerParams, formParams, bodyParam, authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi) { var requestHost = this.basePath.substr( this.basePath.indexOf("//") + 2 ); @@ -219,6 +219,13 @@ _exports.prototype.callApi = function (path, httpMethod, pathParams, queryParams } payload = JSON.stringify(bodyParam); + var isMLEEnabled = configObject.mleEnabled; + + if (isMLEEnabled && isMLESupportedByCybsForApi == true) { + var encryptPayload = require('*/cartridge/scripts/mleEncrypt/jweEncrypt.js'); + payload = encryptPayload.getJWE(payload); + + } var signature = this.getHttpSignature(resource, method, merchantKeyId, requestHost, merchantId, merchantSecretKey, payload); var digest = this.generateDigest(payload); digest = "SHA-256=" + digest; @@ -247,9 +254,9 @@ _exports.prototype.callApi = function (path, httpMethod, pathParams, queryParams if (response.ok) { var responseObj = response.object; - if(path === '/microform/v2/sessions'){ + if (path === '/microform/v2/sessions') { callback(responseObj, false, response); - }else{ + } else { callback(JSON.parse(responseObj), false, response); } } else { diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PayerAuthenticationApi.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PayerAuthenticationApi.js index 7eb7a4b..41df0e0 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PayerAuthenticationApi.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PayerAuthenticationApi.js @@ -86,11 +86,12 @@ var contentTypes = ['application/json;charset=utf-8']; var accepts = ['application/hal+json;charset=utf-8']; var returnType = RiskV1AuthenticationsPost201Response; + var isMLESupportedByCybsForApi = true; return this.apiClient.callApi( '/risk/v1/authentications', 'POST', pathParams, queryParams, headerParams, formParams, postBody, - authNames, contentTypes, accepts, returnType, callback + authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi ); } @@ -131,11 +132,12 @@ var contentTypes = ['application/json;charset=utf-8']; var accepts = ['application/hal+json;charset=utf-8']; var returnType = RiskV1AuthenticationSetupsPost201Response; + var isMLESupportedByCybsForApi = true; return this.apiClient.callApi( '/risk/v1/authentication-setups', 'POST', pathParams, queryParams, headerParams, formParams, postBody, - authNames, contentTypes, accepts, returnType, callback + authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi ); } @@ -176,11 +178,12 @@ var contentTypes = ['application/json;charset=utf-8']; var accepts = ['application/hal+json;charset=utf-8']; var returnType = RiskV1AuthenticationResultsPost201Response; + var isMLESupportedByCybsForApi = true; return this.apiClient.callApi( '/risk/v1/authentication-results', 'POST', pathParams, queryParams, headerParams, formParams, postBody, - authNames, contentTypes, accepts, returnType, callback + authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi ); } }; diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentInstrumentApi.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentInstrumentApi.js index 77f858a..f38ad9e 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentInstrumentApi.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentInstrumentApi.js @@ -254,4 +254,4 @@ }; return exports; -})); +})); \ No newline at end of file diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentsApi.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentsApi.js index 693f6e3..a053039 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentsApi.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/api/PaymentsApi.js @@ -86,11 +86,12 @@ var contentTypes = ['application/json;charset=utf-8']; var accepts = ['application/hal+json;charset=utf-8']; var returnType = PtsV2PaymentsPost201Response; + var isMLESupportedByCybsForApi = true; return this.apiClient.callApi( '/pts/v2/payments', 'POST', pathParams, queryParams, headerParams, formParams, postBody, - authNames, contentTypes, accepts, returnType, callback + authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi ); } @@ -138,11 +139,12 @@ var contentTypes = ['application/json;charset=utf-8']; var accepts = ['application/hal+json;charset=utf-8']; var returnType = PtsV2IncrementalAuthorizationPatch201Response; + var isMLESupportedByCybsForApi = true; return this.apiClient.callApi( '/pts/v2/payments/{id}', 'PATCH', pathParams, queryParams, headerParams, formParams, postBody, - authNames, contentTypes, accepts, returnType, callback + authNames, contentTypes, accepts, returnType, callback, isMLESupportedByCybsForApi ); } }; diff --git a/cartridges/int_cybs_sfra_base/cartridge/apiClient/constants.js b/cartridges/int_cybs_sfra_base/cartridge/apiClient/constants.js index 315c2e6..2e58660 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/apiClient/constants.js +++ b/cartridges/int_cybs_sfra_base/cartridge/apiClient/constants.js @@ -31,7 +31,7 @@ module.exports = { SANDBOX_RUN_ENV: "cybersource.environment.sandbox", PRODUCTION_RUN_ENV: "cybersource.environment.production", APPLICATION_NAME: "Salesforce B2C(REST)", - APPLICATION_VERSION: "25.1.0", + APPLICATION_VERSION: "25.2.0", /* Digest Constants*/ SIGNATURE_ALGORITHAM: "SHA-256=", diff --git a/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js b/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js index 6466aeb..7f55eea 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js +++ b/cartridges/int_cybs_sfra_base/cartridge/configuration/index.js @@ -23,7 +23,7 @@ var LogfileMaxSize = '5242880'; // 10 MB In Bytes * Send this value in all requests that are sent through the partner solution. CyberSource assigns the ID to the partner. * Note When you see a partner ID of 999 in reports, the partner ID that was submitted is incorrect. */ -var SolutionId = 'FTN2UOLO'; +var SolutionId = 'GTIWK1VH'; var CruiseDDCEndPoint = { Stage: 'https://centinelapistag.cardinalcommerce.com/V1/Cruise/Collect', @@ -127,10 +127,15 @@ function getConfig(config) { googlePayEnvironment: config.googlePayEnvironment || customPreferences.GooglePay.Preferences.GooglePayEnvironment.getValue(), enableGooglePayOnCart: config.enableGooglePayOnCart || customPreferences.GooglePay.Preferences.EnableGooglePayOnCart.getValue(), - // DecisionManager + // Click to pay visaSRCEnabled: config.vscCheckoutEnabled || customPreferences.ClicktoPay.Preferences.ClicktoPayEnabled.getValue(), visaSRCKey: config.visaSRCKey || customPreferences.ClicktoPay.Preferences.ClicktoPayKey.getValue(), - VisaSRCProduction: config.VisaSRCProduction || customPreferences.ClicktoPay.Preferences.ClicktoPayProduction.getValue() + VisaSRCProduction: config.VisaSRCProduction || customPreferences.ClicktoPay.Preferences.ClicktoPayProduction.getValue(), + + //MLE + mleEnabled: config.mleEnabled || customPreferences.MLE.Preferences.EnableMLE.getValue(), + mleCertificateSerialNumber: config.mleCertificateSerialNumber || customPreferences.MLE.Preferences.MLECertificateSerialNumber.getValue(), + mleCertificateAlias: config.mleCertificateAlias || customPreferences.MLE.Preferences.MLECertificateAlias.getValue() }; } module.exports = getConfig(); diff --git a/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js b/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js index 14ba0a4..d53c83f 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js +++ b/cartridges/int_cybs_sfra_base/cartridge/configuration/preferences/customPreferences.js @@ -602,5 +602,46 @@ module.exports = { } } } + }, + + /* MLE Custom Preference */ + MLE: { + id: 'Cybersource_MLE', + display_name: 'Message-Level Encryption Configration', + Preferences: { + /** @type {CustomPreference} */ + EnableMLE: { + id: 'Cybersource_MLEEnabled', + display_name: 'Enable Message-Level Encryption', + description: 'Enable or Disable Message-Level Encryption.', + type: Types.boolean, + default: false, + flags: { + mandatory: false + } + }, + /** @type {CustomPreference} */ + MLECertificateSerialNumber: { + id: 'Cybersource_CertificateSerialNo', + display_name: 'Certificate Serial Number', + description: 'Serial Number of "CyberSource_SJC_US" certificate extracted from p12 file.', + type: Types.string, + default: undefined, + flags: { + mandatory: false + } + }, + /** @type {CustomPreference} */ + MLECertificateAlias: { + id: 'Cybersource_CertificateAlias', + display_name: 'Alias of the Certificate', + description: '', + type: Types.string, + default: undefined, + flags: { + mandatory: false + } + } } +}, }; diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit_form_processor.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit_form_processor.js index 76e904d..bd9535a 100644 --- a/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit_form_processor.js +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/hooks/payment/processor/payments_credit_form_processor.js @@ -31,7 +31,7 @@ function processForm(req, paymentForm, viewFormData) { case 'discover': correctCardType = 'Discover'; break; - case 'dinersclub': + case 'diners-club': correctCardType = 'DinersClub'; break; case 'maestro': @@ -40,6 +40,18 @@ function processForm(req, paymentForm, viewFormData) { case 'jcb': correctCardType = 'JCB'; break; + case "cartesbancaires": + correctCardType = "CartesBancaires"; + break; + case "elo": + correctCardType = "Elo"; + break; + case "cup": + correctCardType = "China UnionPay"; + break; + case "jcrew": + correctCardType = "JCrew"; + break; } // eslint-disable-next-line no-param-reassign paymentForm.creditCardFields.cardType.value = correctCardType; diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/aesgcmCustom.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/aesgcmCustom.js new file mode 100644 index 0000000..a34ef5a --- /dev/null +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/aesgcmCustom.js @@ -0,0 +1,151 @@ +'use strict'; + +var Cipher = require('dw/crypto/Cipher'); +var WeakCipher = require('dw/crypto/WeakCipher'); + +var cipher = new Cipher(); +var weakCipher = new WeakCipher(); + +var Bytes = require('dw/util/Bytes'); + +// Utility to XOR two Uint8Arrays +function xorArrays(a, b) { + let result = new Uint8Array(a.length); + for (let i = 0; i < a.length; i++) { + result[i] = a[i] ^ b[i]; + } + return result; +} + +// Custom GF(2^128) multiplication (shift-and-reduce method) +function gf128Multiply(X, H) { + let blockSize = 16; // 128 bits in bytes + let Z = new Uint8Array(blockSize); // Accumulator, initially 0 + let V = new Uint8Array(H); // Working copy of H + let poly = new Uint8Array([0xE1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // x^128 + x^7 + x^2 + x + 1 + + for (let i = 0; i < blockSize; i++) { + let xByte = X[i]; + for (let bit = 7; bit >= 0; bit--) { + if (xByte & (1 << bit)) { + Z = xorArrays(Z, V); // Z ^= V if bit is 1 + } + let highBit = V[blockSize - 1] & 1; // Check LSB + // Right shift V by 1 + for (let j = blockSize - 1; j > 0; j--) { + V[j] = (V[j] >>> 1) | ((V[j - 1] & 1) << 7); + } + V[0] = V[0] >>> 1; + if (highBit) { + V = xorArrays(V, poly); // Reduce modulo polynomial + } + } + } + return Z; +} + + +function concatBytes(a, b) { + const out = new Uint8Array(a.length + b.length); + out.set(a, 0); + out.set(b, a.length); + return out; +} + +// Utility to convert string or array to Uint8Array +function toUint8Array(input) { + if (typeof input === 'string') { + var Bytes = require('dw/util/Bytes'); + const bytes = new Bytes(input, 'UTF-8'); + return bytes.asUint8Array(); + } + return new Uint8Array(input); +} + +// Custom GHASH implementation +function customGhash(H, aad, ciphertext) { + let blockSize = 16; // 128 bits in bytes + let X = new Uint8Array(blockSize); // Initial GHASH state (zero) + + // Process AAD (Additional Authenticated Data) + let aadBytes = aad ? toUint8Array(aad) : null; + if (aadBytes && aadBytes.length > 0) { + for (let i = 0; i < aadBytes.length; i += blockSize) { + let block = new Uint8Array(blockSize); + for (let j = 0; j < blockSize && (i + j) < aadBytes.length; j++) { + block[j] = aadBytes[i + j]; + } + X = xorArrays(X, block); + X = gf128Multiply(X, H); + } + } + + // Process Ciphertext (convert Buffer to Uint8Array) + let cipherBytes = new Uint8Array(ciphertext); + for (let i = 0; i < cipherBytes.length; i += blockSize) { + let blok = new Uint8Array(blockSize); + for (let j = 0; j < blockSize && (i + j) < cipherBytes.length; j++) { + blok[j] = cipherBytes[i + j]; + } + X = xorArrays(X, blok); + X = gf128Multiply(X, H); + } + + // Append length block (AAD length || Ciphertext length in bits) + let lenBlock = new Uint8Array(blockSize); + let aadLenBits = BigInt((aadBytes ? aadBytes.length : 0) * 8); + let cipherLenBits = BigInt(cipherBytes.length * 8); + // Write 64-bit lengths as big-endian + for (let i = 0; i < 8; i++) { + lenBlock[7 - i] = Number((aadLenBits >> BigInt(i * 8)) & 0xFFn); + lenBlock[15 - i] = Number((cipherLenBits >> BigInt(i * 8)) & 0xFFn); + } + X = xorArrays(X, lenBlock); + X = gf128Multiply(X, H); + + return X; +} + +// Main function to encrypt and compute custom tag +function encryptAndTag(key, iv, plaintext, aad) { + + var encryptedpayload = cipher.encrypt(plaintext, key, 'AES/GCM/NOPADDING', iv, 0); + // seperating cipher text and auth tag from encryptedpayload + var encryptedPayloadBytes = dw.crypto.Encoding.fromBase64(encryptedpayload); + var l = encryptedPayloadBytes.getLength(); + + var ciphertextBytes = encryptedPayloadBytes.bytesAt(0, l - 16); + var ciphertext = ciphertextBytes.asUint8Array(); + + // Step 2: Compute H (hash subkey) = AES encryption of zero block + const zeroBlock = new Uint8Array(16); + const IVnull = new Uint8Array(16);// 128-bit zero block + + var zeroblockbase64 = dw.crypto.Encoding.toBase64(new Bytes(zeroBlock)); + var IVnullBase64 = dw.crypto.Encoding.toBase64(new Bytes(IVnull)); + + var aesForHBytes = weakCipher.encryptBytes(new Bytes(zeroBlock), key, 'AES/CBC/NOPADDING', IVnullBase64, 0); + var H = aesForHBytes.asUint8Array().subarray(0, 16); + + // Step 3: Compute GHASH(H, AAD, Ciphertext) + const ghashResult = customGhash(H, aad, ciphertext); + + // Step 4: Compute E_K(IV || 0^31 || 1) - the initial counter block encryption + var ivBytes = dw.crypto.Encoding.fromBase64(iv); + var ivArray = ivBytes.asUint8Array(); + const counterBlock = concatBytes(ivArray, new Uint8Array([0, 0, 0, 1])); // IV || 0^31 || 1 + + var counterBlockbase64 = dw.crypto.Encoding.toBase64(new Bytes(counterBlock)); + + var aesForCounter = weakCipher.encryptBytes(new Bytes(counterBlock), key, 'AES/CBC/NOPADDING', IVnullBase64, 0); + var encCounter = aesForCounter.asUint8Array().subarray(0, 16); + + // Step 5: Finalize Tag = GHASH XOR E_K(IV || 0^31 || 1) + const customTag = xorArrays(ghashResult, encCounter); + + return { ciphertext, customTag }; +} + +module.exports = { + encryptAndTag +}; \ No newline at end of file diff --git a/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/jweEncrypt.js b/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/jweEncrypt.js new file mode 100644 index 0000000..bc64371 --- /dev/null +++ b/cartridges/int_cybs_sfra_base/cartridge/scripts/mleEncrypt/jweEncrypt.js @@ -0,0 +1,77 @@ +'use strict'; + +const { encryptAndTag } = require('~/cartridge/scripts/mleEncrypt/aesgcmCustom.js'); + +var Cipher = require('dw/crypto/Cipher'); +var WeakCipher = require('dw/crypto/WeakCipher'); +var cipher = new Cipher(); +var weakCipher = new WeakCipher(); +var Bytes = require('dw/util/Bytes'); +var SecureRandom = require('dw/crypto/SecureRandom'); +SecureRandom = new SecureRandom(); +var configObject = require('*/cartridge/configuration/index'); + +function getJWE(payload) { + //JWE header + var currentTimestamp = new Date().getTime(); + currentTimestamp = Math.floor(currentTimestamp / 1000); + + var kid = configObject.mleCertificateSerialNumber; + + var joseHeader = { + "alg": "RSA-OAEP", + "enc": "A256GCM", + "iat": currentTimestamp, + "kid": kid, + } + var aad = dw.crypto.Encoding.toBase64URL(new Bytes(JSON.stringify(joseHeader), 'UTF-8')); + + var requestString = payload; + + // generating random key(256 bit) and IV(96 bit) + var key = SecureRandom.nextBytes(32); // AES key + var keybase64 = dw.crypto.Encoding.toBase64(key); + var iv = SecureRandom.nextBytes(12); + var ivbase64 = dw.crypto.Encoding.toBase64(iv); + + const encryptedpayload = encryptAndTag( + keybase64, + ivbase64, + requestString, + aad + ); + + var cipherText = new Bytes(encryptedpayload.ciphertext); + var authTag = new Bytes(encryptedpayload.customTag); + + //public key (certificate extracted from p12 file using openssl) uploaded in Business Manager keystore (Admnistration --> Private Keys and Certificate) + var CertificateRef = require('dw/crypto/CertificateRef'); + var alias = configObject.mleCertificateAlias; + + var publicKeyRef = new CertificateRef(alias); + + //encrypt the AES key using public key + var encryptedAESKey = weakCipher.encryptBytes(key, publicKeyRef, 'RSA/ECB/OAEPWithSHA-1AndMGF1Padding', null, 0); + + joseHeader = dw.crypto.Encoding.toBase64URL(new Bytes(JSON.stringify(joseHeader), 'UTF-8')); + + //base64url encoding all 5 parts of JWE. + encryptedAESKey = dw.crypto.Encoding.toBase64URL(encryptedAESKey); + iv = dw.crypto.Encoding.toBase64URL(iv); + cipherText = dw.crypto.Encoding.toBase64URL(cipherText); + authTag = dw.crypto.Encoding.toBase64URL(authTag); + + + // JWE token + var jwe = joseHeader + '.' + encryptedAESKey + '.' + iv + '.' + cipherText + '.' + authTag; + + var jwePayload = { + encryptedRequest: jwe + } + + return JSON.stringify(jwePayload); +} + +module.exports = { + getJWE + }; \ No newline at end of file diff --git a/documentation/markdown/Configure-features.md b/documentation/markdown/Configure-features.md index 2df9a81..5cf1372 100644 --- a/documentation/markdown/Configure-features.md +++ b/documentation/markdown/Configure-features.md @@ -16,7 +16,7 @@ Reset Interval (in Hours) | Number of hours that saved card attempts are counted **NOTE:** If you want to utilize **"save card to account"** feature through "Payment flow/Checkout flow", make sure to set **"Enable tokenization Services"** to **"Yes"** -### **1.1 Network Tokens** +### **2. Network Tokens** A Network Token is a card scheme generated token, that represents customer card information for secure transactions that references a customer’s actual PAN. @@ -36,7 +36,7 @@ Step 3: Go to Merchant Tools > Custom objects > Custom Object Editor and check i A custom object of this type would be created only if the Network Tokens webhook is subscribed. -### **2. Delivery Address Verification** +### **3. Delivery Address Verification** Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: Upload metadata" or import **"metadata/payment_metadata/meta/DeliveryAddressVerification.xml"** in Business Manager (**Administration > Site Development > Import & Export**) @@ -48,7 +48,7 @@ Enable Delivery Address Verification Services | Enableor Disable Delivery Addres -### **3. Tax Calculation** +### **4. Tax Calculation** Step 1: Upload Cybersource metadata in Business Manager. If not follow "Step 2: Upload metadata" or import **"metadata/payment_metadata/meta/TaxConfiguration.xml"** in Business Manager (**Administration > Site Development > Import & Export**) @@ -76,7 +76,7 @@ Ship From Country Code | Ship From Country Code -### **4. Fraud Management Solutions** +### **5. Fraud Management Solutions** Refer to this [link](https://www.cybersource.com/en-us/solutions/fraud-and-risk-management/decision-manager.html) to learn about Cybersource's Decision Manager and Fraud Management Essentials. Both services use the same cartridge settings and fields, to access the service a retailer has signed up for with Cybersource. . @@ -116,7 +116,7 @@ Step 3.3: Go to **Merchant Tools > Site Preferences > Custom Preferences > cyber -### **5. Device FingerPrint** +### **6. Device FingerPrint** Device FingerPrint is a powerful feature of Decision Manager and Fraud Management Essentials It is always recommended to send a Device Fingerprint when using a Cybersource fraud management service. @@ -134,7 +134,7 @@ TTL (Time To Live) | Time, in milliseconds between generating a new fingerprint -### **6. Capture Service** +### **7. Capture Service** A single function is available to make capture requests. Please note that these functions are not available to use in the Salesforce B2C Commerce UI without customisation. @@ -159,7 +159,7 @@ currency | Currency code (ex. ‘USD’) -### **6. Auth Reversal Service** +### **8. Auth Reversal Service** A single function is available to make auth reversal requests. @@ -183,7 +183,7 @@ currency | Currency code (ex. ‘USD’) -### **7. Advanced Customization** +### **9. Advanced Customization** The Cybersource SFRA cartridge has built-in custom hooks that can be utilized to customize request data being sent to each Service. This can be utilized to send additional custom data that the core cartridge cannot account for. For example, if you want to include Merchant Defined Data in your Credit Card Authorization Requests, you can use these hooks to achieve this. @@ -212,5 +212,23 @@ Capture | Credit Card Capture
Next Step: Test and Go Live
+### **10. Message-Level Encryption (MLE)** + +Refer to this [link](https://developer.cybersource.com/docs/cybs/en-us/platform/developer/all/rest/rest-getting-started/restgs-jwt-message-intro/restgs-mle-intro.html) to learn about Cybersource’s MLE feature. + +#### Step 1: Upload Cybersource metadata in Business Manager +If not, follow “4.2: Upload metadata” or import **metadata/payment_metadata/meta/MLE.xml** in Business Manager (Administration > Site Development > Import & Export). + +#### Step 2: Configure Cybersource MLE +Go to **Merchant Tools > Site Preferences > Custom Preferences > Cybersource_MLE** and set values for the following parameters according to the **documentation -> Cybersource for Salesforce B2C Commerce REST-Message-Level Encryption Guide**. + +#### Field Description + +Field Name | Description +------------ | ------------- +Enable Message-Level Encryption | Enable or Disable Message-Level Encryption. +Alias of the Certificate | Alias of the "CyberSource_SJC_US" Certificate imported in "Private Keys and Certificates". +Certificate Serial Number | Serial Number of "CyberSource_SJC_US" certificate extracted from p12 file. + --- \ No newline at end of file diff --git a/documentation/markdown/Release-notes.md b/documentation/markdown/Release-notes.md index 460752b..84c8ef2 100644 --- a/documentation/markdown/Release-notes.md +++ b/documentation/markdown/Release-notes.md @@ -1,5 +1,9 @@ ## Release Notes +**Version 25.2.0 (March 2025)** +• Added Message-Level Encryption (MLE) feature. +• Added support for additional card types in flex microform. + **Version 25.1.0 (January 2025)** • Upgraded flex v0.11 to v2. • Added webhook subscription deletion from locale if subscription is deleted at Cybersource end and vice-versa diff --git a/metadata/payments_metadata/meta/FlexMicroform.xml b/metadata/payments_metadata/meta/FlexMicroform.xml index 3a684fa..783353a 100644 --- a/metadata/payments_metadata/meta/FlexMicroform.xml +++ b/metadata/payments_metadata/meta/FlexMicroform.xml @@ -45,6 +45,22 @@ JCB JCB + + CUP + CUP + + + CARTESBANCAIRES + CARTESBANCAIRES + + + JCREW + JCREW + + + ELO + ELO + diff --git a/metadata/payments_metadata/meta/MLE.xml b/metadata/payments_metadata/meta/MLE.xml new file mode 100644 index 0000000..2e3c270 --- /dev/null +++ b/metadata/payments_metadata/meta/MLE.xml @@ -0,0 +1,34 @@ + + + + + + Enable Message-Level Encryption + Enable or Disable Message-Level Encryption. + boolean + false + false + + + Certificate Serial Number + Serial Number of "CyberSource_SJC_US" certificate extracted from p12 file. + string + false + + + Alias of the Certificate + Alias of the "CyberSource_SJC_US" Certificate imported in "Private Keys and Certificates". + string + false + + + + + Message-Level Encryption Configuration + + + + + + + diff --git a/metadata/payments_metadata/meta/merged.xml b/metadata/payments_metadata/meta/merged.xml index 1c19cf9..329a6d4 100644 --- a/metadata/payments_metadata/meta/merged.xml +++ b/metadata/payments_metadata/meta/merged.xml @@ -269,6 +269,22 @@ JCB JCB + + CUP + CUP + + + CARTESBANCAIRES + CARTESBANCAIRES + + + JCREW + JCREW + + + ELO + ELO + @@ -414,6 +430,25 @@ + + Enable Message-Level Encryption + Enable or Disable Message-Level Encryption. + boolean + false + false + + + Certificate Serial Number + Serial Number of "CyberSource_SJC_US" certificate extracted from p12 file. + string + false + + + Alias of the Certificate + Alias of the "CyberSource_SJC_US" Certificate imported in "Private Keys and Certificates". + string + false + @@ -496,6 +531,12 @@ + + Message-Level Encryption Configuration + + + +