Skip to content
Open
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
Expand Up @@ -63,6 +63,17 @@ private CryptomanagerConstant() {
public static final String CACHE_AES_KEY = "cacheAESKey";

public static final String CACHE_INT_COUNTER = "cacheIntCounter";


public static final byte[] VERSION_EC256_R1 = "VER_E2".getBytes(); // secp256R1 curve header

public static final byte[] VERSION_EC256_K1 = "VER_K2".getBytes(); // secp256K1 curve header

public static final byte[] VERSION_EC_X25519 = "VER_X2".getBytes(); // X25519 curve header

public static final String EC_SECP256R1 = "SECP256R1";

public static final String EC_SECP256K1 = "SECP256K1";

public static final String EC_X25519 = "X25519";
}

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public enum CryptomanagerErrorCode {

JWE_DECRYPTION_INTERNAL_ERROR("KER-CRY-015", "Internal Error while decrypting data using JWE."),

UNSUPPORTED_EC_CURVE("KER-CRY-016", "Unsupported EC Curve Provided. Please check the curve name."),

JWE_ENCRYPTION_NOT_SUPPORTED("KER-CRY-017", "JWE encryption is not supported for the provided (%S) public key."),

INTERNAL_SERVER_ERROR("KER-CRY-500", "Internal server error");


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.mosip.kernel.cryptomanager.service;

import java.security.PrivateKey;
import java.security.PublicKey;

public interface EcCryptomanagerService {

/**
*
* Encrypts data using an asymmetric EC public key.
*
* @param publicKey the public key to use for encryption
* @param data the data to encrypt
* @param iv the initialization vector (IV) for encryption
* @param aad additional authenticated data (AAD)
* @return the encrypted data
*/
public byte[] asymmetricEcEncrypt(PublicKey publicKey, byte[] data, byte[] iv, byte[] aad, String algorithmName);

/**
*
* Encrypts data using an asymmetric EC public key with a specified curve name.
*
* @param publicKey the public key to use for encryption
* @param data the data to encrypt
* @param curveName the name of the elliptic curve used
* @return the encrypted data
*/
public byte[] asymmetricEcEncrypt(PublicKey publicKey, byte[] data, String curveName);

/**
*
* Decrypts data using an asymmetric EC private key.
*
* @param privateKey the private key to use for decryption
* @param data the data to decrypt
* @param aad additional authenticated data (AAD)
* @param curveName the name of the elliptic curve used
* @return the decrypted data
*/
public byte[] asymmetricEcDecrypt(PrivateKey privateKey, byte[] data, byte[] aad, String curveName);
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
package io.mosip.kernel.cryptomanager.util;

import java.io.IOException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
Expand All @@ -24,6 +25,13 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import io.mosip.kernel.core.keymanager.spi.ECKeyStore;
import io.mosip.kernel.core.util.DateUtils;
import io.mosip.kernel.keymanagerservice.constant.KeyReferenceIdConsts;
import io.mosip.kernel.keymanagerservice.constant.KeymanagerErrorConstant;
import io.mosip.kernel.keymanagerservice.entity.KeyAlias;
import io.mosip.kernel.keymanagerservice.exception.NoUniqueAliasException;
import io.mosip.kernel.keymanagerservice.helper.PrivateKeyDecryptorHelper;
import io.mosip.kernel.signature.constant.SignatureConstant;
import org.apache.commons.codec.digest.DigestUtils;
import org.bouncycastle.util.encoders.Hex;
Expand Down Expand Up @@ -93,6 +101,13 @@ public class CryptomanagerUtils {
@Value("${mosip.kernel.keymanager.jwtEncrypt.validate.json:true}")
private boolean confValidateJson;

@Value("${mosip.sign-certificate-refid:SIGN}")
private String certificateSignRefID;

/** Flag to generate and store Ed25519 key in real HSM. */
@Value("${mosip.kernel.keymanager.ed25519.hsm.support.enabled:false}")
private boolean ed25519SupportFlag;

/** The key manager. */
@Autowired
private KeymanagerService keyManager;
Expand All @@ -103,10 +118,16 @@ public class CryptomanagerUtils {
@Autowired
private KeymanagerDBHelper dbHelper;

@Autowired
private ECKeyStore keyStore;

@Autowired
private PrivateKeyDecryptorHelper privateKeyDecryptorHelper;

/**
* Calls Key-Manager-Service to get public key of an application.
*
* @param cryptomanagerRequestDto {@link CryptomanagerRequestDto} instance
* @param cryptomanagerRequestDto {@link CryptomanagerRequestDto} instance
* @return {@link Certificate} returned by Key Manager Service
*/
public Certificate getCertificate(CryptomanagerRequestDto cryptomanagerRequestDto) {
Expand All @@ -123,15 +144,14 @@ public Certificate getCertificate(CryptomanagerRequestDto cryptomanagerRequestDt
* @param refId the ref id
* @return the certificate data from key manager
*/
private String getCertificateFromKeyManager(String appId, String refId) {
public String getCertificateFromKeyManager(String appId, String refId) {
return keyManager.getCertificate(appId, Optional.ofNullable(refId)).getCertificate();
}


/**
* Calls Key-Manager-Service to decrypt symmetric key.
*
* @param cryptomanagerRequestDto {@link CryptomanagerRequestDto} instance
* @param cryptomanagerRequestDto {@link CryptomanagerRequestDto} instance
* @return Decrypted {@link SecretKey} from Key Manager Service
*/
public SecretKey getDecryptedSymmetricKey(CryptomanagerRequestDto cryptomanagerRequestDto) {
Expand All @@ -156,7 +176,7 @@ private String decryptSymmetricKeyUsingKeyManager(CryptomanagerRequestDto crypto
/**
* Change Parameter form to trim if not null.
*
* @param parameter parameter
* @param parameter parameter
* @return null if null;else trimmed string
*/
public static String nullOrTrim(String parameter) {
Expand All @@ -166,7 +186,7 @@ public static String nullOrTrim(String parameter) {
/**
* Function to check is salt is valid.
*
* @param salt salt
* @param salt salt
* @return true if salt is valid, else false
*/
public boolean isValidSalt(String salt) {
Expand All @@ -187,7 +207,7 @@ public LocalDateTime parseToLocalDateTime(String dateTime) {
/**
* hex decode string to byte array
*
* @param hexData type {@link String}
* @param hexData type {@link String}
* @return a {@link byte[]} of given data
*/
public byte[] hexDecode(String hexData) {
Expand Down Expand Up @@ -262,29 +282,29 @@ public boolean isDataValid(String anyData) {

public byte[] decodeBase64Data(String anyBase64EncodedData){

try{
try {
return CryptoUtil.decodeURLSafeBase64(anyBase64EncodedData);
} catch(IllegalArgumentException argException) {
LOGGER.debug(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, "",
"Error Decoding Base64 URL Safe data, trying with Base64 normal decode.");
} catch (IllegalArgumentException argException) {
LOGGER.debug(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, "",
"Error Decoding Base64 URL Safe data, trying with Base64 normal decode.");
}
try {
return CryptoUtil.decodePlainBase64(anyBase64EncodedData);
} catch(Exception exception) {
LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, "",
"Error Decoding Base64 normal decode, throwing Exception.", exception);
} catch (Exception exception) {
LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, "",
"Error Decoding Base64 normal decode, throwing Exception.", exception);
throw new CryptoManagerSerivceException(CryptomanagerErrorCode.INVALID_DATA.getErrorCode(),
CryptomanagerErrorCode.INVALID_DATA.getErrorMessage());
CryptomanagerErrorCode.INVALID_DATA.getErrorMessage());
}
}

public boolean hasKeyAccess(String applicationId) {
if(Objects.isNull(applicationId) || applicationId.equals(KeymanagerConstant.EMPTY)) {
if (Objects.isNull(applicationId) || applicationId.equals(KeymanagerConstant.EMPTY)) {
return true;
}

Optional<KeyPolicy> keyPolicy = dbHelper.getKeyPolicyFromCache(applicationId);
if(!keyPolicy.isPresent()) // not allowing decryption if not key policy found
if (!keyPolicy.isPresent()) // not allowing decryption if not key policy found
return false;

String accessAllowed = keyPolicy.get().getAccessAllowed();
Expand All @@ -301,7 +321,6 @@ public boolean hasKeyAccess(String applicationId) {
String preferredUserName = userDetail.getUsername();
return allowedList.stream().anyMatch(preferredUserName::equalsIgnoreCase);
}


public void validateKeyIdentifierIds(String applicationId, String referenceId) {
if(!isDataValid(referenceId) ||
Expand All @@ -322,13 +341,16 @@ public Certificate getCertificate(String applicationId, String referenceId) {
public void validateEncKeySize(Certificate encCert) {

if (validateKeySize) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) encCert.getPublicKey();
if (rsaPublicKey.getModulus().bitLength() != 2048) {
LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT,
"Not Allowed to preform encryption with Key size not equal to 2048 bit.");
throw new CryptoManagerSerivceException(CryptomanagerErrorCode.ENCRYPT_NOT_ALLOWED_ERROR.getErrorCode(),
CryptomanagerErrorCode.ENCRYPT_NOT_ALLOWED_ERROR.getErrorMessage());
}
String algorithmName = encCert.getPublicKey().getAlgorithm();
if (algorithmName.equalsIgnoreCase(KeymanagerConstant.RSA)) {
RSAPublicKey rsaPublicKey = (RSAPublicKey) encCert.getPublicKey();
if (rsaPublicKey.getModulus().bitLength() != 2048) {
LOGGER.error(CryptomanagerConstant.SESSIONID, this.getClass().getSimpleName(), CryptomanagerConstant.JWT_ENCRYPT,
"Not Allowed to preform encryption with Key size not equal to 2048 bit.");
throw new CryptoManagerSerivceException(CryptomanagerErrorCode.ENCRYPT_NOT_ALLOWED_ERROR.getErrorCode(),
CryptomanagerErrorCode.ENCRYPT_NOT_ALLOWED_ERROR.getErrorMessage());
}
}
}
}

Expand Down Expand Up @@ -391,10 +413,138 @@ public void validateInputData(String reqDataToDigest) {
}

public boolean isJWSData(String data) {
String [] dataParts = data.split(SignatureConstant.PERIOD);
String[] dataParts = data.split(SignatureConstant.PERIOD);
if (dataParts.length != 3) {
return false;
}
return true;
}

public String getAlgorithmNameFromHeader(byte[] encryptedData) {
int keyDelimiterIndex = 0;
keyDelimiterIndex = CryptoUtil.getSplitterIndex(encryptedData, keyDelimiterIndex, keySplitter);
byte[] algorithmBytes = Arrays.copyOfRange(encryptedData, 0, keyDelimiterIndex);
String algorithmName;

if (Arrays.equals(algorithmBytes, CryptomanagerConstant.VERSION_EC256_R1)) {
algorithmName = CryptomanagerConstant.EC_SECP256R1;
} else if (Arrays.equals(algorithmBytes, CryptomanagerConstant.VERSION_EC256_K1)) {
algorithmName = CryptomanagerConstant.EC_SECP256K1;
} else if (Arrays.equals(algorithmBytes, CryptomanagerConstant.VERSION_EC_X25519)) {
algorithmName = CryptomanagerConstant.EC_X25519;
} else {
algorithmName = KeymanagerConstant.RSA;
}
return algorithmName;
}

/**
* Retrieves the private key for decryption. Handles both HSM-stored keys (for master/signature keys)
* and DB-stored keys (for base keys with referenceId)
* in {@link SessionKeyDecrytorHelper#getPrivateKey} and
* {@link PrivateKeyDecryptorHelper#getKeyObjects}.
*
* @param appId Application ID
* @param refId Optional Reference ID
* @param certThumbprint Certificate thumbprint hex string for DB key lookup
* @return Object array containing [PrivateKey, Certificate]
*/
public Object[] getPrivateKeyForDecryption(String appId, Optional<String> refId, String certThumbprint) {

if (!refId.isPresent() || refId.get().trim().isEmpty()) {
LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.EMPTY, KeymanagerConstant.EMPTY,
"Not valid reference Id. Getting private key from HSM.");
return getKeyFromHSM(appId, KeymanagerConstant.EMPTY);
}

String referenceId = refId.get();

if (isSignatureKeyRefId(appId, referenceId)) {
LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.EMPTY, KeymanagerConstant.EMPTY,
"Reference Id is present and it is " + referenceId
+ " Signature Key ref Id. Getting private key from HSM.");
return getKeyFromHSM(appId, referenceId);
}

// DB store path — retrieve private key using certificate thumbprint
LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.EMPTY, KeymanagerConstant.EMPTY,
"Reference Id is present. Will get private key from DB store using certificate thumbprint.");

io.mosip.kernel.keymanagerservice.entity.KeyStore dbKeyStore = privateKeyDecryptorHelper.getDBKeyStoreData(
certThumbprint, appId, referenceId);

return privateKeyDecryptorHelper.getKeyObjects(dbKeyStore, false);
}

/**
* Retrieves a key from HSM for the given appId and refId.
*/
private Object[] getKeyFromHSM(String appId, String refId) {
LocalDateTime localDateTime = DateUtils.getUTCCurrentDateTime();
Map<String, List<KeyAlias>> keyAliasMap = dbHelper.getKeyAliases(appId, refId, localDateTime);
List<KeyAlias> curkeyAliasList = keyAliasMap.getOrDefault(KeymanagerConstant.CURRENTKEYALIAS,
Collections.emptyList());
List<KeyAlias> keyAliasList = keyAliasMap.getOrDefault(KeymanagerConstant.KEYALIAS, Collections.emptyList());

if (curkeyAliasList.isEmpty() && keyAliasList.isEmpty()) {
LOGGER.error(KeymanagerConstant.SESSIONID, KeymanagerConstant.KEYALIAS, KeymanagerConstant.EMPTY,
"No key alias found for appId: " + appId + ", refId: " + refId);
throw new NoUniqueAliasException(KeymanagerErrorConstant.NO_UNIQUE_ALIAS.getErrorCode(),
KeymanagerErrorConstant.NO_UNIQUE_ALIAS.getErrorMessage());
}

String ksAlias = curkeyAliasList.isEmpty() ? keyAliasList.getFirst().getAlias()
: curkeyAliasList.getFirst().getAlias();
KeyStore.PrivateKeyEntry masterKeyEntry = keyStore.getAsymmetricKey(ksAlias);
return new Object[] { masterKeyEntry.getPrivateKey(), masterKeyEntry.getCertificate() };
}

/**
* Checks if the given referenceId is a signature key reference ID that should be fetched from HSM.
*/
private boolean isSignatureKeyRefId(String appId, String referenceId) {
return (appId.equalsIgnoreCase(signApplicationId) && referenceId.equals(certificateSignRefID)) ||
referenceId.equals(KeyReferenceIdConsts.EC_SECP256K1_SIGN.name()) ||
referenceId.equals(KeyReferenceIdConsts.EC_SECP256R1_SIGN.name()) ||
referenceId.equals(KeyReferenceIdConsts.RSA_2048_SIGN.name()) ||
(referenceId.equals(KeyReferenceIdConsts.ED25519_SIGN.name()) && ed25519SupportFlag);
}

public Object[] getObjects(io.mosip.kernel.keymanagerservice.entity.KeyStore dbKeyStore,
PrivateKey masterPrivateKey, PublicKey masterPublicKey) {
byte[] decryptedPrivateKey = keymanagerUtil.decryptKey(
CryptoUtil.decodeURLSafeBase64(dbKeyStore.getPrivateKey()),
masterPrivateKey, masterPublicKey);

PublicKey publicKey = keymanagerUtil.convertToCertificate(dbKeyStore.getCertificateData()).getPublicKey();
String algorithmName = publicKey.getAlgorithm();
KeyFactory keyFactory = null;
PrivateKey privateKey = null;
try {
keyFactory = KeyFactory.getInstance(algorithmName);
privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decryptedPrivateKey));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new CryptoManagerSerivceException(CryptomanagerErrorCode.UNSUPPORTED_EC_CURVE.getErrorCode(),
CryptomanagerErrorCode.UNSUPPORTED_EC_CURVE.getErrorMessage() + e.getMessage());
}
Certificate certificate = keymanagerUtil.convertToCertificate(dbKeyStore.getCertificateData());
return new Object[] { privateKey, certificate };
}

public byte[] getHeaderByte(String ecCurveName) {
byte[] headerBytes;
if (ecCurveName.equalsIgnoreCase(CryptomanagerConstant.EC_SECP256R1)) {
headerBytes = CryptomanagerConstant.VERSION_EC256_R1;
} else if (ecCurveName.equalsIgnoreCase(CryptomanagerConstant.EC_SECP256K1)) {
headerBytes = CryptomanagerConstant.VERSION_EC256_K1;
} else if (ecCurveName.equalsIgnoreCase(CryptomanagerConstant.EC_X25519)) {
headerBytes = CryptomanagerConstant.VERSION_EC_X25519;
} else {
LOGGER.error(CryptomanagerConstant.SESSIONID, CryptomanagerConstant.ENCRYPT, CryptomanagerConstant.ENCRYPT,
"Unsupported EC Curve Name: " + ecCurveName);
throw new CryptoManagerSerivceException(CryptomanagerErrorCode.UNSUPPORTED_EC_CURVE.getErrorCode(),
CryptomanagerErrorCode.UNSUPPORTED_EC_CURVE.getErrorMessage() + ecCurveName);
}
return headerBytes;
}
}
Loading