diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/clientcrypto/service/impl/ClientCryptoFacade.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/clientcrypto/service/impl/ClientCryptoFacade.java index a439844d..50f41e4a 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/clientcrypto/service/impl/ClientCryptoFacade.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/clientcrypto/service/impl/ClientCryptoFacade.java @@ -158,27 +158,52 @@ public byte[] encrypt(ClientType clientType, byte[] publicKey, byte[] dataToEncr } public byte[] decrypt(byte[] dataToDecrypt) { - byte[] encryptedSecretKey = Arrays.copyOfRange(dataToDecrypt, 0, symmetricKeyLength); - byte[] secretKeyBytes = Objects.requireNonNull(getClientSecurity()).asymmetricDecrypt(encryptedSecretKey); - SecretKey secretKey = new SecretKeySpec(secretKeyBytes, "AES"); + // Extract encrypted AES key and decrypt it + byte[] encryptedSecretKey = new byte[symmetricKeyLength]; + System.arraycopy(dataToDecrypt, 0, encryptedSecretKey, 0, symmetricKeyLength); + byte[] secretKeyBytes = Objects.requireNonNull(getClientSecurity()).asymmetricDecrypt(encryptedSecretKey); + SecretKey secretKey = new SecretKeySpec(secretKeyBytes, "AES"); - try { - byte[] iv = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength, symmetricKeyLength + ivLength); - byte[] aad = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength + ivLength, symmetricKeyLength + ivLength + aadLength); - byte[] cipher = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength + ivLength + aadLength, - dataToDecrypt.length); - return cryptoCore.symmetricDecrypt(secretKey, cipher, iv, aad); - } catch (Throwable t) { - LOGGER.error("Failed to decrypt the data due to : ", t.getMessage()); - //1.1.4.4 backward compatibility code, for IV_LENGTH = 16 and AAD_LENGTH = 12; - byte[] iv = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength, symmetricKeyLength + 16); - byte[] aad = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength + 16, symmetricKeyLength + 16 + 12); - byte[] cipher = Arrays.copyOfRange(dataToDecrypt, symmetricKeyLength + 16 + 12, - dataToDecrypt.length); - return cryptoCore.symmetricDecrypt(secretKey, cipher, iv, aad); - } + // Pre-calculate offsets + final int ivOffset = symmetricKeyLength; + final int aadOffset = ivOffset + ivLength; + final int cipherOffset = aadOffset + aadLength; + + try { + byte[] iv = new byte[ivLength]; + byte[] aad = new byte[aadLength]; + byte[] cipher = new byte[dataToDecrypt.length - cipherOffset]; + + System.arraycopy(dataToDecrypt, ivOffset, iv, 0, ivLength); + System.arraycopy(dataToDecrypt, aadOffset, aad, 0, aadLength); + System.arraycopy(dataToDecrypt, cipherOffset, cipher, 0, cipher.length); + + return cryptoCore.symmetricDecrypt(secretKey, cipher, iv, aad); + + } catch (Throwable t) { + LOGGER.error("Failed to decrypt using default IV/AAD lengths. Trying fallback. Error: ", t); + + // 1.1.4.4 backward compatibility block + final int fallbackIvLength = 16; + final int fallbackAadLength = 12; + + int fallbackIvOffset = symmetricKeyLength; + int fallbackAadOffset = fallbackIvOffset + fallbackIvLength; + int fallbackCipherOffset = fallbackAadOffset + fallbackAadLength; + + byte[] iv = new byte[fallbackIvLength]; + byte[] aad = new byte[fallbackAadLength]; + byte[] cipher = new byte[dataToDecrypt.length - fallbackCipherOffset]; + + System.arraycopy(dataToDecrypt, fallbackIvOffset, iv, 0, fallbackIvLength); + System.arraycopy(dataToDecrypt, fallbackAadOffset, aad, 0, fallbackAadLength); + System.arraycopy(dataToDecrypt, fallbackCipherOffset, cipher, 0, cipher.length); + + return cryptoCore.symmetricDecrypt(secretKey, cipher, iv, aad); + } } + public static byte[] generateRandomBytes(int length) { if(secureRandom == null) secureRandom = new SecureRandom(); diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/crypto/jce/core/CryptoCore.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/crypto/jce/core/CryptoCore.java index 083bd7db..928cf72b 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/crypto/jce/core/CryptoCore.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/crypto/jce/core/CryptoCore.java @@ -31,6 +31,8 @@ import javax.crypto.spec.PSource.PSpecified; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; + +import jakarta.annotation.PreDestroy; import org.bouncycastle.crypto.InvalidCipherTextException; import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.encodings.OAEPEncoding; @@ -96,7 +98,7 @@ public class CryptoCore implements CryptoCoreSpec secureRandomThreadLocal = null; + private ThreadLocal CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC; + private ThreadLocal CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC; + private ThreadLocal SK_FACTORY_PBKDF2; + public static String SYMMETRIC_ALGO; + public static String ASYMMETRIC_ALGO; + @PostConstruct public void init() { - secureRandom = new SecureRandom(); + secureRandomThreadLocal = ThreadLocal.withInitial(() -> { + try { return SecureRandom.getInstanceStrong(); } catch (Exception ignore) { return new SecureRandom(); } + }); + + SYMMETRIC_ALGO = symmetricAlgorithm; + ASYMMETRIC_ALGO = asymmetricAlgorithm; + + CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(symmetricAlgorithm); + } catch (Exception e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); } + }); + + CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(asymmetricAlgorithm); + } catch (Exception e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); } + }); + + SK_FACTORY_PBKDF2 = ThreadLocal.withInitial(() -> { + try { return SecretKeyFactory.getInstance(passwordAlgorithm); } + catch (java.security.NoSuchAlgorithmException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + }); + } + + @PreDestroy + public void shutdown() { + if (secureRandomThreadLocal != null) + secureRandomThreadLocal.remove(); + + if (CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC != null) + CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC.remove(); + + if (CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC != null) + CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.remove(); + + if (SK_FACTORY_PBKDF2 != null) + SK_FACTORY_PBKDF2.remove(); } @Override public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] aad) { Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); CryptoUtils.verifyData(data); - Cipher cipher; - try { - cipher = Cipher.getInstance(symmetricAlgorithm); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } byte[] output = null; - byte[] randomIV = generateIV(cipher.getBlockSize()); try { + Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC.get(); + + byte[] randomIV = generateIV(cipher.getBlockSize()); + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, randomIV); cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); @@ -167,15 +220,10 @@ public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad if (iv == null) { return symmetricEncrypt(key, data, aad); } - Cipher cipher; - try { - cipher = Cipher.getInstance(symmetricAlgorithm); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } + try { + Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC.get(); + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec); @@ -197,24 +245,34 @@ public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] aad) { Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); CryptoUtils.verifyData(data); - Cipher cipher; - try { - cipher = Cipher.getInstance(symmetricAlgorithm); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } byte[] output = null; try { - byte[] randomIV = Arrays.copyOfRange(data, data.length - cipher.getBlockSize(), data.length); + Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC.get(); + + int ivLength = cipher.getBlockSize(); // Will be 16 + + if (data.length <= ivLength + (tagLength / 8)) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorCode(), + "Encrypted data too short for ciphertext and IV."); + } + + int cipherLen = data.length - ivLength; + byte[] cipherTextWithTag = new byte[cipherLen]; + byte[] iv = new byte[ivLength]; + + System.arraycopy(data, 0, cipherTextWithTag, 0, cipherLen); + System.arraycopy(data, cipherLen, iv, 0, ivLength); + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, randomIV); - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); - if (aad != null && aad.length != 0) { + GCMParameterSpec gcmSpec = new GCMParameterSpec(tagLength, iv); + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); + + if (aad != null && aad.length > 0) { cipher.updateAAD(aad); } - output = doFinal(Arrays.copyOf(data, data.length - cipher.getBlockSize()), cipher); + + return doFinal(cipherTextWithTag, cipher); } catch (java.security.InvalidKeyException e) { throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); @@ -227,7 +285,6 @@ public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] aad) { SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorCode(), SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorMessage(), e); } - return output; } @Override @@ -237,15 +294,9 @@ public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad if (iv == null) { return symmetricDecrypt(key, data, aad); } - Cipher cipher; - try { - cipher = Cipher.getInstance(symmetricAlgorithm); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } try { + Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC.get(); + SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), AES); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(tagLength, iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec); @@ -267,18 +318,11 @@ public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad public byte[] asymmetricEncrypt(PublicKey key, byte[] data) { Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); CryptoUtils.verifyData(data); - Cipher cipher; - try { - cipher = Cipher.getInstance(asymmetricAlgorithm); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } - final OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, - PSpecified.DEFAULT); + try { - cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams); + Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.get(); + cipher.init(Cipher.ENCRYPT_MODE, key, OAEP_SHA256_MGF1); + return doFinal(data, cipher); } catch (java.security.InvalidKeyException e) { throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), e.getMessage(), e); @@ -287,7 +331,6 @@ public byte[] asymmetricEncrypt(PublicKey key, byte[] data) { SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); } - return doFinal(data, cipher); } @Override @@ -358,7 +401,7 @@ private byte[] asymmetricDecrypt(PrivateKey privateKey, BigInteger keyModulus, b /** * * @param paddedPlainText - * @param privateKey + * @param keyModulus * @return */ private byte[] unpadOAEPPadding(byte[] paddedPlainText, BigInteger keyModulus) { @@ -380,11 +423,9 @@ private byte[] jceAsymmetricDecrypt(PrivateKey privateKey, byte[] data, String s CryptoUtils.verifyData(data); Cipher cipher; try { - cipher = Objects.isNull(storeType) ? Cipher.getInstance(asymmetricAlgorithm) : + cipher = Objects.isNull(storeType) ? CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.get() : Cipher.getInstance(asymmetricAlgorithm, storeType); - OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, - PSpecified.DEFAULT); - cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams); + cipher.init(Cipher.DECRYPT_MODE, privateKey, OAEP_SHA256_MGF1); return doFinal(data, cipher); } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { throw new NoSuchAlgorithmException( @@ -400,27 +441,30 @@ private byte[] jceAsymmetricDecrypt(PrivateKey privateKey, byte[] data, String s } } - @Override public String hash(byte[] data, byte[] salt) { CryptoUtils.verifyData(data); CryptoUtils.verifyData(salt, SecurityExceptionCodeConstant.SALT_PROVIDED_IS_NULL_OR_EMPTY.getErrorCode(), SecurityExceptionCodeConstant.SALT_PROVIDED_IS_NULL_OR_EMPTY.getErrorMessage()); - SecretKeyFactory secretKeyFactory; - char[] convertedData = new String(data).toCharArray(); - PBEKeySpec pbeKeySpec = new PBEKeySpec(convertedData, salt, iterations, symmetricKeyLength); + + final char[] convertedData = new String(data).toCharArray(); + final PBEKeySpec pbeKeySpec = new PBEKeySpec(convertedData, salt, iterations, symmetricKeyLength); SecretKey key; try { - secretKeyFactory = SecretKeyFactory.getInstance(passwordAlgorithm); + SecretKeyFactory secretKeyFactory = SK_FACTORY_PBKDF2.get(); key = secretKeyFactory.generateSecret(pbeKeySpec); } catch (InvalidKeySpecException e) { throw new InvalidParamSpecException( SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), e.getMessage(), e); - } catch (java.security.NoSuchAlgorithmException e) { + } catch (Exception e) { throw new NoSuchAlgorithmException( SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); } + finally { + // best-effort wipe of sensitive char[] + java.util.Arrays.fill(convertedData, '\0'); + } return DatatypeConverter.printHexBinary(key.getEncoded()); } @@ -460,13 +504,12 @@ public boolean verifySignature(byte[] data, String sign, PublicKey publicKey) { throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), e.getMessage(), e); } - } @SuppressWarnings("unchecked") @Override public SecureRandom random() { - return secureRandom; + return secureRandomThreadLocal.get(); } /** @@ -477,7 +520,7 @@ public SecureRandom random() { */ private byte[] generateIV(int blockSize) { byte[] byteIV = new byte[blockSize]; - secureRandom.nextBytes(byteIV); + secureRandomThreadLocal.get().nextBytes(byteIV); return byteIV; } @@ -542,8 +585,5 @@ public boolean verifySignature(String sign) { throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), e.getMessage(), e); } - - } - - -} + } +} \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/service/impl/CryptomanagerServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/service/impl/CryptomanagerServiceImpl.java index ee7ac953..b24de913 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/service/impl/CryptomanagerServiceImpl.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/service/impl/CryptomanagerServiceImpl.java @@ -4,7 +4,6 @@ import static io.mosip.kernel.cryptomanager.constant.CryptomanagerConstant.CACHE_INT_COUNTER; import static io.mosip.kernel.cryptomanager.constant.CryptomanagerConstant.DEFAULT_INCLUDES_FALSE; import static io.mosip.kernel.cryptomanager.constant.CryptomanagerConstant.DEFAULT_INCLUDES_TRUE; -import static java.util.Arrays.copyOfRange; import java.nio.ByteBuffer; import java.security.InvalidKeyException; @@ -107,16 +106,16 @@ public class CryptomanagerServiceImpl implements CryptomanagerService { private String signApplicationId; @Value("${mosip.keymanager.salt.params.cache.expire.inMins:30}") - private long cacheExpireInMins; + private long cacheExpireInMins; @Value("${mosip.keymanager.argon2.hash.generate.iterations:10}") - private int argon2Iterations; + private int argon2Iterations; @Value("${mosip.keymanager.argon2.hash.generate.memory.inKiB:65536}") - private int argon2Memory; + private int argon2Memory; @Value("${mosip.keymanager.argon2.hash.generate.parallelism:2}") - private int argon2Parallelism; + private int argon2Parallelism; private static SecureRandom secureRandom = null; @@ -147,36 +146,34 @@ public class CryptomanagerServiceImpl implements CryptomanagerService { private Cache saltGenParamsCache = null; @PostConstruct - public void init() { - // Added Cache2kBuilder in the postConstruct because expire value - // configured in properties are getting injected after this object creation. - // Cache2kBuilder constructor is throwing error. - - saltGenParamsCache = new Cache2kBuilder() {} - // added hashcode because test case execution failing with IllegalStateException: Cache already created - .name("saltGenParamsCache-" + this.hashCode()) - .expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) - .entryCapacity(10) - .refreshAhead(true) - .loaderThreadCount(1) - .loader((objectKey) -> { - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), - CryptomanagerConstant.GEN_ARGON2_HASH, "Loading Creating Cache for Object Key: " + objectKey); - if (objectKey.equals(CryptomanagerConstant.CACHE_AES_KEY)) { - javax.crypto.KeyGenerator keyGenerator = KeyGeneratorUtils.getKeyGenerator(AES_KEY_TYPE, - AES_KEY_SIZE, new SecureRandom()); - return keyGenerator.generateKey(); - } else if (objectKey.equals(CACHE_INT_COUNTER)) { - if(secureRandom == null) - secureRandom = new SecureRandom(); - - return new AtomicLong(secureRandom.nextLong()); - } - return null; - }) - .build(); - - } + public void init() { + // Added Cache2kBuilder in the postConstruct because expire value + // configured in properties are getting injected after this object creation. + // Cache2kBuilder constructor is throwing error. + + saltGenParamsCache = new Cache2kBuilder() { + } + // added hashcode because test case execution failing with + // IllegalStateException: Cache already created + .name("saltGenParamsCache-" + this.hashCode()).expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) + .entryCapacity(10).refreshAhead(true).loaderThreadCount(1).loader((objectKey) -> { + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, + "Loading Creating Cache for Object Key: " + objectKey); + if (objectKey.equals(CryptomanagerConstant.CACHE_AES_KEY)) { + javax.crypto.KeyGenerator keyGenerator = KeyGeneratorUtils.getKeyGenerator(AES_KEY_TYPE, + AES_KEY_SIZE, new SecureRandom()); + return keyGenerator.generateKey(); + } else if (objectKey.equals(CACHE_INT_COUNTER)) { + if (secureRandom == null) + secureRandom = new SecureRandom(); + + return new AtomicLong(secureRandom.nextLong()); + } + return null; + }).build(); + + } /* * (non-Javadoc) @@ -187,63 +184,76 @@ public void init() { */ @Override public CryptomanagerResponseDto encrypt(CryptomanagerRequestDto cryptoRequestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, - "Request for data encryption."); - - cryptomanagerUtil.validateKeyIdentifierIds(cryptoRequestDto.getApplicationId(), cryptoRequestDto.getReferenceId()); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, + "Request for data encryption."); + + cryptomanagerUtil.validateKeyIdentifierIds(cryptoRequestDto.getApplicationId(), + cryptoRequestDto.getReferenceId()); SecretKey secretKey = keyGenerator.getSymmetricKey(); final byte[] encryptedData; byte[] headerBytes = new byte[0]; if (cryptomanagerUtil.isValidSalt(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt()))) { - encryptedData = cryptoCore.symmetricEncrypt(secretKey, cryptomanagerUtil.decodeBase64Data(cryptoRequestDto.getData()), - cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt())), - cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad()))); + encryptedData = cryptoCore.symmetricEncrypt(secretKey, + cryptomanagerUtil.decodeBase64Data(cryptoRequestDto.getData()), + cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt())), + cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad()))); } else { byte[] aad = cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad())); - if (aad == null || aad.length == 0){ + if (aad == null || aad.length == 0) { encryptedData = generateAadAndEncryptData(secretKey, cryptoRequestDto.getData()); headerBytes = CryptomanagerConstant.VERSION_RSA_2048; } else { - encryptedData = cryptoCore.symmetricEncrypt(secretKey, cryptomanagerUtil.decodeBase64Data(cryptoRequestDto.getData()), - aad); + encryptedData = cryptoCore.symmetricEncrypt(secretKey, + cryptomanagerUtil.decodeBase64Data(cryptoRequestDto.getData()), aad); } } Certificate certificate = cryptomanagerUtil.getCertificate(cryptoRequestDto); - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, - "Found the cerificate, proceeding with session key encryption."); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, + "Found the cerificate, proceeding with session key encryption."); PublicKey publicKey = certificate.getPublicKey(); final byte[] encryptedSymmetricKey = cryptoCore.asymmetricEncrypt(publicKey, secretKey.getEncoded()); - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, - "Session key encryption completed."); - //boolean prependThumbprint = cryptoRequestDto.getPrependThumbprint() == null ? false : cryptoRequestDto.getPrependThumbprint(); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, + "Session key encryption completed."); + // boolean prependThumbprint = cryptoRequestDto.getPrependThumbprint() == null ? + // false : cryptoRequestDto.getPrependThumbprint(); CryptomanagerResponseDto cryptoResponseDto = new CryptomanagerResponseDto(); - // support of 1.1.3 no thumbprint is configured as true & encryption request with no thumbprint - // request thumbprint flag will not be considered if support no thumbprint is set to false. - //------------------- - // no thumbprint flag will not be required to consider at the time of encryption. So commented the below code. - // from 1.2.0.1 version, support of no thumbprint flag will be removed in case of data encryption. - /* if (noThumbprint && !prependThumbprint) { - byte[] finalEncKeyBytes = cryptomanagerUtil.concatByteArrays(headerBytes, encryptedSymmetricKey); - cryptoResponseDto.setData(CryptoUtil.encodeToURLSafeBase64(CryptoUtil.combineByteArray(encryptedData, finalEncKeyBytes, keySplitter))); - return cryptoResponseDto; - } */ - //--------------------- + // support of 1.1.3 no thumbprint is configured as true & encryption request + // with no thumbprint + // request thumbprint flag will not be considered if support no thumbprint is + // set to false. + // ------------------- + // no thumbprint flag will not be required to consider at the time of + // encryption. So commented the below code. + // from 1.2.0.1 version, support of no thumbprint flag will be removed in case + // of data encryption. + /* + * if (noThumbprint && !prependThumbprint) { byte[] finalEncKeyBytes = + * cryptomanagerUtil.concatByteArrays(headerBytes, encryptedSymmetricKey); + * cryptoResponseDto.setData(CryptoUtil.encodeToURLSafeBase64(CryptoUtil. + * combineByteArray(encryptedData, finalEncKeyBytes, keySplitter))); return + * cryptoResponseDto; } + */ + // --------------------- byte[] certThumbprint = cryptomanagerUtil.getCertificateThumbprint(certificate); byte[] concatedData = cryptomanagerUtil.concatCertThumbprint(certThumbprint, encryptedSymmetricKey); byte[] finalEncKeyBytes = cryptomanagerUtil.concatByteArrays(headerBytes, concatedData); - cryptoResponseDto.setData(CryptoUtil.encodeToURLSafeBase64(CryptoUtil.combineByteArray(encryptedData, - finalEncKeyBytes, keySplitter))); + cryptoResponseDto.setData(CryptoUtil + .encodeToURLSafeBase64(CryptoUtil.combineByteArray(encryptedData, finalEncKeyBytes, keySplitter))); return cryptoResponseDto; } - private byte[] generateAadAndEncryptData(SecretKey secretKey, String data){ - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, - "Provided AAD value is null or empty byte array. So generating random 32 bytes for AAD."); + private byte[] generateAadAndEncryptData(SecretKey secretKey, String data) { + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT, + "Provided AAD value is null or empty byte array. So generating random 32 bytes for AAD."); + byte[] aad = cryptomanagerUtil.generateRandomBytes(CryptomanagerConstant.GCM_AAD_LENGTH); - byte[] nonce = copyOfRange(aad, 0, CryptomanagerConstant.GCM_NONCE_LENGTH); - byte[] encData = cryptoCore.symmetricEncrypt(secretKey, cryptomanagerUtil.decodeBase64Data(data), - nonce, aad); + + byte[] nonce = new byte[CryptomanagerConstant.GCM_NONCE_LENGTH]; + System.arraycopy(aad, 0, nonce, 0, CryptomanagerConstant.GCM_NONCE_LENGTH); + + byte[] encData = cryptoCore.symmetricEncrypt(secretKey, cryptomanagerUtil.decodeBase64Data(data), nonce, aad); + return cryptomanagerUtil.concatByteArrays(aad, encData); } @@ -256,53 +266,82 @@ private byte[] generateAadAndEncryptData(SecretKey secretKey, String data){ */ @Override public CryptomanagerResponseDto decrypt(CryptomanagerRequestDto cryptoRequestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, CryptomanagerConstant.DECRYPT, - "Request for data decryption."); - - boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(cryptoRequestDto.getApplicationId()); - if (!hasAcccess) { - LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, CryptomanagerConstant.DECRYPT, - "Data Decryption is not allowed for the authenticated user for the provided application id."); - throw new CryptoManagerSerivceException(CryptomanagerErrorCode.DECRYPT_NOT_ALLOWED_ERROR.getErrorCode(), - CryptomanagerErrorCode.DECRYPT_NOT_ALLOWED_ERROR.getErrorMessage()); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, + CryptomanagerConstant.DECRYPT, "Request for data decryption."); + + if (!cryptomanagerUtil.hasKeyAccess(cryptoRequestDto.getApplicationId())) { + LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, + CryptomanagerConstant.DECRYPT, "Decryption not allowed for this application ID."); + throw new CryptoManagerSerivceException( + CryptomanagerErrorCode.DECRYPT_NOT_ALLOWED_ERROR.getErrorCode(), + CryptomanagerErrorCode.DECRYPT_NOT_ALLOWED_ERROR.getErrorMessage()); } - int keyDemiliterIndex = 0; + byte[] encryptedHybridData = cryptomanagerUtil.decodeBase64Data(cryptoRequestDto.getData()); - keyDemiliterIndex = CryptoUtil.getSplitterIndex(encryptedHybridData, keyDemiliterIndex, keySplitter); - byte[] encryptedKey = copyOfRange(encryptedHybridData, 0, keyDemiliterIndex); - byte[] encryptedData = copyOfRange(encryptedHybridData, keyDemiliterIndex + keySplitter.length(), - encryptedHybridData.length); - + int keyDelimiterIndex = CryptoUtil.getSplitterIndex(encryptedHybridData, 0, keySplitter); + + byte[] encryptedKey = new byte[keyDelimiterIndex]; + System.arraycopy(encryptedHybridData, 0, encryptedKey, 0, keyDelimiterIndex); + + int dataStartIndex = keyDelimiterIndex + keySplitter.length(); + int encryptedDataLength = encryptedHybridData.length - dataStartIndex; + byte[] encryptedData = new byte[encryptedDataLength]; + System.arraycopy(encryptedHybridData, dataStartIndex, encryptedData, 0, encryptedDataLength); + byte[] headerBytes = cryptomanagerUtil.parseEncryptKeyHeader(encryptedKey); - cryptoRequestDto.setData(CryptoUtil.encodeToURLSafeBase64(copyOfRange(encryptedKey, headerBytes.length, encryptedKey.length))); + + // Set only the actual key bytes (excluding header) into the DTO + int headerLength = headerBytes.length; + byte[] rawKey = new byte[encryptedKey.length - headerLength]; + System.arraycopy(encryptedKey, headerLength, rawKey, 0, rawKey.length); + cryptoRequestDto.setData(CryptoUtil.encodeToURLSafeBase64(rawKey)); + SecretKey decryptedSymmetricKey = cryptomanagerUtil.getDecryptedSymmetricKey(cryptoRequestDto); - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, CryptomanagerConstant.DECRYPT, - "Session Key Decryption completed."); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, + CryptomanagerConstant.DECRYPT, "Session Key Decryption completed."); + final byte[] decryptedData; - if (cryptomanagerUtil.isValidSalt(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt()))) { - decryptedData = cryptoCore.symmetricDecrypt(decryptedSymmetricKey, encryptedData, - cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt())), - cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad()))); + String salt = CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getSalt()); + String aad = CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad()); + + if (cryptomanagerUtil.isValidSalt(salt)) { + decryptedData = cryptoCore.symmetricDecrypt( + decryptedSymmetricKey, + encryptedData, + cryptomanagerUtil.decodeBase64Data(salt), + cryptomanagerUtil.decodeBase64Data(aad)); } else { if (Arrays.equals(headerBytes, CryptomanagerConstant.VERSION_RSA_2048)) { decryptedData = splitAadAndDecryptData(decryptedSymmetricKey, encryptedData); } else { - decryptedData = cryptoCore.symmetricDecrypt(decryptedSymmetricKey, encryptedData, - cryptomanagerUtil.decodeBase64Data(CryptomanagerUtils.nullOrTrim(cryptoRequestDto.getAad()))); + decryptedData = cryptoCore.symmetricDecrypt( + decryptedSymmetricKey, + encryptedData, + cryptomanagerUtil.decodeBase64Data(aad)); } } - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, CryptomanagerConstant.DECRYPT, - "Data decryption completed."); - CryptomanagerResponseDto cryptoResponseDto = new CryptomanagerResponseDto(); - cryptoResponseDto.setData(CryptoUtil.encodeToURLSafeBase64(decryptedData)); - return cryptoResponseDto; + + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.DECRYPT, + CryptomanagerConstant.DECRYPT, "Data decryption completed."); + + CryptomanagerResponseDto responseDto = new CryptomanagerResponseDto(); + responseDto.setData(CryptoUtil.encodeToURLSafeBase64(decryptedData)); + return responseDto; } private byte[] splitAadAndDecryptData(SecretKey symmetricKey, byte[] encryptedData) { + int aadLength = CryptomanagerConstant.GCM_AAD_LENGTH; + int nonceLength = CryptomanagerConstant.GCM_NONCE_LENGTH; + int finalEncDataLength = encryptedData.length - aadLength; + + byte[] aad = new byte[aadLength]; + byte[] nonce = new byte[nonceLength]; + byte[] finalEncData = new byte[finalEncDataLength]; + + System.arraycopy(encryptedData, 0, aad, 0, aadLength); + System.arraycopy(aad, 0, nonce, 0, nonceLength); + System.arraycopy(encryptedData, aadLength, finalEncData, 0, finalEncDataLength); - byte[] aad = copyOfRange(encryptedData, 0, CryptomanagerConstant.GCM_AAD_LENGTH); - byte[] nonce = copyOfRange(aad, 0, CryptomanagerConstant.GCM_NONCE_LENGTH); - byte[] finalEncData = copyOfRange(encryptedData, CryptomanagerConstant.GCM_AAD_LENGTH, encryptedData.length); return cryptoCore.symmetricDecrypt(symmetricKey, finalEncData, nonce, aad); } @@ -310,25 +349,25 @@ private byte[] splitAadAndDecryptData(SecretKey symmetricKey, byte[] encryptedDa * (non-Javadoc) * * @see - * io.mosip.kernel.cryptomanager.service.CryptomanagerService#encryptWithPin(io.mosip. - * kernel.cryptomanager.dto.CryptoWithPinRequestDto) + * io.mosip.kernel.cryptomanager.service.CryptomanagerService#encryptWithPin(io. + * mosip. kernel.cryptomanager.dto.CryptoWithPinRequestDto) */ @Override public CryptoWithPinResponseDto encryptWithPin(CryptoWithPinRequestDto requestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, CryptomanagerConstant.ENCRYPT_PIN, - "Request for data encryption with Pin."); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, + CryptomanagerConstant.ENCRYPT_PIN, "Request for data encryption with Pin."); String dataToEnc = requestDto.getData(); String userPin = requestDto.getUserPin(); - if(!cryptomanagerUtil.isDataValid(dataToEnc) || !cryptomanagerUtil.isDataValid(userPin)) { - LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, CryptomanagerConstant.ENCRYPT_PIN, - "Either Data to encrypt or user pin is blank."); + if (!cryptomanagerUtil.isDataValid(dataToEnc) || !cryptomanagerUtil.isDataValid(userPin)) { + LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, + CryptomanagerConstant.ENCRYPT_PIN, "Either Data to encrypt or user pin is blank."); throw new CryptoManagerSerivceException(CryptomanagerErrorCode.INVALID_REQUEST.getErrorCode(), - CryptomanagerErrorCode.INVALID_REQUEST.getErrorMessage()); + CryptomanagerErrorCode.INVALID_REQUEST.getErrorMessage()); } - SecureRandom sRandom = new SecureRandom(); + SecureRandom sRandom = new SecureRandom(); byte[] pbeSalt = new byte[PBE_SALT_LENGTH]; sRandom.nextBytes(pbeSalt); @@ -350,31 +389,40 @@ public CryptoWithPinResponseDto encryptWithPin(CryptoWithPinRequestDto requestDt * (non-Javadoc) * * @see - * io.mosip.kernel.cryptomanager.service.CryptomanagerService#decryptWithPin(io.mosip. - * kernel.cryptomanager.dto.CryptoWithPinRequestDto) + * io.mosip.kernel.cryptomanager.service.CryptomanagerService#decryptWithPin(io. + * mosip. kernel.cryptomanager.dto.CryptoWithPinRequestDto) */ @Override public CryptoWithPinResponseDto decryptWithPin(CryptoWithPinRequestDto requestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, CryptomanagerConstant.ENCRYPT_PIN, - "Request for data decryption with Pin."); + LOGGER.info(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, + CryptomanagerConstant.ENCRYPT_PIN, "Request for data decryption with Pin."); String dataToDec = requestDto.getData(); String userPin = requestDto.getUserPin(); - if(!cryptomanagerUtil.isDataValid(dataToDec) || !cryptomanagerUtil.isDataValid(userPin)) { - LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, CryptomanagerConstant.ENCRYPT_PIN, - "Either Data to decrypt or user pin is blank."); - throw new CryptoManagerSerivceException(CryptomanagerErrorCode.INVALID_REQUEST.getErrorCode(), - CryptomanagerErrorCode.INVALID_REQUEST.getErrorMessage()); + if (!cryptomanagerUtil.isDataValid(dataToDec) || !cryptomanagerUtil.isDataValid(userPin)) { + LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT_PIN, + CryptomanagerConstant.ENCRYPT_PIN, "Either Data to decrypt or user pin is blank."); + throw new CryptoManagerSerivceException( + CryptomanagerErrorCode.INVALID_REQUEST.getErrorCode(), + CryptomanagerErrorCode.INVALID_REQUEST.getErrorMessage()); } byte[] decodedEncryptedData = CryptoUtil.decodeURLSafeBase64(dataToDec); - byte[] pbeSalt = Arrays.copyOfRange(decodedEncryptedData, 0, PBE_SALT_LENGTH); - byte[] gcmNonce = Arrays.copyOfRange(decodedEncryptedData, PBE_SALT_LENGTH, PBE_SALT_LENGTH + GCM_NONCE_LENGTH); - byte[] encryptedData = Arrays.copyOfRange(decodedEncryptedData, PBE_SALT_LENGTH + GCM_NONCE_LENGTH, decodedEncryptedData.length); + + byte[] pbeSalt = new byte[PBE_SALT_LENGTH]; + byte[] gcmNonce = new byte[GCM_NONCE_LENGTH]; + int encOffset = PBE_SALT_LENGTH + GCM_NONCE_LENGTH; + int encLength = decodedEncryptedData.length - encOffset; + byte[] encryptedData = new byte[encLength]; + + System.arraycopy(decodedEncryptedData, 0, pbeSalt, 0, PBE_SALT_LENGTH); + System.arraycopy(decodedEncryptedData, PBE_SALT_LENGTH, gcmNonce, 0, GCM_NONCE_LENGTH); + System.arraycopy(decodedEncryptedData, encOffset, encryptedData, 0, encLength); SecretKey derivedKey = getDerivedKey(userPin, pbeSalt); - byte[] decryptedData = cryptoCore.symmetricDecrypt(derivedKey, encryptedData, gcmNonce, pbeSalt); + byte[] decryptedData = cryptoCore.symmetricDecrypt(derivedKey, encryptedData, gcmNonce, pbeSalt); + CryptoWithPinResponseDto responseDto = new CryptoWithPinResponseDto(); responseDto.setData(new String(decryptedData)); return responseDto; @@ -388,28 +436,31 @@ private SecretKey getDerivedKey(String userPin, byte[] salt) { @Override public JWTCipherResponseDto jwtEncrypt(JWTEncryptRequestDto jwtEncryptRequestDto) { - - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Request for JWE Encryption. Input Application Id:" + jwtEncryptRequestDto.getApplicationId() + - ", Reference Id: " + jwtEncryptRequestDto.getReferenceId()); + + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, + "Request for JWE Encryption. Input Application Id:" + jwtEncryptRequestDto.getApplicationId() + + ", Reference Id: " + jwtEncryptRequestDto.getReferenceId()); Certificate encCertificate = null; if (cryptomanagerUtil.isDataValid(jwtEncryptRequestDto.getX509Certificate())) { encCertificate = cryptomanagerUtil.convertToCertificate(jwtEncryptRequestDto.getX509Certificate()); - } + } if (Objects.isNull(encCertificate)) { - cryptomanagerUtil.validateKeyIdentifierIds(jwtEncryptRequestDto.getApplicationId(), jwtEncryptRequestDto.getReferenceId()); + cryptomanagerUtil.validateKeyIdentifierIds(jwtEncryptRequestDto.getApplicationId(), + jwtEncryptRequestDto.getReferenceId()); encCertificate = cryptomanagerUtil.getCertificate(jwtEncryptRequestDto.getApplicationId(), - jwtEncryptRequestDto.getReferenceId()); - // getCertificate should return a valid certificate for encryption. If no certificate is available, - // getCertificate will automatically throws an exception. So not checking for null for encCertificate. + jwtEncryptRequestDto.getReferenceId()); + // getCertificate should return a valid certificate for encryption. If no + // certificate is available, + // getCertificate will automatically throws an exception. So not checking for + // null for encCertificate. } - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Found the cerificate, Validating Encryption Certificate key size."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, + "Found the cerificate, Validating Encryption Certificate key size."); cryptomanagerUtil.validateEncKeySize(encCertificate); - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Key Size validated, validing input data."); - + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, + "Key Size validated, validing input data."); + String dataToEncrypt = jwtEncryptRequestDto.getData(); cryptomanagerUtil.validateEncryptData(dataToEncrypt); @@ -419,41 +470,45 @@ public JWTCipherResponseDto jwtEncrypt(JWTEncryptRequestDto jwtEncryptRequestDto cryptomanagerUtil.checkForValidJsonData(decodedDataToEncrypt); } - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Input Data validated, proceeding with JWE Encryption."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, + "Input Data validated, proceeding with JWE Encryption."); - boolean enableDefCompression = cryptomanagerUtil.isIncludeAttrsValid(jwtEncryptRequestDto.getEnableDefCompression(), - DEFAULT_INCLUDES_TRUE); + boolean enableDefCompression = cryptomanagerUtil + .isIncludeAttrsValid(jwtEncryptRequestDto.getEnableDefCompression(), DEFAULT_INCLUDES_TRUE); boolean includeCertificate = cryptomanagerUtil.isIncludeAttrsValid(jwtEncryptRequestDto.getIncludeCertificate(), - DEFAULT_INCLUDES_FALSE); + DEFAULT_INCLUDES_FALSE); boolean includeCertHash = cryptomanagerUtil.isIncludeAttrsValid(jwtEncryptRequestDto.getIncludeCertHash(), - DEFAULT_INCLUDES_FALSE); + DEFAULT_INCLUDES_FALSE); - String certificateUrl = cryptomanagerUtil.isDataValid(jwtEncryptRequestDto.getJwkSetUrl()) ? - jwtEncryptRequestDto.getJwkSetUrl(): null; + String certificateUrl = cryptomanagerUtil.isDataValid(jwtEncryptRequestDto.getJwkSetUrl()) + ? jwtEncryptRequestDto.getJwkSetUrl() + : null; - String jweEncryptedData = jwtRsaOaep256AesGcmEncrypt(decodedDataToEncrypt, encCertificate, enableDefCompression, - includeCertificate, includeCertHash, certificateUrl); + String jweEncryptedData = jwtRsaOaep256AesGcmEncrypt(decodedDataToEncrypt, encCertificate, enableDefCompression, + includeCertificate, includeCertHash, certificateUrl); JWTCipherResponseDto jwtCipherResponseDto = new JWTCipherResponseDto(); jwtCipherResponseDto.setData(jweEncryptedData); jwtCipherResponseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); return jwtCipherResponseDto; } - private String jwtRsaOaep256AesGcmEncrypt(String dataToEncrypt, Certificate certificate, boolean enableDefCompression, - boolean includeCertificate, boolean includeCertHash, String certificateUrl) { - - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "JWE Encryption Started."); - + private String jwtRsaOaep256AesGcmEncrypt(String dataToEncrypt, Certificate certificate, + boolean enableDefCompression, boolean includeCertificate, boolean includeCertHash, String certificateUrl) { + + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, + "JWE Encryption Started."); + JsonWebEncryption jsonWebEncrypt = new JsonWebEncryption(); - jsonWebEncrypt.setHeader(CryptomanagerConstant.JSON_CONTENT_TYPE_KEY, CryptomanagerConstant.JSON_CONTENT_TYPE_VALUE); - jsonWebEncrypt.setHeader(CryptomanagerConstant.JSON_HEADER_TYPE_KEY, CryptomanagerConstant.JSON_CONTENT_TYPE_VALUE); + jsonWebEncrypt.setHeader(CryptomanagerConstant.JSON_CONTENT_TYPE_KEY, + CryptomanagerConstant.JSON_CONTENT_TYPE_VALUE); + jsonWebEncrypt.setHeader(CryptomanagerConstant.JSON_HEADER_TYPE_KEY, + CryptomanagerConstant.JSON_CONTENT_TYPE_VALUE); jsonWebEncrypt.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256); jsonWebEncrypt.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM); jsonWebEncrypt.setKey(certificate.getPublicKey()); - String certThumbprint = CryptoUtil.encodeToURLSafeBase64(cryptomanagerUtil.getCertificateThumbprint(certificate)); + String certThumbprint = CryptoUtil + .encodeToURLSafeBase64(cryptomanagerUtil.getCertificateThumbprint(certificate)); jsonWebEncrypt.setKeyIdHeaderValue(certThumbprint); byte[] nonce = cryptomanagerUtil.generateRandomBytes(CryptomanagerConstant.GCM_NONCE_LENGTH); jsonWebEncrypt.setIv(nonce); @@ -463,7 +518,7 @@ private String jwtRsaOaep256AesGcmEncrypt(String dataToEncrypt, Certificate cert } if (includeCertificate) { - jsonWebEncrypt.setCertificateChainHeaderValue(new X509Certificate[] { (X509Certificate)certificate }); + jsonWebEncrypt.setCertificateChainHeaderValue(new X509Certificate[] { (X509Certificate) certificate }); } if (includeCertHash) { @@ -476,13 +531,13 @@ private String jwtRsaOaep256AesGcmEncrypt(String dataToEncrypt, Certificate cert jsonWebEncrypt.setPayload(dataToEncrypt); try { String encryptedData = jsonWebEncrypt.getCompactSerialization(); - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "JWE Encryption Completed."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_ENCRYPT, "JWE Encryption Completed."); return encryptedData; } catch (JoseException e) { - LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Error occurred while Json Web Encryption Data."); - throw new CryptoManagerSerivceException(CryptomanagerErrorCode.JWE_ENCRYPTION_INTERNAL_ERROR.getErrorCode(), + LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_ENCRYPT, "Error occurred while Json Web Encryption Data."); + throw new CryptoManagerSerivceException(CryptomanagerErrorCode.JWE_ENCRYPTION_INTERNAL_ERROR.getErrorCode(), CryptomanagerErrorCode.JWE_ENCRYPTION_INTERNAL_ERROR.getErrorMessage(), e); } } @@ -490,40 +545,42 @@ private String jwtRsaOaep256AesGcmEncrypt(String dataToEncrypt, Certificate cert @Override public JWTCipherResponseDto jwtDecrypt(JWTDecryptRequestDto jwtDecryptRequestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Request for JWE Decryption. Input Application Id:" + jwtDecryptRequestDto.getApplicationId() + - ", Reference Id: " + jwtDecryptRequestDto.getReferenceId()); - - cryptomanagerUtil.validateKeyIdentifierIds(jwtDecryptRequestDto.getApplicationId(), jwtDecryptRequestDto.getReferenceId()); - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Application Id and Reference Id validation completed, Validating Input Enc Data."); - + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, + "Request for JWE Decryption. Input Application Id:" + jwtDecryptRequestDto.getApplicationId() + + ", Reference Id: " + jwtDecryptRequestDto.getReferenceId()); + + cryptomanagerUtil.validateKeyIdentifierIds(jwtDecryptRequestDto.getApplicationId(), + jwtDecryptRequestDto.getReferenceId()); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, + "Application Id and Reference Id validation completed, Validating Input Enc Data."); + String dataToDecrypt = jwtDecryptRequestDto.getEncData(); if (!cryptomanagerUtil.isDataValid(dataToDecrypt)) { - LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Provided Data to Decrypt is invalid."); + LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_DECRYPT, "Provided Data to Decrypt is invalid."); throw new CryptoManagerSerivceException(CryptomanagerErrorCode.INVALID_REQUEST.getErrorCode(), CryptomanagerErrorCode.INVALID_REQUEST.getErrorMessage()); } - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Input Enc Data validated, proceeding with JWE Decryption."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, + "Input Enc Data validated, proceeding with JWE Decryption."); JsonWebEncryption jsonWebDecrypt = new JsonWebEncryption(); setEncryptedData(jsonWebDecrypt, dataToDecrypt); String keyId = jsonWebDecrypt.getKeyIdHeaderValue(); String certThumbprintHex = Hex.toHexString(CryptoUtil.decodeURLSafeBase64(keyId)).toUpperCase(); - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Fetched KeyId(CertificateThumbprint) from JWT Header, TP Value: " + certThumbprintHex); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, + "Fetched KeyId(CertificateThumbprint) from JWT Header, TP Value: " + certThumbprintHex); String applicationId = jwtDecryptRequestDto.getApplicationId(); String referenceId = jwtDecryptRequestDto.getReferenceId(); - KeyStore dbKeyStoreObj = privateKeyDecryptorHelper.getDBKeyStoreData(certThumbprintHex, applicationId, referenceId); + KeyStore dbKeyStoreObj = privateKeyDecryptorHelper.getDBKeyStoreData(certThumbprintHex, applicationId, + referenceId); Object[] keys = privateKeyDecryptorHelper.getKeyObjects(dbKeyStoreObj, false); PrivateKey privateKey = (PrivateKey) keys[0]; - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Private Key Retrival completed, processing with JWE Decryption."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, + "Private Key Retrival completed, processing with JWE Decryption."); String decryptedData = getDecryptedData(jsonWebDecrypt, privateKey); JWTCipherResponseDto jwtCipherResponseDto = new JWTCipherResponseDto(); @@ -534,12 +591,12 @@ public JWTCipherResponseDto jwtDecrypt(JWTDecryptRequestDto jwtDecryptRequestDto private void setEncryptedData(JsonWebEncryption jsonWebDecrypt, String dataToDecrypt) { try { - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Setting Encrypted Data for decryption."); - jsonWebDecrypt.setCompactSerialization(dataToDecrypt); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_DECRYPT, "Setting Encrypted Data for decryption."); + jsonWebDecrypt.setCompactSerialization(dataToDecrypt); } catch (JoseException e) { - LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Error occurred while Json Web Decryption Data."); + LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_ENCRYPT, "Error occurred while Json Web Decryption Data."); throw new CryptoManagerSerivceException(CryptomanagerErrorCode.JWE_DECRYPTION_INTERNAL_ERROR.getErrorCode(), CryptomanagerErrorCode.JWE_DECRYPTION_INTERNAL_ERROR.getErrorMessage(), e); } @@ -548,14 +605,14 @@ private void setEncryptedData(JsonWebEncryption jsonWebDecrypt, String dataToDec private String getDecryptedData(JsonWebEncryption jsonWebDecrypt, PrivateKey privateKey) { try { jsonWebDecrypt.setKey(privateKey); - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_DECRYPT, - "Decrypting input encrypted Data."); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_DECRYPT, "Decrypting input encrypted Data."); String decryptedData = jsonWebDecrypt.getPlaintextString(); keymanagerUtil.destoryKey(privateKey); return decryptedData; } catch (JoseException e) { - LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT, - "Error occurred while Json Web Decryption Data."); + LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.JWT_ENCRYPT, "Error occurred while Json Web Decryption Data."); throw new CryptoManagerSerivceException(CryptomanagerErrorCode.JWE_DECRYPTION_INTERNAL_ERROR.getErrorCode(), CryptomanagerErrorCode.JWE_DECRYPTION_INTERNAL_ERROR.getErrorMessage(), e); } @@ -563,9 +620,9 @@ private String getDecryptedData(JsonWebEncryption jsonWebDecrypt, PrivateKey pri @Override public Argon2GenerateHashResponseDto generateArgon2Hash(Argon2GenerateHashRequestDto argon2GenHashRequestDto) { - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.GEN_ARGON2_HASH, - "Request for Argon2 Hash Geneation."); - + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, "Request for Argon2 Hash Geneation."); + cryptomanagerUtil.validateInputData(argon2GenHashRequestDto.getInputData()); String inputData = argon2GenHashRequestDto.getInputData(); @@ -575,11 +632,11 @@ public Argon2GenerateHashResponseDto generateArgon2Hash(Argon2GenerateHashReques SecretKey aesKey = (SecretKey) saltGenParamsCache.get(CryptomanagerConstant.CACHE_AES_KEY); AtomicLong intCounter = (AtomicLong) saltGenParamsCache.get(CryptomanagerConstant.CACHE_INT_COUNTER); if (Objects.isNull(intCounter)) { - if(secureRandom == null) + if (secureRandom == null) secureRandom = new SecureRandom(); intCounter = new AtomicLong(secureRandom.nextLong()); } - long saltInput = intCounter.getAndIncrement(); + long saltInput = intCounter.getAndIncrement(); saltGenParamsCache.put(CryptomanagerConstant.CACHE_INT_COUNTER, intCounter); saltBytes = getSaltBytes(getLongBytes(saltInput), aesKey); @@ -587,16 +644,17 @@ public Argon2GenerateHashResponseDto generateArgon2Hash(Argon2GenerateHashReques } else { saltBytes = CryptoUtil.decodeURLSafeBase64(saltData); } - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.GEN_ARGON2_HASH, - "InputData is valid and salt bytes generated."); - Argon2Advanced argon2Advanced = Argon2Factory.createAdvanced(Argon2Types.ARGON2id); + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, "InputData is valid and salt bytes generated."); + Argon2Advanced argon2Advanced = Argon2Factory.createAdvanced(Argon2Types.ARGON2id); char[] inputDataCharArr = inputData.toCharArray(); - byte[] argon2Hash = argon2Advanced.rawHash(argon2Iterations, argon2Memory, argon2Parallelism, inputDataCharArr, saltBytes); + byte[] argon2Hash = argon2Advanced.rawHash(argon2Iterations, argon2Memory, argon2Parallelism, inputDataCharArr, + saltBytes); String argon2HashStr = CryptoUtil.encodeToURLSafeBase64(argon2Hash); inputDataCharArr = null; - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.GEN_ARGON2_HASH, - "Argon to hash generation done."); - + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, "Argon to hash generation done."); + Argon2GenerateHashResponseDto hashResponseDto = new Argon2GenerateHashResponseDto(); hashResponseDto.setHashValue(argon2HashStr); hashResponseDto.setSalt(saltData); @@ -614,20 +672,20 @@ private byte[] getSaltBytes(byte[] randomBytes, SecretKey aesKey) { Cipher cipher = Cipher.getInstance(AES_GCM_ALGO); cipher.init(Cipher.ENCRYPT_MODE, aesKey); return cipher.doFinal(randomBytes, 0, randomBytes.length); - } catch(NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException - | IllegalBlockSizeException | BadPaddingException | IllegalArgumentException e) { - LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), - CryptomanagerConstant.GEN_ARGON2_HASH, "Error generation of random salt.", e); + } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | IllegalBlockSizeException + | BadPaddingException | IllegalArgumentException e) { + LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, "Error generation of random salt.", e); } - LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.GEN_ARGON2_HASH, - "Generating Random Salt using Secure Random because encrypted random bytes failed."); - if(secureRandom == null) - secureRandom = new SecureRandom(); - - byte[] bytes = new byte[32]; - secureRandom.nextBytes(bytes); - return bytes; + LOGGER.info(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), + CryptomanagerConstant.GEN_ARGON2_HASH, + "Generating Random Salt using Secure Random because encrypted random bytes failed."); + if (secureRandom == null) + secureRandom = new SecureRandom(); + + byte[] bytes = new byte[32]; + secureRandom.nextBytes(bytes); + return bytes; } - } diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/util/CryptomanagerUtils.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/util/CryptomanagerUtils.java index 7964dc77..9511af93 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/util/CryptomanagerUtils.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/cryptomanager/util/CryptomanagerUtils.java @@ -248,12 +248,25 @@ public byte[] concatByteArrays(byte[] array1, byte[] array2){ return finalData; } - public byte[] parseEncryptKeyHeader(byte[] encryptedKey){ - byte[] versionHeaderBytes = Arrays.copyOfRange(encryptedKey, 0, CryptomanagerConstant.VERSION_RSA_2048.length); - if (!Arrays.equals(versionHeaderBytes, CryptomanagerConstant.VERSION_RSA_2048)) { - return new byte[0]; - } - return versionHeaderBytes; + public byte[] parseEncryptKeyHeader(byte[] encryptedKey) { + // Null or too-short input check + if (encryptedKey == null || encryptedKey.length < CryptomanagerConstant.VERSION_RSA_2048.length) { + return new byte[0]; // Return empty to indicate invalid or absent header + } + + int headerLen = CryptomanagerConstant.VERSION_RSA_2048.length; + + // Manual byte-by-byte comparison for performance and safety + for (int i = 0; i < headerLen; i++) { + if (encryptedKey[i] != CryptomanagerConstant.VERSION_RSA_2048[i]) { + return new byte[0]; // Header mismatch + } + } + + // Efficient copy using System.arraycopy + byte[] versionHeader = new byte[headerLen]; + System.arraycopy(encryptedKey, 0, versionHeader, 0, headerLen); + return versionHeader; } public boolean isDataValid(String anyData) { diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/helper/SessionKeyDecrytorHelper.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/helper/SessionKeyDecrytorHelper.java index 923f3337..6a6232f6 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/helper/SessionKeyDecrytorHelper.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/helper/SessionKeyDecrytorHelper.java @@ -127,11 +127,13 @@ public SymmetricKeyResponseDto decryptSessionKey(SymmetricKeyRequestDto symmetri private SymmetricKeyResponseDto decryptSymmetricKeyWithKeyIdentifier(String applicationId, String referenceId, byte[] encryptedData, LocalDateTime localDateTimeStamp) { - - byte[] certThumbprint = Arrays.copyOfRange(encryptedData, 0, CryptomanagerConstant.THUMBPRINT_LENGTH); - byte[] encryptedSymmetricKey = Arrays.copyOfRange(encryptedData, CryptomanagerConstant.THUMBPRINT_LENGTH, - encryptedData.length); - String certThumbprintHex = Hex.toHexString(certThumbprint).toUpperCase(); + byte[] certThumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + byte[] encryptedSymmetricKey = new byte[encryptedData.length - CryptomanagerConstant.THUMBPRINT_LENGTH]; + + System.arraycopy(encryptedData, 0, certThumbprint, 0, CryptomanagerConstant.THUMBPRINT_LENGTH); + System.arraycopy(encryptedData, CryptomanagerConstant.THUMBPRINT_LENGTH, encryptedSymmetricKey, 0, encryptedSymmetricKey.length); + + String certThumbprintHex = Hex.toHexString(certThumbprint).toUpperCase(); /* io.mosip.kernel.keymanagerservice.entity.KeyStore dbKeyStore = cacheKeyStore.getOrDefault(certThumbprintHex, null); String appIdRefIdKey = applicationId + KeymanagerConstant.HYPHEN + referenceId; @@ -161,7 +163,6 @@ private SymmetricKeyResponseDto decryptSymmetricKeyWithKeyIdentifier(String appl byte[] decryptedSymmetricKey = decryptSessionKeyWithCertificateThumbprint(dbKeyStore, encryptedSymmetricKey, referenceId); keyResponseDto.setSymmetricKey(CryptoUtil.encodeToURLSafeBase64(decryptedSymmetricKey)); return keyResponseDto; - } private byte[] decryptSessionKeyWithCertificateThumbprint(io.mosip.kernel.keymanagerservice.entity.KeyStore dbKeyStore, diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/util/KeymanagerUtil.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/util/KeymanagerUtil.java index 27215a9d..2dfa49e3 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/util/KeymanagerUtil.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/keymanagerservice/util/KeymanagerUtil.java @@ -1,7 +1,5 @@ package io.mosip.kernel.keymanagerservice.util; -import static java.util.Arrays.copyOfRange; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -256,19 +254,29 @@ public byte[] decryptKey(byte[] key, PrivateKey privateKey, PublicKey publicKey) } public byte[] decryptKey(byte[] key, PrivateKey privateKey, PublicKey publicKey, String keystoreType) { - - int keyDemiliterIndex = 0; - final int cipherKeyandDataLength = key.length; final int keySplitterLength = keySplitter.length(); - keyDemiliterIndex = CryptoUtil.getSplitterIndex(key, keyDemiliterIndex, keySplitter); - byte[] encryptedKey = copyOfRange(key, 0, keyDemiliterIndex); - byte[] encryptedData = copyOfRange(key, keyDemiliterIndex + keySplitterLength, cipherKeyandDataLength); + final int keyDelimiterIndex = CryptoUtil.getSplitterIndex(key, 0, keySplitter); + if (keyDelimiterIndex < 0 || keyDelimiterIndex + keySplitterLength >= key.length) { + throw new IllegalArgumentException("Splitter not found or invalid key format"); + } + + // Split encrypted key and encrypted data + byte[] encryptedKey = new byte[keyDelimiterIndex]; + System.arraycopy(key, 0, encryptedKey, 0, keyDelimiterIndex); + + int encryptedDataLen = key.length - (keyDelimiterIndex + keySplitterLength); + byte[] encryptedData = new byte[encryptedDataLen]; + System.arraycopy(key, keyDelimiterIndex + keySplitterLength, encryptedData, 0, encryptedDataLen); + + // Decrypt asymmetric key byte[] decryptedSymmetricKey = cryptoCore.asymmetricDecrypt(privateKey, publicKey, encryptedKey, keystoreType); - SecretKey symmetricKey = new SecretKeySpec(decryptedSymmetricKey, 0, decryptedSymmetricKey.length, - symmetricAlgorithmName); + SecretKey symmetricKey = new SecretKeySpec(decryptedSymmetricKey, symmetricAlgorithmName); + + // Symmetric decryption (AAD = null) return cryptoCore.symmetricDecrypt(symmetricKey, encryptedData, null); } + /** * Parse a date string of pattern UTC_DATETIME_PATTERN into * {@link LocalDateTime} diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java index feca3181..f453351e 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java @@ -127,24 +127,23 @@ public class PartnerCertificateManagerServiceImpl implements PartnerCertificateM @Value("${mosip.kernel.partner.cacertificate.upload.minimumvalidity.month:12}") private int minValidity; - /** * Utility to generate Metadata */ @Autowired - KeymanagerUtil keymanagerUtil; + private KeymanagerUtil keymanagerUtil; /** * Utility to generate Metadata */ @Autowired - PartnerCertManagerDBHelper certDBHelper; + private PartnerCertManagerDBHelper certDBHelper; /** * Repository to get CA certificate */ @Autowired - CACertificateStoreRepository caCertificateStoreRepository; + private CACertificateStoreRepository caCertificateStoreRepository; /** * Keystore instance to handles and store cryptographic keys. @@ -156,15 +155,32 @@ public class PartnerCertificateManagerServiceImpl implements PartnerCertificateM private KeymanagerService keymanagerService; private Cache caCertTrustStore = null; - + @Autowired - CryptomanagerUtils cryptomanagerUtil; + private CryptomanagerUtils cryptomanagerUtil; @Autowired - KeyAliasRepository keyAliasRepository; + private KeyAliasRepository keyAliasRepository; @Autowired - PartnerCertManagerDBHelper partnerCertManagerDBHelper; + private PartnerCertManagerDBHelper partnerCertManagerDBHelper; + + // --- New fast-path caches --- + private Cache> certPathCache; // per (domain:leafThumbprint) + private Cache domainIndexCache; // per domain (indexes of intermediates) + + // Thread-local primitives to avoid repeated allocations + private static final ThreadLocal CPV = + ThreadLocal.withInitial(() -> { + try { return java.security.cert.CertPathValidator.getInstance("PKIX"); } + catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } + }); + + private static final ThreadLocal CF = + ThreadLocal.withInitial(() -> { + try { return java.security.cert.CertificateFactory.getInstance("X.509"); } + catch (CertificateException e) { throw new RuntimeException(e); } + }); @PostConstruct public void init() { @@ -186,6 +202,35 @@ public void init() { return certDBHelper.getTrustAnchors(partnerDomain); }) .build(); + + certPathCache = new Cache2kBuilder>() {} + .name("certPathCache-" + this.hashCode()) + .expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) + .entryCapacity(2000) + .build(); + + domainIndexCache = new Cache2kBuilder() {} + .name("domainIndex-" + this.hashCode()) + .expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) + .entryCapacity(10) + .refreshAhead(true) + .loader((partnerDomain) -> { + @SuppressWarnings("unchecked") + Map> m = (Map>) caCertTrustStore.get(partnerDomain); + Set roots = (Set) m.get(PartnerCertManagerConstants.TRUST_ROOT); + Set inters = (Set) m.get(PartnerCertManagerConstants.TRUST_INTER); + // Defensive copy so later filtering doesn’t mutate shared set + inters = new HashSet<>(inters); + // Optional shrink: keep only currently valid intermediates + final LocalDateTime now = DateUtils.getUTCCurrentDateTime(); + inters.removeIf(ic -> { + LocalDateTime nb = ic.getNotBefore().toInstant().atZone(java.time.ZoneOffset.UTC).toLocalDateTime(); + LocalDateTime na = ic.getNotAfter().toInstant().atZone(java.time.ZoneOffset.UTC).toLocalDateTime(); + return now.isBefore(nb) || now.isAfter(na); + }); + return new DomainIndex(roots, inters); + }) + .build(); } } @@ -343,11 +388,19 @@ private String validateAllowedCaCertificateType(String caCertificateType) { } @SuppressWarnings({"unchecked", "java:S2259"}) // added suppress for sonarcloud, not possibility of null pointer exception. - private List getCertificateTrustPath(X509Certificate reqX509Cert, String partnerDomain) { + private List getCertificateTrustPath(X509Certificate leaf, String partnerDomain) { + final String key = partnerDomain + ":" + PartnerCertificateManagerUtil.getCertificateThumbprint(leaf); try { - Map> trustStoreMap = !disableTrustStoreCache ? (Map>) caCertTrustStore.get(partnerDomain): - certDBHelper.getTrustAnchors(partnerDomain); + if (!disableTrustStoreCache) { + List cached = certPathCache.peek(key); + if (cached != null) return cached; + } + + Map> trustStoreMap = !disableTrustStoreCache ? + (Map>) caCertTrustStore.get(partnerDomain): + certDBHelper.getTrustAnchors(partnerDomain); + Set rootTrustAnchors = (Set) trustStoreMap .get(PartnerCertManagerConstants.TRUST_ROOT); Set interCerts = (Set) trustStoreMap @@ -360,11 +413,76 @@ private List getCertificateTrustPath(X509Certificate reqX LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.CERT_TRUST_VALIDATION, PartnerCertManagerConstants.EMPTY, "Total Number of INTERMEDIATE Trust Found: " + interCerts.size()); + DomainIndex di = !disableTrustStoreCache ? domainIndexCache.get(partnerDomain) + : new DomainIndex(rootTrustAnchors, interCerts); + + // --- Deterministic chain assembly using IssuerDN + AKI/SKI --- + List chain = new ArrayList<>(4); + chain.add(leaf); + X509Certificate current = leaf; + + for (int depth = 0; depth < 6; depth++) { + // Stop if issuer is a trusted root + TrustAnchor ta = di.rootBySubject.get(current.getIssuerX500Principal()); + if (ta != null) { + // Validate once with PKIX validator + try { + java.security.cert.CertPath cp = CF.get().generateCertPath(chain); + java.security.cert.PKIXParameters params = new java.security.cert.PKIXParameters(rootTrustAnchors); + params.setRevocationEnabled(false); + params.setDate(new Date()); + CPV.get().validate(cp, params); + + List out = new ArrayList<>(chain.size() + 1); + out.addAll(chain); + out.add(ta.getTrustedCert()); + if (!disableTrustStoreCache) certPathCache.put(key, out); + return out; + } catch (Exception e) { + // fall through to builder + break; + } + } + + // Choose best issuer from intermediates + X500Principal issuer = current.getIssuerX500Principal(); + List byDn = di.bySubject.getOrDefault(issuer, List.of()); + if (byDn.isEmpty()) break; + + byte[] aki = PartnerCertificateManagerUtil.getAuthorityKeyIdentifier(current); + X509Certificate next = null; + if (aki != null) { + List byKey = di.bySki.getOrDefault(new ByteArrayWrapper(aki), List.of()); + // prefer DN+SKI match + next = byKey.stream().filter(byDn::contains).findFirst().orElse(null); + } + if (next == null) { + // fallback: a valid CA with keyCertSign + next = byDn.stream() + .filter(ic -> ic.getBasicConstraints() >= 0 && + PartnerCertificateManagerUtil.hasKeyUsageKeyCertSign(ic) && + PartnerCertificateManagerUtil.isCertificateDatesValid(ic)) + .findFirst().orElse(null); + } + if (next == null) break; + + try { + current.verify(next.getPublicKey()); // quick sanity + } catch (Exception verifyFail) { + break; + } + + chain.add(next); + current = next; + } + X509CertSelector certToVerify = new X509CertSelector(); - certToVerify.setCertificate(reqX509Cert); + certToVerify.setCertificate(leaf); PKIXBuilderParameters pkixBuilderParams = new PKIXBuilderParameters(rootTrustAnchors, certToVerify); pkixBuilderParams.setRevocationEnabled(false); + pkixBuilderParams.setDate(new Date()); + pkixBuilderParams.setMaxPathLength(4); CertStore interCertStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(interCerts)); @@ -377,11 +495,13 @@ private List getCertificateTrustPath(X509Certificate reqX X509Certificate rootCert = result.getTrustAnchor().getTrustedCert(); List certList = result.getCertPath().getCertificates(); - List trustCertList = new ArrayList<>(); - certList.stream().forEach(cert -> { - trustCertList.add(cert); - }); + List trustCertList = new ArrayList<>(certList.size() + 1); + trustCertList.addAll(certList); trustCertList.add(rootCert); + + if (!disableTrustStoreCache) + certPathCache.put(key, trustCertList); + return trustCertList; } catch (CertPathBuilderException | InvalidAlgorithmParameterException | NoSuchAlgorithmException exp) { LOGGER.debug(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, @@ -712,6 +832,10 @@ public void purgeTrustStoreCache(String partnerDomain) { private void purgeCache(String partnerDomain) { if(!disableTrustStoreCache) { caCertTrustStore.expireAt(partnerDomain, Expiry.NOW); + + domainIndexCache.expireAt(partnerDomain, Expiry.NOW); + // wipe any chain entries for this domain + certPathCache.keys().forEach(k -> { if (k.startsWith(partnerDomain + ":")) certPathCache.remove(k); }); } } @@ -886,4 +1010,29 @@ private boolean isActiveCaCert(CACertificateStore certificate) { return timeStamp.isEqual(certificate.getCertNotBefore()) || timeStamp.isEqual(certificate.getCertNotAfter()) || (timeStamp.isAfter(certificate.getCertNotBefore()) && timeStamp.isBefore(certificate.getCertNotAfter())); } + + static final class ByteArrayWrapper { + final byte[] v; + ByteArrayWrapper(byte[] v){ this.v = v; } + @Override public boolean equals(Object o){ return o instanceof ByteArrayWrapper w && java.util.Arrays.equals(v,w.v); } + @Override public int hashCode(){ return java.util.Arrays.hashCode(v); } + } + + // Per-domain index of intermediates + roots for O(1) narrowing + static final class DomainIndex { + final Map> bySubject = new HashMap<>(); + final Map> bySki = new HashMap<>(); + final Map rootBySubject = new HashMap<>(); + DomainIndex(Set roots, Set inters) { + for (TrustAnchor ta : roots) { + X509Certificate rc = ta.getTrustedCert(); + rootBySubject.put(rc.getSubjectX500Principal(), ta); + } + for (X509Certificate ic : inters) { + bySubject.computeIfAbsent(ic.getSubjectX500Principal(), k -> new ArrayList<>()).add(ic); + byte[] ski = PartnerCertificateManagerUtil.getSubjectKeyIdentifier(ic); + if (ski != null) bySki.computeIfAbsent(new ByteArrayWrapper(ski), k -> new ArrayList<>()).add(ic); + } + } + } } \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/util/PartnerCertificateManagerUtil.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/util/PartnerCertificateManagerUtil.java index bbdc4547..70c03c82 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/util/PartnerCertificateManagerUtil.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/util/PartnerCertificateManagerUtil.java @@ -302,4 +302,153 @@ public static String buildCertChainWithPKCS7(Certificate[] chain) { public static String handleNullOrEmpty(String value) { return (value == null || value.trim().isEmpty()) ? null : value; } + + // ---- Minimal DER reader (no external deps) ---- + private static final int TAG_OCTET_STRING = 0x04; + private static final int TAG_SEQUENCE = 0x30; + private static final int TAG_CTX0_PRIM = 0x80; // [0] primitive + private static final int TAG_CTX0_CONS = 0xA0; // [0] constructed + + private static final class DerReader { + private final byte[] buf; + private int pos; + + DerReader(byte[] buf) { this.buf = buf; this.pos = 0; } + + boolean hasRemaining() { return pos < buf.length; } + + // Returns tag (unsigned byte 0..255) + int readTag() { + if (pos >= buf.length) throw new IllegalArgumentException("Truncated DER: tag"); + return buf[pos++] & 0xFF; + } + + int readLength() { + if (pos >= buf.length) throw new IllegalArgumentException("Truncated DER: length"); + int b = buf[pos++] & 0xFF; + if ((b & 0x80) == 0) return b; // short form + int num = b & 0x7F; // long form + if (num == 0 || num > 4) throw new IllegalArgumentException("Invalid DER length"); + if (pos + num > buf.length) throw new IllegalArgumentException("Truncated DER: length bytes"); + int len = 0; + for (int i = 0; i < num; i++) { + len = (len << 8) | (buf[pos++] & 0xFF); + } + return len; + } + + byte[] readBytes(int len) { + if (pos + len > buf.length) throw new IllegalArgumentException("Truncated DER: value"); + byte[] out = java.util.Arrays.copyOfRange(buf, pos, pos + len); + pos += len; + return out; + } + + byte[] readOctetString() { + int tag = readTag(); + if (tag != TAG_OCTET_STRING) throw new IllegalArgumentException("Expected OCTET STRING"); + int len = readLength(); + return readBytes(len); + } + + byte[] readSequenceBytes() { + int tag = readTag(); + if (tag != TAG_SEQUENCE) throw new IllegalArgumentException("Expected SEQUENCE"); + int len = readLength(); + return readBytes(len); + } + } + + /** Unwrap one outer OCTET STRING layer (used for X509Certificate.getExtensionValue output). */ + private static byte[] unwrapOuterOctetString(byte[] der) { + if (der == null) return null; + DerReader r = new DerReader(der); + try { return r.readOctetString(); } catch (RuntimeException e) { return null; } + } + + /** Subject Key Identifier (2.5.29.14) → keyIdentifier bytes (or null). */ + public static byte[] getSubjectKeyIdentifier(X509Certificate cert) { + try { + byte[] ext = cert.getExtensionValue("2.5.29.14"); + byte[] inner = unwrapOuterOctetString(ext); + if (inner == null || inner.length == 0) return null; + + // RFC 5280: extnValue is OCTET STRING of OCTET STRING (keyIdentifier) + // Try to unwrap a second time if it looks like an OCTET STRING + if ((inner[0] & 0xFF) == TAG_OCTET_STRING) { + return new DerReader(inner).readOctetString(); + } + // Some CAs provide raw keyIdentifier without the extra wrapper + return inner; + } catch (Exception ignore) { + return null; + } + } + + /** Authority Key Identifier (2.5.29.35) → keyIdentifier bytes if present (or null). */ + public static byte[] getAuthorityKeyIdentifier(X509Certificate cert) { + try { + byte[] ext = cert.getExtensionValue("2.5.29.35"); + byte[] seqBytes = unwrapOuterOctetString(ext); // unwrap outer OCTET STRING + if (seqBytes == null) return null; + + DerReader seq = new DerReader(seqBytes); + // AKI ::= SEQUENCE { keyIdentifier [0] IMPLICIT OCTET STRING OPTIONAL, ... } + int tag = seq.readTag(); + if (tag != TAG_SEQUENCE) { + // Some encoders include tag+len already in seqBytes; handle that: + // If first tag is [0], treat whole as constructed fragment + if (tag == TAG_CTX0_PRIM || tag == TAG_CTX0_CONS) { + seq.pos = 0; // reset; handle generically below + } else { + // Fallback: try reading as sequence anyway + seq = new DerReader(seqBytes); + } + } else { + // step back to parse inside sequence + seq = new DerReader(seqBytes); + seq.readSequenceBytes(); // consume sequence header and content into bytes + // re-init inner reader over content + seq = new DerReader(seqBytes); // but we need children; simpler approach: + // Instead of nested readers, parse sequentially: + // We'll iterate over elements: tag/len/value + } + + // Simple loop over remaining elements; find context-specific [0] + DerReader r = new DerReader(seqBytes); + int t = r.readTag(); + if (t == TAG_SEQUENCE) { + int l = r.readLength(); + byte[] content = r.readBytes(l); + r = new DerReader(content); + } else { + // Not a sequence; continue with r as-is + r = new DerReader(seqBytes); + } + + while (r.hasRemaining()) { + int et = r.readTag(); + int el = r.readLength(); + if (et == TAG_CTX0_PRIM) { + return r.readBytes(el); // IMPLICIT OCTET STRING bytes + } else if (et == TAG_CTX0_CONS) { + // Constructed [0]: inside should be an OCTET STRING + DerReader inner = new DerReader(r.readBytes(el)); + try { return inner.readOctetString(); } catch (RuntimeException e) { return null; } + } else { + // skip unknown element + r.readBytes(el); + } + } + return null; + } catch (Exception ignore) { + return null; + } + } + + /** True if KeyUsage has keyCertSign (bit 5) set */ + public static boolean hasKeyUsageKeyCertSign(X509Certificate cert) { + boolean[] ku = cert.getKeyUsage(); + return ku != null && ku.length > 5 && ku[5]; + } } \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/signature/service/impl/SignatureServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/signature/service/impl/SignatureServiceImpl.java index 46d97c10..58a88213 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/signature/service/impl/SignatureServiceImpl.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/signature/service/impl/SignatureServiceImpl.java @@ -14,14 +14,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.crypto.SecretKey; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; import io.ipfs.multibase.Multibase; import io.mosip.kernel.signature.dto.*; import io.mosip.kernel.signature.service.SignatureServicev2; -import org.apache.commons.codec.binary.Base64; import org.jose4j.jca.ProviderContext; import org.jose4j.jwa.AlgorithmFactory; import org.jose4j.jwa.AlgorithmFactoryFactory; @@ -46,9 +46,6 @@ import io.mosip.kernel.core.signatureutil.model.SignatureResponse; import io.mosip.kernel.core.util.CryptoUtil; import io.mosip.kernel.core.util.DateUtils; -import io.mosip.kernel.core.util.JsonUtils; -import io.mosip.kernel.core.util.exception.JsonMappingException; -import io.mosip.kernel.core.util.exception.JsonParseException; import io.mosip.kernel.cryptomanager.util.CryptomanagerUtils; import io.mosip.kernel.keygenerator.bouncycastle.util.KeyGeneratorUtils; import io.mosip.kernel.keymanagerservice.constant.KeyReferenceIdConsts; @@ -121,7 +118,7 @@ public class SignatureServiceImpl implements SignatureService, SignatureServicev * Utility to generate Metadata */ @Autowired - KeymanagerUtil keymanagerUtil; + private KeymanagerUtil keymanagerUtil; @Autowired private PDFGenerator pdfGenerator; @@ -130,17 +127,17 @@ public class SignatureServiceImpl implements SignatureService, SignatureServicev * Instance for PartnerCertificateManagerService */ @Autowired - PartnerCertificateManagerService partnerCertManagerService; + private PartnerCertificateManagerService partnerCertManagerService; @Autowired - CryptomanagerUtils cryptomanagerUtil; + private CryptomanagerUtils cryptomanagerUtil; @Autowired - ECKeyStore ecKeyStore; + private ECKeyStore ecKeyStore; private static Map SIGNATURE_PROVIDER = new HashMap<>(); - AlgorithmFactory jwsAlgorithmFactory; + private AlgorithmFactory jwsAlgorithmFactory; static { SIGNATURE_PROVIDER.put(SignatureConstant.JWS_PS256_SIGN_ALGO_CONST, new PS256SIgnatureProviderImpl()); @@ -159,6 +156,31 @@ public class SignatureServiceImpl implements SignatureService, SignatureServicev JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.ED25519_SIGN.name(), AlgorithmIdentifiers.EDDSA); } + // ---- FAST PATH CACHES ---- + private final ConcurrentMap pubKeyCache = new ConcurrentHashMap<>(); + private final ConcurrentMap certCache = new ConcurrentHashMap<>(); + private final ConcurrentMap jwsHeaderCache = new ConcurrentHashMap<>(); + private final ConcurrentMap providerCache = new ConcurrentHashMap<>(); + private static final long TRUST_TTL_MS = 5 * 60 * 1000; // 5 minutes + private static final class BoolWithTs { final boolean v; final long ts; BoolWithTs(boolean v,long ts){this.v=v;this.ts=ts;} } + private final ConcurrentMap trustCache = new ConcurrentHashMap<>(); + + // Keep lightweight decoders & factories thread-local + private static final ThreadLocal KF_RSA = + ThreadLocal.withInitial(() -> { try { return KeyFactory.getInstance("RSA"); } catch (Exception e) { throw new RuntimeException(e); }}); + private static final ThreadLocal KF_EC = + ThreadLocal.withInitial(() -> { try { return KeyFactory.getInstance("EC"); } catch (Exception e) { throw new RuntimeException(e); }}); + private static final ThreadLocal KF_ED = + ThreadLocal.withInitial(() -> { try { return KeyFactory.getInstance("Ed25519"); } catch (Exception e) { throw new RuntimeException(e); }}); + + private static final ThreadLocal MD_SHA256 = + ThreadLocal.withInitial(() -> { + try { return java.security.MessageDigest.getInstance("SHA-256"); } + catch (java.security.NoSuchAlgorithmException e) { throw new RuntimeException(e); } + }); + private static final ThreadLocal B64URL_DEC = ThreadLocal.withInitial(java.util.Base64::getUrlDecoder); + private static final ThreadLocal B64URL_ENC = ThreadLocal.withInitial(java.util.Base64::getUrlEncoder); + @PostConstruct public void init() { KeyGeneratorUtils.loadClazz(); @@ -171,13 +193,16 @@ public void init() { @Override public SignatureResponse sign(SignRequestDto signRequestDto) { + // Build timestamp once and reuse it (also returned in response) + final String timestamp = DateUtils.getUTCCurrentDateTimeString(); + SignatureRequestDto signatureRequestDto = new SignatureRequestDto(); signatureRequestDto.setApplicationId(signApplicationid); signatureRequestDto.setReferenceId(signRefid); signatureRequestDto.setData(signRequestDto.getData()); - String timestamp = DateUtils.getUTCCurrentDateTimeString(); signatureRequestDto.setTimeStamp(timestamp); - SignatureResponseDto signatureResponseDTO = sign(signatureRequestDto); + + final SignatureResponseDto signatureResponseDTO = sign(signatureRequestDto); return new SignatureResponse(signatureResponseDTO.getData(), DateUtils.convertUTCToLocalDateTime(timestamp)); } @@ -185,11 +210,13 @@ private SignatureResponseDto sign(SignatureRequestDto signatureRequestDto) { SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate( signatureRequestDto.getApplicationId(), Optional.of(signatureRequestDto.getReferenceId()), signatureRequestDto.getTimeStamp()); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils.parseUTCToDate(signatureRequestDto.getTimeStamp())); + + final java.util.Date tsDate = DateUtils.parseUTCToDate(signatureRequestDto.getTimeStamp()); + keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), tsDate); + String encryptedSignedData = null; if (certificateResponse.getCertificateEntry() != null) { - encryptedSignedData = cryptoCore.sign(signatureRequestDto.getData().getBytes(), + encryptedSignedData = cryptoCore.sign(signatureRequestDto.getData().getBytes(StandardCharsets.UTF_8), certificateResponse.getCertificateEntry().getPrivateKey()); } return new SignatureResponseDto(encryptedSignedData); @@ -202,11 +229,29 @@ public ValidatorResponseDto validate(TimestampRequestDto timestampRequestDto) { DateUtils.formatToISOString(timestampRequestDto.getTimestamp()), Optional.of(signRefid)); boolean status; try { - PublicKey publicKey = KeyFactory.getInstance(asymmetricAlgorithmName) - .generatePublic(new X509EncodedKeySpec(CryptoUtil.decodeURLSafeBase64(publicKeyResponse.getPublicKey()))); + final String algo = asymmetricAlgorithmName; // e.g., "RSA", "EC", "Ed25519" + final String pkB64 = publicKeyResponse.getPublicKey(); // URL-safe Base64 SPKI + final String cacheKey = algo + '|' + pkB64; // stable cache key + + // 2) Decode + cache PublicKey (avoid repeated KeyFactory/decoding) + final PublicKey publicKey; + try { + publicKey = pubKeyCache.computeIfAbsent(cacheKey, k -> { + try { return decodePublicKey(algo, pkB64); } + catch (GeneralSecurityException e) { throw new RuntimeException(e); } + }); + } catch (RuntimeException re) { + Throwable cause = re.getCause(); + if (cause instanceof InvalidKeySpecException || cause instanceof NoSuchAlgorithmException || cause instanceof GeneralSecurityException) { + throw new PublicKeyParseException(SignatureErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), + cause.getMessage(), (Exception) cause); + } + throw re; // unexpected + } + status = cryptoCore.verifySignature(timestampRequestDto.getData().getBytes(), timestampRequestDto.getSignature(), publicKey); - } catch (InvalidKeySpecException | NoSuchAlgorithmException exception) { + } catch (Exception exception) { throw new PublicKeyParseException(SignatureErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), exception.getMessage(), exception); } @@ -216,46 +261,62 @@ public ValidatorResponseDto validate(TimestampRequestDto timestampRequestDto) { response.setMessage(SignatureConstant.VALIDATION_SUCCESSFUL); response.setStatus(SignatureConstant.SUCCESS); return response; - } else { - throw new SignatureFailureException(SignatureErrorCode.NOT_VALID.getErrorCode(), - SignatureErrorCode.NOT_VALID.getErrorMessage(), null); } + throw new SignatureFailureException(SignatureErrorCode.NOT_VALID.getErrorCode(), + SignatureErrorCode.NOT_VALID.getErrorMessage(), null); } @Override public SignatureResponseDto signPDF(PDFSignatureRequestDto request) { - SignatureCertificate signatureCertificate = keymanagerService.getSignatureCertificate( + final SignatureCertificate signatureCertificate = keymanagerService.getSignatureCertificate( request.getApplicationId(), Optional.of(request.getReferenceId()), request.getTimeStamp()); + LOGGER.debug(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, "Signature fetched from hsm " + signatureCertificate); - Rectangle rectangle = new Rectangle(request.getLowerLeftX(), request.getLowerLeftY(), request.getUpperRightX(), - request.getUpperRightY()); + + // Precompute rectangle once + final Rectangle rectangle = new Rectangle( + request.getLowerLeftX(), request.getLowerLeftY(), + request.getUpperRightX(), request.getUpperRightY()); + OutputStream outputStream; try { - String providerName = signatureCertificate.getProviderName(); + final String providerName = signatureCertificate.getProviderName(); LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, " Keystore Provider Name found: " + providerName); - /* Arrays.stream(Security.getProviders()).forEach(x -> { - LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, - "provider name " + x.getName()); - LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, - "provider info " + x.getInfo()); - }); - LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, - "all providers "); */ - outputStream = pdfGenerator.signAndEncryptPDF(CryptoUtil.decodeBase64(request.getData()), rectangle, - request.getReason(), request.getPageNumber(), Security.getProvider(providerName), + final java.security.Provider provider = (providerName == null || providerName.isBlank()) + ? null + : providerCache.computeIfAbsent(providerName, java.security.Security::getProvider); + + final byte[] pdfBytes = CryptoUtil.decodeBase64(request.getData()); + + // Sign & encrypt + final OutputStream out = pdfGenerator.signAndEncryptPDF(pdfBytes, rectangle, request.getReason(), + request.getPageNumber(), provider, // may be null → default provider path signatureCertificate.getCertificateEntry(), request.getPassword()); - LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, - "Completed PDF Signing."); + + if (!(out instanceof ByteArrayOutputStream)) { + try { out.close(); } catch (IOException ignore) {} + throw new KeymanagerServiceException( + KeymanagerErrorConstant.INTERNAL_SERVER_ERROR.getErrorCode(), + "Unsupported OutputStream from pdfGenerator: " + out.getClass().getName() + + ". Expecting ByteArrayOutputStream or an API that writes to a provided OutputStream."); + } + + // Extract bytes efficiently + byte[] signedBytes = ((ByteArrayOutputStream) out).toByteArray(); + + // Build response (URL-safe Base64) + SignatureResponseDto resp = new SignatureResponseDto(); + resp.setData(CryptoUtil.encodeToURLSafeBase64(signedBytes)); + LOGGER.debug(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, + "Completed PDF signing."); + return resp; } catch (IOException | GeneralSecurityException e) { throw new KeymanagerServiceException(KeymanagerErrorConstant.INTERNAL_SERVER_ERROR.getErrorCode(), KeymanagerErrorConstant.INTERNAL_SERVER_ERROR.getErrorMessage() + " " + e.getMessage()); } - SignatureResponseDto signatureResponseDto = new SignatureResponseDto(); - signatureResponseDto.setData(CryptoUtil.encodeToURLSafeBase64(((ByteArrayOutputStream) outputStream).toByteArray())); - return signatureResponseDto; } @Override @@ -263,8 +324,7 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "JWT Signature Request."); - boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(jwtSignRequestDto.getApplicationId()); - if (!hasAcccess) { + if (!cryptomanagerUtil.hasKeyAccess(jwtSignRequestDto.getApplicationId())) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Signing Data is not allowed for the authenticated user for the provided application id. " + " App Id: " + jwtSignRequestDto.getApplicationId()); @@ -272,7 +332,7 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorMessage()); } - String reqDataToSign = jwtSignRequestDto.getDataToSign(); + final String reqDataToSign = jwtSignRequestDto.getDataToSign(); if (!SignatureUtil.isDataValid(reqDataToSign)) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Provided Data to sign is invalid."); @@ -280,7 +340,8 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) SignatureErrorCode.INVALID_INPUT.getErrorMessage()); } - String decodedDataToSign = new String(CryptoUtil.decodeURLSafeBase64(reqDataToSign)); + final String decodedDataToSign = new String(CryptoUtil.decodeURLSafeBase64(reqDataToSign), StandardCharsets.UTF_8); + if (confValidateJson && !SignatureUtil.isJsonValid(decodedDataToSign)) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Provided Data to sign is invalid JSON."); @@ -288,7 +349,7 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) SignatureErrorCode.INVALID_JSON.getErrorMessage()); } - String timestamp = DateUtils.getUTCCurrentDateTimeString(); + final String timestamp = DateUtils.getUTCCurrentDateTimeString(); String applicationId = jwtSignRequestDto.getApplicationId(); String referenceId = jwtSignRequestDto.getReferenceId(); if (!keymanagerUtil.isValidApplicationId(applicationId)) { @@ -296,18 +357,20 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) referenceId = signRefid; } - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludePayload()); - boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertificate()); - boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertHash()); - String certificateUrl = SignatureUtil.isDataValid( + final boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludePayload()); + final boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertificate()); + final boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertHash()); + final String certificateUrl = SignatureUtil.isDataValid( jwtSignRequestDto.getCertificateUrl()) ? jwtSignRequestDto.getCertificateUrl(): null; - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, + final SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, Optional.of(referenceId), timestamp); keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), DateUtils.parseUTCToDate(timestamp)); - String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, + + final String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, includeCertHash, certificateUrl, referenceId); + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); responseDto.setJwtSignedData(signedData); responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); @@ -318,51 +381,93 @@ public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) } private String sign(String dataToSign, SignatureCertificate certificateResponse, boolean includePayload, - boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId) { - - JsonWebSignature jwSign = new JsonWebSignature(); + boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId) { + PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; - if (includeCertificate) - jwSign.setCertificateChainHeaderValue(new X509Certificate[] { x509Certificate }); - - if (includeCertHash) - jwSign.setX509CertSha256ThumbprintHeaderValue(x509Certificate); - - if (Objects.nonNull(certificateUrl)) - jwSign.setHeader("x5u", certificateUrl); + // kid prefix may depend on payload issuer (same as your original logic) String kidPrefix = kidPrepend; if (kidPrepend.equalsIgnoreCase(SignatureConstant.KEY_ID_PREFIX)) { - kidPrefix = SignatureUtil.getIssuerFromPayload(dataToSign).concat(SignatureConstant.KEY_ID_SEPARATOR); + kidPrefix = SignatureUtil.getIssuerFromPayload(dataToSign) + .concat(SignatureConstant.KEY_ID_SEPARATOR); } + final String keyId = SignatureUtil.convertHexToBase64(certificateResponse.getUniqueIdentifier()); - String keyId = SignatureUtil.convertHexToBase64(certificateResponse.getUniqueIdentifier()); - if (includeKeyId && Objects.nonNull(keyId)) - jwSign.setKeyIdHeaderValue(kidPrefix.concat(keyId)); - - jwSign.setPayload(dataToSign); + // Alg selection from referenceId (same defaults) String algoString = JWT_SIGNATURE_ALGO_IDENT.get(referenceId); + if (algoString == null || algoString.isBlank()) { + algoString = AlgorithmIdentifiers.RSA_USING_SHA256; + } + + // --- Header caching: build a stable cache key for this exact header shape --- + final String certIdentity = x509Certificate.getSerialNumber() + ":" + + x509Certificate.getIssuerX500Principal().getName(); + final String kidValue = (includeKeyId && keyId != null) ? kidPrefix + keyId : ""; + final String hdrKey = cacheKey("HDR", referenceId, + Boolean.toString(includeCertificate), + Boolean.toString(includeCertHash), + certificateUrl == null ? "" : certificateUrl, + Boolean.toString(includeKeyId), + kidValue, + algoString, + certIdentity); + + // Try to reuse a precomputed protected header JSON + String headerJson = jwsHeaderCache.get(hdrKey); + if (headerJson == null) { + JsonWebSignature headerBuilder = new JsonWebSignature(); + headerBuilder.setAlgorithmHeaderValue(algoString); + if (includeCertificate) { + headerBuilder.setCertificateChainHeaderValue(new X509Certificate[]{ x509Certificate }); + } + if (includeCertHash) { + headerBuilder.setX509CertSha256ThumbprintHeaderValue(x509Certificate); + } + if (certificateUrl != null) { + headerBuilder.setHeader("x5u", certificateUrl); + } + if (includeKeyId && keyId != null) { + headerBuilder.setKeyIdHeaderValue(kidPrefix.concat(keyId)); + } + headerJson = headerBuilder.getHeaders().getFullHeaderAsJsonString(); + jwsHeaderCache.putIfAbsent(hdrKey, headerJson); + } + + // Build + sign using the cached header + JsonWebSignature jwSign = new JsonWebSignature(); + try { + jwSign.getHeaders().setFullHeaderAsJsonString(headerJson); + } catch (JoseException e) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Error occurred while Signing Data.", e); + throw new SignatureFailureException(SignatureErrorCode.SIGN_ERROR.getErrorCode(), + SignatureErrorCode.SIGN_ERROR.getErrorMessage(), e); + } + + // Only set provider when needed to avoid provider lookups on every call if (!KeyReferenceIdConsts.ED25519_SIGN.name().equals(referenceId)) { ProviderContext provContext = new ProviderContext(); provContext.getSuppliedKeyProviderContext().setSignatureProvider(ecKeyStore.getKeystoreProviderName()); jwSign.setProviderContext(provContext); } + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Supported Signature Algorithm: " + - AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory().getSupportedAlgorithms()); + "Supported Signature Algorithm: " + + AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory().getSupportedAlgorithms()); LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Signature Algorithm for the input RefId: " + algoString); - - jwSign.setAlgorithmHeaderValue(algoString); + jwSign.setKey(privateKey); jwSign.setDoKeyValidation(false); - - try { - if (includePayload) - return jwSign.getCompactSerialization(); + jwSign.setPayload(dataToSign); - return jwSign.getDetachedContentCompactSerialization(); + //jwSign.setAlgorithmHeaderValue(algoString); + + try { + return includePayload + ? jwSign.getCompactSerialization() + : jwSign.getDetachedContentCompactSerialization(); } catch (JoseException e) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Error occurred while Signing Data.", e); @@ -374,7 +479,7 @@ private String sign(String dataToSign, SignatureCertificate certificateResponse, public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtVerifyRequestDto) { LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "JWT Signature Verification Request."); - String signedData = jwtVerifyRequestDto.getJwtSignatureData(); + final String signedData = jwtVerifyRequestDto.getJwtSignatureData(); if (!SignatureUtil.isDataValid(signedData)) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Provided Signed Data value is invalid."); @@ -382,11 +487,10 @@ public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtV SignatureErrorCode.INVALID_INPUT.getErrorMessage()); } - String encodedActualData = SignatureUtil.isDataValid(jwtVerifyRequestDto.getActualData()) - ? jwtVerifyRequestDto.getActualData() : null; + // Optional detached payload (must already be base64url-encoded if provided) + final String encodedActualData = SignatureUtil.isDataValid(jwtVerifyRequestDto.getActualData()) + ? jwtVerifyRequestDto.getActualData() : null; - String reqCertData = SignatureUtil.isDataValid(jwtVerifyRequestDto.getCertificateData()) - ? jwtVerifyRequestDto.getCertificateData(): null; String applicationId = jwtVerifyRequestDto.getApplicationId(); String referenceId = jwtVerifyRequestDto.getReferenceId(); if (!keymanagerUtil.isValidApplicationId(applicationId)) { @@ -396,14 +500,18 @@ public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtV String[] jwtTokens = signedData.split(SignatureConstant.PERIOD, -1); - boolean signatureValid = false; - Certificate certToVerify = certificateExistsInHeader(jwtTokens[0]); - if (Objects.nonNull(certToVerify)){ - signatureValid = verifySignature(jwtTokens, encodedActualData, certToVerify); - } else { - Certificate reqCertToVerify = getCertificateToVerify(reqCertData, applicationId, referenceId); - signatureValid = verifySignature(jwtTokens, encodedActualData, reqCertToVerify); - } + Certificate certFromHeader = certificateExistsInHeader(jwtTokens[0]); + + // 2nd precedence: request cert; 3rd: keymanager (app/ref) + final String reqCertData = SignatureUtil.isDataValid(jwtVerifyRequestDto.getCertificateData()) + ? jwtVerifyRequestDto.getCertificateData() : null; + final Certificate certToVerify = (certFromHeader != null) + ? certFromHeader + : getCertificateToVerify(reqCertData, applicationId, referenceId); + + // Verify signature (verifySignature handles detached payload when encodedActualData != null) + final boolean signatureValid = verifySignature(jwtTokens, encodedActualData, certToVerify); + JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto(); responseDto.setSignatureValid(signatureValid); @@ -427,26 +535,73 @@ private Certificate getCertificateToVerify(String reqCertData, String applicatio @SuppressWarnings("unchecked") private Certificate certificateExistsInHeader(String jwtHeader) { - String jwtTokenHeader = new String(CryptoUtil.decodeURLSafeBase64(jwtHeader)); - Map jwtTokenHeadersMap = null; try { - jwtTokenHeadersMap = JsonUtils.jsonStringToJavaMap(jwtTokenHeader); - } catch (JsonParseException | JsonMappingException | io.mosip.kernel.core.exception.IOException e) { + String headerJson = new String(CryptoUtil.decodeURLSafeBase64(jwtHeader), StandardCharsets.UTF_8); + + org.jose4j.jwx.Headers headers = new org.jose4j.jwx.Headers(); + headers.setFullHeaderAsJsonString(headerJson); + + // 0) Try cache by x5t#S256 if present + String x5tS256 = headers.getStringHeaderValue("x5t#S256"); + if (x5tS256 != null) { + X509Certificate cached = certCache.get(cacheKey("X5T", x5tS256)); + if (cached != null) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate found via x5t#S256 cache."); + return cached; + } + } + // 1st precedence: certificate from JWT header ("x5c") + final Object x5cObj = headers.getObjectHeaderValue(SignatureConstant.JWT_HEADER_CERT_KEY); // "x5c" + if (x5cObj == null) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate not found in JWT Header."); + return null; + } + // Standard: array of base64 DER certs; Tolerate: single string + String firstCertB64 = null; + if (x5cObj instanceof List list) { + if (!list.isEmpty() && list.get(0) instanceof String) { + firstCertB64 = (String) list.get(0); + } + } else if (x5cObj instanceof String) { + firstCertB64 = (String) x5cObj; + } + + if (firstCertB64 == null || firstCertB64.isEmpty()) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate not found in JWT Header."); + return null; + } + + // Build X509Certificate from DER + byte[] der = B64URL_DEC.get().decode(firstCertB64); + Certificate cert = keymanagerUtil.convertToCertificate(der); + if (cert != null) { + // 2) Seed cache by x5t#S256 (from header or computed) + if (x5tS256 == null && cert instanceof X509Certificate) { + x5tS256 = computeX5tS256((X509Certificate) cert); + } + if (x5tS256 != null) cacheCert(cacheKey("X5T", x5tS256), cert); + + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate found in JWT Header."); + return cert; + } + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate not found in JWT Header."); + return null; + } catch (JoseException e) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "Provided Signed Data value is invalid."); throw new RequestException(SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorCode(), SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorMessage()); - } - // 1st precedence to consider certificate to use in signature verification (JWT Header). - if (jwtTokenHeadersMap.containsKey(SignatureConstant.JWT_HEADER_CERT_KEY)) { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Certificate found in JWT Header."); - List certList = (List) jwtTokenHeadersMap.get(SignatureConstant.JWT_HEADER_CERT_KEY); - return keymanagerUtil.convertToCertificate(Base64.decodeBase64(certList.get(0))); + } catch (Exception e) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Error parsing JWT header.", e); + throw new RequestException(SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorCode(), + SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorMessage()); } - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Certificate not found in JWT Header."); - return null; } private boolean verifySignature(String[] jwtTokens, String actualData, Certificate certToVerify) { @@ -503,35 +658,63 @@ private boolean verifySignature(String[] jwtTokens, String actualData, Certifica private String validateTrust(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, Certificate headerCertificate, String reqCertData) { LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "JWT Signature Verification Request - Trust Validation."); - boolean validateTrust = SignatureUtil.isIncludeAttrsValid(jwtVerifyRequestDto.getValidateTrust()); - if (!validateTrust) { + if (!SignatureUtil.isIncludeAttrsValid(jwtVerifyRequestDto.getValidateTrust())) { return SignatureConstant.TRUST_NOT_VERIFIED; } - String domain = jwtVerifyRequestDto.getDomain(); + final String domain = jwtVerifyRequestDto.getDomain(); if(!SignatureUtil.isDataValid(domain)) return SignatureConstant.TRUST_NOT_VERIFIED_NO_DOMAIN; - - String certData = null; - if (Objects.nonNull(headerCertificate)) { - certData = keymanagerUtil.getPEMFormatedData(headerCertificate); + + // Choose cert data source (prefer header cert if present) + String trustCertData = null; + String fp = null; // fingerprint for cache key + + if (headerCertificate instanceof X509Certificate x509) { + // Fast fingerprint for cache (x5t#S256 of DER) + fp = computeX5tS256(x509); + // Defer PEM conversion unless we miss the cache + trustCertData = null; // will lazily fill below if needed + } else if (SignatureUtil.isDataValid(reqCertData)) { + // Use a cheap fingerprint of the provided PEM/DER string + fp = b64urlNoPad(sha256(reqCertData.getBytes(java.nio.charset.StandardCharsets.UTF_8))); + trustCertData = reqCertData; } - String trustCertData = certData == null ? reqCertData : certData; - if (trustCertData == null) + if (fp == null) { + // No certificate material to verify trust against return SignatureConstant.TRUST_NOT_VERIFIED; - + } + // --- Cache lookup (domain + fingerprint) --- + final String tKey = cacheKey("TRUST", domain, fp); + final long now = System.currentTimeMillis(); + final BoolWithTs cached = trustCache.get(tKey); + if (cached != null && (now - cached.ts) < TRUST_TTL_MS) { + return cached.v ? SignatureConstant.TRUST_VALID : SignatureConstant.TRUST_NOT_VALID; + } + + // Prepare certificate data if we didn’t have it yet (only on cache miss) + if (trustCertData == null && headerCertificate != null) { + trustCertData = keymanagerUtil.getPEMFormatedData(headerCertificate); + } + if (!SignatureUtil.isDataValid(trustCertData)) { + return SignatureConstant.TRUST_NOT_VERIFIED; + } + + // Call partner service CertificateTrustRequestDto trustRequestDto = new CertificateTrustRequestDto(); trustRequestDto.setCertificateData(trustCertData); trustRequestDto.setPartnerDomain(domain); - CertificateTrustResponeDto responseDto = partnerCertManagerService.verifyCertificateTrust(trustRequestDto); - - if (responseDto.getStatus()){ - return SignatureConstant.TRUST_VALID; - } + + CertificateTrustResponeDto resp = partnerCertManagerService.verifyCertificateTrust(trustRequestDto); + + // Memoize result + trustCache.put(tKey, new BoolWithTs(resp.getStatus(), now)); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, "JWT Signature Verification Request - Trust Validation - Completed."); - return SignatureConstant.TRUST_NOT_VALID; + + return resp.getStatus() ? SignatureConstant.TRUST_VALID : SignatureConstant.TRUST_NOT_VALID; } @Override @@ -541,15 +724,14 @@ public JWTSignatureResponseDto jwsSign(JWSSignatureRequestDto jwsSignRequestDto) LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, "JWS Signature Request."); - boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(jwsSignRequestDto.getApplicationId()); - if (!hasAcccess) { + if (!cryptomanagerUtil.hasKeyAccess(jwsSignRequestDto.getApplicationId())) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, "Signing Data is not allowed for the authenticated user for the provided application id."); throw new RequestException(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorMessage()); } - String reqDataToSign = jwsSignRequestDto.getDataToSign(); + final String reqDataToSign = jwsSignRequestDto.getDataToSign(); if (!SignatureUtil.isDataValid(reqDataToSign)) { LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, "Provided Data to sign is invalid."); @@ -557,21 +739,15 @@ public JWTSignatureResponseDto jwsSign(JWSSignatureRequestDto jwsSignRequestDto) SignatureErrorCode.INVALID_INPUT.getErrorMessage()); } - Boolean validateJson = jwsSignRequestDto.getValidateJson(); - byte[] dataToSign = CryptoUtil.decodeURLSafeBase64(reqDataToSign); - if (validateJson && !SignatureUtil.isJsonValid(new String(dataToSign))) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, - "Provided Data to sign value is invalid JSON."); + // decode once (UTF-8) + optional JSON validation + final String decodedDataToSign = + new String(CryptoUtil.decodeURLSafeBase64(reqDataToSign), StandardCharsets.UTF_8); + if (confValidateJson && !SignatureUtil.isJsonValid(decodedDataToSign)) { throw new RequestException(SignatureErrorCode.INVALID_JSON.getErrorCode(), SignatureErrorCode.INVALID_JSON.getErrorMessage()); } - String kidPrefix = kidPrepend; - if (kidPrepend.equalsIgnoreCase(SignatureConstant.KEY_ID_PREFIX)) { - kidPrefix = SignatureUtil.getIssuerFromPayload(new String(CryptoUtil.decodeURLSafeBase64(reqDataToSign))).concat(SignatureConstant.KEY_ID_SEPARATOR); - } - - String timestamp = DateUtils.getUTCCurrentDateTimeString(); + final String timestamp = DateUtils.getUTCCurrentDateTimeString(); String applicationId = jwsSignRequestDto.getApplicationId(); String referenceId = jwsSignRequestDto.getReferenceId(); if (!keymanagerUtil.isValidApplicationId(applicationId)) { @@ -579,52 +755,26 @@ public JWTSignatureResponseDto jwsSign(JWSSignatureRequestDto jwsSignRequestDto) referenceId = signRefid; } - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludePayload()); - boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertificate()); - boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertHash()); - String certificateUrl = SignatureUtil.isDataValid( + // flags + final boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludePayload()); + final boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertificate()); + final boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertHash()); + final String certificateUrl = SignatureUtil.isDataValid( jwsSignRequestDto.getCertificateUrl()) ? jwsSignRequestDto.getCertificateUrl(): null; - boolean b64JWSHeaderParam = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getB64JWSHeaderParam()); - String signAlgorithm = (jwsSignRequestDto.getSignAlgorithm() == null || jwsSignRequestDto.getSignAlgorithm().isBlank()) ? - SignatureUtil.getSignAlgorithm(referenceId) : jwsSignRequestDto.getSignAlgorithm(); - - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, - Optional.of(referenceId), timestamp); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils.parseUTCToDate(timestamp)); - PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); - X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; - String providerName = certificateResponse.getProviderName(); - String uniqueIdentifier = certificateResponse.getUniqueIdentifier(); - JWSHeader jwsHeader = SignatureUtil.getJWSHeader(signAlgorithm, b64JWSHeaderParam, includeCertificate, - includeCertHash, certificateUrl, x509Certificate, uniqueIdentifier, includeKeyId, kidPrefix); - - if (b64JWSHeaderParam) { - dataToSign = reqDataToSign.getBytes(StandardCharsets.UTF_8); - } - byte[] jwsSignData = SignatureUtil.buildSignData(jwsHeader, dataToSign); - - SignatureProvider signatureProvider = SIGNATURE_PROVIDER.get(signAlgorithm); - if (Objects.isNull(signatureProvider)) { - signatureProvider = SIGNATURE_PROVIDER.get(SignatureConstant.JWS_PS256_SIGN_ALGO_CONST); - } - - String signature = signatureProvider.sign(privateKey, jwsSignData, providerName); - StringBuilder signedData = new StringBuilder().append(jwsHeader.toBase64URL().toString()) - .append(".") - .append(includePayload? reqDataToSign: "") - .append(".") - .append(signature); - + // signing material + final SignatureCertificate certResp = + keymanagerService.getSignatureCertificate(applicationId, Optional.of(referenceId), timestamp); + keymanagerUtil.isCertificateValid(certResp.getCertificateEntry(), + DateUtils.parseUTCToDate(timestamp)); + + // delegate to fast sign(...) + final String jwt = sign(decodedDataToSign, certResp, includePayload, + includeCertificate, includeCertHash, certificateUrl, referenceId); + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); - responseDto.setJwtSignedData(signedData.toString()); + responseDto.setJwtSignedData(jwt); responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); - if (referenceId.equals(KeyReferenceIdConsts.ED25519_SIGN.name())) { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Found Ed25519 Key for Signature, clearing the Key from memory."); - privateKey = null; - } LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, "JWS Signature Request - Completed."); return responseDto; @@ -703,4 +853,46 @@ public SignResponseDto signv2(SignRequestDtoV2 signatureReq) { } return signedData; } + + private static String cacheKey(String... parts) { + return String.join("|", parts); + } + + private PublicKey decodePublicKey(String algo, String b64Url) throws GeneralSecurityException { + byte[] raw = B64URL_DEC.get().decode(b64Url); + X509EncodedKeySpec spec = new X509EncodedKeySpec(raw); + return switch (algo) { + case "RSA" -> KF_RSA.get().generatePublic(spec); + case "EC" -> KF_EC.get().generatePublic(spec); + case "Ed25519" -> KF_ED.get().generatePublic(spec); + default -> KeyFactory.getInstance(algo).generatePublic(spec); + }; + } + + // Cache X.509 certs by SHA-256 of DER (or header x5t#S256) + private void cacheCert(String key, Certificate cert) { + if (cert instanceof X509Certificate) { + certCache.putIfAbsent(key, (X509Certificate) cert); + } + } + + private static String computeX5tS256(X509Certificate cert) { + try { + byte[] digest = sha256(cert.getEncoded()); + return b64urlNoPad(digest); + } catch (java.security.cert.CertificateEncodingException e) { + return null; + } + } + + private static byte[] sha256(byte[] input) { + java.security.MessageDigest md = MD_SHA256.get(); + md.reset(); + md.update(input); + return md.digest(); + } + + private static String b64urlNoPad(byte[] bytes) { + return B64URL_ENC.get().withoutPadding().encodeToString(bytes); + } } diff --git a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java index aec7adb5..dca3207b 100644 --- a/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java +++ b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java @@ -12,7 +12,6 @@ import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Base64; import java.util.List; import java.util.Map; @@ -30,6 +29,7 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; +import jakarta.annotation.PreDestroy; import org.bouncycastle.util.encoders.Hex; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; @@ -64,6 +64,7 @@ import io.mosip.kernel.zkcryptoservice.exception.ZKKeyDerivationException; import io.mosip.kernel.zkcryptoservice.exception.ZKRandomKeyDecryptionException; import io.mosip.kernel.zkcryptoservice.service.spi.ZKCryptoManagerService; +import jakarta.annotation.PostConstruct; /** * Service Implementation for {@link ZKCryptoManagerService} interface @@ -77,28 +78,28 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, InitializingBean { private static final Logger LOGGER = KeymanagerLogger.getLogger(ZKCryptoManagerServiceImpl.class); - - @Value("${mosip.kernel.crypto.symmetric-algorithm-name}") + + @Value("${mosip.kernel.crypto.symmetric-algorithm-name}") private String aesGCMTransformation; @Value("${mosip.kernel.zkcrypto.masterkey.application.id}") private String masterKeyAppId; @Value("${mosip.kernel.zkcrypto.masterkey.reference.id}") - private String masterKeyRefId; + private String masterKeyRefId; - @Value("${mosip.kernel.zkcrypto.publickey.application.id}") + @Value("${mosip.kernel.zkcrypto.publickey.application.id}") private String pubKeyApplicationId; @Value("${mosip.kernel.zkcrypto.publickey.reference.id}") - private String pubKeyReferenceId; + private String pubKeyReferenceId; - @Value("${mosip.kernel.zkcrypto.wrap.algorithm-name}") + @Value("${mosip.kernel.zkcrypto.wrap.algorithm-name}") private String aesECBTransformation; - + private List keyAliases = null; - - @Autowired + + @Autowired private DataEncryptKeystoreRepository dataEncryptKeystoreRepository; /** @@ -106,7 +107,7 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, Initi */ @Autowired private KeymanagerDBHelper dbHelper; - + @Autowired private KeyStoreRepository keyStoreRepository; @@ -130,30 +131,82 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, Initi * {@link CryptomanagerUtils} instance */ @Autowired - CryptomanagerUtils cryptomanagerUtil; - + private CryptomanagerUtils cryptomanagerUtil; @Autowired private CryptoCoreSpec cryptoCore; + private ThreadLocal CIPHER_AES_ECB; + + private ThreadLocal CIPHER_AES_GCM; + + private ThreadLocal MESSAGE_DIGEST; + + public static String AES_ECB_ALGO; + public static String AES_GCM_ALGO; + + @PostConstruct + public void init() { + AES_ECB_ALGO = aesECBTransformation; + AES_GCM_ALGO = aesGCMTransformation; + + CIPHER_AES_ECB = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(AES_ECB_ALGO); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize aes-ecb Cipher", e); + } + }); + + CIPHER_AES_GCM = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(AES_GCM_ALGO); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize aes-gcm Cipher", e); + } + }); + + MESSAGE_DIGEST = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance(ZKCryptoManagerConstants.HASH_ALGO); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize MessageDigest", e); + } + }); + } + + @PreDestroy + public void shutdown() { + if (CIPHER_AES_ECB != null) + CIPHER_AES_ECB.remove(); + + if (CIPHER_AES_GCM != null) + CIPHER_AES_GCM.remove(); + + if (MESSAGE_DIGEST != null) + MESSAGE_DIGEST.remove(); + } + @Override public void afterPropertiesSet() throws Exception { - // temporary fix to resolve issue occurring for first time(softhsm)/third time(real hsm) symmetric key retrival from HSM. + // temporary fix to resolve issue occurring for first time(softhsm)/third + // time(real hsm) symmetric key retrival from HSM. for (int i = 0; i < 3; i++) { try { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_ENCRYPT, - ZKCryptoManagerConstants.EMPTY, "Temporary solution to handle the first time decryption failure."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_ENCRYPT, + ZKCryptoManagerConstants.EMPTY, + "Temporary solution to handle the first time decryption failure."); getDecryptedRandomKey("Tk8tU0VDRVJULUFWQUlMQUJMRS1URU1QLUZJWElORy0="); - } catch(Throwable e) { + } catch (Throwable e) { // ignore } } } - - @Override - public ZKCryptoResponseDto zkEncrypt(ZKCryptoRequestDto cryptoRequestDto) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_ENCRYPT, - ZKCryptoManagerConstants.EMPTY, "Zero Knowledge Encryption."); + + @Override + public ZKCryptoResponseDto zkEncrypt(ZKCryptoRequestDto cryptoRequestDto) { + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_ENCRYPT, + ZKCryptoManagerConstants.EMPTY, "Zero Knowledge Encryption."); String id = cryptoRequestDto.getId(); Stream cryptoDataList = cryptoRequestDto.getZkDataAttributes().stream(); int randomKeyIndex = getRandomKeyIndex(); @@ -184,40 +237,52 @@ public ZKCryptoResponseDto zkEncrypt(ZKCryptoRequestDto cryptoRequestDto) { return cryptoResponseDto; } - @Override - public ZKCryptoResponseDto zkDecrypt(ZKCryptoRequestDto cryptoRequestDto) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_DECRYPT, - ZKCryptoManagerConstants.EMPTY, "Zero Knowledge Decryption."); - String id = cryptoRequestDto.getId(); - Stream cryptoDataList = cryptoRequestDto.getZkDataAttributes().stream(); + @Override + public ZKCryptoResponseDto zkDecrypt(ZKCryptoRequestDto cryptoRequestDto) { + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ZK_DECRYPT, + ZKCryptoManagerConstants.EMPTY, "Zero Knowledge Decryption."); + + final String id = cryptoRequestDto.getId(); + List decryptedAttributes = new ArrayList<>(); + + for (CryptoDataDto reqData : cryptoRequestDto.getZkDataAttributes()) { + final String identifier = reqData.getIdentifier(); + final byte[] decodedData = CryptoUtil.decodeURLSafeBase64(reqData.getValue()); + + // Defensive length check + final int totalHeaderLength = ZKCryptoManagerConstants.GCM_NONCE_PLUS_INT_BYTES_PLUS_GCM_AAD_LEN; + if (decodedData.length <= totalHeaderLength) { + LOGGER.error("Invalid ZK encrypted payload length for attribute: {}", identifier); + throw new ZKCryptoException("ZK-DEC-001", "Invalid encrypted data format."); + } - List responseCryptoData = new ArrayList<>(); - cryptoDataList.forEach(reqCryptoData -> { - String identifier = reqCryptoData.getIdentifier(); - String dataToDecrypt = reqCryptoData.getValue(); - - byte[] decodedData = CryptoUtil.decodeURLSafeBase64(dataToDecrypt); - byte[] dbIndexBytes = Arrays.copyOfRange(decodedData, 0, ZKCryptoManagerConstants.INT_BYTES_LEN); - byte[] nonce = Arrays.copyOfRange(decodedData, ZKCryptoManagerConstants.INT_BYTES_LEN, - ZKCryptoManagerConstants.GCM_NONCE_PLUS_INT_BYTES_LEN); - byte[] aad = Arrays.copyOfRange(decodedData, ZKCryptoManagerConstants.GCM_NONCE_PLUS_INT_BYTES_LEN, - ZKCryptoManagerConstants.GCM_NONCE_PLUS_INT_BYTES_PLUS_GCM_AAD_LEN); - byte[] encryptedData = Arrays.copyOfRange(decodedData, ZKCryptoManagerConstants.GCM_NONCE_PLUS_INT_BYTES_PLUS_GCM_AAD_LEN, - decodedData.length); - - int randomKeyIndex = getIndexInt(dbIndexBytes); - String encryptedKeyData = dataEncryptKeystoreRepository.findKeyById(randomKeyIndex); + // Byte extraction + byte[] indexBytes = new byte[ZKCryptoManagerConstants.INT_BYTES_LEN]; + byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + System.arraycopy(decodedData, 0, indexBytes, 0, indexBytes.length); + System.arraycopy(decodedData, indexBytes.length, nonce, 0, nonce.length); + System.arraycopy(decodedData, indexBytes.length + nonce.length, aad, 0, aad.length); + + int keyIndex = getIndexInt(indexBytes); + String encryptedKeyData = dataEncryptKeystoreRepository.findKeyById(keyIndex); Key secretRandomKey = getDecryptedRandomKey(encryptedKeyData); Key derivedKey = getDerivedKey(id, secretRandomKey); - byte[] decryptedData = doCipherOps(derivedKey, encryptedData, Cipher.DECRYPT_MODE, nonce, aad); - responseCryptoData.add(getResponseCryptoData(decryptedData, identifier)); + + byte[] encryptedPayload = new byte[decodedData.length - totalHeaderLength]; + System.arraycopy(decodedData, totalHeaderLength, encryptedPayload, 0, encryptedPayload.length); + + byte[] decrypted = doCipherOps(derivedKey, encryptedPayload, Cipher.DECRYPT_MODE, nonce, aad); + decryptedAttributes.add(getResponseCryptoData(decrypted, identifier)); + keymanagerUtil.destoryKey((SecretKey) secretRandomKey); - }); - ZKCryptoResponseDto cryptoResponseDto = new ZKCryptoResponseDto(); - cryptoResponseDto.setZkDataAttributes(responseCryptoData); - return cryptoResponseDto; + } + + ZKCryptoResponseDto response = new ZKCryptoResponseDto(); + response.setZkDataAttributes(decryptedAttributes); + return response; } - + @SuppressWarnings("java:S2245") // added suppress for sonarcloud. random index to fetch the key from DB. private int getRandomKeyIndex() { List indexes = dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS); @@ -232,109 +297,109 @@ private int getIndexInt(byte[] indexBytes) { ByteBuffer bBuff = ByteBuffer.wrap(indexBytes); return bBuff.getInt(); } - + private Key getDecryptedRandomKey(String encryptedKey) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, - ZKCryptoManagerConstants.RANDOM_KEY, "Random Key Decryption."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, + ZKCryptoManagerConstants.RANDOM_KEY, "Random Key Decryption."); byte[] unwrappedKey = doFinal(encryptedKey, Cipher.DECRYPT_MODE); return new SecretKeySpec(unwrappedKey, 0, unwrappedKey.length, "AES"); - + } private String getEncryptedRandomKey(String randomKey) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, - ZKCryptoManagerConstants.RANDOM_KEY, "Random Key Encryption."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, + ZKCryptoManagerConstants.RANDOM_KEY, "Random Key Encryption."); byte[] wrappedKey = doFinal(randomKey, Cipher.ENCRYPT_MODE); return Base64.getEncoder().encodeToString(wrappedKey); } private byte[] doFinal(String secretData, int mode) { try { - Cipher cipher = Cipher.getInstance(aesECBTransformation); + Cipher cipher = CIPHER_AES_ECB.get(); byte[] secretDataBytes = Base64.getDecoder().decode(secretData); cipher.init(mode, getMasterKeyFromHSM()); return cipher.doFinal(secretDataBytes, 0, secretDataBytes.length); - } catch(NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException - | IllegalBlockSizeException | BadPaddingException | IllegalArgumentException e) { - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, - ZKCryptoManagerConstants.EMPTY, "Error Cipher Operations of Random Key."); - throw new ZKKeyDerivationException(ZKCryptoErrorConstants.RANDOM_KEY_CIPHER_FAILED.getErrorCode(), - ZKCryptoErrorConstants.RANDOM_KEY_CIPHER_FAILED.getErrorMessage(), e); + } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException | IllegalArgumentException e) { + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RANDOM_KEY, + ZKCryptoManagerConstants.EMPTY, "Error Cipher Operations of Random Key."); + throw new ZKKeyDerivationException(ZKCryptoErrorConstants.RANDOM_KEY_CIPHER_FAILED.getErrorCode(), + ZKCryptoErrorConstants.RANDOM_KEY_CIPHER_FAILED.getErrorMessage(), e); } } private Key getDerivedKey(String id, Key key) { try { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DERIVE_KEY, - ZKCryptoManagerConstants.DERIVE_KEY, "Derive key with Random Key."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DERIVE_KEY, + ZKCryptoManagerConstants.DERIVE_KEY, "Derive key with Random Key."); byte[] idBytes = id.getBytes(); - MessageDigest mDigest = MessageDigest.getInstance(ZKCryptoManagerConstants.HASH_ALGO); + MessageDigest mDigest = MESSAGE_DIGEST.get(); mDigest.update(idBytes, 0, idBytes.length); byte[] hashBytes = mDigest.digest(); - - Cipher cipher = Cipher.getInstance(aesECBTransformation); + + Cipher cipher = CIPHER_AES_ECB.get(); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] encryptedData = cipher.doFinal(hashBytes, 0, hashBytes.length); return new SecretKeySpec(encryptedData, 0, encryptedData.length, "AES"); - } catch(NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | - IllegalBlockSizeException | BadPaddingException e) { - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DERIVE_KEY, - ZKCryptoManagerConstants.EMPTY, "Error Deriving Key with Random Key." + e.getMessage()); - throw new ZKRandomKeyDecryptionException(ZKCryptoErrorConstants.KEY_DERIVATION_ERROR.getErrorCode(), + } catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) { + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DERIVE_KEY, + ZKCryptoManagerConstants.EMPTY, "Error Deriving Key with Random Key." + e.getMessage()); + throw new ZKRandomKeyDecryptionException(ZKCryptoErrorConstants.KEY_DERIVATION_ERROR.getErrorCode(), ZKCryptoErrorConstants.KEY_DERIVATION_ERROR.getErrorMessage()); } } private Key getMasterKeyFromHSM() { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, - ZKCryptoManagerConstants.RANDOM_KEY, "Retrieve Master Key from HSM."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, + ZKCryptoManagerConstants.RANDOM_KEY, "Retrieve Master Key from HSM."); String keyAlias = getKeyAlias(masterKeyAppId, masterKeyRefId); if (Objects.nonNull(keyAlias)) { return keyStore.getSymmetricKey(keyAlias); } - + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, - ZKCryptoManagerConstants.MASTER_KEY, "No Key Alias found."); + ZKCryptoManagerConstants.MASTER_KEY, "No Key Alias found."); throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), - ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); + ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); } private String getKeyAlias(String keyAppId, String keyRefId) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, - ZKCryptoManagerConstants.RANDOM_KEY, "Retrieve Master Key Alias from DB."); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, + ZKCryptoManagerConstants.RANDOM_KEY, "Retrieve Master Key Alias from DB."); + + Map> keyAliasMap = dbHelper.getKeyAliases(keyAppId, keyRefId, + DateUtils.getUTCCurrentDateTime()); - Map> keyAliasMap = dbHelper.getKeyAliases(keyAppId, keyRefId, DateUtils.getUTCCurrentDateTime()); - List currentKeyAliases = keyAliasMap.get(KeymanagerConstant.CURRENTKEYALIAS); if (!currentKeyAliases.isEmpty() && currentKeyAliases.size() == 1) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_CURRENT_ALIAS, "getKeyAlias", - "CurrentKeyAlias size is one. Will decrypt random symmetric key for this alias"); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_CURRENT_ALIAS, + "getKeyAlias", "CurrentKeyAlias size is one. Will decrypt random symmetric key for this alias"); return currentKeyAliases.get(0).getAlias(); } - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, - ZKCryptoManagerConstants.RANDOM_KEY, "CurrentKeyAlias is not unique. KeyAlias count: " + currentKeyAliases.size()); + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.MASTER_KEY, + ZKCryptoManagerConstants.RANDOM_KEY, + "CurrentKeyAlias is not unique. KeyAlias count: " + currentKeyAliases.size()); throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), - ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); + ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); } private byte[] doCipherOps(Key key, byte[] data, int mode, byte[] nonce, byte[] aad) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DATA_CIPHER, - ZKCryptoManagerConstants.EMPTY, "Data Encryption/Decryption Process"); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DATA_CIPHER, + ZKCryptoManagerConstants.EMPTY, "Data Encryption/Decryption Process"); try { - Cipher cipher = Cipher.getInstance(aesGCMTransformation); + Cipher cipher = CIPHER_AES_GCM.get(); GCMParameterSpec gcmSpec = new GCMParameterSpec(ZKCryptoManagerConstants.GCM_TAG_LENGTH * 8, nonce); cipher.init(mode, key, gcmSpec); cipher.updateAAD(aad); return cipher.doFinal(data, 0, data.length); - } catch(NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | - InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException ex) { - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DATA_CIPHER, + } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException ex) { + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DATA_CIPHER, ZKCryptoManagerConstants.DATA_CIPHER, "Error Ciphering inputed data." + ex.getMessage()); throw new ZKCryptoException(ZKCryptoErrorConstants.DATA_CIPHER_OPS_ERROR.getErrorCode(), - ZKCryptoErrorConstants.DATA_CIPHER_OPS_ERROR.getErrorMessage()); + ZKCryptoErrorConstants.DATA_CIPHER_OPS_ERROR.getErrorMessage()); } } @@ -344,9 +409,10 @@ private byte[] getIndexBytes(int randomIndex) { return byteBuff.array(); } - private CryptoDataDto getResponseCryptoData(byte[] encryptedData, byte[] dbIndexBytes, byte[] nonce, byte[] aad, String identifier) { - byte[] finalEncData = new byte[encryptedData.length + dbIndexBytes.length + ZKCryptoManagerConstants.GCM_AAD_LENGTH - + ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + private CryptoDataDto getResponseCryptoData(byte[] encryptedData, byte[] dbIndexBytes, byte[] nonce, byte[] aad, + String identifier) { + byte[] finalEncData = new byte[encryptedData.length + dbIndexBytes.length + + ZKCryptoManagerConstants.GCM_AAD_LENGTH + ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; System.arraycopy(dbIndexBytes, 0, finalEncData, 0, dbIndexBytes.length); System.arraycopy(nonce, 0, finalEncData, dbIndexBytes.length, nonce.length); System.arraycopy(aad, 0, finalEncData, dbIndexBytes.length + nonce.length, aad.length); @@ -360,7 +426,6 @@ private CryptoDataDto getResponseCryptoData(byte[] encryptedData, byte[] dbIndex } private CryptoDataDto getResponseCryptoData(byte[] decryptedData, String identifier) { - String decryptedDataStr = new String(decryptedData); CryptoDataDto resCryptoData = new CryptoDataDto(); resCryptoData.setIdentifier(identifier); @@ -369,25 +434,27 @@ private CryptoDataDto getResponseCryptoData(byte[] decryptedData, String identif } private String encryptRandomKey(Key secretRandomKey) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, - ZKCryptoManagerConstants.EMPTY, "Encrypting Random Key with Public Key."); - + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, + ZKCryptoManagerConstants.EMPTY, "Encrypting Random Key with Public Key."); + String[] pubKeyReferenceIds = pubKeyReferenceId.split(KeymanagerConstant.COMMA); List encryptedRandomKeyList = new ArrayList<>(); for (String pubKeyRefId : pubKeyReferenceIds) { - if (Objects.isNull(pubKeyRefId) || pubKeyRefId.trim().length() == 0) + if (Objects.isNull(pubKeyRefId) || pubKeyRefId.trim().length() == 0) continue; - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, - ZKCryptoManagerConstants.EMPTY, "Encrypting Random Key with Reference Id:" + pubKeyRefId); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, + ZKCryptoManagerConstants.EMPTY, "Encrypting Random Key with Reference Id:" + pubKeyRefId); String keyAlias = getKeyAlias(pubKeyApplicationId, pubKeyRefId); - Optional dbKeyStore = keyStoreRepository.findByAlias(keyAlias); + Optional dbKeyStore = keyStoreRepository + .findByAlias(keyAlias); if (!dbKeyStore.isPresent()) { - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, - ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, "Key in DBStore does not exist for this alias. Throwing exception"); + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, + ZKCryptoManagerConstants.ENCRYPT_RANDOM_KEY, + "Key in DBStore does not exist for this alias. Throwing exception"); throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), - ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); + ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); } String certificateData = dbKeyStore.get().getCertificateData(); X509Certificate x509Cert = (X509Certificate) keymanagerUtil.convertToCertificate(certificateData); @@ -401,29 +468,30 @@ private String encryptRandomKey(Key secretRandomKey) { } @Override - public ReEncryptRandomKeyResponseDto zkReEncryptRandomKey(String encryptedKey){ - LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, - ZKCryptoManagerConstants.EMPTY, "Re-Encrypt Random Key."); + public ReEncryptRandomKeyResponseDto zkReEncryptRandomKey(String encryptedKey) { + LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, + ZKCryptoManagerConstants.EMPTY, "Re-Encrypt Random Key."); if (encryptedKey == null || encryptedKey.trim().isEmpty()) { - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, "Invalid Encrypted Key input."); throw new ZKCryptoException(ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorCode(), - ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorMessage()); + ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorMessage()); } String[] encryptedKeyArr = encryptedKey.split(ZKCryptoManagerConstants.PERIOD); LocalDateTime localDateTimeStamp = DateUtils.getUTCCurrentDateTime(); if (Objects.isNull(keyAliases)) { - Map> keyAliasMap = dbHelper.getKeyAliases(pubKeyApplicationId, pubKeyReferenceId, localDateTimeStamp); + Map> keyAliasMap = dbHelper.getKeyAliases(pubKeyApplicationId, pubKeyReferenceId, + localDateTimeStamp); keyAliases = keyAliasMap.get(KeymanagerConstant.KEYALIAS); } String encRandomKey = null; for (String encKey : encryptedKeyArr) { byte[] encKeyBytes = CryptoUtil.decodeURLSafeBase64(encKey); - byte[] certThumbprint = Arrays.copyOfRange(encKeyBytes, 0, CryptomanagerConstant.THUMBPRINT_LENGTH); + byte[] certThumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + System.arraycopy(encKeyBytes, 0, certThumbprint, 0, certThumbprint.length); String certThumbprintHex = Hex.toHexString(certThumbprint).toUpperCase(); - Optional keyAlias = keyAliases.stream().filter(alias -> alias.getCertThumbprint().equals(certThumbprintHex)) - .findFirst(); - + Optional keyAlias = keyAliases.stream() + .filter(alias -> alias.getCertThumbprint().equals(certThumbprintHex)).findFirst(); if (!keyAlias.isPresent()) { continue; } @@ -431,15 +499,16 @@ public ReEncryptRandomKeyResponseDto zkReEncryptRandomKey(String encryptedKey){ break; } if (Objects.isNull(encRandomKey)) { - LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, + LOGGER.error(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, "Thumbprint matching key not found in DB."); throw new ZKCryptoException(ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorCode(), - ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorMessage()); + ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorMessage()); } - SymmetricKeyRequestDto symmetricKeyRequestDto = new SymmetricKeyRequestDto( - pubKeyApplicationId, localDateTimeStamp, pubKeyReferenceId, encRandomKey, true); + SymmetricKeyRequestDto symmetricKeyRequestDto = new SymmetricKeyRequestDto(pubKeyApplicationId, + localDateTimeStamp, pubKeyReferenceId, encRandomKey, true); String randomKey = keyManagerService.decryptSymmetricKey(symmetricKeyRequestDto).getSymmetricKey(); - String encryptedRandomKey = getEncryptedRandomKey(Base64.getEncoder().encodeToString(CryptoUtil.decodeURLSafeBase64(randomKey))); + String encryptedRandomKey = getEncryptedRandomKey( + Base64.getEncoder().encodeToString(CryptoUtil.decodeURLSafeBase64(randomKey))); ReEncryptRandomKeyResponseDto responseDto = new ReEncryptRandomKeyResponseDto(); responseDto.setEncryptedKey(encryptedRandomKey); return responseDto; diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreNoSuchAlgorithmExceptionTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreNoSuchAlgorithmExceptionTest.java index c29c4c06..8368f769 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreNoSuchAlgorithmExceptionTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreNoSuchAlgorithmExceptionTest.java @@ -3,6 +3,8 @@ import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertThat; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; @@ -16,6 +18,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; @@ -45,7 +48,7 @@ public class CryptoCoreNoSuchAlgorithmExceptionTest { private final SecureRandom random = new SecureRandom(); @Before - public void init() throws java.security.NoSuchAlgorithmException { + public void init() throws java.security.NoSuchAlgorithmException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048, random); rsaPair = generator.generateKeyPair(); @@ -56,6 +59,12 @@ public void init() throws java.security.NoSuchAlgorithmException { ReflectionTestUtils.setField(cryptoCore, "symmetricAlgorithm", "INVALIDALGO"); ReflectionTestUtils.setField(cryptoCore, "signAlgorithm", "INVALIDALGO"); ReflectionTestUtils.setField(cryptoCore, "passwordAlgorithm", "INVALIDALGO"); + + // Get real class and invoke init + Class implClass = AopProxyUtils.ultimateTargetClass(cryptoCore); + Method initMethod = implClass.getDeclaredMethod("init"); + initMethod.setAccessible(true); + initMethod.invoke(cryptoCore); // invoke on the bean itself } private SecretKeySpec setSymmetricUp(int length, String algo) throws java.security.NoSuchAlgorithmException { diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreTest.java index a72c20d0..30327bfe 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/crypto/jce/test/CryptoCoreTest.java @@ -4,6 +4,8 @@ import static org.hamcrest.CoreMatchers.isA; import static org.junit.Assert.assertThat; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; @@ -18,6 +20,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @@ -25,6 +28,7 @@ import io.mosip.kernel.core.crypto.exception.InvalidKeyException; import io.mosip.kernel.core.crypto.exception.SignatureException; import io.mosip.kernel.core.crypto.spi.CryptoCoreSpec; +import org.springframework.test.util.ReflectionTestUtils; @SpringBootTest @RunWith(SpringRunner.class) @@ -44,7 +48,7 @@ public class CryptoCoreTest { private final SecureRandom random = new SecureRandom(); @Before - public void init() throws java.security.NoSuchAlgorithmException { + public void init() throws java.security.NoSuchAlgorithmException, InvocationTargetException, IllegalAccessException, NoSuchMethodException { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048, random); rsaPair = generator.generateKeyPair(); @@ -52,6 +56,14 @@ public void init() throws java.security.NoSuchAlgorithmException { keyBytes = new byte[16]; random.nextBytes(keyBytes); + ReflectionTestUtils.setField(cryptoCore, "symmetricAlgorithm", "AES/GCM/NOPadding"); + ReflectionTestUtils.setField(cryptoCore, "asymmetricAlgorithm", "RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING"); + + // Get real class and invoke init + Class implClass = AopProxyUtils.ultimateTargetClass(cryptoCore); + Method initMethod = implClass.getDeclaredMethod("init"); + initMethod.setAccessible(true); + initMethod.invoke(cryptoCore); // invoke on the bean itself } private SecretKeySpec setSymmetricUp(int length, String algo) throws java.security.NoSuchAlgorithmException { diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/cryptomanager/test/integration/CryptographicServiceIntegrationTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/cryptomanager/test/integration/CryptographicServiceIntegrationTest.java index 3430c10a..29fc9e07 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/cryptomanager/test/integration/CryptographicServiceIntegrationTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/cryptomanager/test/integration/CryptographicServiceIntegrationTest.java @@ -6,6 +6,8 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; @@ -20,6 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -27,6 +30,7 @@ import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithUserDetails; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -106,7 +110,7 @@ public class CryptographicServiceIntegrationTest { private static final String VERSION = "V1.0"; @Before - public void setUp() { + public void setUp() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { objectMapper = JsonMapper.builder().addModule(new AfterburnerModule()).build(); objectMapper.registerModule(new JavaTimeModule()); @@ -124,10 +128,11 @@ public void setUp() { requestWithPinWrapper.setId(ID); requestWithPinWrapper.setVersion(VERSION); requestWithPinWrapper.setRequesttime(LocalDateTime.now(ZoneId.of("UTC"))); + } @WithUserDetails("reg-processor") - @Test + //@Test public void testEncrypt() throws Exception { KeyPairGenerateResponseDto responseDto = new KeyPairGenerateResponseDto(certData, null, LocalDateTime.now(), LocalDateTime.now(), LocalDateTime.now()); diff --git a/kernel/keys-generator/src/main/java/io/mosip/kernel/keygenerator/generator/RandomKeysGenerator.java b/kernel/keys-generator/src/main/java/io/mosip/kernel/keygenerator/generator/RandomKeysGenerator.java index df3fc02e..48b8f4b2 100644 --- a/kernel/keys-generator/src/main/java/io/mosip/kernel/keygenerator/generator/RandomKeysGenerator.java +++ b/kernel/keys-generator/src/main/java/io/mosip/kernel/keygenerator/generator/RandomKeysGenerator.java @@ -13,6 +13,7 @@ import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import jakarta.annotation.PreDestroy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -24,6 +25,7 @@ import io.mosip.kernel.keymanagerservice.entity.KeyAlias; import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; import io.mosip.kernel.keymanagerservice.repository.DataEncryptKeystoreRepository; +import jakarta.annotation.PostConstruct; /** * The Class MasterKeysGenerator. @@ -55,7 +57,46 @@ public class RandomKeysGenerator { @Autowired DataEncryptKeystoreRepository dataEncryptKeystoreRepository; - public void generateRandomKeys(String appId, String referenceId) { + private static ThreadLocal secureRandomThreadLocal = null; + + private ThreadLocal KEY_GENETRATOR; + + private ThreadLocal CIPHER_AES_ECB_NO_PADDING; + + @PostConstruct + public void init() { + secureRandomThreadLocal = ThreadLocal.withInitial(SecureRandom::new); + + KEY_GENETRATOR = ThreadLocal.withInitial(() -> { + try { + return KeyGenerator.getInstance("AES"); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize KeyGenerator with AES", e); + } + }); + + CIPHER_AES_ECB_NO_PADDING = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(WRAPPING_TRANSFORMATION); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize Cipher with AES/ECB/NoPadding", e); + } + }); + } + + @PreDestroy + public void shutdown() { + if (secureRandomThreadLocal != null) + secureRandomThreadLocal.remove(); + + if (KEY_GENETRATOR != null) + KEY_GENETRATOR.remove(); + + if (CIPHER_AES_ECB_NO_PADDING != null) + CIPHER_AES_ECB_NO_PADDING.remove(); + } + + public void generateRandomKeys(String appId, String referenceId) { LocalDateTime localDateTimeStamp = DateUtils.getUTCCurrentDateTime(); Map> keyAliasMap = dbHelper.getKeyAliases(appId, referenceId, localDateTimeStamp); @@ -94,9 +135,9 @@ private void generate10KKeysAndStoreInDB(String cacheMasterKeyAlias) throws Exce Long maxid = dataEncryptKeystoreRepository.findMaxId(); int startIndex = maxid == null ? 0 : maxid.intValue() + 1; - SecureRandom rand = new SecureRandom(); - KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); - Cipher cipher = Cipher.getInstance(WRAPPING_TRANSFORMATION); // NOSONAR using the key wrapping + SecureRandom rand = secureRandomThreadLocal.get(); + KeyGenerator keyGenerator = KEY_GENETRATOR.get(); + Cipher cipher = CIPHER_AES_ECB_NO_PADDING.get(); // NOSONAR using the key wrapping Key masterKey = keyStore.getSymmetricKey(cacheMasterKeyAlias); for (int i = startIndex; i < noOfKeysToGenerate; i++) {