Skip to content

Commit 9c59501

Browse files
changes for MLE Request for HTTP Signature
1 parent e78f816 commit 9c59501

File tree

6 files changed

+319
-67
lines changed

6 files changed

+319
-67
lines changed

src/authentication/core/MerchantConfig.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ var Constants = require('../util/Constants');
44
var Logger = require('../logging/Logger');
55
var ApiException = require('../util/ApiException');
66
var LogConfiguration = require('../logging/LogConfiguration');
7+
var path = require('path');
8+
var fs = require('fs');
79

810
/**
911
* This function has all the merchentConfig properties getters and setters methods
@@ -80,6 +82,7 @@ function MerchantConfig(result) {
8082
this.useMLEGlobally = result.useMLEGlobally;
8183
this.mapToControlMLEonAPI = result.mapToControlMLEonAPI;
8284
this.mleKeyAlias = result.mleKeyAlias; //mleKeyAlias is optional parameter, default value is "CyberSource_SJC_US".
85+
this.mleForRequestPublicCertPath = result.mleForRequestPublicCertPath;
8386

8487
/* Fallback logic*/
8588
this.defaultPropValues();
@@ -415,6 +418,14 @@ MerchantConfig.prototype.setMleKeyAlias = function setMleKeyAlias(mleKeyAlias) {
415418
this.mleKeyAlias = mleKeyAlias;
416419
}
417420

