88import java .security .cert .Certificate ;
99import java .security .spec .InvalidKeySpecException ;
1010import java .security .spec .PKCS8EncodedKeySpec ;
11- import java .util .Map ;
1211import java .util .Objects ;
13- import java .util .concurrent .ConcurrentHashMap ;
12+ import java .util .concurrent .TimeUnit ;
1413
14+ import jakarta .annotation .PostConstruct ;
15+
16+ import org .cache2k .Cache ;
17+ import org .cache2k .Cache2kBuilder ;
1518import org .springframework .beans .factory .annotation .Autowired ;
19+ import org .springframework .beans .factory .annotation .Value ;
1620import org .springframework .stereotype .Component ;
1721
1822import io .mosip .kernel .core .crypto .exception .InvalidDataException ;
3337
3438/**
3539 * Private key decryption Helper class for Keymanager
36- *
40+ *
3741 * @author Mahammed Taheer
3842 * @since 1.2.1
3943 *
@@ -43,9 +47,29 @@ public class PrivateKeyDecryptorHelper {
4347
4448 private static final Logger LOGGER = KeymanagerLogger .getLogger (PrivateKeyDecryptorHelper .class );
4549
46- private Map <String , io .mosip .kernel .keymanagerservice .entity .KeyStore > cacheKeyStore = new ConcurrentHashMap <>();
50+ /**
51+ * Holds the DB KeyStore entry and its resolved referenceId together so that
52+ * both are always evicted atomically. Using two separate maps would allow
53+ * LRU eviction to evict one without the other, causing spurious
54+ * APP_ID_REFERENCE_ID_NOT_MATCHING exceptions.
55+ */
56+ private static final class CacheEntry {
57+ final KeyStore keyStore ;
58+ final String refId ;
59+ CacheEntry (KeyStore keyStore , String refId ) {
60+ this .keyStore = keyStore ;
61+ this .refId = refId ;
62+ }
63+ }
64+
65+ // Replaces the previous unbounded ConcurrentHashMaps (cacheKeyStore +
66+ // cacheReferenceIds). Bounded by entryCapacity so memory does not grow
67+ // indefinitely as new partner certificates are registered over time.
68+ private Cache <String , CacheEntry > cacheDecryptData = null ;
4769
48- private Map <String , String > cacheReferenceIds = new ConcurrentHashMap <>();
70+ // Reuses the same property as keyAliasCache for consistent cache lifecycle.
71+ @ Value ("${mosip.kernel.keymanager.key.cache.expire.inMins:1440}" )
72+ private long cacheExpireInMins ;
4973
5074 /**
5175 * Utility to generate Metadata
@@ -59,35 +83,45 @@ public class PrivateKeyDecryptorHelper {
5983 @ Autowired
6084 private ECKeyStore keyStore ;
6185
86+ @ PostConstruct
87+ public void init () {
88+ cacheDecryptData = new Cache2kBuilder <String , CacheEntry >() {}
89+ // hashCode suffix prevents name collision in test contexts where the Spring
90+ // context is reloaded multiple times within the same JVM.
91+ .name ("privateKeyDecryptorData-" + this .hashCode ())
92+ .expireAfterWrite (cacheExpireInMins , TimeUnit .MINUTES )
93+ // 1000 entries covers large MOSIP deployments with many partner certificates
94+ // while bounding the heap footprint (~3-5 KB per entry × 1000 = ~3-5 MB max).
95+ .entryCapacity (1000 )
96+ .build ();
97+ }
98+
6299 public KeyStore getDBKeyStoreData (String certThumbprintHex , String applicationId , String referenceId ) {
63100
64- KeyStore dbKeyStore = cacheKeyStore .getOrDefault (certThumbprintHex , null );
65-
66- String appIdRefIdKey = applicationId + KeymanagerConstant .HYPHEN + referenceId ;
67- String compMasterKeyRefId = applicationId + KeymanagerConstant .HYPHEN + KeymanagerConstant .COMPONENT_MASTER_KEY_DUMMY_REF ;
68- if (Objects .isNull (dbKeyStore )) {
69- dbKeyStore = dbHelper .getKeyAlias (certThumbprintHex , appIdRefIdKey , applicationId , referenceId );
70- cacheKeyStore .put (certThumbprintHex , dbKeyStore );
71- // Added condition to handle issue related to decryption error with Master key.
72- if (Objects .isNull (dbKeyStore .getPrivateKey ())) {
73- cacheReferenceIds .put (certThumbprintHex , compMasterKeyRefId );
74- } else {
75- cacheReferenceIds .put (certThumbprintHex , appIdRefIdKey );
76- }
77- }
101+ String appIdRefIdKey = applicationId + KeymanagerConstant .HYPHEN + referenceId ;
102+ String compMasterKeyRefId = applicationId + KeymanagerConstant .HYPHEN + KeymanagerConstant .COMPONENT_MASTER_KEY_DUMMY_REF ;
103+
104+ CacheEntry cacheEntry = cacheDecryptData .get (certThumbprintHex );
105+ if (Objects .isNull (cacheEntry )) {
106+ KeyStore dbKeyStore = dbHelper .getKeyAlias (certThumbprintHex , appIdRefIdKey , applicationId , referenceId );
107+ // Added condition to handle issue related to decryption error with Master key.
108+ String refIdToCache = Objects .isNull (dbKeyStore .getPrivateKey ()) ? compMasterKeyRefId : appIdRefIdKey ;
109+ cacheEntry = new CacheEntry (dbKeyStore , refIdToCache );
110+ cacheDecryptData .put (certThumbprintHex , cacheEntry );
111+ }
78112
79- String cachedRefId = cacheReferenceIds . getOrDefault ( certThumbprintHex , null ) ;
80- if (!appIdRefIdKey .equals (cachedRefId ) && !compMasterKeyRefId .equals (cachedRefId )){
113+ String cachedRefId = cacheEntry . refId ;
114+ if (!appIdRefIdKey .equals (cachedRefId ) && !compMasterKeyRefId .equals (cachedRefId )){
81115 LOGGER .error (KeymanagerConstant .SESSIONID , this .getClass ().getSimpleName (), KeymanagerConstant .EMPTY ,
82116 "Application Id & Reference ID not matching with the input thumbprint value(decrypt)." );
83117 throw new KeymanagerServiceException (KeymanagerErrorConstant .APP_ID_REFERENCE_ID_NOT_MATCHING .getErrorCode (),
84118 KeymanagerErrorConstant .APP_ID_REFERENCE_ID_NOT_MATCHING .getErrorMessage ());
85119 }
86- return dbKeyStore ;
120+ return cacheEntry . keyStore ;
87121 }
88122
89123 public Object [] getKeyObjects (KeyStore dbKeyStore , boolean fetchMasterKey ) {
90-
124+
91125 String ksAlias = dbKeyStore .getAlias ();
92126
93127 String privateKeyObj = dbKeyStore .getPrivateKey ();
@@ -105,21 +139,21 @@ public Object[] getKeyObjects(KeyStore dbKeyStore, boolean fetchMasterKey) {
105139 Certificate masterCert = masterKeyEntry .getCertificate ();
106140 return new Object [] {masterPrivateKey , masterCert };
107141 }
108-
142+
109143 String masterKeyAlias = dbKeyStore .getMasterAlias ();
110-
144+
111145 if (ksAlias .equals (masterKeyAlias ) || privateKeyObj .equals (KeymanagerConstant .KS_PK_NA )) {
112146 LOGGER .error (KeymanagerConstant .SESSIONID , KeymanagerConstant .APPLICATIONID , null ,
113147 "Not Allowed to perform decryption with other domain key." );
114148 throw new KeymanagerServiceException (KeymanagerErrorConstant .DECRYPTION_NOT_ALLOWED .getErrorCode (),
115149 KeymanagerErrorConstant .DECRYPTION_NOT_ALLOWED .getErrorMessage ());
116150 }
117-
151+
118152 PrivateKeyEntry masterKeyEntry = keyStore .getAsymmetricKey (dbKeyStore .getMasterAlias ());
119153 PrivateKey masterPrivateKey = masterKeyEntry .getPrivateKey ();
120154 PublicKey masterPublicKey = masterKeyEntry .getCertificate ().getPublicKey ();
121155 try {
122- byte [] decryptedPrivateKey = keymanagerUtil .decryptKey (CryptoUtil .decodeURLSafeBase64 (dbKeyStore .getPrivateKey ()),
156+ byte [] decryptedPrivateKey = keymanagerUtil .decryptKey (CryptoUtil .decodeURLSafeBase64 (dbKeyStore .getPrivateKey ()),
123157 masterPrivateKey , masterPublicKey );
124158 KeyFactory keyFactory = KeyFactory .getInstance (KeymanagerConstant .RSA );
125159 PrivateKey privateKey = keyFactory .generatePrivate (new PKCS8EncodedKeySpec (decryptedPrivateKey ));
@@ -131,5 +165,5 @@ public Object[] getKeyObjects(KeyStore dbKeyStore, boolean fetchMasterKey) {
131165 KeymanagerErrorConstant .CRYPTO_EXCEPTION .getErrorMessage () + e .getMessage (), e );
132166 }
133167 }
134-
168+
135169}
0 commit comments