From a4233f1dd0aa4d2a87ca904fbcf119e92ee12173 Mon Sep 17 00:00:00 2001 From: Janardhan B S <72377118+JanardhanBS-SyncByte@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:52:37 +0530 Subject: [PATCH 1/5] [MOSIP-40013] (#404) * [MOSIP-40013] Signed-off-by: JanardhanBS-SyncByte * [MOSIP-40013] Signed-off-by: JanardhanBS-SyncByte --------- Signed-off-by: JanardhanBS-SyncByte --- .../service/impl/ClientCryptoFacade.java | 61 ++- .../kernel/crypto/jce/core/CryptoCore.java | 124 ++--- .../impl/CryptomanagerServiceImpl.java | 484 ++++++++++-------- .../util/CryptomanagerUtils.java | 25 +- .../helper/SessionKeyDecrytorHelper.java | 13 +- .../util/KeymanagerUtil.java | 28 +- .../impl/ZKCryptoManagerServiceImpl.java | 290 ++++++----- ...ryptoCoreNoSuchAlgorithmExceptionTest.java | 11 +- .../crypto/jce/test/CryptoCoreTest.java | 14 +- .../CryptographicServiceIntegrationTest.java | 9 +- .../generator/RandomKeysGenerator.java | 36 +- 11 files changed, 654 insertions(+), 441 deletions(-) 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..9e13c1f1 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 @@ -96,7 +96,7 @@ public class CryptoCore implements CryptoCoreSpec secureRandomThreadLocal = null; + private ThreadLocal CIPHER_GCM_ENCRYPT_DECRYPT_SYMMETRIC; + private ThreadLocal CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC; + + public static String SYMMETRIC_ALGO; + public static String ASYMMETRIC_ALGO; + @PostConstruct public void init() { - secureRandom = new SecureRandom(); + secureRandomThreadLocal = ThreadLocal.withInitial(SecureRandom::new); + 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); } + }); } @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 +188,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 +213,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 +253,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 +262,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 +286,12 @@ 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 cipher = CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.get(); cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams); + return doFinal(data, cipher); } catch (java.security.InvalidKeyException e) { throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), e.getMessage(), e); @@ -287,7 +300,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 @@ -466,7 +478,7 @@ public boolean verifySignature(byte[] data, String sign, PublicKey publicKey) { @SuppressWarnings("unchecked") @Override public SecureRandom random() { - return secureRandom; + return secureRandomThreadLocal.get(); } /** @@ -477,7 +489,7 @@ public SecureRandom random() { */ private byte[] generateIV(int blockSize) { byte[] byteIV = new byte[blockSize]; - secureRandom.nextBytes(byteIV); + secureRandomThreadLocal.get().nextBytes(byteIV); return byteIV; } @@ -543,7 +555,5 @@ public boolean verifySignature(String sign) { 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 7c3007c1..a02d46ee 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; @@ -264,19 +262,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/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/zkcryptoservice/service/impl/ZKCryptoManagerServiceImpl.java index aec7adb5..bddbf364 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; @@ -64,6 +63,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 +77,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 +106,7 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, Initi */ @Autowired private KeymanagerDBHelper dbHelper; - + @Autowired private KeyStoreRepository keyStoreRepository; @@ -132,28 +132,56 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, Initi @Autowired CryptomanagerUtils cryptomanagerUtil; - @Autowired private CryptoCoreSpec cryptoCore; + private ThreadLocal CIPHER_AES_ECB; + + private ThreadLocal MESSAGE_DIGEST; + + public static String AES_ECB_ALGO; + + @PostConstruct + public void init() { + AES_ECB_ALGO = aesECBTransformation; + + 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); + } + }); + + MESSAGE_DIGEST = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance(ZKCryptoManagerConstants.HASH_ALGO); + } catch (Exception e) { + throw new IllegalStateException("Unable to initialize MessageDigest", e); + } + }); + } + @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 +212,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 +272,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_ECB.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 +384,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 +401,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 +409,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 +443,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 +474,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..eb7737cf 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 @@ -24,6 +24,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 +56,34 @@ 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); + } + }); + } + + public void generateRandomKeys(String appId, String referenceId) { LocalDateTime localDateTimeStamp = DateUtils.getUTCCurrentDateTime(); Map> keyAliasMap = dbHelper.getKeyAliases(appId, referenceId, localDateTimeStamp); @@ -94,9 +122,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++) { From 54fe46375630b4f49a0e3e939b75aae3be1933bd Mon Sep 17 00:00:00 2001 From: kameshsr <47484458+kameshsr@users.noreply.github.com> Date: Tue, 5 Aug 2025 13:32:55 +0530 Subject: [PATCH 2/5] MOSIP-42334 corrected version (#405) * MOSIP-42334 corrected version Signed-off-by: kameshsr * MOSIP-42334 Changed AES_ECB AES_GCM in doCipherOps Signed-off-by: kameshsr * MOSIP-42334 Changed AES_ECB AES_GCM in doCipherOps Signed-off-by: kameshsr * MOSIP-42334 Added predestroy Signed-off-by: kameshsr --------- Signed-off-by: kameshsr --- kernel/kernel-keymanager-service/pom.xml | 2 +- .../kernel/crypto/jce/core/CryptoCore.java | 13 ++++++++ .../impl/ZKCryptoManagerServiceImpl.java | 32 +++++++++++++++++-- kernel/keys-generator/pom.xml | 2 +- .../generator/RandomKeysGenerator.java | 13 ++++++++ kernel/keys-migrator/pom.xml | 2 +- kernel/pom.xml | 2 +- 7 files changed, 59 insertions(+), 7 deletions(-) diff --git a/kernel/kernel-keymanager-service/pom.xml b/kernel/kernel-keymanager-service/pom.xml index 687a28eb..5d12cfbc 100644 --- a/kernel/kernel-keymanager-service/pom.xml +++ b/kernel/kernel-keymanager-service/pom.xml @@ -6,7 +6,7 @@ 4.0.0 io.mosip.kernel kernel-keymanager-service - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT kernel-keymanager-service Mosip commons project https://github.com/mosip/keymanager 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 9e13c1f1..c64cd09c 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 @@ -41,6 +41,7 @@ import org.jose4j.lang.JoseException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import jakarta.annotation.PreDestroy; import io.mosip.kernel.core.crypto.exception.InvalidDataException; import io.mosip.kernel.core.crypto.exception.InvalidKeyException; @@ -150,6 +151,18 @@ public void init() { }); } + @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(); + } + @Override public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] aad) { Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); 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 bddbf364..6ba28a4c 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 @@ -64,6 +64,7 @@ import io.mosip.kernel.zkcryptoservice.exception.ZKRandomKeyDecryptionException; import io.mosip.kernel.zkcryptoservice.service.spi.ZKCryptoManagerService; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; /** * Service Implementation for {@link ZKCryptoManagerService} interface @@ -137,14 +138,19 @@ public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, Initi 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); @@ -153,6 +159,14 @@ public void init() { } }); + 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); @@ -162,6 +176,18 @@ public void init() { }); } + @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 @@ -364,7 +390,7 @@ private byte[] doCipherOps(Key key, byte[] data, int mode, byte[] nonce, byte[] LOGGER.info(ZKCryptoManagerConstants.SESSIONID, ZKCryptoManagerConstants.DATA_CIPHER, ZKCryptoManagerConstants.EMPTY, "Data Encryption/Decryption Process"); try { - Cipher cipher = CIPHER_AES_ECB.get(); + Cipher cipher = CIPHER_AES_GCM.get(); GCMParameterSpec gcmSpec = new GCMParameterSpec(ZKCryptoManagerConstants.GCM_TAG_LENGTH * 8, nonce); cipher.init(mode, key, gcmSpec); cipher.updateAAD(aad); diff --git a/kernel/keys-generator/pom.xml b/kernel/keys-generator/pom.xml index 5f4e8bcd..be53439e 100644 --- a/kernel/keys-generator/pom.xml +++ b/kernel/keys-generator/pom.xml @@ -5,7 +5,7 @@ io.mosip.kernel keys-generator - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT UTF-8 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 eb7737cf..6bd60513 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 @@ -25,6 +25,7 @@ import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; import io.mosip.kernel.keymanagerservice.repository.DataEncryptKeystoreRepository; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; /** * The Class MasterKeysGenerator. @@ -83,6 +84,18 @@ public void init() { }); } + @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(); diff --git a/kernel/keys-migrator/pom.xml b/kernel/keys-migrator/pom.xml index 062142a0..e75d0fd8 100755 --- a/kernel/keys-migrator/pom.xml +++ b/kernel/keys-migrator/pom.xml @@ -5,7 +5,7 @@ io.mosip.kernel keys-migrator - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT https://github.com/mosip/keymanager diff --git a/kernel/pom.xml b/kernel/pom.xml index 98f227cc..bc42ce60 100644 --- a/kernel/pom.xml +++ b/kernel/pom.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT io.mosip.kernel keymanager-parent pom From 6e9b26c5e43e12dd5c395e4209ec98974f06a665 Mon Sep 17 00:00:00 2001 From: kameshsr Date: Thu, 14 Aug 2025 19:30:02 +0530 Subject: [PATCH 3/5] MOSIP-42357 corrected version Signed-off-by: kameshsr --- kernel/kernel-keymanager-service/pom.xml | 4 ++-- kernel/keys-generator/pom.xml | 6 +++--- kernel/keys-migrator/pom.xml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/kernel-keymanager-service/pom.xml b/kernel/kernel-keymanager-service/pom.xml index 5d12cfbc..42a9c2ca 100644 --- a/kernel/kernel-keymanager-service/pom.xml +++ b/kernel/kernel-keymanager-service/pom.xml @@ -26,8 +26,8 @@ 0.7.0 - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.2-SNAPSHOT 1.2.1-SNAPSHOT 1.2.1-SNAPSHOT 1.2.1-SNAPSHOT diff --git a/kernel/keys-generator/pom.xml b/kernel/keys-generator/pom.xml index be53439e..cfbc96a4 100644 --- a/kernel/keys-generator/pom.xml +++ b/kernel/keys-generator/pom.xml @@ -21,9 +21,9 @@ 0.7.0 - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.2-SNAPSHOT 3.2.3 0.8.11 diff --git a/kernel/keys-migrator/pom.xml b/kernel/keys-migrator/pom.xml index e75d0fd8..44305aee 100755 --- a/kernel/keys-migrator/pom.xml +++ b/kernel/keys-migrator/pom.xml @@ -24,9 +24,9 @@ 0.7.0 - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT - 1.2.1-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.2-SNAPSHOT + 1.2.2-SNAPSHOT 3.2.3 0.8.11 From 11fa713caf23a555a5cd5e2b617382fdc1303695 Mon Sep 17 00:00:00 2001 From: kameshsr <47484458+kameshsr@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:17:56 +0530 Subject: [PATCH 4/5] MOSIP-42357 Corrected CryptoCore to improve performance (#416) * MOSIP-42357 Corrected CryptoCore to improve performance Signed-off-by: kameshsr * MOSIP-42357 Corrected keymanager util Signed-off-by: kameshsr --------- Signed-off-by: kameshsr --- .../kernel/crypto/jce/core/CryptoCore.java | 961 ++++++------ .../PartnerCertificateManagerServiceImpl.java | 309 +++- .../util/PartnerCertificateManagerUtil.java | 213 ++- .../service/impl/SignatureServiceImpl.java | 1395 +++++++++-------- .../impl/ZKCryptoManagerServiceImpl.java | 877 ++++++----- .../generator/RandomKeysGenerator.java | 140 +- 6 files changed, 2172 insertions(+), 1723 deletions(-) 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 c64cd09c..d2869231 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; @@ -41,7 +43,6 @@ import org.jose4j.lang.JoseException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import jakarta.annotation.PreDestroy; import io.mosip.kernel.core.crypto.exception.InvalidDataException; import io.mosip.kernel.core.crypto.exception.InvalidKeyException; @@ -56,14 +57,14 @@ /** * This class provided Basic and Core Cryptographic functionalities . - * + * * This class follows {@link CryptoCoreSpec} and implement all basic * Cryptographic functions. - * + * * @author Urvil Joshi * @author Rajath * @since 1.0.0 - * + * * @see CryptoCoreSpec * @see PrivateKey * @see PublicKey @@ -76,497 +77,513 @@ @Component public class CryptoCore implements CryptoCoreSpec { - private static final String PERIOD_SEPARATOR_REGEX = "\\."; + private static final String PERIOD_SEPARATOR_REGEX = "\\."; - // Used as a hack for softhsm oeap padding decryption usecase will be when we - // will use in HSM - @SuppressWarnings("java:S106") - private static final String RSA_ECB_NO_PADDING = "RSA/ECB/NoPadding"; // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + // Used as a hack for softhsm oeap padding decryption usecase will be when we + // will use in HSM + @SuppressWarnings("java:S106") + private static final String RSA_ECB_NO_PADDING = "RSA/ECB/NoPadding"; // NOSONAR using the padding for allowing OAEP padding in PKCS11 library - private static final String PKCS11_STORE_TYPE = "PKCS11"; + private static final String PKCS11_STORE_TYPE = "PKCS11"; - @Value("${mosip.kernel.keygenerator.asymmetric-key-length:2048}") - private int asymmetricKeyLength; + @Value("${mosip.kernel.keygenerator.asymmetric-key-length:2048}") + private int asymmetricKeyLength; - private static final String MGF1 = "MGF1"; + private static final String MGF1 = "MGF1"; - private static final String HASH_ALGO = "SHA-256"; + private static final String HASH_ALGO = "SHA-256"; - private static final String AES = "AES"; + private static final String AES = "AES"; - @Value("${mosip.kernel.crypto.gcm-tag-length:128}") - private int tagLength; + @Value("${mosip.kernel.crypto.gcm-tag-length:128}") + private int tagLength; - @Value("${mosip.kernel.crypto.symmetric-algorithm-name:AES/GCM/NOPadding}") - private String symmetricAlgorithm; + @Value("${mosip.kernel.crypto.symmetric-algorithm-name:AES/GCM/NOPadding}") + private String symmetricAlgorithm; - @Value("${mosip.kernel.crypto.asymmetric-algorithm-name:RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING}") - private String asymmetricAlgorithm; + @Value("${mosip.kernel.crypto.asymmetric-algorithm-name:RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING}") + private String asymmetricAlgorithm; - @Value("${mosip.kernel.crypto.hash-algorithm-name:PBKDF2WithHmacSHA512}") - private String passwordAlgorithm; + @Value("${mosip.kernel.crypto.hash-algorithm-name:PBKDF2WithHmacSHA512}") + private String passwordAlgorithm; - @Value("${mosip.kernel.crypto.sign-algorithm-name:RS256}") - private String signAlgorithm; + @Value("${mosip.kernel.crypto.sign-algorithm-name:RS256}") + private String signAlgorithm; - @Value("${mosip.kernel.crypto.hash-symmetric-key-length:256}") - private int symmetricKeyLength; + @Value("${mosip.kernel.crypto.hash-symmetric-key-length:256}") + private int symmetricKeyLength; - @Value("${mosip.kernel.crypto.hash-iteration:100000}") - private int iterations; + @Value("${mosip.kernel.crypto.hash-iteration:100000}") + private int iterations; - @Value("${mosip.kernel.keymanager.hsm.keystore-type:PKCS11}") - private String keystoreType; + @Value("${mosip.kernel.keymanager.hsm.keystore-type:PKCS11}") + private String keystoreType; - private static ThreadLocal secureRandomThreadLocal = null; + private static final OAEPParameterSpec OAEP_SHA256_MGF1 = + new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, PSpecified.DEFAULT); + private static ThreadLocal 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() { - secureRandomThreadLocal = ThreadLocal.withInitial(SecureRandom::new); - SYMMETRIC_ALGO = symmetricAlgorithm; + + @PostConstruct + public void init() { + 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); } - }); + 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); } - }); - } - - @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(); - } - - @Override - public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] aad) { - Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - byte[] output = null; - 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); - output = new byte[cipher.getOutputSize(data.length) + cipher.getBlockSize()]; - if (aad != null && aad.length != 0) { - cipher.updateAAD(aad); - } - byte[] processData = doFinal(data, cipher); - System.arraycopy(processData, 0, output, 0, processData.length); - System.arraycopy(randomIV, 0, output, processData.length, randomIV.length); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidKeyException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } - return output; - } - - @Override - public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { - Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - if (iv == null) { - return symmetricEncrypt(key, data, aad); - } - - 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); - if (aad != null && aad.length != 0) { - cipher.updateAAD(aad); - } - return doFinal(data, cipher); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidParamSpecException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } - } - - @Override - public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] aad) { - Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - byte[] output = null; - try { - 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 gcmSpec = new GCMParameterSpec(tagLength, iv); - cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); - - if (aad != null && aad.length > 0) { - cipher.updateAAD(aad); - } - - 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); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidKeyException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } catch (ArrayIndexOutOfBoundsException e) { - throw new InvalidDataException( - SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorMessage(), e); - } - } - - @Override - public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { - Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - if (iv == null) { - return symmetricDecrypt(key, data, aad); - } - 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); - if (aad != null) { - cipher.updateAAD(aad); - } - return doFinal(data, cipher); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidParamSpecException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } - } - - @Override - public byte[] asymmetricEncrypt(PublicKey key, byte[] data) { - Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - final OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, - PSpecified.DEFAULT); - try { - Cipher cipher = CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.get(); - cipher.init(Cipher.ENCRYPT_MODE, key, oaepParams); - return doFinal(data, cipher); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidParamSpecException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } - } - - @Override - public byte[] asymmetricDecrypt(PrivateKey privateKey, byte[] data) { - if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { - BigInteger keyModulus = ((RSAPrivateKey) privateKey).getModulus(); - return asymmetricDecrypt(privateKey, keyModulus, data, null); - } - return jceAsymmetricDecrypt(privateKey, data, null); - } - - @Override - public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data) { - if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { - BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : - ((RSAPrivateKey) privateKey).getModulus(); - return asymmetricDecrypt(privateKey, keyModulus, data, null); - } - return jceAsymmetricDecrypt(privateKey, data, null); - } - - @Override - public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data, String storeType) { - if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { - BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : - ((RSAPrivateKey) privateKey).getModulus(); - return asymmetricDecrypt(privateKey, keyModulus, data, storeType); - } - return jceAsymmetricDecrypt(privateKey, data, storeType); - } - - private byte[] asymmetricDecrypt(PrivateKey privateKey, BigInteger keyModulus, byte[] data, String storeType) { - Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - Cipher cipher; - try { - cipher = Objects.isNull(storeType) ? Cipher.getInstance(RSA_ECB_NO_PADDING) : // NOSONAR using the padding for allowing OAEP padding in PKCS11 library - Cipher.getInstance(RSA_ECB_NO_PADDING, storeType); // NOSONAR using the padding for allowing OAEP padding in PKCS11 library - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } - - try { - cipher.init(Cipher.DECRYPT_MODE, privateKey); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - /* - * This is a hack of removing OEAP padding after decryption with NO Padding as - * SoftHSM does not support it.Will be removed after HSM implementation - */ - byte[] paddedPlainText = doFinal(data, cipher); - if (paddedPlainText.length < asymmetricKeyLength / 8) { - byte[] tempPipe = new byte[asymmetricKeyLength / 8]; - System.arraycopy(paddedPlainText, 0, tempPipe, tempPipe.length - paddedPlainText.length, - paddedPlainText.length); - paddedPlainText = tempPipe; - } - - return unpadOAEPPadding(paddedPlainText, keyModulus); - } - - // This is a hack of removing OEAP padding after decryption with NO Padding as - // SoftHSM does not support it.Will be removed after HSM implementation - /** - * - * @param paddedPlainText - * @param privateKey - * @return - */ - private byte[] unpadOAEPPadding(byte[] paddedPlainText, BigInteger keyModulus) { - - try { - OAEPEncoding encode = new OAEPEncoding(new RSAEngine(), new SHA256Digest()); - BigInteger exponent = new BigInteger("1"); - RSAKeyParameters keyParams = new RSAKeyParameters(false, keyModulus, exponent); - encode.init(false, keyParams); - return encode.processBlock(paddedPlainText, 0, paddedPlainText.length); - } catch (InvalidCipherTextException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION - .getErrorCode(), e.getMessage(), e); - } - } - - private byte[] jceAsymmetricDecrypt(PrivateKey privateKey, byte[] data, String storeType){ - Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - Cipher cipher; - try { - cipher = Objects.isNull(storeType) ? Cipher.getInstance(asymmetricAlgorithm) : - Cipher.getInstance(asymmetricAlgorithm, storeType); - OAEPParameterSpec oaepParams = new OAEPParameterSpec(HASH_ALGO, MGF1, MGF1ParameterSpec.SHA256, - PSpecified.DEFAULT); - cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams); - return doFinal(data, cipher); - } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } catch (java.security.InvalidKeyException e) { - throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } catch (InvalidAlgorithmParameterException e) { - throw new InvalidParamSpecException( - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); - } - } - - - @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); - SecretKey key; - try { - secretKeyFactory = SecretKeyFactory.getInstance(passwordAlgorithm); - 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) { - throw new NoSuchAlgorithmException( - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); - } - return DatatypeConverter.printHexBinary(key.getEncoded()); - } - - @Override - public String sign(byte[] data, PrivateKey privateKey) { - Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - JsonWebSignature jws = new JsonWebSignature(); - jws.setPayloadBytes(data); - jws.setAlgorithmHeaderValue(signAlgorithm); - jws.setKey(privateKey); - jws.setDoKeyValidation(false); - try { - return jws.getDetachedContentCompactSerialization(); - } catch (JoseException e) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - } - - @Override - public boolean verifySignature(byte[] data, String sign, PublicKey publicKey) { - if (EmptyCheckUtils.isNullEmpty(sign)) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); - } - Objects.requireNonNull(publicKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - JsonWebSignature jws = new JsonWebSignature(); - try { - String[] parts = sign.split(PERIOD_SEPARATOR_REGEX); - parts[1] = CryptoUtil.encodeBase64(data); - jws.setCompactSerialization(CompactSerializer.serialize(parts)); - jws.setKey(publicKey); - return jws.verifySignature(); - } catch (ArrayIndexOutOfBoundsException | JoseException e) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - - } - - @SuppressWarnings("unchecked") - @Override - public SecureRandom random() { - return secureRandomThreadLocal.get(); - } - - /** - * Generator for IV(Initialisation Vector) - * - * @param blockSize blocksize of current cipher - * @return generated IV - */ - private byte[] generateIV(int blockSize) { - byte[] byteIV = new byte[blockSize]; - secureRandomThreadLocal.get().nextBytes(byteIV); - return byteIV; - } - - private byte[] doFinal(byte[] data, Cipher cipher) { - try { - return cipher.doFinal(data); - } catch (IllegalBlockSizeException e) { - throw new InvalidDataException( - SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_SIZE_EXCEPTION.getErrorCode(), e.getMessage(), e); - } catch (BadPaddingException e) { - throw new InvalidDataException( - SecurityExceptionCodeConstant.MOSIP_INVALID_ENCRYPTED_DATA_CORRUPT_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - } - - /* - * This two methods here are for temporary, Unit test for this will be written - * in next versions - */ - @Override - public String sign(byte[] data, PrivateKey privateKey, X509Certificate x509Certificate) { - Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); - CryptoUtils.verifyData(data); - JsonWebSignature jws = new JsonWebSignature(); - List certList = new ArrayList<>(); - certList.add(x509Certificate); - X509Certificate[] certArray = certList.toArray(new X509Certificate[] {}); - jws.setCertificateChainHeaderValue(certArray); - jws.setPayloadBytes(data); - jws.setAlgorithmHeaderValue(signAlgorithm); - jws.setKey(privateKey); - jws.setDoKeyValidation(false); - try { - return jws.getCompactSerialization(); - } catch (JoseException e) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - } - - /* - * This two methods here are for temporary, Unit test for this will be written - * in next versions - */ - @Override - public boolean verifySignature(String sign) { - if (EmptyCheckUtils.isNullEmpty(sign)) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); - } - JsonWebSignature jws = new JsonWebSignature(); - try { - jws.setCompactSerialization(sign); - List certificateChainHeaderValue = jws.getCertificateChainHeaderValue(); - X509Certificate certificate = certificateChainHeaderValue.get(0); - certificate.checkValidity(); - PublicKey publicKey = certificate.getPublicKey(); - jws.setKey(publicKey); - return jws.verifySignature(); - } catch (JoseException | CertificateExpiredException | CertificateNotYetValidException e) { - throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), - e.getMessage(), e); - } - - } + 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); + byte[] output = null; + 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); + output = new byte[cipher.getOutputSize(data.length) + cipher.getBlockSize()]; + if (aad != null && aad.length != 0) { + cipher.updateAAD(aad); + } + byte[] processData = doFinal(data, cipher); + System.arraycopy(processData, 0, output, 0, processData.length); + System.arraycopy(randomIV, 0, output, processData.length, randomIV.length); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + return output; + } + + @Override + public byte[] symmetricEncrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + if (iv == null) { + return symmetricEncrypt(key, data, aad); + } + + 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); + if (aad != null && aad.length != 0) { + cipher.updateAAD(aad); + } + return doFinal(data, cipher); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + byte[] output = null; + try { + 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 gcmSpec = new GCMParameterSpec(tagLength, iv); + cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); + + if (aad != null && aad.length > 0) { + cipher.updateAAD(aad); + } + + 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); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_LENGTH_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] symmetricDecrypt(SecretKey key, byte[] data, byte[] iv, byte[] aad) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + if (iv == null) { + return symmetricDecrypt(key, data, aad); + } + 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); + if (aad != null) { + cipher.updateAAD(aad); + } + return doFinal(data, cipher); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] asymmetricEncrypt(PublicKey key, byte[] data) { + Objects.requireNonNull(key, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + + try { + 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); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, byte[] data) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, null); + } + return jceAsymmetricDecrypt(privateKey, data, null); + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : + ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, null); + } + return jceAsymmetricDecrypt(privateKey, data, null); + } + + @Override + public byte[] asymmetricDecrypt(PrivateKey privateKey, PublicKey publicKey, byte[] data, String storeType) { + if (PKCS11_STORE_TYPE.equalsIgnoreCase(keystoreType)) { + BigInteger keyModulus = Objects.nonNull(publicKey) ? ((RSAPublicKey) publicKey).getModulus() : + ((RSAPrivateKey) privateKey).getModulus(); + return asymmetricDecrypt(privateKey, keyModulus, data, storeType); + } + return jceAsymmetricDecrypt(privateKey, data, storeType); + } + + private byte[] asymmetricDecrypt(PrivateKey privateKey, BigInteger keyModulus, byte[] data, String storeType) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Objects.isNull(storeType) ? Cipher.getInstance(RSA_ECB_NO_PADDING) : // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + Cipher.getInstance(RSA_ECB_NO_PADDING, storeType); // NOSONAR using the padding for allowing OAEP padding in PKCS11 library + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } + + try { + cipher.init(Cipher.DECRYPT_MODE, privateKey); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + /* + * This is a hack of removing OEAP padding after decryption with NO Padding as + * SoftHSM does not support it.Will be removed after HSM implementation + */ + byte[] paddedPlainText = doFinal(data, cipher); + if (paddedPlainText.length < asymmetricKeyLength / 8) { + byte[] tempPipe = new byte[asymmetricKeyLength / 8]; + System.arraycopy(paddedPlainText, 0, tempPipe, tempPipe.length - paddedPlainText.length, + paddedPlainText.length); + paddedPlainText = tempPipe; + } + + return unpadOAEPPadding(paddedPlainText, keyModulus); + } + + // This is a hack of removing OEAP padding after decryption with NO Padding as + // SoftHSM does not support it.Will be removed after HSM implementation + /** + * + * @param paddedPlainText + * @param keyModulus + * @return + */ + private byte[] unpadOAEPPadding(byte[] paddedPlainText, BigInteger keyModulus) { + + try { + OAEPEncoding encode = new OAEPEncoding(new RSAEngine(), new SHA256Digest()); + BigInteger exponent = new BigInteger("1"); + RSAKeyParameters keyParams = new RSAKeyParameters(false, keyModulus, exponent); + encode.init(false, keyParams); + return encode.processBlock(paddedPlainText, 0, paddedPlainText.length); + } catch (InvalidCipherTextException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION + .getErrorCode(), e.getMessage(), e); + } + } + + private byte[] jceAsymmetricDecrypt(PrivateKey privateKey, byte[] data, String storeType){ + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + Cipher cipher; + try { + cipher = Objects.isNull(storeType) ? CIPHER_GCM_ENCRYPT_DECRYPT_ASYMMETRIC.get() : + Cipher.getInstance(asymmetricAlgorithm, storeType); + cipher.init(Cipher.DECRYPT_MODE, privateKey, OAEP_SHA256_MGF1); + return doFinal(data, cipher); + } catch (java.security.NoSuchAlgorithmException | NoSuchPaddingException | NoSuchProviderException e) { + throw new NoSuchAlgorithmException( + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_NO_SUCH_ALGORITHM_EXCEPTION.getErrorMessage(), e); + } catch (java.security.InvalidKeyException e) { + throw new InvalidKeyException(SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidParamSpecException( + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_INVALID_PARAM_SPEC_EXCEPTION.getErrorMessage(), e); + } + } + + @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()); + + final char[] convertedData = new String(data).toCharArray(); + final PBEKeySpec pbeKeySpec = new PBEKeySpec(convertedData, salt, iterations, symmetricKeyLength); + SecretKey key; + try { + 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 (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()); + } + + @Override + public String sign(byte[] data, PrivateKey privateKey) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayloadBytes(data); + jws.setAlgorithmHeaderValue(signAlgorithm); + jws.setKey(privateKey); + jws.setDoKeyValidation(false); + try { + return jws.getDetachedContentCompactSerialization(); + } catch (JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + @Override + public boolean verifySignature(byte[] data, String sign, PublicKey publicKey) { + if (EmptyCheckUtils.isNullEmpty(sign)) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); + } + Objects.requireNonNull(publicKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + try { + String[] parts = sign.split(PERIOD_SEPARATOR_REGEX); + parts[1] = CryptoUtil.encodeBase64(data); + jws.setCompactSerialization(CompactSerializer.serialize(parts)); + jws.setKey(publicKey); + return jws.verifySignature(); + } catch (ArrayIndexOutOfBoundsException | JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + @SuppressWarnings("unchecked") + @Override + public SecureRandom random() { + return secureRandomThreadLocal.get(); + } + + /** + * Generator for IV(Initialisation Vector) + * + * @param blockSize blocksize of current cipher + * @return generated IV + */ + private byte[] generateIV(int blockSize) { + byte[] byteIV = new byte[blockSize]; + secureRandomThreadLocal.get().nextBytes(byteIV); + return byteIV; + } + + private byte[] doFinal(byte[] data, Cipher cipher) { + try { + return cipher.doFinal(data); + } catch (IllegalBlockSizeException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_DATA_SIZE_EXCEPTION.getErrorCode(), e.getMessage(), e); + } catch (BadPaddingException e) { + throw new InvalidDataException( + SecurityExceptionCodeConstant.MOSIP_INVALID_ENCRYPTED_DATA_CORRUPT_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + /* + * This two methods here are for temporary, Unit test for this will be written + * in next versions + */ + @Override + public String sign(byte[] data, PrivateKey privateKey, X509Certificate x509Certificate) { + Objects.requireNonNull(privateKey, SecurityExceptionCodeConstant.MOSIP_INVALID_KEY_EXCEPTION.getErrorMessage()); + CryptoUtils.verifyData(data); + JsonWebSignature jws = new JsonWebSignature(); + List certList = new ArrayList<>(); + certList.add(x509Certificate); + X509Certificate[] certArray = certList.toArray(new X509Certificate[] {}); + jws.setCertificateChainHeaderValue(certArray); + jws.setPayloadBytes(data); + jws.setAlgorithmHeaderValue(signAlgorithm); + jws.setKey(privateKey); + jws.setDoKeyValidation(false); + try { + return jws.getCompactSerialization(); + } catch (JoseException e) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + e.getMessage(), e); + } + } + + /* + * This two methods here are for temporary, Unit test for this will be written + * in next versions + */ + @Override + public boolean verifySignature(String sign) { + if (EmptyCheckUtils.isNullEmpty(sign)) { + throw new SignatureException(SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorCode(), + SecurityExceptionCodeConstant.MOSIP_SIGNATURE_EXCEPTION.getErrorMessage()); + } + JsonWebSignature jws = new JsonWebSignature(); + try { + jws.setCompactSerialization(sign); + List certificateChainHeaderValue = jws.getCertificateChainHeaderValue(); + X509Certificate certificate = certificateChainHeaderValue.get(0); + certificate.checkValidity(); + PublicKey publicKey = certificate.getPublicKey(); + jws.setKey(publicKey); + return jws.verifySignature(); + } catch (JoseException | CertificateExpiredException | CertificateNotYetValidException e) { + 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/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java b/kernel/kernel-keymanager-service/src/main/java/io/mosip/kernel/partnercertservice/service/impl/PartnerCertificateManagerServiceImpl.java index feca3181..cb1b3a91 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. @@ -154,17 +153,34 @@ public class PartnerCertificateManagerServiceImpl implements PartnerCertificateM @Autowired 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() { @@ -173,19 +189,48 @@ public void init() { // Cache2kBuilder constructor is throwing error. checkAndUpdateCaCertificateTypeIsNull(); if (!disableTrustStoreCache) { - caCertTrustStore = new Cache2kBuilder() {} - // added hashcode because test case execution failing with IllegalStateException: Cache already created - .name("caCertTrustStore-" + this.hashCode()) - .expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) - .entryCapacity(10) - .refreshAhead(true) - .loaderThreadCount(1) - .loader((partnerDomain) -> { + caCertTrustStore = new Cache2kBuilder() {} + // added hashcode because test case execution failing with IllegalStateException: Cache already created + .name("caCertTrustStore-" + this.hashCode()) + .expireAfterWrite(cacheExpireInMins, TimeUnit.MINUTES) + .entryCapacity(10) + .refreshAhead(true) + .loaderThreadCount(1) + .loader((partnerDomain) -> { LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.EMPTY, PartnerCertManagerConstants.EMPTY, "Loading CA TrustStore Cache for partnerDomain: " + partnerDomain); return certDBHelper.getTrustAnchors(partnerDomain); - }) - .build(); + }) + .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(); } } @@ -224,7 +269,7 @@ public CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCe List certList = parseCertificateData(certificateData); int certsCount = certList.size(); LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.EMPTY, "Number of Certificates inputed: " + certsCount); + PartnerCertManagerConstants.EMPTY, "Number of Certificates inputed: " + certsCount); String partnerDomain = validateAllowedDomains(caCertRequestDto.getPartnerDomain()); boolean foundError = false; @@ -259,15 +304,15 @@ public CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCe boolean certValid = validateCertificatePath(reqX509Cert, partnerDomain); if (!certValid) { - LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.EMPTY, - "Sub-CA Certificate not allowed to upload as root CA is not available."); - if (certsCount == 1) { + LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, + PartnerCertManagerConstants.EMPTY, + "Sub-CA Certificate not allowed to upload as root CA is not available."); + if (certsCount == 1) { throw new PartnerCertManagerException(PartnerCertManagerErrorConstants.ROOT_CA_NOT_FOUND.getErrorCode(), - PartnerCertManagerErrorConstants.ROOT_CA_NOT_FOUND.getErrorMessage()); - } - foundError = true; - continue; + PartnerCertManagerErrorConstants.ROOT_CA_NOT_FOUND.getErrorMessage()); + } + foundError = true; + continue; } String issuerId = certDBHelper.getIssuerCertId(certIssuer); String certId = UUID.randomUUID().toString(); @@ -283,7 +328,7 @@ public CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCe responseDto.setStatus(PartnerCertManagerConstants.SUCCESS_UPLOAD); else if (uploadedCert && foundError) responseDto.setStatus(PartnerCertManagerConstants.PARTIAL_SUCCESS_UPLOAD); - else + else responseDto.setStatus(PartnerCertManagerConstants.UPLOAD_FAILED); responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); return responseDto; @@ -297,8 +342,8 @@ private List parseCertificateData(String certificateData) { return certList; } catch(KeymanagerServiceException kse) { LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.EMPTY, "Ignore this exception, the exception thrown when certificate is not" - + " able to parse, may be p7b certificate data inputed."); + PartnerCertManagerConstants.EMPTY, "Ignore this exception, the exception thrown when certificate is not" + + " able to parse, may be p7b certificate data inputed."); } // Try to Parse as P7B file. byte[] p7bBytes = CryptoUtil.decodeURLSafeBase64(certificateData); @@ -312,7 +357,7 @@ private List parseCertificateData(String certificateData) { return certList; } catch(CertificateException | IOException exp) { LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.EMPTY, "Error Parsing P7B Certificate data.", exp); + PartnerCertManagerConstants.EMPTY, "Error Parsing P7B Certificate data.", exp); } throw new PartnerCertManagerException( PartnerCertManagerErrorConstants.INVALID_CERTIFICATE.getErrorCode(), @@ -343,16 +388,24 @@ 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 .get(PartnerCertManagerConstants.TRUST_INTER); - + LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.CERT_TRUST_VALIDATION, PartnerCertManagerConstants.EMPTY, "Certificate Trust Path Validation for domain: " + partnerDomain); LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.CERT_TRUST_VALIDATION, @@ -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, @@ -440,26 +560,26 @@ public PartnerCertificateResponseDto uploadPartnerCertificate(PartnerCertificate String certId = UUID.randomUUID().toString(); X509Certificate rootCert = (X509Certificate) keymanagerUtil.convertToCertificate( - keymanagerService.getCertificate(PartnerCertManagerConstants.ROOT_APP_ID, - Optional.of(PartnerCertManagerConstants.EMPTY)).getCertificate()); + keymanagerService.getCertificate(PartnerCertManagerConstants.ROOT_APP_ID, + Optional.of(PartnerCertManagerConstants.EMPTY)).getCertificate()); String timestamp = DateUtils.getUTCCurrentDateTimeString(); SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(masterSignKeyAppId, - Optional.of(PartnerCertManagerConstants.EMPTY), timestamp); + Optional.of(PartnerCertManagerConstants.EMPTY), timestamp); X509Certificate pmsCert = certificateResponse.getCertificateEntry().getChain()[0]; X509Certificate resignedCert = reSignPartnerKey(reqX509Cert, certificateResponse, partnerDomain); String signedCertData = keymanagerUtil.getPEMFormatedData(resignedCert); certDBHelper.storePartnerCertificate(certId, certSubject, certIssuer, issuerId, reqX509Cert, certThumbprint, reqOrgName, partnerDomain, signedCertData); - - String p7bCertChain = PartnerCertificateManagerUtil.buildP7BCertificateChain(certList, resignedCert, partnerDomain, - resignFTMDomainCerts, rootCert, pmsCert); + + String p7bCertChain = PartnerCertificateManagerUtil.buildP7BCertificateChain(certList, resignedCert, partnerDomain, + resignFTMDomainCerts, rootCert, pmsCert); CACertificateRequestDto caCertReqDto = new CACertificateRequestDto(); caCertReqDto.setCertificateData(p7bCertChain); caCertReqDto.setPartnerDomain(partnerDomain); CACertificateResponseDto uploadResponseDto = uploadCACertificate(caCertReqDto); LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, - "Chain Upload Status: ", uploadResponseDto.getStatus()); + "Chain Upload Status: ", uploadResponseDto.getStatus()); PartnerCertificateResponseDto responseDto = new PartnerCertificateResponseDto(); responseDto.setCertificateId(certId); responseDto.setSignedCertificateData(p7bCertChain); @@ -468,7 +588,7 @@ public PartnerCertificateResponseDto uploadPartnerCertificate(PartnerCertificate } private void validateBasicPartnerCertParams(X509Certificate reqX509Cert, String certThumbprint, String reqOrgName, - String partnerDomain) { + String partnerDomain) { boolean certExist = certDBHelper.isPartnerCertificateExist(certThumbprint, partnerDomain); if (certExist) { LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, @@ -511,27 +631,27 @@ private void validateBasicPartnerCertParams(X509Certificate reqX509Cert, String boolean selfSigned = PartnerCertificateManagerUtil.isSelfSignedCertificate(reqX509Cert); if (selfSigned) { LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, - PartnerCertManagerConstants.EMPTY, "Self Signed Certificate are not in allowed as Partner."); + PartnerCertManagerConstants.EMPTY, "Self Signed Certificate are not in allowed as Partner."); throw new PartnerCertManagerException( - PartnerCertManagerErrorConstants.SELF_SIGNED_CERT_NOT_ALLOWED.getErrorCode(), - PartnerCertManagerErrorConstants.SELF_SIGNED_CERT_NOT_ALLOWED.getErrorMessage()); + PartnerCertManagerErrorConstants.SELF_SIGNED_CERT_NOT_ALLOWED.getErrorCode(), + PartnerCertManagerErrorConstants.SELF_SIGNED_CERT_NOT_ALLOWED.getErrorMessage()); } } private boolean validateBasicCaCertificateParams(X509Certificate reqX509Cert, String certThumbprint, int certsCount, - String partnerDomain) { + String partnerDomain) { boolean foundError = false; boolean certExist = certDBHelper.isCertificateExist(certThumbprint, partnerDomain); - if (certExist) { - LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.EMPTY, "CA/sub-CA certificate already exists in Store."); - if (certsCount == 1) { - throw new PartnerCertManagerException( - PartnerCertManagerErrorConstants.CERTIFICATE_EXIST_ERROR.getErrorCode(), - PartnerCertManagerErrorConstants.CERTIFICATE_EXIST_ERROR.getErrorMessage()); - } - foundError = true; + if (certExist) { + LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, + PartnerCertManagerConstants.EMPTY, "CA/sub-CA certificate already exists in Store."); + if (certsCount == 1) { + throw new PartnerCertManagerException( + PartnerCertManagerErrorConstants.CERTIFICATE_EXIST_ERROR.getErrorCode(), + PartnerCertManagerErrorConstants.CERTIFICATE_EXIST_ERROR.getErrorMessage()); } + foundError = true; + } boolean futureDated = PartnerCertificateManagerUtil.isFutureDatedCertificate(reqX509Cert); if (!futureDated) { @@ -572,13 +692,13 @@ private boolean validateBasicCaCertificateParams(X509Certificate reqX509Cert, St LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, PartnerCertManagerConstants.EMPTY, "CA Certificate version not valid, the version has to be V3"); - if (certsCount == 1){ - throw new PartnerCertManagerException(PartnerCertManagerErrorConstants.INVALID_CERT_VERSION.getErrorCode(), - PartnerCertManagerErrorConstants.INVALID_CERT_VERSION.getErrorMessage()); - } - foundError = true; + if (certsCount == 1){ + throw new PartnerCertManagerException(PartnerCertManagerErrorConstants.INVALID_CERT_VERSION.getErrorCode(), + PartnerCertManagerErrorConstants.INVALID_CERT_VERSION.getErrorMessage()); + } + foundError = true; } - return foundError; + return foundError; } private void validateOtherPartnerCertParams(X509Certificate reqX509Cert, String reqOrgName) { @@ -630,18 +750,18 @@ private void validateOtherPartnerCertParams(X509Certificate reqX509Cert, String } } - private X509Certificate reSignPartnerKey(X509Certificate reqX509Cert, SignatureCertificate certificateResponse, - String partnerDomain) { + private X509Certificate reSignPartnerKey(X509Certificate reqX509Cert, SignatureCertificate certificateResponse, + String partnerDomain) { LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, "KeyAlias", "Found Master Key Alias: " + certificateResponse.getAlias()); - + boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(masterSignKeyAppId); if (!hasAcccess) { - LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, PartnerCertManagerConstants.EMPTY, - "Signing Certifiate is not allowed for the authenticated user for the provided application id."); - throw new PartnerCertManagerException(PartnerCertManagerErrorConstants.SIGN_CERT_NOT_ALLOWED.getErrorCode(), - PartnerCertManagerErrorConstants.SIGN_CERT_NOT_ALLOWED.getErrorMessage()); + LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, PartnerCertManagerConstants.EMPTY, + "Signing Certifiate is not allowed for the authenticated user for the provided application id."); + throw new PartnerCertManagerException(PartnerCertManagerErrorConstants.SIGN_CERT_NOT_ALLOWED.getErrorCode(), + PartnerCertManagerErrorConstants.SIGN_CERT_NOT_ALLOWED.getErrorMessage()); } PrivateKey signPrivateKey = certificateResponse.getCertificateEntry().getPrivateKey(); X509Certificate signCert = certificateResponse.getCertificateEntry().getChain()[0]; @@ -649,11 +769,11 @@ private X509Certificate reSignPartnerKey(X509Certificate reqX509Cert, SignatureC X500Principal subjectPrincipal = reqX509Cert.getSubjectX500Principal(); PublicKey partnerPublicKey = reqX509Cert.getPublicKey(); - + int noOfDays = PartnerCertManagerConstants.YEAR_DAYS * issuerCertDuration; LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, "Cert Duration", "Calculated Signed Certficiate Number of Days for expire: " + noOfDays); - LocalDateTime notBeforeDate = DateUtils.getUTCCurrentDateTime(); + LocalDateTime notBeforeDate = DateUtils.getUTCCurrentDateTime(); LocalDateTime notAfterDate = notBeforeDate.plus(noOfDays, ChronoUnit.DAYS); CertificateParameters certParams = PartnerCertificateManagerUtil.getCertificateParameters(subjectPrincipal, notBeforeDate, notAfterDate); @@ -698,10 +818,10 @@ public CertificateTrustResponeDto verifyCertificateTrust(CertificateTrustRequest boolean certValid = validateCertificatePath(reqX509Cert, partnerDomain); CertificateTrustResponeDto responseDto = new CertificateTrustResponeDto(); - responseDto.setStatus(certValid); + responseDto.setStatus(certValid); return responseDto; } - + @Override public void purgeTrustStoreCache(String partnerDomain) { purgeCache(partnerDomain); @@ -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); }); } } @@ -791,7 +915,7 @@ private CACertificateStore getCACertificate(String caCertId) { private PartnerCertificateStore getPartnerCertificate(String partnetCertId) { LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.GET_PARTNER_CERT, PartnerCertManagerConstants.EMPTY, - "Request to get Certificate for partnerId: " + partnetCertId); + "Request to get Certificate for partnerId: " + partnetCertId); if (!PartnerCertificateManagerUtil.isValidCertificateID(partnetCertId)) { LOGGER.error(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_PARTNER_CERT, @@ -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..f8b0061c 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 @@ -44,7 +44,7 @@ /** * Utility class for Partner Certificate Management - * + * * @author Mahammed Taheer * @since 1.1.3 * @@ -57,9 +57,9 @@ public class PartnerCertificateManagerUtil { /** * Function to check certificate is self-signed. - * + * * @param x509Cert X509Certificate - * + * * @return true if x509Cert is self-signed, else false */ public static boolean isSelfSignedCertificate(X509Certificate x509Cert) { @@ -67,7 +67,7 @@ public static boolean isSelfSignedCertificate(X509Certificate x509Cert) { x509Cert.verify(x509Cert.getPublicKey()); return true; } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException - | NoSuchProviderException exp) { + | NoSuchProviderException exp) { LOGGER.debug(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, PartnerCertManagerConstants.PCM_UTIL, "Ignore this exception, the exception thrown when signature validation failed."); @@ -101,9 +101,9 @@ public static boolean isFutureDatedCertificate(X509Certificate x509Certificate) /** * Function to format X500Principal of certificate. - * + * * @param certPrincipal String form of X500Principal - * + * * @return String of Custom format of certificateDN. */ public static String formatCertificateDN(String certPrincipal) { @@ -145,7 +145,7 @@ public static String getCertificateThumbprint(X509Certificate x509Cert) { } public static boolean isCertificateDatesValid(X509Certificate x509Cert) { - + try { Date currentDate = Date.from(DateUtils.getUTCCurrentDateTime().atZone(ZoneId.systemDefault()).toInstant()); x509Cert.checkValidity(currentDate); @@ -168,35 +168,35 @@ public static boolean isCertificateDatesValid(X509Certificate x509Cert) { } public static boolean isCertificateValidForDuration(X509Certificate x509Cert, int issuerCertDuration, int gracePeriod) { - + int noOfDays = (issuerCertDuration * PartnerCertManagerConstants.YEAR_DAYS) - gracePeriod; if (noOfDays < 0) { noOfDays = DEFAULT_ALLOWED_CERTIFICATE_DAYS; - } + } LocalDateTime localDateTimeStamp = DateUtils.getUTCCurrentDateTime();//.plus(noOfDays, ChronoUnit.DAYS); LocalDateTime certNotAfter = x509Cert.getNotAfter().toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime(); long validDays = ChronoUnit.DAYS.between(localDateTimeStamp, certNotAfter); - if ((validDays - noOfDays) >= 0) + if ((validDays - noOfDays) >= 0) return true; LOGGER.info(PartnerCertManagerConstants.SESSIONID, PartnerCertManagerConstants.UPLOAD_CA_CERT, - PartnerCertManagerConstants.PCM_UTIL, "Remaining validity for the Certificate is " + validDays + - " days, grace days configured is " + gracePeriod); + PartnerCertManagerConstants.PCM_UTIL, "Remaining validity for the Certificate is " + validDays + + " days, grace days configured is " + gracePeriod); return false; } public static boolean isValidTimestamp(LocalDateTime timeStamp, CACertificateStore certStore) { - boolean valid = timeStamp.isEqual(certStore.getCertNotBefore()) || timeStamp.isEqual(certStore.getCertNotAfter()) - || (timeStamp.isAfter(certStore.getCertNotBefore()) - && timeStamp.isBefore(certStore.getCertNotAfter())); + boolean valid = timeStamp.isEqual(certStore.getCertNotBefore()) || timeStamp.isEqual(certStore.getCertNotAfter()) + || (timeStamp.isAfter(certStore.getCertNotBefore()) + && timeStamp.isBefore(certStore.getCertNotAfter())); if (!valid) { LocalDateTime localDateTimeNow = LocalDateTime.now(); valid = localDateTimeNow.isEqual(certStore.getCertNotBefore()) || localDateTimeNow.isEqual(certStore.getCertNotAfter()) - || (localDateTimeNow.isAfter(certStore.getCertNotBefore()) - && localDateTimeNow.isBefore(certStore.getCertNotAfter())); + || (localDateTimeNow.isAfter(certStore.getCertNotBefore()) + && localDateTimeNow.isBefore(certStore.getCertNotAfter())); } return valid; - } + } public static String getCertificateOrgName(X500Principal x500CertPrincipal) { X500Name x500Name = new X500Name(x500CertPrincipal.getName()); @@ -208,14 +208,14 @@ public static String getCertificateOrgName(X500Principal x500CertPrincipal) { } public static boolean isValidCertificateID(String certID) { - return certID != null && !certID.trim().isEmpty(); + return certID != null && !certID.trim().isEmpty(); } - - public static CertificateParameters getCertificateParameters(X500Principal latestCertPrincipal, LocalDateTime notBefore, - LocalDateTime notAfter) { - CertificateParameters certParams = new CertificateParameters(); - X500Name x500Name = new X500Name(latestCertPrincipal.getName()); + public static CertificateParameters getCertificateParameters(X500Principal latestCertPrincipal, LocalDateTime notBefore, + LocalDateTime notAfter) { + + CertificateParameters certParams = new CertificateParameters(); + X500Name x500Name = new X500Name(latestCertPrincipal.getName()); certParams.setCommonName(IETFUtils.valueToString((x500Name.getRDNs(BCStyle.CN)[0]).getFirst().getValue())); certParams.setOrganizationUnit(getAttributeValueIfExist(x500Name, BCStyle.OU)); @@ -223,10 +223,10 @@ public static CertificateParameters getCertificateParameters(X500Principal lates certParams.setLocation(getAttributeValueIfExist(x500Name, BCStyle.L)); certParams.setState(getAttributeValueIfExist(x500Name, BCStyle.ST)); certParams.setCountry(getAttributeValueIfExist(x500Name, BCStyle.C)); - certParams.setNotBefore(notBefore); - certParams.setNotAfter(notAfter); + certParams.setNotBefore(notBefore); + certParams.setNotAfter(notAfter); return certParams; - } + } private static String getAttributeValueIfExist(X500Name x500Name, ASN1ObjectIdentifier identifier) { RDN[] rdns = x500Name.getRDNs(identifier); @@ -236,13 +236,13 @@ private static String getAttributeValueIfExist(X500Name x500Name, ASN1ObjectIden return IETFUtils.valueToString((rdns[0]).getFirst().getValue()); } - public static String buildP7BCertificateChain(List certList, X509Certificate resignedCert, - String partnerDomain, boolean resignFTMDomainCerts, X509Certificate rootCert, X509Certificate pmsCert) { - + public static String buildP7BCertificateChain(List certList, X509Certificate resignedCert, + String partnerDomain, boolean resignFTMDomainCerts, X509Certificate rootCert, X509Certificate pmsCert) { + if (partnerDomain.toUpperCase().equals(PartnerCertManagerConstants.FTM_PARTNER_DOMAIN) && !resignFTMDomainCerts) { return buildCertChain(certList.toArray(new Certificate[0])); } - + List chain = new ArrayList<>(); chain.add(resignedCert); chain.add(pmsCert); @@ -255,7 +255,7 @@ public static String buildp7bFile(Certificate[] chain) { } private static String buildCertChain(Certificate[] chain) { - + try { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); JcaCertStore jcaStore = new JcaCertStore(Arrays.asList(chain)); @@ -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 849d01e7..d61c3579 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,13 +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 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; @@ -45,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; @@ -82,558 +80,711 @@ @Service public class SignatureServiceImpl implements SignatureService, SignatureServicev2 { - private static final Logger LOGGER = KeymanagerLogger.getLogger(SignatureServiceImpl.class); - - @Autowired - private KeymanagerService keymanagerService; - - @Autowired - private CryptoCoreSpec cryptoCore; - - @Value("${mosip.kernel.keygenerator.asymmetric-algorithm-name}") - private String asymmetricAlgorithmName; - - /** The sign applicationid. */ - @Value("${mosip.sign.applicationid:KERNEL}") - private String signApplicationid; - - /** The sign refid. */ - @Value("${mosip.sign.refid:SIGN}") - private String signRefid; - - @Value("${mosip.kernel.crypto.sign-algorithm-name:RS256}") - private String signAlgorithm; - - @Value("${mosip.kernel.keymanager.jwtsign.validate.json:true}") - private boolean confValidateJson; - - @Value("${mosip.kernel.keymanager.jwtsign.include.keyid:true}") - private boolean includeKeyId; - - @Value("${mosip.kernel.keymanager.jwtsign.enable.secp256k1.algorithm:true}") - private boolean enableSecp256k1Algo; - - @Value("${mosip.kernel.keymanager.signature.kid.prepend:}") - private String kidPrepend; - - /** - * Utility to generate Metadata - */ - @Autowired - KeymanagerUtil keymanagerUtil; - - @Autowired - private PDFGenerator pdfGenerator; - - /** - * Instance for PartnerCertificateManagerService - */ - @Autowired - PartnerCertificateManagerService partnerCertManagerService; - - @Autowired - CryptomanagerUtils cryptomanagerUtil; - - @Autowired - ECKeyStore ecKeyStore; - - private static Map SIGNATURE_PROVIDER = new HashMap<>(); - - AlgorithmFactory jwsAlgorithmFactory; - - static { - SIGNATURE_PROVIDER.put(SignatureConstant.JWS_PS256_SIGN_ALGO_CONST, new PS256SIgnatureProviderImpl()); - SIGNATURE_PROVIDER.put(SignatureConstant.JWS_RS256_SIGN_ALGO_CONST, new RS256SignatureProviderImpl()); - SIGNATURE_PROVIDER.put(SignatureConstant.JWS_ES256_SIGN_ALGO_CONST, new EC256SignatureProviderImpl()); - SIGNATURE_PROVIDER.put(SignatureConstant.JWS_ES256K_SIGN_ALGO_CONST, new EC256SignatureProviderImpl()); - SIGNATURE_PROVIDER.put(SignatureConstant.JWS_EDDSA_SIGN_ALGO_CONST, new Ed25519SignatureProviderImpl()); - } - - private static Map JWT_SIGNATURE_ALGO_IDENT = new HashMap<>(); - static { - JWT_SIGNATURE_ALGO_IDENT.put(SignatureConstant.BLANK, AlgorithmIdentifiers.RSA_USING_SHA256); - JWT_SIGNATURE_ALGO_IDENT.put(SignatureConstant.REF_ID_SIGN_CONST, AlgorithmIdentifiers.RSA_USING_SHA256); - JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.EC_SECP256K1_SIGN.name(), AlgorithmIdentifiers.ECDSA_USING_SECP256K1_CURVE_AND_SHA256); - JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.EC_SECP256R1_SIGN.name(), AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256); - JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.ED25519_SIGN.name(), AlgorithmIdentifiers.EDDSA); - } - - @PostConstruct - public void init() { - KeyGeneratorUtils.loadClazz(); - if (enableSecp256k1Algo) { - AlgorithmFactory jwsAlgorithmFactory = - AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory(); - jwsAlgorithmFactory.registerAlgorithm(new EcdsaSECP256K1UsingSha256()); - } - } - - @Override - public SignatureResponse sign(SignRequestDto signRequestDto) { - 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); - return new SignatureResponse(signatureResponseDTO.getData(), DateUtils.convertUTCToLocalDateTime(timestamp)); - } - - 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())); - String encryptedSignedData = null; - if (certificateResponse.getCertificateEntry() != null) { - encryptedSignedData = cryptoCore.sign(signatureRequestDto.getData().getBytes(), - certificateResponse.getCertificateEntry().getPrivateKey()); - } - return new SignatureResponseDto(encryptedSignedData); - } - - @Override - public ValidatorResponseDto validate(TimestampRequestDto timestampRequestDto) { - - PublicKeyResponse publicKeyResponse = keymanagerService.getSignPublicKey(signApplicationid, - DateUtils.formatToISOString(timestampRequestDto.getTimestamp()), Optional.of(signRefid)); - boolean status; - try { - PublicKey publicKey = KeyFactory.getInstance(asymmetricAlgorithmName) - .generatePublic(new X509EncodedKeySpec(CryptoUtil.decodeURLSafeBase64(publicKeyResponse.getPublicKey()))); - status = cryptoCore.verifySignature(timestampRequestDto.getData().getBytes(), - timestampRequestDto.getSignature(), publicKey); - } catch (InvalidKeySpecException | NoSuchAlgorithmException exception) { - throw new PublicKeyParseException(SignatureErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), - exception.getMessage(), exception); - } - - if (status) { - ValidatorResponseDto response = new ValidatorResponseDto(); - response.setMessage(SignatureConstant.VALIDATION_SUCCESSFUL); - response.setStatus(SignatureConstant.SUCCESS); - return response; - } else { - throw new SignatureFailureException(SignatureErrorCode.NOT_VALID.getErrorCode(), - SignatureErrorCode.NOT_VALID.getErrorMessage(), null); - } - } - - @Override - public SignatureResponseDto signPDF(PDFSignatureRequestDto request) { - 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()); - OutputStream outputStream; - try { - 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), - signatureCertificate.getCertificateEntry(), request.getPassword()); - LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, - "Completed PDF Signing."); - } 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 - 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) { - 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()); - throw new RequestException(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), - SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorMessage()); - } - - String reqDataToSign = jwtSignRequestDto.getDataToSign(); - if (!SignatureUtil.isDataValid(reqDataToSign)) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Provided Data to sign is invalid."); - throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), - SignatureErrorCode.INVALID_INPUT.getErrorMessage()); - } - - String decodedDataToSign = new String(CryptoUtil.decodeURLSafeBase64(reqDataToSign)); - if (confValidateJson && !SignatureUtil.isJsonValid(decodedDataToSign)) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Provided Data to sign is invalid JSON."); - throw new RequestException(SignatureErrorCode.INVALID_JSON.getErrorCode(), - SignatureErrorCode.INVALID_JSON.getErrorMessage()); - } - - String timestamp = DateUtils.getUTCCurrentDateTimeString(); - String applicationId = jwtSignRequestDto.getApplicationId(); - String referenceId = jwtSignRequestDto.getReferenceId(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludePayload()); - boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertificate()); - boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertHash()); - String certificateUrl = SignatureUtil.isDataValid( - jwtSignRequestDto.getCertificateUrl()) ? jwtSignRequestDto.getCertificateUrl(): null; - - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, - Optional.of(referenceId), timestamp); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils.parseUTCToDate(timestamp)); - String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, - includeCertHash, certificateUrl, referenceId); - JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); - responseDto.setJwtSignedData(signedData); - responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Request - Completed"); - - return responseDto; - } - - private String sign(String dataToSign, SignatureCertificate certificateResponse, boolean includePayload, - boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId) { - - JsonWebSignature jwSign = new JsonWebSignature(); - 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); - - String kidPrefix = kidPrepend; - if (kidPrepend.equalsIgnoreCase(SignatureConstant.KEY_ID_PREFIX)) { - kidPrefix = SignatureUtil.getIssuerFromPayload(dataToSign).concat(SignatureConstant.KEY_ID_SEPARATOR); - } - - String keyId = SignatureUtil.convertHexToBase64(certificateResponse.getUniqueIdentifier()); - if (includeKeyId && Objects.nonNull(keyId)) - jwSign.setKeyIdHeaderValue(kidPrefix.concat(keyId)); - - jwSign.setPayload(dataToSign); - String algoString = JWT_SIGNATURE_ALGO_IDENT.get(referenceId); - 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()); - 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(); - - return jwSign.getDetachedContentCompactSerialization(); - } 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); - } - } - - public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtVerifyRequestDto) { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Verification Request."); - String signedData = jwtVerifyRequestDto.getJwtSignatureData(); - if (!SignatureUtil.isDataValid(signedData)) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Provided Signed Data value is invalid."); - throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), - SignatureErrorCode.INVALID_INPUT.getErrorMessage()); - } - - 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)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - 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); - } - - JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto(); - responseDto.setSignatureValid(signatureValid); - responseDto.setMessage(signatureValid ? SignatureConstant.VALIDATION_SUCCESSFUL : SignatureConstant.VALIDATION_FAILED); - responseDto.setTrustValid(validateTrust(jwtVerifyRequestDto, certToVerify, reqCertData)); - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Verification Request - Completed."); - return responseDto; - } - - private Certificate getCertificateToVerify(String reqCertData, String applicationId, String referenceId) { - // 2nd precedence to consider certificate to use in signature verification (Certificate Data provided in request). - if (reqCertData != null) - return keymanagerUtil.convertToCertificate(reqCertData); - - // 3rd precedence to consider certificate to use in signature verification. (based on AppId & RefId) - KeyPairGenerateResponseDto certificateResponse = keymanagerService.getCertificate(applicationId, - Optional.of(referenceId)); - return keymanagerUtil.convertToCertificate(certificateResponse.getCertificate()); - } - - @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) { - 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))); - } - 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) { - JsonWebSignature jws = new JsonWebSignature(); - try { - X509Certificate x509CertToVerify = (X509Certificate) certToVerify; - boolean validCert = SignatureUtil.isCertificateDatesValid(x509CertToVerify); - if (!validCert) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Error certificate dates are not valid."); - throw new CertificateNotValidException(SignatureErrorCode.CERT_NOT_VALID.getErrorCode(), - SignatureErrorCode.CERT_NOT_VALID.getErrorMessage()); - } - - String keyAlgorithm = x509CertToVerify.getPublicKey().getAlgorithm(); - PublicKey publicKey = null; - if (keyAlgorithm.equals(KeymanagerConstant.EDDSA_KEY_TYPE)) { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Found Ed25519 Certificate for Signature verification."); - publicKey = KeyGeneratorUtils.createPublicKey(KeymanagerConstant.ED25519_KEY_TYPE, - x509CertToVerify.getPublicKey().getEncoded()); - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Supported Signature Algorithm: " + - AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory().getSupportedAlgorithms()); - } else { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "KeyStore Provider Name:" + ecKeyStore.getKeystoreProviderName()); - if (!ecKeyStore.getKeystoreProviderName().equals( - io.mosip.kernel.keymanager.hsm.constant.KeymanagerConstant.KEYSTORE_TYPE_OFFLINE)) { - ProviderContext provContext = new ProviderContext(); - provContext.getSuppliedKeyProviderContext().setSignatureProvider(ecKeyStore.getKeystoreProviderName()); - jws.setProviderContext(provContext); - } - publicKey = certToVerify.getPublicKey(); - } - - if (Objects.nonNull(actualData)) - jwtTokens[1] = actualData; - - jws.setCompactSerialization(CompactSerializer.serialize(jwtTokens)); - jws.setDoKeyValidation(false); - if (Objects.nonNull(publicKey)) - jws.setKey(publicKey); - - return jws.verifySignature(); - } catch (JoseException e) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Provided Signed Data value is invalid.", e); - throw new SignatureFailureException(SignatureErrorCode.VERIFY_ERROR.getErrorCode(), - SignatureErrorCode.VERIFY_ERROR.getErrorMessage(), e); - } - } - - 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) { - return SignatureConstant.TRUST_NOT_VERIFIED; - } - - 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); - } - String trustCertData = certData == null ? reqCertData : certData; - - if (trustCertData == null) - return SignatureConstant.TRUST_NOT_VERIFIED; - - CertificateTrustRequestDto trustRequestDto = new CertificateTrustRequestDto(); - trustRequestDto.setCertificateData(trustCertData); - trustRequestDto.setPartnerDomain(domain); - CertificateTrustResponeDto responseDto = partnerCertManagerService.verifyCertificateTrust(trustRequestDto); - - if (responseDto.getStatus()){ - return SignatureConstant.TRUST_VALID; - } - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Verification Request - Trust Validation - Completed."); - return SignatureConstant.TRUST_NOT_VALID; - } - - @Override - public JWTSignatureResponseDto jwsSign(JWSSignatureRequestDto jwsSignRequestDto) { - // TODO Code is duplicated from jwtSign method. Duplicate code will be removed later when VC verification is implement. - // Code duplicated because now does not want to make any change to existing code which is well tested. - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, - "JWS Signature Request."); - - boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(jwsSignRequestDto.getApplicationId()); - if (!hasAcccess) { - 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(); - if (!SignatureUtil.isDataValid(reqDataToSign)) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, - "Provided Data to sign is invalid."); - throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), - 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."); - 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(); - String applicationId = jwsSignRequestDto.getApplicationId(); - String referenceId = jwsSignRequestDto.getReferenceId(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludePayload()); - boolean includeCertificate = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertificate()); - boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertHash()); - 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); - - JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); - responseDto.setJwtSignedData(signedData.toString()); - 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; - } - - public static class EcdsaSECP256K1UsingSha256 extends EcdsaUsingShaAlgorithm + private static final Logger LOGGER = KeymanagerLogger.getLogger(SignatureServiceImpl.class); + + @Autowired + private KeymanagerService keymanagerService; + + @Autowired + private CryptoCoreSpec cryptoCore; + + @Value("${mosip.kernel.keygenerator.asymmetric-algorithm-name}") + private String asymmetricAlgorithmName; + + /** The sign applicationid. */ + @Value("${mosip.sign.applicationid:KERNEL}") + private String signApplicationid; + + /** The sign refid. */ + @Value("${mosip.sign.refid:SIGN}") + private String signRefid; + + @Value("${mosip.kernel.crypto.sign-algorithm-name:RS256}") + private String signAlgorithm; + + @Value("${mosip.kernel.keymanager.jwtsign.validate.json:true}") + private boolean confValidateJson; + + @Value("${mosip.kernel.keymanager.jwtsign.include.keyid:true}") + private boolean includeKeyId; + + @Value("${mosip.kernel.keymanager.jwtsign.enable.secp256k1.algorithm:true}") + private boolean enableSecp256k1Algo; + + @Value("${mosip.kernel.keymanager.signature.kid.prepend:}") + private String kidPrepend; + + /** + * Utility to generate Metadata + */ + @Autowired + private KeymanagerUtil keymanagerUtil; + + @Autowired + private PDFGenerator pdfGenerator; + + /** + * Instance for PartnerCertificateManagerService + */ + @Autowired + private PartnerCertificateManagerService partnerCertManagerService; + + @Autowired + private CryptomanagerUtils cryptomanagerUtil; + + @Autowired + private ECKeyStore ecKeyStore; + + private static Map SIGNATURE_PROVIDER = new HashMap<>(); + + private AlgorithmFactory jwsAlgorithmFactory; + + static { + SIGNATURE_PROVIDER.put(SignatureConstant.JWS_PS256_SIGN_ALGO_CONST, new PS256SIgnatureProviderImpl()); + SIGNATURE_PROVIDER.put(SignatureConstant.JWS_RS256_SIGN_ALGO_CONST, new RS256SignatureProviderImpl()); + SIGNATURE_PROVIDER.put(SignatureConstant.JWS_ES256_SIGN_ALGO_CONST, new EC256SignatureProviderImpl()); + SIGNATURE_PROVIDER.put(SignatureConstant.JWS_ES256K_SIGN_ALGO_CONST, new EC256SignatureProviderImpl()); + SIGNATURE_PROVIDER.put(SignatureConstant.JWS_EDDSA_SIGN_ALGO_CONST, new Ed25519SignatureProviderImpl()); + } + + private static Map JWT_SIGNATURE_ALGO_IDENT = new HashMap<>(); + static { + JWT_SIGNATURE_ALGO_IDENT.put(SignatureConstant.BLANK, AlgorithmIdentifiers.RSA_USING_SHA256); + JWT_SIGNATURE_ALGO_IDENT.put(SignatureConstant.REF_ID_SIGN_CONST, AlgorithmIdentifiers.RSA_USING_SHA256); + JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.EC_SECP256K1_SIGN.name(), AlgorithmIdentifiers.ECDSA_USING_SECP256K1_CURVE_AND_SHA256); + JWT_SIGNATURE_ALGO_IDENT.put(KeyReferenceIdConsts.EC_SECP256R1_SIGN.name(), AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256); + 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(); + if (enableSecp256k1Algo) { + AlgorithmFactory jwsAlgorithmFactory = + AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory(); + jwsAlgorithmFactory.registerAlgorithm(new EcdsaSECP256K1UsingSha256()); + } + } + + @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()); + signatureRequestDto.setTimeStamp(timestamp); + + final SignatureResponseDto signatureResponseDTO = sign(signatureRequestDto); + return new SignatureResponse(signatureResponseDTO.getData(), DateUtils.convertUTCToLocalDateTime(timestamp)); + } + + private SignatureResponseDto sign(SignatureRequestDto signatureRequestDto) { + SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate( + signatureRequestDto.getApplicationId(), Optional.of(signatureRequestDto.getReferenceId()), + 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(StandardCharsets.UTF_8), + certificateResponse.getCertificateEntry().getPrivateKey()); + } + return new SignatureResponseDto(encryptedSignedData); + } + + @Override + public ValidatorResponseDto validate(TimestampRequestDto timestampRequestDto) { + + PublicKeyResponse publicKeyResponse = keymanagerService.getSignPublicKey(signApplicationid, + DateUtils.formatToISOString(timestampRequestDto.getTimestamp()), Optional.of(signRefid)); + boolean status; + try { + 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 (Exception exception) { + throw new PublicKeyParseException(SignatureErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), + exception.getMessage(), exception); + } + + if (status) { + ValidatorResponseDto response = new ValidatorResponseDto(); + response.setMessage(SignatureConstant.VALIDATION_SUCCESSFUL); + response.setStatus(SignatureConstant.SUCCESS); + return response; + } + throw new SignatureFailureException(SignatureErrorCode.NOT_VALID.getErrorCode(), + SignatureErrorCode.NOT_VALID.getErrorMessage(), null); + } + + @Override + public SignatureResponseDto signPDF(PDFSignatureRequestDto request) { + 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); + + // Precompute rectangle once + final Rectangle rectangle = new Rectangle( + request.getLowerLeftX(), request.getLowerLeftY(), + request.getUpperRightX(), request.getUpperRightY()); + + OutputStream outputStream; + try { + final String providerName = signatureCertificate.getProviderName(); + LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, + " Keystore Provider Name found: " + 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()); + + 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()); + } + } + + @Override + public JWTSignatureResponseDto jwtSign(JWTSignatureRequestDto jwtSignRequestDto) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Request."); + + 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()); + throw new RequestException(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), + SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorMessage()); + } + + final String reqDataToSign = jwtSignRequestDto.getDataToSign(); + if (!SignatureUtil.isDataValid(reqDataToSign)) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Provided Data to sign is invalid."); + throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), + SignatureErrorCode.INVALID_INPUT.getErrorMessage()); + } + + 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."); + throw new RequestException(SignatureErrorCode.INVALID_JSON.getErrorCode(), + SignatureErrorCode.INVALID_JSON.getErrorMessage()); + } + + final String timestamp = DateUtils.getUTCCurrentDateTimeString(); + String applicationId = jwtSignRequestDto.getApplicationId(); + String referenceId = jwtSignRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + 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; + + final SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, + Optional.of(referenceId), timestamp); + keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), + DateUtils.parseUTCToDate(timestamp)); + + final String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, + includeCertHash, certificateUrl, referenceId); + + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); + responseDto.setJwtSignedData(signedData); + responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Request - Completed"); + + return responseDto; + } + + private String sign(String dataToSign, SignatureCertificate certificateResponse, boolean includePayload, + boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId) { + + PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); + X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; + + // 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); + } + final String keyId = SignatureUtil.convertHexToBase64(certificateResponse.getUniqueIdentifier()); + + // 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()); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Signature Algorithm for the input RefId: " + algoString); + + jwSign.setKey(privateKey); + jwSign.setDoKeyValidation(false); + jwSign.setPayload(dataToSign); + + //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); + throw new SignatureFailureException(SignatureErrorCode.SIGN_ERROR.getErrorCode(), + SignatureErrorCode.SIGN_ERROR.getErrorMessage(), e); + } + } + + public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtVerifyRequestDto) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request."); + final String signedData = jwtVerifyRequestDto.getJwtSignatureData(); + if (!SignatureUtil.isDataValid(signedData)) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Provided Signed Data value is invalid."); + throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), + SignatureErrorCode.INVALID_INPUT.getErrorMessage()); + } + + // Optional detached payload (must already be base64url-encoded if provided) + final String encodedActualData = SignatureUtil.isDataValid(jwtVerifyRequestDto.getActualData()) + ? jwtVerifyRequestDto.getActualData() : null; + + String applicationId = jwtVerifyRequestDto.getApplicationId(); + String referenceId = jwtVerifyRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + String[] jwtTokens = signedData.split(SignatureConstant.PERIOD, -1); + + 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); + responseDto.setMessage(signatureValid ? SignatureConstant.VALIDATION_SUCCESSFUL : SignatureConstant.VALIDATION_FAILED); + responseDto.setTrustValid(validateTrust(jwtVerifyRequestDto, certToVerify, reqCertData)); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request - Completed."); + return responseDto; + } + + private Certificate getCertificateToVerify(String reqCertData, String applicationId, String referenceId) { + // 2nd precedence to consider certificate to use in signature verification (Certificate Data provided in request). + if (reqCertData != null) + return keymanagerUtil.convertToCertificate(reqCertData); + + // 3rd precedence to consider certificate to use in signature verification. (based on AppId & RefId) + KeyPairGenerateResponseDto certificateResponse = keymanagerService.getCertificate(applicationId, + Optional.of(referenceId)); + return keymanagerUtil.convertToCertificate(certificateResponse.getCertificate()); + } + + @SuppressWarnings("unchecked") + private Certificate certificateExistsInHeader(String jwtHeader) { + try { + 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()); + } 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()); + } + } + + private boolean verifySignature(String[] jwtTokens, String actualData, Certificate certToVerify) { + JsonWebSignature jws = new JsonWebSignature(); + try { + X509Certificate x509CertToVerify = (X509Certificate) certToVerify; + boolean validCert = SignatureUtil.isCertificateDatesValid(x509CertToVerify); + if (!validCert) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Error certificate dates are not valid."); + throw new CertificateNotValidException(SignatureErrorCode.CERT_NOT_VALID.getErrorCode(), + SignatureErrorCode.CERT_NOT_VALID.getErrorMessage()); + } + + String keyAlgorithm = x509CertToVerify.getPublicKey().getAlgorithm(); + PublicKey publicKey = null; + if (keyAlgorithm.equals(KeymanagerConstant.EDDSA_KEY_TYPE)) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Found Ed25519 Certificate for Signature verification."); + publicKey = KeyGeneratorUtils.createPublicKey(KeymanagerConstant.ED25519_KEY_TYPE, + x509CertToVerify.getPublicKey().getEncoded()); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Supported Signature Algorithm: " + + AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory().getSupportedAlgorithms()); + } else { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "KeyStore Provider Name:" + ecKeyStore.getKeystoreProviderName()); + if (!ecKeyStore.getKeystoreProviderName().equals( + io.mosip.kernel.keymanager.hsm.constant.KeymanagerConstant.KEYSTORE_TYPE_OFFLINE)) { + ProviderContext provContext = new ProviderContext(); + provContext.getSuppliedKeyProviderContext().setSignatureProvider(ecKeyStore.getKeystoreProviderName()); + jws.setProviderContext(provContext); + } + publicKey = certToVerify.getPublicKey(); + } + + if (Objects.nonNull(actualData)) + jwtTokens[1] = actualData; + + jws.setCompactSerialization(CompactSerializer.serialize(jwtTokens)); + jws.setDoKeyValidation(false); + if (Objects.nonNull(publicKey)) + jws.setKey(publicKey); + + return jws.verifySignature(); + } catch (JoseException e) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Provided Signed Data value is invalid.", e); + throw new SignatureFailureException(SignatureErrorCode.VERIFY_ERROR.getErrorCode(), + SignatureErrorCode.VERIFY_ERROR.getErrorMessage(), e); + } + } + + private String validateTrust(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, Certificate headerCertificate, String reqCertData) { + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request - Trust Validation."); + if (!SignatureUtil.isIncludeAttrsValid(jwtVerifyRequestDto.getValidateTrust())) { + return SignatureConstant.TRUST_NOT_VERIFIED; + } + + final String domain = jwtVerifyRequestDto.getDomain(); + if(!SignatureUtil.isDataValid(domain)) + return SignatureConstant.TRUST_NOT_VERIFIED_NO_DOMAIN; + + // 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; + } + + 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 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 resp.getStatus() ? SignatureConstant.TRUST_VALID : SignatureConstant.TRUST_NOT_VALID; + } + + @Override + public JWTSignatureResponseDto jwsSign(JWSSignatureRequestDto jwsSignRequestDto) { + // TODO Code is duplicated from jwtSign method. Duplicate code will be removed later when VC verification is implement. + // Code duplicated because now does not want to make any change to existing code which is well tested. + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, + "JWS Signature Request."); + + 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()); + } + + final String reqDataToSign = jwsSignRequestDto.getDataToSign(); + if (!SignatureUtil.isDataValid(reqDataToSign)) { + LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, + "Provided Data to sign is invalid."); + throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), + SignatureErrorCode.INVALID_INPUT.getErrorMessage()); + } + + // 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()); + } + + final String timestamp = DateUtils.getUTCCurrentDateTimeString(); + String applicationId = jwsSignRequestDto.getApplicationId(); + String referenceId = jwsSignRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + // 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; + + // 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(jwt); + responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, + "JWS Signature Request - Completed."); + return responseDto; + } + + public static class EcdsaSECP256K1UsingSha256 extends EcdsaUsingShaAlgorithm { public EcdsaSECP256K1UsingSha256() { - super(AlgorithmIdentifiers.ECDSA_USING_SECP256K1_CURVE_AND_SHA256, - "SHA256withECDSA", EllipticCurves.SECP_256K1, 64); + super(AlgorithmIdentifiers.ECDSA_USING_SECP256K1_CURVE_AND_SHA256, + "SHA256withECDSA", EllipticCurves.SECP_256K1, 64); } @Override @@ -642,69 +793,8 @@ public boolean isAvailable(){ } } - @Override - public SignResponseDto signv2(SignRequestDtoV2 signatureReq) { - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.RAW_SIGN, SignatureConstant.BLANK, - "Raw Sign Signature Request."); - String applicationId = signatureReq.getApplicationId(); - String referenceId = signatureReq.getReferenceId(); - boolean hasAcccess = cryptomanagerUtil.hasKeyAccess(applicationId); - String reqDataToSign = signatureReq.getDataToSign(); - if (!hasAcccess) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.RAW_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()); - } - - if (!SignatureUtil.isDataValid(reqDataToSign)) { - LOGGER.error(SignatureConstant.SESSIONID, SignatureConstant.RAW_SIGN, SignatureConstant.BLANK, - "Provided Data to sign is invalid."); - throw new RequestException(SignatureErrorCode.INVALID_INPUT.getErrorCode(), - SignatureErrorCode.INVALID_INPUT.getErrorMessage()); - } - byte[] dataToSign = CryptoUtil.decodeURLSafeBase64(reqDataToSign); - String timestamp = DateUtils.getUTCCurrentDateTimeString(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - String signAlgorithm = SignatureUtil.isDataValid(signatureReq.getSignAlgorithm()) ? - signatureReq.getSignAlgorithm() : SignatureConstant.JWS_PS256_SIGN_ALGO_CONST; - - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, - Optional.of(referenceId), timestamp); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils.parseUTCToDate(timestamp)); - PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); - certificateResponse.getCertificateEntry().getChain(); - String providerName = certificateResponse.getProviderName(); - 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, dataToSign, providerName); - SignResponseDto signedDataResponse = new SignResponseDto(); - signedDataResponse.setTimestamp(DateUtils.getUTCCurrentDateTime()); - String encodingFromat = (signatureReq.getResponseEncodingFormat() == null || signatureReq.getResponseEncodingFormat().isBlank()) ? SignatureConstant.BASE58BTC : signatureReq.getResponseEncodingFormat(); - switch (encodingFromat) { - case SignatureConstant.BASE64URL: - signedDataResponse.setSignature(signature); - break; - case SignatureConstant.BASE58BTC: - byte[] data = java.util.Base64.getUrlDecoder().decode(signature); - signedDataResponse.setSignature( - Multibase.encode(Multibase.Base.Base58BTC, data)); - break; - default: - throw new KeymanagerServiceException(KeymanagerErrorConstant.INVALID_FORMAT_ERROR.getErrorCode(), - KeymanagerErrorConstant.INVALID_FORMAT_ERROR.getErrorMessage()); - } - return signedDataResponse; - } - @Override - public SignResponseDtoV2 rawSign(SignRequestDtoV2 signatureReq) { + public SignResponseDto signv2(SignRequestDtoV2 signatureReq) { LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.RAW_SIGN, SignatureConstant.BLANK, "Raw Sign Signature Request."); String applicationId = signatureReq.getApplicationId(); @@ -731,7 +821,7 @@ public SignResponseDtoV2 rawSign(SignRequestDtoV2 signatureReq) { referenceId = signRefid; } String signAlgorithm = SignatureUtil.isDataValid(signatureReq.getSignAlgorithm()) ? - signatureReq.getSignAlgorithm() : SignatureConstant.JWS_PS256_SIGN_ALGO_CONST; + signatureReq.getSignAlgorithm(): SignatureConstant.ED25519_ALGORITHM; SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, Optional.of(referenceId), timestamp); @@ -745,24 +835,69 @@ public SignResponseDtoV2 rawSign(SignRequestDtoV2 signatureReq) { signatureProvider = SIGNATURE_PROVIDER.get(SignatureConstant.JWS_PS256_SIGN_ALGO_CONST); } String signature = signatureProvider.sign(privateKey, dataToSign, providerName); - SignResponseDtoV2 responseDto = new SignResponseDtoV2(); - responseDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); - String encodingFromat = (signatureReq.getResponseEncodingFormat() == null || signatureReq.getResponseEncodingFormat().isBlank()) ? SignatureConstant.BASE58BTC : signatureReq.getResponseEncodingFormat(); - switch (encodingFromat) { - case SignatureConstant.BASE64URL: - responseDto.setSignedData(signature); + byte[] data = java.util.Base64.getUrlDecoder().decode(signature); + SignResponseDto signedData = new SignResponseDto(); + signedData.setTimestamp(DateUtils.getUTCCurrentDateTime()); + switch (signatureReq.getResponseEncodingFormat()) { + case "base64url": + signedData.setSignature( + Multibase.encode(Multibase.Base.Base64Url, data)); break; - case SignatureConstant.BASE58BTC: - byte[] data = java.util.Base64.getUrlDecoder().decode(signature); - responseDto.setSignedData(Multibase.encode(Multibase.Base.Base58BTC, data)); + case "base58btc": + signedData.setSignature( + Multibase.encode(Multibase.Base.Base58BTC, data)); break; default: throw new KeymanagerServiceException(KeymanagerErrorConstant.INVALID_FORMAT_ERROR.getErrorCode(), KeymanagerErrorConstant.INVALID_FORMAT_ERROR.getErrorMessage()); } - responseDto.setCertificate(keymanagerUtil.getPEMFormatedData(certificateResponse.getCertificateEntry().getChain()[0])); - responseDto.setSignatureAlgorithm(signAlgorithm); - responseDto.setKeyId(SignatureUtil.convertHexToBase64(certificateResponse.getUniqueIdentifier())); - return responseDto; + return signedData; + } + + @Override + public SignResponseDtoV2 rawSign(SignRequestDtoV2 signatureReq) { + return null; + } + + 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 6ba28a4c..a72a724f 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 @@ -29,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,11 +65,10 @@ import io.mosip.kernel.zkcryptoservice.exception.ZKRandomKeyDecryptionException; import io.mosip.kernel.zkcryptoservice.service.spi.ZKCryptoManagerService; import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; /** * Service Implementation for {@link ZKCryptoManagerService} interface - * + * * @author Mahammed Taheer * * @since 1.1.2 @@ -77,441 +77,440 @@ @Transactional public class ZKCryptoManagerServiceImpl implements ZKCryptoManagerService, InitializingBean { - private static final Logger LOGGER = KeymanagerLogger.getLogger(ZKCryptoManagerServiceImpl.class); - - @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; - - @Value("${mosip.kernel.zkcrypto.publickey.application.id}") - private String pubKeyApplicationId; - - @Value("${mosip.kernel.zkcrypto.publickey.reference.id}") - private String pubKeyReferenceId; - - @Value("${mosip.kernel.zkcrypto.wrap.algorithm-name}") - private String aesECBTransformation; - - private List keyAliases = null; - - @Autowired - private DataEncryptKeystoreRepository dataEncryptKeystoreRepository; - - /** - * KeymanagerDBHelper instance to handle all DB operations - */ - @Autowired - private KeymanagerDBHelper dbHelper; - - @Autowired - private KeyStoreRepository keyStoreRepository; - - /** - * Keystore instance to handles and store cryptographic keys. - */ - @Autowired - private ECKeyStore keyStore; - - /** - * Utility to generate Metadata - */ - @Autowired - KeymanagerUtil keymanagerUtil; - - /** The key manager. */ - @Autowired - private KeymanagerService keyManagerService; - - /** - * {@link CryptomanagerUtils} instance - */ - @Autowired - 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. - 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."); - getDecryptedRandomKey("Tk8tU0VDRVJULUFWQUlMQUJMRS1URU1QLUZJWElORy0="); - } catch (Throwable e) { - // ignore - } - } - } - - @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(); - String encryptedKeyData = dataEncryptKeystoreRepository.findKeyById(randomKeyIndex); - Key secretRandomKey = getDecryptedRandomKey(encryptedKeyData); - Key derivedKey = getDerivedKey(id, secretRandomKey); - - SecureRandom sRandom = new SecureRandom(); - List responseCryptoData = new ArrayList<>(); - cryptoDataList.forEach(reqCryptoData -> { - String identifier = reqCryptoData.getIdentifier(); - byte[] dataToEncrypt = reqCryptoData.getValue().getBytes(); - byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; - byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; - - sRandom.nextBytes(nonce); - sRandom.nextBytes(aad); - - byte[] encryptedData = doCipherOps(derivedKey, dataToEncrypt, Cipher.ENCRYPT_MODE, nonce, aad); - byte[] dbIndexBytes = getIndexBytes(randomKeyIndex); - responseCryptoData.add(getResponseCryptoData(encryptedData, dbIndexBytes, nonce, aad, identifier)); - }); - ZKCryptoResponseDto cryptoResponseDto = new ZKCryptoResponseDto(); - cryptoResponseDto.setRankomKeyIndex(Integer.toString(randomKeyIndex)); - cryptoResponseDto.setZkDataAttributes(responseCryptoData); - cryptoResponseDto.setEncryptedRandomKey(encryptRandomKey(secretRandomKey)); - keymanagerUtil.destoryKey((SecretKey) secretRandomKey); - return cryptoResponseDto; - } - - @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."); - } - - // 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[] 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 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); - // Removed plus one ( + 1) because 10000 random number is generated - // but in DB we have indexes from 0 - 9999 only. - // So removed + 1 - int randomNum = ThreadLocalRandom.current().nextInt(0, indexes.size()); - return indexes.get(randomNum); - } - - 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."); - 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."); - byte[] wrappedKey = doFinal(randomKey, Cipher.ENCRYPT_MODE); - return Base64.getEncoder().encodeToString(wrappedKey); - } - - private byte[] doFinal(String secretData, int mode) { - try { - Cipher cipher = CIPHER_AES_ECB.get(); - - byte[] secretDataBytes = Base64.getDecoder().decode(secretData); - cipher.init(mode, getMasterKeyFromHSM()); - return cipher.doFinal(secretDataBytes, 0, secretDataBytes.length); - } 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."); - byte[] idBytes = id.getBytes(); - MessageDigest mDigest = MESSAGE_DIGEST.get(); - mDigest.update(idBytes, 0, idBytes.length); - byte[] hashBytes = mDigest.digest(); - - 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 (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."); - 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."); - throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), - 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."); - - 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"); - return currentKeyAliases.get(0).getAlias(); - } - - 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()); - } - - 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"); - try { - 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 (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()); - } - } - - private byte[] getIndexBytes(int randomIndex) { - ByteBuffer byteBuff = ByteBuffer.allocate(ZKCryptoManagerConstants.INT_BYTES_LEN); - byteBuff.putInt(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]; - 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); - System.arraycopy(encryptedData, 0, finalEncData, dbIndexBytes.length + nonce.length + aad.length, - encryptedData.length); - String concatEncryptedData = CryptoUtil.encodeToURLSafeBase64(finalEncData); - CryptoDataDto resCryptoData = new CryptoDataDto(); - resCryptoData.setIdentifier(identifier); - resCryptoData.setValue(concatEncryptedData); - return resCryptoData; - } - - private CryptoDataDto getResponseCryptoData(byte[] decryptedData, String identifier) { - String decryptedDataStr = new String(decryptedData); - CryptoDataDto resCryptoData = new CryptoDataDto(); - resCryptoData.setIdentifier(identifier); - resCryptoData.setValue(decryptedDataStr); - return resCryptoData; - } - - private String encryptRandomKey(Key secretRandomKey) { - 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) - continue; - 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); - 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"); - throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), - ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); - } - String certificateData = dbKeyStore.get().getCertificateData(); - X509Certificate x509Cert = (X509Certificate) keymanagerUtil.convertToCertificate(certificateData); - PublicKey publicKey = x509Cert.getPublicKey(); - byte[] encryptedRandomKey = cryptoCore.asymmetricEncrypt(publicKey, secretRandomKey.getEncoded()); - byte[] certThumbprint = cryptomanagerUtil.getCertificateThumbprint(x509Cert); - byte[] concatedData = cryptomanagerUtil.concatCertThumbprint(certThumbprint, encryptedRandomKey); - encryptedRandomKeyList.add(CryptoUtil.encodeToURLSafeBase64(concatedData)); - } - return encryptedRandomKeyList.stream().collect(Collectors.joining(KeymanagerConstant.DOT)); - } - - @Override - 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, - ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, "Invalid Encrypted Key input."); - throw new ZKCryptoException(ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorCode(), - 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); - keyAliases = keyAliasMap.get(KeymanagerConstant.KEYALIAS); - } - String encRandomKey = null; - for (String encKey : encryptedKeyArr) { - byte[] encKeyBytes = CryptoUtil.decodeURLSafeBase64(encKey); - 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(); - if (!keyAlias.isPresent()) { - continue; - } - encRandomKey = encKey; - break; - } - if (Objects.isNull(encRandomKey)) { - 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()); - } - SymmetricKeyRequestDto symmetricKeyRequestDto = new SymmetricKeyRequestDto(pubKeyApplicationId, - localDateTimeStamp, pubKeyReferenceId, encRandomKey, true); - String randomKey = keyManagerService.decryptSymmetricKey(symmetricKeyRequestDto).getSymmetricKey(); - String encryptedRandomKey = getEncryptedRandomKey( - Base64.getEncoder().encodeToString(CryptoUtil.decodeURLSafeBase64(randomKey))); - ReEncryptRandomKeyResponseDto responseDto = new ReEncryptRandomKeyResponseDto(); - responseDto.setEncryptedKey(encryptedRandomKey); - return responseDto; - } + private static final Logger LOGGER = KeymanagerLogger.getLogger(ZKCryptoManagerServiceImpl.class); + + @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; + + @Value("${mosip.kernel.zkcrypto.publickey.application.id}") + private String pubKeyApplicationId; + + @Value("${mosip.kernel.zkcrypto.publickey.reference.id}") + private String pubKeyReferenceId; + + @Value("${mosip.kernel.zkcrypto.wrap.algorithm-name}") + private String aesECBTransformation; + + private List keyAliases = null; + + @Autowired + private DataEncryptKeystoreRepository dataEncryptKeystoreRepository; + + /** + * KeymanagerDBHelper instance to handle all DB operations + */ + @Autowired + private KeymanagerDBHelper dbHelper; + + @Autowired + private KeyStoreRepository keyStoreRepository; + + /** + * Keystore instance to handles and store cryptographic keys. + */ + @Autowired + private ECKeyStore keyStore; + + /** + * Utility to generate Metadata + */ + @Autowired + KeymanagerUtil keymanagerUtil; + + /** The key manager. */ + @Autowired + private KeymanagerService keyManagerService; + + /** + * {@link CryptomanagerUtils} instance + */ + @Autowired + 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. + 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."); + getDecryptedRandomKey("Tk8tU0VDRVJULUFWQUlMQUJMRS1URU1QLUZJWElORy0="); + } catch (Throwable e) { + // ignore + } + } + } + + @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(); + String encryptedKeyData = dataEncryptKeystoreRepository.findKeyById(randomKeyIndex); + Key secretRandomKey = getDecryptedRandomKey(encryptedKeyData); + Key derivedKey = getDerivedKey(id, secretRandomKey); + + SecureRandom sRandom = new SecureRandom(); + List responseCryptoData = new ArrayList<>(); + cryptoDataList.forEach(reqCryptoData -> { + String identifier = reqCryptoData.getIdentifier(); + byte[] dataToEncrypt = reqCryptoData.getValue().getBytes(); + byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + + sRandom.nextBytes(nonce); + sRandom.nextBytes(aad); + + byte[] encryptedData = doCipherOps(derivedKey, dataToEncrypt, Cipher.ENCRYPT_MODE, nonce, aad); + byte[] dbIndexBytes = getIndexBytes(randomKeyIndex); + responseCryptoData.add(getResponseCryptoData(encryptedData, dbIndexBytes, nonce, aad, identifier)); + }); + ZKCryptoResponseDto cryptoResponseDto = new ZKCryptoResponseDto(); + cryptoResponseDto.setRankomKeyIndex(Integer.toString(randomKeyIndex)); + cryptoResponseDto.setZkDataAttributes(responseCryptoData); + cryptoResponseDto.setEncryptedRandomKey(encryptRandomKey(secretRandomKey)); + keymanagerUtil.destoryKey((SecretKey) secretRandomKey); + return cryptoResponseDto; + } + + @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."); + } + + // 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[] 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 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); + // Removed plus one ( + 1) because 10000 random number is generated + // but in DB we have indexes from 0 - 9999 only. + // So removed + 1 + int randomNum = ThreadLocalRandom.current().nextInt(0, indexes.size()); + return indexes.get(randomNum); + } + + 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."); + 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."); + byte[] wrappedKey = doFinal(randomKey, Cipher.ENCRYPT_MODE); + return Base64.getEncoder().encodeToString(wrappedKey); + } + + private byte[] doFinal(String secretData, int mode) { + try { + Cipher cipher = CIPHER_AES_ECB.get(); + + byte[] secretDataBytes = Base64.getDecoder().decode(secretData); + cipher.init(mode, getMasterKeyFromHSM()); + return cipher.doFinal(secretDataBytes, 0, secretDataBytes.length); + } 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."); + byte[] idBytes = id.getBytes(); + MessageDigest mDigest = MESSAGE_DIGEST.get(); + mDigest.update(idBytes, 0, idBytes.length); + byte[] hashBytes = mDigest.digest(); + + 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 (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."); + 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."); + throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), + 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."); + + 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"); + return currentKeyAliases.get(0).getAlias(); + } + + 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()); + } + + 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"); + try { + 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 (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()); + } + } + + private byte[] getIndexBytes(int randomIndex) { + ByteBuffer byteBuff = ByteBuffer.allocate(ZKCryptoManagerConstants.INT_BYTES_LEN); + byteBuff.putInt(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]; + 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); + System.arraycopy(encryptedData, 0, finalEncData, dbIndexBytes.length + nonce.length + aad.length, + encryptedData.length); + String concatEncryptedData = CryptoUtil.encodeToURLSafeBase64(finalEncData); + CryptoDataDto resCryptoData = new CryptoDataDto(); + resCryptoData.setIdentifier(identifier); + resCryptoData.setValue(concatEncryptedData); + return resCryptoData; + } + + private CryptoDataDto getResponseCryptoData(byte[] decryptedData, String identifier) { + String decryptedDataStr = new String(decryptedData); + CryptoDataDto resCryptoData = new CryptoDataDto(); + resCryptoData.setIdentifier(identifier); + resCryptoData.setValue(decryptedDataStr); + return resCryptoData; + } + + private String encryptRandomKey(Key secretRandomKey) { + 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) + continue; + 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); + 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"); + throw new NoUniqueAliasException(ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorCode(), + ZKCryptoErrorConstants.NO_UNIQUE_ALIAS.getErrorMessage()); + } + String certificateData = dbKeyStore.get().getCertificateData(); + X509Certificate x509Cert = (X509Certificate) keymanagerUtil.convertToCertificate(certificateData); + PublicKey publicKey = x509Cert.getPublicKey(); + byte[] encryptedRandomKey = cryptoCore.asymmetricEncrypt(publicKey, secretRandomKey.getEncoded()); + byte[] certThumbprint = cryptomanagerUtil.getCertificateThumbprint(x509Cert); + byte[] concatedData = cryptomanagerUtil.concatCertThumbprint(certThumbprint, encryptedRandomKey); + encryptedRandomKeyList.add(CryptoUtil.encodeToURLSafeBase64(concatedData)); + } + return encryptedRandomKeyList.stream().collect(Collectors.joining(KeymanagerConstant.DOT)); + } + + @Override + 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, + ZKCryptoManagerConstants.RE_ENCRYPT_RANDOM_KEY, "Invalid Encrypted Key input."); + throw new ZKCryptoException(ZKCryptoErrorConstants.INVALID_ENCRYPTED_RANDOM_KEY.getErrorCode(), + 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); + keyAliases = keyAliasMap.get(KeymanagerConstant.KEYALIAS); + } + String encRandomKey = null; + for (String encKey : encryptedKeyArr) { + byte[] encKeyBytes = CryptoUtil.decodeURLSafeBase64(encKey); + 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(); + if (!keyAlias.isPresent()) { + continue; + } + encRandomKey = encKey; + break; + } + if (Objects.isNull(encRandomKey)) { + 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()); + } + SymmetricKeyRequestDto symmetricKeyRequestDto = new SymmetricKeyRequestDto(pubKeyApplicationId, + localDateTimeStamp, pubKeyReferenceId, encRandomKey, true); + String randomKey = keyManagerService.decryptSymmetricKey(symmetricKeyRequestDto).getSymmetricKey(); + String encryptedRandomKey = getEncryptedRandomKey( + Base64.getEncoder().encodeToString(CryptoUtil.decodeURLSafeBase64(randomKey))); + ReEncryptRandomKeyResponseDto responseDto = new ReEncryptRandomKeyResponseDto(); + responseDto.setEncryptedKey(encryptedRandomKey); + return responseDto; + } } \ No newline at end of file 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 6bd60513..e51e0869 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; @@ -25,7 +26,6 @@ import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; import io.mosip.kernel.keymanagerservice.repository.DataEncryptKeystoreRepository; import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; /** * The Class MasterKeysGenerator. @@ -63,40 +63,40 @@ public class RandomKeysGenerator { 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) { + @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); @@ -117,47 +117,47 @@ public void generateRandomKeys(String appId, String referenceId) { } private void generateAndStore(String appId, String referenceId, String keyAlias, LocalDateTime localDateTimeStamp) { - keyStore.generateAndStoreSymmetricKey(keyAlias); + keyStore.generateAndStoreSymmetricKey(keyAlias); dbHelper.storeKeyInAlias(appId, localDateTimeStamp, referenceId, keyAlias, localDateTimeStamp.plusDays(1825), null, null); } - + private void generate10KKeysAndStoreInDB(String cacheMasterKeyAlias) throws Exception { - + int noOfActiveKeys = (int) dataEncryptKeystoreRepository.findAll().stream() - .filter(k->k.getKeyStatus().equals("active")).count(); - int noOfKeysToGenerate = 0; - if((noOfKeysRequire-noOfActiveKeys) > 0) { - noOfKeysToGenerate = (int) (noOfKeysRequire-noOfActiveKeys); - } - - LOGGER.info("No Of Keys To Generate:" + noOfKeysToGenerate); - - Long maxid = dataEncryptKeystoreRepository.findMaxId(); - int startIndex = maxid == null ? 0 : maxid.intValue() + 1; - + .filter(k->k.getKeyStatus().equals("active")).count(); + int noOfKeysToGenerate = 0; + if((noOfKeysRequire-noOfActiveKeys) > 0) { + noOfKeysToGenerate = (int) (noOfKeysRequire-noOfActiveKeys); + } + + LOGGER.info("No Of Keys To Generate:" + noOfKeysToGenerate); + + Long maxid = dataEncryptKeystoreRepository.findMaxId(); + int startIndex = maxid == null ? 0 : maxid.intValue() + 1; + SecureRandom rand = secureRandomThreadLocal.get(); - KeyGenerator keyGenerator = KEY_GENETRATOR.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++) { - keyGenerator.init(256, rand); - SecretKey sKey = keyGenerator.generateKey(); - cipher.init(Cipher.ENCRYPT_MODE, masterKey); - byte[] wrappedKey = cipher.doFinal(sKey.getEncoded()); - String encodedKey = Base64.getEncoder().encodeToString(wrappedKey); - insertKeyIntoTable(i, encodedKey, "Active"); - LOGGER.info("Insert secrets in DB: " + i); - } - } - - private void insertKeyIntoTable(int id, String secretData, String status) throws Exception { - DataEncryptKeystore data = new DataEncryptKeystore(); - data.setId(id); - data.setKey(secretData); - data.setKeyStatus(status); - data.setCrBy(CREATED_BY); - data.setCrDTimes(LocalDateTime.now()); - dataEncryptKeystoreRepository.save(data); - } + for (int i = startIndex; i < noOfKeysToGenerate; i++) { + keyGenerator.init(256, rand); + SecretKey sKey = keyGenerator.generateKey(); + cipher.init(Cipher.ENCRYPT_MODE, masterKey); + byte[] wrappedKey = cipher.doFinal(sKey.getEncoded()); + String encodedKey = Base64.getEncoder().encodeToString(wrappedKey); + insertKeyIntoTable(i, encodedKey, "Active"); + LOGGER.info("Insert secrets in DB: " + i); + } + } + + private void insertKeyIntoTable(int id, String secretData, String status) throws Exception { + DataEncryptKeystore data = new DataEncryptKeystore(); + data.setId(id); + data.setKey(secretData); + data.setKeyStatus(status); + data.setCrBy(CREATED_BY); + data.setCrDTimes(LocalDateTime.now()); + dataEncryptKeystoreRepository.save(data); + } } From 1791bdb03256dae09f642a6d5c253a8eb923adf6 Mon Sep 17 00:00:00 2001 From: kameshsr <47484458+kameshsr@users.noreply.github.com> Date: Mon, 18 Aug 2025 16:07:33 +0530 Subject: [PATCH 5/5] MOSIP-42357 Corrected decode (#418) Signed-off-by: kameshsr --- .../service/impl/SignatureServiceImpl.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 d61c3579..70fd7d7c 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 @@ -178,9 +178,8 @@ private static final class BoolWithTs { final boolean v; final long ts; BoolWith 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); - + private static final ThreadLocal B64_DEC = ThreadLocal.withInitial(java.util.Base64::getDecoder); + private static final ThreadLocal B64_ENC = ThreadLocal.withInitial(java.util.Base64::getEncoder); @PostConstruct public void init() { KeyGeneratorUtils.loadClazz(); @@ -575,7 +574,7 @@ private Certificate certificateExistsInHeader(String jwtHeader) { } // Build X509Certificate from DER - byte[] der = B64URL_DEC.get().decode(firstCertB64); + byte[] der = B64_DEC.get().decode(firstCertB64); Certificate cert = keymanagerUtil.convertToCertificate(der); if (cert != null) { // 2) Seed cache by x5t#S256 (from header or computed) @@ -677,7 +676,7 @@ private String validateTrust(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, C 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))); + fp = b64NoPad(sha256(reqCertData.getBytes(java.nio.charset.StandardCharsets.UTF_8))); trustCertData = reqCertData; } @@ -864,7 +863,7 @@ private static String cacheKey(String... parts) { } private PublicKey decodePublicKey(String algo, String b64Url) throws GeneralSecurityException { - byte[] raw = B64URL_DEC.get().decode(b64Url); + byte[] raw = B64_DEC.get().decode(b64Url); X509EncodedKeySpec spec = new X509EncodedKeySpec(raw); return switch (algo) { case "RSA" -> KF_RSA.get().generatePublic(spec); @@ -884,7 +883,7 @@ private void cacheCert(String key, Certificate cert) { private static String computeX5tS256(X509Certificate cert) { try { byte[] digest = sha256(cert.getEncoded()); - return b64urlNoPad(digest); + return b64NoPad(digest); } catch (java.security.cert.CertificateEncodingException e) { return null; } @@ -897,7 +896,7 @@ private static byte[] sha256(byte[] input) { return md.digest(); } - private static String b64urlNoPad(byte[] bytes) { - return B64URL_ENC.get().withoutPadding().encodeToString(bytes); + private static String b64NoPad(byte[] bytes) { + return B64_ENC.get().withoutPadding().encodeToString(bytes); } }