421+
MerchantConfig.prototype.getMleForRequestPublicCertPath = function getMleForRequestPublicCertPath() {
422+
return this.mleForRequestPublicCertPath;
423+
}
424+
425+
MerchantConfig.prototype.setMleForRequestPublicCertPath = function setMleForRequestPublicCertPath(mleForRequestPublicCertPath) {
426+
this.mleForRequestPublicCertPath = mleForRequestPublicCertPath;
427+
}
428+
418429
MerchantConfig.prototype.runEnvironmentCheck = function runEnvironmentCheck(logger) {
419430

420431
/*url*/
@@ -610,25 +621,34 @@ MerchantConfig.prototype.defaultPropValues = function defaultPropValues() {
610621

611622
//useMLEGlobally check for auth Type
612623
if (this.useMLEGlobally === true || this.mapToControlMLEonAPI != null) {
613-
if (this.useMLEGlobally === true && this.authenticationType.toLowerCase() !== Constants.JWT) {
614-
ApiException.ApiException("MLE is only supported in JWT auth type", logger);
615-
}
624+
// if (this.useMLEGlobally === true && this.authenticationType.toLowerCase() !== Constants.JWT) {
625+
// ApiException.ApiException("MLE is only supported in JWT auth type", logger);
626+
// }
616627

617628
if (this.mapToControlMLEonAPI != null && typeof (this.mapToControlMLEonAPI) !== "object") {
618629
ApiException.ApiException("mapToControlMLEonAPI in merchantConfig should be key value pair", logger);
619630
}
620631

621-
if (this.mapToControlMLEonAPI != null && Object.keys(this.mapToControlMLEonAPI).length !== 0) {
622-
var hasTrueValue = false;
623-
for (const[key, value] of Object.entries(this.mapToControlMLEonAPI)) {
624-
if (value === true) {
625-
hasTrueValue = true;
626-
break;
627-
}
628-
}
629-
if (hasTrueValue && this.authenticationType.toLowerCase() !== Constants.JWT) {
630-
ApiException.ApiException("MLE is only supported in JWT auth type", logger);
631-
}
632+
// if (this.mapToControlMLEonAPI != null && Object.keys(this.mapToControlMLEonAPI).length !== 0) {
633+
// var hasTrueValue = false;
634+
// for (const[key, value] of Object.entries(this.mapToControlMLEonAPI)) {
635+
// if (value === true) {
636+
// hasTrueValue = true;
637+
// break;
638+
// }
639+
// }
640+
// if (hasTrueValue && this.authenticationType.toLowerCase() !== Constants.JWT) {
641+
// ApiException.ApiException("MLE is only supported in JWT auth type", logger);
642+
// }
643+
// }
644+
}
645+
646+
if (this.mleForRequestPublicCertPath !== null && this.mleForRequestPublicCertPath !== undefined) {
647+
var certFile = path.resolve(path.join(this.mleForRequestPublicCertPath));
648+
try {
649+
fs.accessSync(certFile, fs.constants.R_OK);
650+
} catch (err) {
651+
ApiException.ApiException("mleForRequestPublicCertPath file is not readable or does not exist" + ": " + certFile, logger);
632652
}
633653
}
634654

src/authentication/logging/SensitiveDataTags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ exports.getSensitiveDataTags = function () {
3434
tags.push("signature");
3535
tags.push("prefix");
3636
tags.push("bin");
37+
tags.push("encryptedRequest");
3738

3839
return tags;
3940
}

src/authentication/util/Cache.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ var cache = require('memory-cache');
66
var path = require('path');
77
var Constants = require('./Constants');
88
var ApiException = require('./ApiException');
9+
var Logger = require('../logging/Logger');
10+
var Utility = require('./Utility');
911

1012

1113
/**
@@ -77,3 +79,169 @@ exports.fetchPEMFileForNetworkTokenization = function(merchantConfig) {
7779
}
7880
return cache.get("privateKeyFromPEMFile");
7981
}
82+
83+
84+
exports.getRequestMLECertFromCache = function(merchantConfig) {
85+
var logger = Logger.getLogger(merchantConfig, 'Cache');
86+
var merchantId = merchantConfig.getMerchantID();
87+
var cacheKey = null;
88+
var mleCertPath = null;
89+
if (merchantConfig.getMleForRequestPublicCertPath() !== null && merchantConfig.getMleForRequestPublicCertPath() !== undefined) {
90+
cacheKey = merchantId + Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT;
91+
mleCertPath = merchantConfig.getMleForRequestPublicCertPath();
92+
} else if (Constants.JWT === merchantConfig.getAuthenticationType().toLowerCase()) {
93+
mleCertPath = path.resolve(path.join(merchantConfig.getKeysDirectory(), merchantConfig.getKeyFileName() + '.p12'));
94+
try {
95+
fs.accessSync(mleCertPath, fs.constants.R_OK);
96+
} catch (err) {
97+
logger.warn("MLE certificate file not found or not readable: " + mleCertPath);
98+
return null;
99+
}
100+
cacheKey = merchantId + Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT;
101+
} else {
102+
logger.debug("The certificate to use for MLE for requests is not provided in the merchant configuration. Please ensure that the certificate path is provided.");
103+
return null;
104+
}
105+
return getMLECertBasedOnCacheKey(merchantConfig, cacheKey, mleCertPath);
106+
107+
}
108+
109+
function getMLECertBasedOnCacheKey(merchantConfig, cacheKey, mleCertPath) {
110+
var cachedMLECert = cache.get(cacheKey);
111+
var logger = Logger.getLogger(merchantConfig, 'Cache');
112+
if (cachedMLECert === null || cachedMLECert === undefined || cachedMLECert.fileLastModifiedTime !== fs.statSync(mleCertPath).mtimeMs) {
113+
logger.debug("MLE certificate not found in cache or has been modified. Loading from file: " + mleCertPath);
114+
setupMLECache(merchantConfig, cacheKey, mleCertPath);
115+
} else {
116+
logger.debug("MLE certificate found in cache for key: " + cacheKey);
117+
}
118+
return cache.get(cacheKey).mleCert;
119+
}
120+
121+
function setupMLECache(merchantConfig, cacheKey, mleCertPath) {
122+
var fileLastModifiedTime = fs.statSync(mleCertPath).mtimeMs;
123+
var mleCert = null;
124+
if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT)) {
125+
mleCert = loadCertificateFromPem(merchantConfig, mleCertPath);
126+
}
127+
else if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT)) {
128+
mleCert = loadCertificateFromP12(merchantConfig, mleCertPath);
129+
}
130+
cache.put(cacheKey, {
131+
mleCert: mleCert,
132+
fileLastModifiedTime: fileLastModifiedTime
133+
});
134+
validateCertificateExpiry(mleCert, merchantConfig.getMleKeyAlias(), cacheKey, merchantConfig);
135+
}
136+
137+
138+
function loadCertificateFromP12(merchantConfig, mleCertPath) {
139+
const logger = Logger.getLogger(merchantConfig, 'Cache');
140+
try {
141+
// Read the P12 file as before
142+
var p12Buffer = fs.readFileSync(mleCertPath);
143+
var p12Der = forge.util.binary.raw.encode(new Uint8Array(p12Buffer));
144+
var p12Asn1 = forge.asn1.fromDer(p12Der);
145+
var p12Cert = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, merchantConfig.getKeyPass());
146+
147+
// Extract the certificate from the P12 container
148+
var certBags = p12Cert.getBags({ bagType: forge.pki.oids.certBag });
149+
if (certBags && certBags[forge.pki.oids.certBag] && certBags[forge.pki.oids.certBag].length > 0) {
150+
// Process all certificates in the P12 file
151+
var certs = [];
152+
for (var i = 0; i < certBags[forge.pki.oids.certBag].length; i++) {
153+
var cert = certBags[forge.pki.oids.certBag][i].cert;
154+
var certPem = forge.pki.certificateToPem(cert);
155+
certs.push(certPem);
156+
}
157+
158+
// Try to find the certificate by alias among all certificates
159+
var mleCert = Utility.findCertificateByAlias(certs, merchantConfig.getMleKeyAlias());
160+
return forge.pki.certificateFromPem(mleCert);
161+
} else {
162+
throw new Error("No certificate found in P12 file");
163+
}
164+
} catch (error) {
165+
ApiException.ApiException(error.message + ". " + Constants.INCORRECT_KEY_PASS, logger);
166+
}
167+
}
168+
169+
function loadCertificateFromPem(merchantConfig, mleCertPath) {
170+
try {
171+
const logger = Logger.getLogger(merchantConfig, 'Cache');
172+
var pemData = fs.readFileSync(mleCertPath, 'utf8');
173+
var certs = Utility.loadPemCertificates(pemData);
174+
var mleCert = null;
175+
if (!certs || certs.length === 0) {
176+
throw new Error("No valid PEM certificates found in the provided path : " + mleCertPath);
177+
}
178+
try {
179+
mleCert = Utility.findCertificateByAlias(certs, merchantConfig.getMleKeyAlias());
180+
181+
} catch (error) {
182+
logger.warn("No certificate found for the specified mleKeyAlias '" + merchantConfig.getMleKeyAlias() + "'. Using the first certificate from file " + mleCertPath + " as the MLE request certificate.");
183+
mleCert = certs[0];
184+
}
185+
// Use node forge to parse the PEM certificate
186+
var forgeCert = forge.pki.certificateFromPem(mleCert);
187+
return forgeCert;
188+
} catch (error) {
189+
ApiException.AuthException("Error occurred while loading MLE certificate from PEM file : " + error.message);
190+
}
191+
}
192+
193+
function validateCertificateExpiry(certificate, keyAlias, cacheKey, merchantConfig) {
194+
var logger = Logger.getLogger(merchantConfig, 'Cache');
195+
196+
var warningMessageForNoExpiryDate = "Certificate does not have expiry date";
197+
var warningMessageForCertificateExpiringSoon = "Certificate with alias {} is going to expire on {}. Please update the certificate before then.";
198+
var warningMessageForExpiredCertificate = "Certificate with alias {} is expired as of {}. Please update the certificate.";
199+
200+
if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT)) {
201+
warningMessageForNoExpiryDate = "Certificate for MLE Requests does not have expiry date from mleForRequestPublicCertPath in merchant configuration.";
202+
warningMessageForCertificateExpiringSoon = "Certificate for MLE Requests with alias {} is going to expire on {}. Please update the certificate provided in mleForRequestPublicCertPath in merchant configuration before then.";
203+
warningMessageForExpiredCertificate = "Certificate for MLE Requests with alias {} is expired as of {}. Please update the certificate provided in mleForRequestPublicCertPath in merchant configuration.";
204+
}
205+
206+
if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT)) {
207+
warningMessageForNoExpiryDate = "Certificate for MLE Requests does not have expiry date in the P12 file.";
208+
warningMessageForCertificateExpiringSoon = "Certificate for MLE Requests with alias {} is going to expire on {}. Please update the P12 file before then.";
209+
warningMessageForExpiredCertificate = "Certificate for MLE Requests with alias {} is expired as of {}. Please update the P12 file.";
210+
}
211+
212+
// Get the certificate's notAfter date (expiry date)
213+
var notAfter = null;
214+
try {
215+
// All certificates are now in PEM format
216+
if (certificate.validity && certificate.validity.notAfter) {
217+
notAfter = certificate.validity.notAfter;
218+
} else {
219+
logger.warn("Unknown certificate format. Cannot extract expiry date.");
220+
}
221+
} catch (error) {
222+
logger.warn("Error extracting certificate expiry date: " + error.message);
223+
return;
224+
}
225+
226+
if (!notAfter) {
227+
// Certificate does not have an expiry date
228+
logger.warn(warningMessageForNoExpiryDate);
229+
} else {
230+
var now = new Date();
231+
232+
if (notAfter < now) {
233+
// Certificate is already expired
234+
var expiredMessage = warningMessageForExpiredCertificate.replace("{}", keyAlias).replace("{}", notAfter.toISOString().split('T')[0]);
235+
logger.warn(expiredMessage);
236+
} else {
237+
// Calculate days until expiry
238+
var timeToExpire = notAfter.getTime() - now.getTime();
239+
var daysToExpire = Math.floor(timeToExpire / Constants.FACTOR_DAYS_TO_MILLISECONDS);
240+
241+
if (daysToExpire < Constants.CERTIFICATE_EXPIRY_DATE_WARNING_DAYS) {
242+
var expiringMessage = warningMessageForCertificateExpiringSoon.replace("{}", keyAlias).replace("{}", notAfter.toISOString().split('T')[0]);
243+
logger.warn(expiringMessage);
244+
}
245+
}
246+
}
247+
};

src/authentication/util/Constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ module.exports = {
2929
CERTIFICATE_EXPIRY_DATE_WARNING_DAYS : 90,
3030
FACTOR_DAYS_TO_MILLISECONDS : 24 * 60 * 60 * 1000,
3131
DEFAULT_MLE_ALIAS_FOR_CERT : "CyberSource_SJC_US",
32+
MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT : "_mleCertFromMerchantConfig",
33+
MLE_CACHE_IDENTIFIER_FOR_P12_CERT : "_mleCertFromP12",
3234

3335
OLD_RUN_ENVIRONMENT_CONSTANTS : ["CYBERSOURCE.ENVIRONMENT.SANDBOX", "CYBERSOURCE.ENVIRONMENT.PRODUCTION",
3436
"CYBERSOURCE.IN.ENVIRONMENT.SANDBOX", "CYBERSOURCE.IN.ENVIRONMENT.PRODUCTION"],

0 commit comments

Comments
 (0)