Skip to content

Commit

Permalink
HMAC Validation (#914)
Browse files Browse the repository at this point in the history
  • Loading branch information
zenit2001 committed Jun 5, 2023
1 parent c3737fc commit 7c12eb3
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 3 deletions.
6 changes: 5 additions & 1 deletion jest/sfccCartridgeMocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ jest.mock('*/cartridge/scripts/adyenZeroAuth', () => {
}, {virtual: true});

jest.mock('*/cartridge/scripts/checkNotificationAuth', () => {
return { check: jest.fn(() => true) };
return {
check: jest.fn(() => true),
validateHmacSignature: jest.fn(() => true),
};
}, {virtual: true});

jest.mock('*/cartridge/scripts/handleNotify', () => {
Expand Down Expand Up @@ -272,6 +275,7 @@ jest.mock('*/cartridge/scripts/util/adyenConfigs', () => {
),
getAdyenGivingLogoUrl: jest.fn(() => 'mocked_logo_url'),
getAdyenSFRA6Compatibility: jest.fn(() => false),
getAdyenHmacKey : jest.fn(() => 'mocked_hmacKey'),
getAdyenBasketFieldsEnabled: jest.fn(() => false),
getAdyen3DS2Enabled: jest.fn(() => false),
getAdyenLevel23DataEnabled: jest.fn(() => false),
Expand Down
9 changes: 9 additions & 0 deletions metadata/site_import/meta/system-objecttype-extensions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,15 @@
<min-length>0</min-length>
<field-length>0</field-length>
</attribute-definition>
<attribute-definition attribute-id="Adyen_Hmac_Key">
<display-name xml:lang="x-default">Adyen HMAC Key</display-name>
<description xml:lang="x-default">Adyen HMAC Key used optionally for notification validation</description>
<type>string</type>
<mandatory-flag>false</mandatory-flag>
<externally-managed-flag>false</externally-managed-flag>
<min-length>0</min-length>
<field-length>0</field-length>
</attribute-definition>
<attribute-definition attribute-id="AdyenSalePaymentMethods">
<display-name xml:lang="x-default">Payment methods with SALE flow</display-name>
<description xml:lang="x-default">Only applicable for Salesforce OMS. List the txvariants with Authorisation type SALE comma separated (e.g. ideal,sepadirectdebit,paypal).</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@
padding-left: 30px;
}

#hmacKey{
padding-left: 30px;
}

.line-divider{
padding-left: 48px;
padding-right: 51px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ document.addEventListener('DOMContentLoaded', () => {
const formButtons = Array.from(document.getElementsByClassName('formButton'));
const testConnectionButton = document.querySelector('#testConnectionButton');
const togglePassword = document.querySelector('#togglePassword');
const toggleHmacKey = document.querySelector('#toggleHmacKey');
const toggleApi = document.querySelector('#toggleApi');
const formBody = document.querySelector('#formBody');
const password = document.querySelector('#notificationsPassword');
const hmacKey = document.querySelector('#hmacKey');
const merchAccount = document.getElementById('merchantAccount');
const classicPageButton = document.querySelector('#classicButton');
const apiKeyVal = document.getElementById('apiKey');
Expand Down Expand Up @@ -217,6 +219,13 @@ document.addEventListener('DOMContentLoaded', () => {
this.classList.toggle('bi-eye');
}

function showHmacKey() {
const type =
hmacKey.getAttribute('type') === 'password' ? 'text' : 'password';
hmacKey.setAttribute('type', type);
this.classList.toggle('bi-eye');
}

// open Adyen Giving Background upload page
function uploadAdyenGivingBackground() {
const openedWindow = window.open(
Expand Down Expand Up @@ -289,6 +298,8 @@ document.addEventListener('DOMContentLoaded', () => {

togglePassword.addEventListener('click', showPassword);

toggleHmacKey.addEventListener('click', showHmacKey);

toggleApi.addEventListener('click', showApiKey);

adyenGivingBackground.addEventListener('click', uploadAdyenGivingBackground);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
placeholder="•••••••••••••••" value="${AdyenConfigs.getAdyenNotificationPassword()}">
</div>
</div>
<div class="form-group">
<label class="form-title mb-0" for="hmacKey">HMAC Key (Optional)</label>
<small id="hmacKeyHelp" class="form-text mb-1">This is the HMAC key you created under the Developers section in the Customer Area.</small>
<span>
<i class="bi bi-eye-slash" id="toggleHmacKey"></i>
</span>
<div class="input-fields">
<input type="password" class="form-control" name="Adyen_Hmac_Key" id="hmacKey"
aria-describedby="hmacKeyHelp" placeholder=""
value="${AdyenConfigs.getAdyenHmacKey()}">
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
const Transaction = require('dw/system/Transaction');
const checkAuth = require('*/cartridge/scripts/checkNotificationAuth');
const handleNotify = require('*/cartridge/scripts/handleNotify');
const AdyenConfigs = require('*/cartridge/scripts/util/adyenConfigs');

/**
* Called by Adyen to update status of payments. It should always display [accepted] when finished.
*/

function handleHmacVerification(hmacKey, req) {
if (!hmacKey) {
return false;
}
return checkAuth.validateHmacSignature(req);
}

function notify(req, res, next) {
const status = checkAuth.check(req);
if (!status) {
const hmacKey = AdyenConfigs.getAdyenHmacKey();
const isHmacValid = handleHmacVerification(hmacKey, req);
if (!status || !isHmacValid) {
res.render('/adyen/error');
return {};
}
Transaction.begin();
const notificationResult = handleNotify.notify(req.form);

if (notificationResult.success) {
Transaction.commit();
res.render('/notify');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ module.exports = {

GIFTCARD_EXPIRATION_MINUTES: 30,
OMS_NAMESPACE: 'adyen_payment',
NOTIFICATION_PAYLOAD_DATA_SEPARATOR : ':',

CHECKOUT_ENVIRONMENT_TEST: 'test',
CHECKOUT_ENVIRONMENT_LIVE_EU: 'live',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
const Site = require('dw/system/Site');
const AuthenticationUtils = require('*/cartridge/scripts/libs/libAuthenticationUtils');
const AdyenLogs = require('*/cartridge/scripts/adyenCustomLogs');

function check(request) {
const baUser = Site.getCurrent().getCustomPreferenceValue(
Expand All @@ -40,6 +41,30 @@ function check(request) {
);
}

function compareHmac(hmacSignature, merchantSignature){
let bitwiseComparison;
if (hmacSignature.length !== merchantSignature.length){
return false;
}
for (let i = 0; i < hmacSignature.length; i++) {
bitwiseComparison |= hmacSignature.charCodeAt(i) ^ merchantSignature.charCodeAt(i);
}
return bitwiseComparison === 0;
};

function validateHmacSignature(request){
const notificationData = request.form;
const hmacSignature = notificationData['additionalData.hmacSignature'];
const merchantSignature = AuthenticationUtils.calculateHmacSignature(request);
// Checking for timing attacks
if (compareHmac(hmacSignature, merchantSignature)){
return true;
};
AdyenLogs.error_log(`HMAC signatures mismatch, the notification request is not valid`);
return false;
};

module.exports = {
check,
validateHmacSignature,
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
*/

const StringUtils = require('dw/util/StringUtils');
const Encoding = require('dw/crypto/Encoding');
const Mac = require('dw/crypto/Mac');
const Bytes = require('dw/util/Bytes');
const constants = require('*/cartridge/adyenConstants/constants');
const AdyenLogs = require('*/cartridge/scripts/adyenCustomLogs');
const AdyenConfigs = require('*/cartridge/scripts/util/adyenConfigs');

/**
*
Expand All @@ -42,6 +48,35 @@ function checkGivenCredentials(baHeader, baUser, baPassword) {
return false;
}

function constructPayload(notificationData){
const signedDataList = [];
signedDataList.push(notificationData.pspReference);
signedDataList.push(notificationData.originalReference);
signedDataList.push(notificationData.merchantAccountCode);
signedDataList.push(notificationData.merchantReference);
signedDataList.push(notificationData.value);
signedDataList.push(notificationData.currency);
signedDataList.push(notificationData.eventCode);
signedDataList.push(notificationData.success);
return signedDataList.join(constants.NOTIFICATION_PAYLOAD_DATA_SEPARATOR);
};

function calculateHmacSignature(request){
try{
const hmacKey = Encoding.fromHex(new Bytes(AdyenConfigs.getAdyenHmacKey(), 'UTF-8'));
const payload = constructPayload(request.form);
const macSHA256 = new Mac(Mac.HMAC_SHA_256);
const merchantSignature = Encoding.toBase64(macSHA256.digest(payload, hmacKey));
return merchantSignature;
}
catch(e) {
AdyenLogs.fatal_log(
`Cannot calculate HMAC signature: ${e.toString()} in ${e.fileName}:${e.lineNumber}`,
);
};
};

module.exports = {
checkGivenCredentials,
calculateHmacSignature,
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const adyenConfigsObj = {
return getCustomPreference('Adyen_notification_password');
},

getAdyenHmacKey() {
return getCustomPreference('Adyen_Hmac_Key');
},

getAdyenRecurringPaymentsEnabled() {
return getCustomPreference('AdyenOneClickEnabled');
},
Expand Down

0 comments on commit 7c12eb3

Please sign in to comment.