Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ES-628 #32

Merged
merged 2 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,39 +1,54 @@
package io.mosip.signup.helper;

import io.mosip.esignet.core.util.IdentityProviderUtil;
import io.mosip.kernel.core.util.UUIDUtils;
import io.mosip.signup.exception.SignUpException;
import io.mosip.signup.services.CacheUtilService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import static io.mosip.kernel.core.util.UUIDUtils.NAMESPACE_OID;

@Slf4j
@Component
public class CryptoHelper {

private static final String AES_TRANSFORMATION = "AES/CFB/PKCS5Padding";
public static final String CACHE_KEY = "aes";
public static final String ALIAS_CACHE_KEY = "CURRENT_ACTIVE_ALIAS";

@Value("${mosip.signup.cache.symmetric-algorithm-name}")
private String symmetricAlgorithm;

@Value("${mosip.signup.cache.symmetric-key.algorithm-name:AES}")
private String symmetricKeyAlgorithm;

@Value("${mosip.signup.cache.symmetric-key.size:256}")
private int symmetricKeySize;

@Autowired
private CacheUtilService cacheUtilService;

public String symmetricEncrypt(String transactionId, String data, SecretKey secretKey) {
public String symmetricEncrypt(String data) {
try {
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);

String keyAlias = getActiveKeyAlias();
SecretKey secretKey = getSecretKey(keyAlias);

Cipher cipher = Cipher.getInstance(symmetricAlgorithm);
byte[] initializationVector = IdentityProviderUtil.generateSalt(cipher.getBlockSize());
byte[] secretDataBytes = data.getBytes(StandardCharsets.UTF_8);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(initializationVector));
byte[] encryptedBytes = cipher.doFinal(secretDataBytes, 0, secretDataBytes.length);

String keyAlias = getKeyAlias(transactionId);
byte[] keyAliasBytes = keyAlias.getBytes();
cacheUtilService.setSecretKeyBasedOnAlias(keyAlias, IdentityProviderUtil.b64Encode(secretKey.getEncoded()));

byte[] output = new byte[cipher.getOutputSize(secretDataBytes.length)+cipher.getBlockSize()+keyAliasBytes.length];
System.arraycopy(encryptedBytes, 0, output, 0, encryptedBytes.length);
Expand All @@ -49,12 +64,12 @@ public String symmetricEncrypt(String transactionId, String data, SecretKey secr

public String symmetricDecrypt(String encryptedData) {
try {
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
Cipher cipher = Cipher.getInstance(symmetricAlgorithm);

byte[] data = IdentityProviderUtil.b64Decode(encryptedData);
byte[] keyAlias = Arrays.copyOfRange(data, data.length - 10, data.length);
byte[] iv = Arrays.copyOfRange(data, data.length-(cipher.getBlockSize()+10), data.length-10);
byte[] encryptedBytes = Arrays.copyOfRange(data, 0, data.length-(cipher.getBlockSize()+10));
byte[] keyAlias = Arrays.copyOfRange(data, data.length-36, data.length);
byte[] iv = Arrays.copyOfRange(data, data.length-(cipher.getBlockSize()+36), data.length-36);
byte[] encryptedBytes = Arrays.copyOfRange(data, 0, data.length-(cipher.getBlockSize()+36));

String encodedSecretKey = cacheUtilService.getSecretKey(new String(keyAlias));
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(IdentityProviderUtil.b64Decode(encodedSecretKey), "AES"),
Expand All @@ -67,23 +82,31 @@ public String symmetricDecrypt(String encryptedData) {
}


public SecretKey getSecretKey() {
String encodedSecretKey = cacheUtilService.getSecretKey();
public SecretKey getSecretKey(String alias) {
String encodedSecretKey = cacheUtilService.getSecretKey(alias);
return new SecretKeySpec(IdentityProviderUtil.b64Decode(encodedSecretKey), "AES");
}

private String getActiveKeyAlias() {
String alias = cacheUtilService.getActiveKeyAlias();
if(alias != null)
return alias;

log.debug("No active alias found, generating new alias and AES key.");
alias = UUIDUtils.getUUID(NAMESPACE_OID, "signup-service").toString();
generateSecretKey(alias);
return alias;
}

private void generateSecretKey(String alias) {
try {
if(encodedSecretKey == null) {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
cacheUtilService.setSecretKey(CACHE_KEY, IdentityProviderUtil.b64Encode(keyGenerator.generateKey().getEncoded()));
encodedSecretKey = cacheUtilService.getSecretKey();
}
return new SecretKeySpec(IdentityProviderUtil.b64Decode(encodedSecretKey), "AES");
} catch (Exception e) {
log.error("Error getting secret key", e);
KeyGenerator keyGenerator = KeyGenerator.getInstance(symmetricKeyAlgorithm);
keyGenerator.init(symmetricKeySize);
cacheUtilService.setSecretKey(alias, IdentityProviderUtil.b64Encode(keyGenerator.generateKey().getEncoded()));
cacheUtilService.setActiveKeyAlias(ALIAS_CACHE_KEY, alias);
} catch (NoSuchAlgorithmException e) {
log.error("Error generating secret key", e);
throw new SignUpException("crypto_error");
}
}

private String getKeyAlias(String transactionId) {
return transactionId.substring(transactionId.length()-10);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public String setSecretKey(String key, String secretKey) {
return secretKey;
}

@Cacheable(value = SignUpConstants.KEY_ALIAS, key = "#alias")
public String setSecretKeyBasedOnAlias(String alias, String secretKey) {
return secretKey;
@Cacheable(value = SignUpConstants.KEY_ALIAS, key = "#key")
public String setActiveKeyAlias(String key, String alias) {
return alias;
}

//---Getter---
Expand All @@ -75,11 +75,11 @@ public boolean isIdentifierBlocked(String identifier) {
return value == null ? false : true;
}

public String getSecretKey() {
return cacheManager.getCache(SignUpConstants.KEYSTORE).get(CryptoHelper.CACHE_KEY, String.class);
public String getSecretKey(String keyAlias) {
return cacheManager.getCache(SignUpConstants.KEYSTORE).get(keyAlias, String.class);
}

public String getSecretKey(String alias) {
return cacheManager.getCache(SignUpConstants.KEY_ALIAS).get(alias, String.class);
public String getActiveKeyAlias() {
return cacheManager.getCache(SignUpConstants.KEY_ALIAS).get(CryptoHelper.ALIAS_CACHE_KEY, String.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,7 @@ private void checkActiveIdentityExists(String transactionId,
}

//set UIN in the cache to be further used for update UIN endpoint
SecretKey secretKey = cryptoHelper.getSecretKey();
registrationTransaction.setUin(cryptoHelper.symmetricEncrypt(transactionId,
restResponseWrapper.getResponse().getIdentity().getUIN(), secretKey));
registrationTransaction.setUin(cryptoHelper.symmetricEncrypt(restResponseWrapper.getResponse().getIdentity().getUIN()));

}

Expand Down
10 changes: 7 additions & 3 deletions signup-service/src/main/resources/application-default.properties
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mosip.signup.supported.challenge.otp.length=6

## ------------------------------------- Cache configuration -----------------------------------------------------------

mosip.signup.cache.symmetric-algorithm-name=AES/CFB/PKCS5Padding
spring.cache.type=simple

#spring.cache.type=redis
Expand All @@ -50,13 +51,16 @@ mosip.esignet.cache.size={'challenge_generated': 200, \
'challenge_verified': 200,\
'status_check': 200,\
'blocked_identifier':2000,\
'keystore' : 5, \
'key_alias' : 200 }
'keystore' : 10, \
'key_alias' : 1 }

## Note: keystore TTL should be more than the key_alias cache TTL.
## So that key rotation happens before the actual key is removed from the keystore cache.
mosip.esignet.cache.expire-in-seconds={'challenge_generated': ${mosip.signup.unauthenticated.txn.timeout},\
'challenge_verified': ${mosip.signup.verified.txn.timeout},\
'status_check': ${mosip.signup.status-check.txn.timeout}, \
'blocked_identifier': ${mosip.signup.generate-challenge.blocked.timeout},\
'keystore' : 10, \
'keystore' : 600, \
'key_alias' : ${mosip.signup.verified.txn.timeout} }

## ------------------------------------- Auth adapter ------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions signup-service/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ invalid_challenge_channel=Invalid Challenge channel provided.
invalid_no_of_challenges=Null or empty authentication challenges not allowed.
invalid_auth_factor_type=Null or empty authentication factor type not allowed.
invalid_challenge=Invalid Authentication challenge provided.
crypto_error=Internal Error, Please try again.
14 changes: 9 additions & 5 deletions signup-service/src/test/resources/application-test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ mosip.signup.identifier.regex=^\\+855[1-9]\\d{7,8}$
mosip.signup.identifier.prefix=+855
mosip.signup.supported-languages={'khm','eng'}
mosip.signup.password.pattern=^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[\\x5F\\W])(?=.{8,20})[a-zA-Z0-9\\x5F\\W]{8,20}$
mosip.signup.fullname.pattern=^[\\u1780-\\u17FF\\u19E0-\\u19FF\\u1A00-\\u1A9F\\u0020]{1,30}$
mosip.signup.password.max-length=20
mosip.signup.password.min-length=6
mosip.signup.generate-challenge.blocked.timeout=300
mosip.signup.challenge.timeout=60
mosip.signup.audit.description.max-length=2048
mosip.signup.password.min-length=8
mosip.signup.fullname.pattern=^[\\u1780-\\u17FF\\u19E0-\\u19FF\\u1A00-\\u1A9F\\u0020]{1,30}$

## Time given to generate and verify the challenge in seconds.
## Default resend delay is 60 seconds, with 3 attempts, so 60*3=180 seconds.
Expand All @@ -55,7 +58,7 @@ mosip.signup.supported.challenge-types={'OTP', 'KBA'}
mosip.signup.supported.challenge.otp.length=6

## ------------------------------------- Cache configuration -----------------------------------------------------------

mosip.signup.cache.symmetric-algorithm-name=AES/CFB/PKCS5Padding
spring.cache.type=simple

#spring.cache.type=redis
Expand All @@ -70,12 +73,13 @@ mosip.esignet.cache.size={'challenge_generated': 200, \
'status_check': 200,\
'blocked_identifier':2000,\
'keystore' : 5, \
'key_alias' : 200 }
'key_alias' : 1 }

mosip.esignet.cache.expire-in-seconds={'challenge_generated': ${mosip.signup.unauthenticated.txn.timeout},\
'challenge_verified': ${mosip.signup.verified.txn.timeout},\
'status_check': ${mosip.signup.status-check.txn.timeout}, \
'blocked_identifier': ${mosip.signup.generate-challenge.blocked.timeout},\
'keystore' : 10, \
'keystore' : 600, \
'key_alias' : ${mosip.signup.verified.txn.timeout} }

## ------------------------------------- Auth adapter ------------------------------------------------------------------
Expand Down
Loading