UNPKG

cybersource-rest-client

Version:

Node.js SDK for the CyberSource REST API

245 lines (213 loc) 11.8 kB
'use strict'; var fs = require('fs'); var forge = require('node-forge'); var cache = require('memory-cache'); var path = require('path'); var Constants = require('./Constants'); var ApiException = require('./ApiException'); var Logger = require('../logging/Logger'); var Utility = require('./Utility'); function loadP12FileToAsn1(filePath) { var p12Buffer = fs.readFileSync(filePath); var p12Der = forge.util.binary.raw.encode(new Uint8Array(p12Buffer)); var p12Asn1 = forge.asn1.fromDer(p12Der); return p12Asn1; } /** * This module is doing Caching. * Certificate will be available in the memory cache if it has initialized once. */ exports.fetchCachedCertificate = function (merchantConfig, logger) { var cachedCertificateFromP12File = cache.get("certificateFromP12File"); var cachedLastModifiedTimeStamp = cache.get("certificateLastModifideTimeStamp"); var filePath = merchantConfig.getP12FilePath(); if (fs.existsSync(filePath)) { const stats = fs.statSync(filePath); const currentFileLastModifiedTime = stats.mtime; //If no entry found from cache, create a cache entry and return the created object if ((cachedCertificateFromP12File === null || cachedCertificateFromP12File === undefined) || (cachedLastModifiedTimeStamp === null || cachedLastModifiedTimeStamp === undefined) || (currentFileLastModifiedTime > cachedLastModifiedTimeStamp)) { //Function call to read the file and put values to new cache var keyPass = merchantConfig.getKeyPass(); var certificateFromP12File = getCertificate(keyPass, filePath, currentFileLastModifiedTime, logger); return certificateFromP12File; } else { return cachedCertificateFromP12File; } } else { ApiException.AuthException(Constants.FILE_NOT_FOUND + filePath); } } //Function to read the file and put values to new cache function getCertificate(keyPass, filePath, fileLastModifiedTime, logger) { try { var p12Asn1 = loadP12FileToAsn1(filePath); var certificate = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, keyPass); cache.put("certificateFromP12File", certificate); cache.put("certificateLastModifideTimeStamp", fileLastModifiedTime); return certificate; } catch (error) { ApiException.AuthException(error.message + ". " + Constants.INCORRECT_KEY_PASS); } } /** * @deprecated This method has been marked as Deprecated and will be removed in coming releases. * */ exports.fetchPEMFileForNetworkTokenization = function(merchantConfig) { var filePath = merchantConfig.getpemFileDirectory(); var pemFileData = cache.get("privateKeyFromPEMFile"); var cachedPemFileLastUpdatedTime = cache.get("cachedLastModifiedTimeOfPEMFile"); if (fs.existsSync(filePath)) { const stats = fs.statSync(filePath); const currentFileLastModifiedTime = stats.mtime; if (pemFileData === undefined || pemFileData === null || cachedPemFileLastUpdatedTime < currentFileLastModifiedTime) { pemFileData = fs.readFileSync(filePath, 'utf8'); cache.put("privateKeyFromPEMFile", pemFileData); cache.put("cachedLastModifiedTimeOfPEMFile", currentFileLastModifiedTime); } } return cache.get("privateKeyFromPEMFile"); } exports.getRequestMLECertFromCache = function(merchantConfig) { var logger = Logger.getLogger(merchantConfig, 'Cache'); var merchantId = merchantConfig.getMerchantID(); var cacheKey = null; var certificatePath = null; if (merchantConfig.getMleForRequestPublicCertPath() !== null && merchantConfig.getMleForRequestPublicCertPath() !== undefined) { cacheKey = merchantId + Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT; certificatePath = merchantConfig.getMleForRequestPublicCertPath(); } else if (Constants.JWT === merchantConfig.getAuthenticationType().toLowerCase()) { certificatePath = merchantConfig.getP12FilePath(); cacheKey = merchantId + Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT; } else { 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."); return null; } return getMLECertBasedOnCacheKey(merchantConfig, cacheKey, certificatePath); } function getMLECertBasedOnCacheKey(merchantConfig, cacheKey, certificatePath) { var cachedMLECert = cache.get(cacheKey); var logger = Logger.getLogger(merchantConfig, 'Cache'); if (cachedMLECert === null || cachedMLECert === undefined || cachedMLECert.fileLastModifiedTime !== fs.statSync(certificatePath).mtimeMs) { logger.debug("MLE certificate not found in cache or has been modified. Loading from file: " + certificatePath); setupMLECache(merchantConfig, cacheKey, certificatePath); } else { logger.debug("MLE certificate found in cache for key: " + cacheKey); } return cache.get(cacheKey).mleCert; } function setupMLECache(merchantConfig, cacheKey, certificateSourcePath) { var fileLastModifiedTime = fs.statSync(certificateSourcePath).mtimeMs; var mleCert = null; if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT)) { mleCert = loadCertificateFromPem(merchantConfig, certificateSourcePath); } else if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT)) { mleCert = loadCertificateFromP12(merchantConfig, certificateSourcePath); } cache.put(cacheKey, { mleCert: mleCert, fileLastModifiedTime: fileLastModifiedTime }); validateCertificateExpiry(mleCert, merchantConfig.getMleKeyAlias(), cacheKey, merchantConfig); } function loadCertificateFromP12(merchantConfig, certificatePath) { const logger = Logger.getLogger(merchantConfig, 'Cache'); try { // Read the P12 file and convert to ASN1 var p12Asn1 = loadP12FileToAsn1(certificatePath); var p12Cert = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, merchantConfig.getKeyPass()); // Extract the certificate from the P12 container var certBags = p12Cert.getBags({ bagType: forge.pki.oids.certBag }); if (certBags && certBags[forge.pki.oids.certBag] && certBags[forge.pki.oids.certBag].length > 0) { // Process all certificates in the P12 file var certs = []; for (var i = 0; i < certBags[forge.pki.oids.certBag].length; i++) { var cert = certBags[forge.pki.oids.certBag][i].cert; var certPem = forge.pki.certificateToPem(cert); certs.push(certPem); } // Try to find the certificate by alias among all certificates var mleCert = Utility.findCertificateByAlias(certs, merchantConfig.getMleKeyAlias()); return forge.pki.certificateFromPem(mleCert); } else { throw new Error("No certificate found in P12 file"); } } catch (error) { ApiException.ApiException(error.message + ". " + Constants.INCORRECT_KEY_PASS, logger); } } function loadCertificateFromPem(merchantConfig, mleCertPath) { try { const logger = Logger.getLogger(merchantConfig, 'Cache'); var pemData = fs.readFileSync(mleCertPath, 'utf8'); var certs = Utility.loadPemCertificates(pemData); var mleCert = null; if (!certs || certs.length === 0) { throw new Error("No valid PEM certificates found in the provided path : " + mleCertPath); } try { mleCert = Utility.findCertificateByAlias(certs, merchantConfig.getMleKeyAlias()); } catch (error) { logger.warn("No certificate found for the specified mleKeyAlias '" + merchantConfig.getMleKeyAlias() + "'. Using the first certificate from file " + mleCertPath + " as the MLE request certificate."); mleCert = certs[0]; } // Use node forge to parse the PEM certificate var forgeCert = forge.pki.certificateFromPem(mleCert); return forgeCert; } catch (error) { ApiException.AuthException("Error occurred while loading MLE certificate from PEM file : " + error.message); } } function validateCertificateExpiry(certificate, keyAlias, cacheKey, merchantConfig) { var logger = Logger.getLogger(merchantConfig, 'Cache'); var warningMessageForNoExpiryDate = "Certificate does not have expiry date"; var warningMessageForCertificateExpiringSoon = "Certificate with alias {} is going to expire on {}. Please update the certificate before then."; var warningMessageForExpiredCertificate = "Certificate with alias {} is expired as of {}. Please update the certificate."; if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_CONFIG_CERT)) { warningMessageForNoExpiryDate = "Certificate for MLE Requests does not have expiry date from mleForRequestPublicCertPath in merchant configuration."; warningMessageForCertificateExpiringSoon = "Certificate for MLE Requests with alias {} is going to expire on {}. Please update the certificate provided in mleForRequestPublicCertPath in merchant configuration before then."; warningMessageForExpiredCertificate = "Certificate for MLE Requests with alias {} is expired as of {}. Please update the certificate provided in mleForRequestPublicCertPath in merchant configuration."; } if (cacheKey.endsWith(Constants.MLE_CACHE_IDENTIFIER_FOR_P12_CERT)) { warningMessageForNoExpiryDate = "Certificate for MLE Requests does not have expiry date in the P12 file."; warningMessageForCertificateExpiringSoon = "Certificate for MLE Requests with alias {} is going to expire on {}. Please update the P12 file before then."; warningMessageForExpiredCertificate = "Certificate for MLE Requests with alias {} is expired as of {}. Please update the P12 file."; } // Get the certificate's notAfter date (expiry date) var notAfter = null; try { // All certificates are now in PEM format if (certificate.validity && certificate.validity.notAfter) { notAfter = certificate.validity.notAfter; } else { logger.warn("Unknown certificate format. Cannot extract expiry date."); } } catch (error) { logger.warn("Error extracting certificate expiry date: " + error.message); return; } if (!notAfter) { // Certificate does not have an expiry date logger.warn(warningMessageForNoExpiryDate); } else { var now = new Date(); if (notAfter < now) { // Certificate is already expired var expiredMessage = warningMessageForExpiredCertificate.replace("{}", keyAlias).replace("{}", notAfter.toISOString().split('T')[0]); logger.warn(expiredMessage); } else { // Calculate days until expiry var timeToExpire = notAfter.getTime() - now.getTime(); var daysToExpire = Math.floor(timeToExpire / Constants.FACTOR_DAYS_TO_MILLISECONDS); if (daysToExpire < Constants.CERTIFICATE_EXPIRY_DATE_WARNING_DAYS) { var expiringMessage = warningMessageForCertificateExpiringSoon.replace("{}", keyAlias).replace("{}", notAfter.toISOString().split('T')[0]); logger.warn(expiringMessage); } } } };