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