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 50bdfd62..7845f566 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 @@ -88,84 +88,84 @@ @Service public class SignatureServiceImpl implements SignatureService, SignatureServicev2 { - private static final Logger LOGGER = KeymanagerLogger.getLogger(SignatureServiceImpl.class); + private static final Logger LOGGER = KeymanagerLogger.getLogger(SignatureServiceImpl.class); - @Autowired - private KeymanagerService keymanagerService; + @Autowired + private KeymanagerService keymanagerService; - @Autowired - private CryptoCoreSpec cryptoCore; + @Autowired + private CryptoCoreSpec cryptoCore; - @Value("${mosip.kernel.keygenerator.asymmetric-algorithm-name}") - private String asymmetricAlgorithmName; + @Value("${mosip.kernel.keygenerator.asymmetric-algorithm-name}") + private String asymmetricAlgorithmName; - /** The sign applicationid. */ - @Value("${mosip.sign.applicationid:KERNEL}") - private String signApplicationid; + /** The sign applicationid. */ + @Value("${mosip.sign.applicationid:KERNEL}") + private String signApplicationid; - /** The sign refid. */ - @Value("${mosip.sign.refid:SIGN}") - private String signRefid; + /** 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.crypto.sign-algorithm-name:RS256}") + private String signAlgorithm; - @Value("${mosip.kernel.keymanager.jwtsign.validate.json:true}") - private boolean confValidateJson; + @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.include.keyid:true}") + private boolean includeKeyId; - @Value("${mosip.kernel.keymanager.jwtsign.enable.secp256k1.algorithm:true}") - private boolean enableSecp256k1Algo; + @Value("${mosip.kernel.keymanager.jwtsign.enable.secp256k1.algorithm:true}") + private boolean enableSecp256k1Algo; - @Value("${mosip.kernel.keymanager.signature.kid.prepend:}") - private String kidPrepend; + @Value("${mosip.kernel.keymanager.signature.kid.prepend:}") + private String kidPrepend; - /** - * Utility to generate Metadata - */ - @Autowired - KeymanagerUtil keymanagerUtil; + /** + * Utility to generate Metadata + */ + @Autowired + KeymanagerUtil keymanagerUtil; - @Autowired - private PDFGenerator pdfGenerator; + @Autowired + private PDFGenerator pdfGenerator; - /** - * Instance for PartnerCertificateManagerService - */ - @Autowired - PartnerCertificateManagerService partnerCertManagerService; + /** + * Instance for PartnerCertificateManagerService + */ + @Autowired + PartnerCertificateManagerService partnerCertManagerService; - @Autowired - CryptomanagerUtils cryptomanagerUtil; + @Autowired + CryptomanagerUtils cryptomanagerUtil; - @Autowired - ECKeyStore ecKeyStore; + @Autowired + ECKeyStore ecKeyStore; - @Autowired - SignatureUtil signatureUtil; + @Autowired + SignatureUtil signatureUtil; - private static Map SIGNATURE_PROVIDER = new HashMap<>(); + 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); - } + 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<>(); @@ -190,15 +190,15 @@ public class SignatureServiceImpl implements SignatureService, SignatureServicev 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(); - if (enableSecp256k1Algo) { - AlgorithmFactory jwsAlgorithmFactory = - AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory(); - jwsAlgorithmFactory.registerAlgorithm(new EcdsaSECP256K1UsingSha256()); - } - } + @PostConstruct + public void init() { + KeyGeneratorUtils.loadClazz(); + if (enableSecp256k1Algo) { + AlgorithmFactory jwsAlgorithmFactory = + AlgorithmFactoryFactory.getInstance().getJwsAlgorithmFactory(); + jwsAlgorithmFactory.registerAlgorithm(new EcdsaSECP256K1UsingSha256()); + } + } @PreDestroy public void destroy() { @@ -214,39 +214,39 @@ public void destroy() { providerCache.clear(); } - @Override - public SignatureResponse sign(SignRequestDto signRequestDto) { - SignatureRequestDto signatureRequestDto = new SignatureRequestDto(); - signatureRequestDto.setApplicationId(signApplicationid); - signatureRequestDto.setReferenceId(signRefid); - signatureRequestDto.setData(signRequestDto.getData()); - String timestamp = DateUtils2.getUTCCurrentDateTimeString(); - signatureRequestDto.setTimeStamp(timestamp); - SignatureResponseDto signatureResponseDTO = sign(signatureRequestDto); - return new SignatureResponse(signatureResponseDTO.getData(), DateUtils2.convertUTCToLocalDateTime(timestamp)); - } - - private SignatureResponseDto sign(SignatureRequestDto signatureRequestDto) { - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate( - signatureRequestDto.getApplicationId(), Optional.of(signatureRequestDto.getReferenceId()), - signatureRequestDto.getTimeStamp()); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils2.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, - DateUtils2.formatToISOString(timestampRequestDto.getTimestamp()), Optional.of(signRefid)); - boolean status; - try { + @Override + public SignatureResponse sign(SignRequestDto signRequestDto) { + SignatureRequestDto signatureRequestDto = new SignatureRequestDto(); + signatureRequestDto.setApplicationId(signApplicationid); + signatureRequestDto.setReferenceId(signRefid); + signatureRequestDto.setData(signRequestDto.getData()); + String timestamp = DateUtils2.getUTCCurrentDateTimeString(); + signatureRequestDto.setTimeStamp(timestamp); + SignatureResponseDto signatureResponseDTO = sign(signatureRequestDto); + return new SignatureResponse(signatureResponseDTO.getData(), DateUtils2.convertUTCToLocalDateTime(timestamp)); + } + + private SignatureResponseDto sign(SignatureRequestDto signatureRequestDto) { + SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate( + signatureRequestDto.getApplicationId(), Optional.of(signatureRequestDto.getReferenceId()), + signatureRequestDto.getTimeStamp()); + keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), + DateUtils2.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, + DateUtils2.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 @@ -274,26 +274,26 @@ public ValidatorResponseDto validate(TimestampRequestDto timestampRequestDto) { 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) { - 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); - final Rectangle rectangle = new Rectangle(request.getLowerLeftX(), request.getLowerLeftY(), request.getUpperRightX(), - request.getUpperRightY()); - try { + 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) { + 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); + final Rectangle rectangle = new Rectangle(request.getLowerLeftX(), request.getLowerLeftY(), request.getUpperRightX(), + request.getUpperRightY()); + try { final String providerName = signatureCertificate.getProviderName(); LOGGER.info(KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, KeymanagerConstant.SESSIONID, " Keystore Provider Name found: " + providerName); @@ -330,69 +330,69 @@ public SignatureResponseDto signPDF(PDFSignatureRequestDto request) { 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."); - - 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()); - } - - final 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 = DateUtils2.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(), - DateUtils2.parseUTCToDate(timestamp)); - String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, - includeCertHash, certificateUrl, referenceId); - JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); - responseDto.setJwtSignedData(signedData); - responseDto.setTimestamp(DateUtils2.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) { + } + + @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()); + } + + final 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 = DateUtils2.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(), + DateUtils2.parseUTCToDate(timestamp)); + String signedData = sign(decodedDataToSign, certificateResponse, includePayload, includeCertificate, + includeCertHash, certificateUrl, referenceId); + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); + responseDto.setJwtSignedData(signedData); + responseDto.setTimestamp(DateUtils2.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]; @@ -479,39 +479,39 @@ private String sign(String dataToSign, SignatureCertificate certificateResponse, 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); - } - } - - @Override - 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()); - } - - 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]); + } 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); + } + } + + @Override + 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()); + } + + 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; @@ -522,28 +522,28 @@ public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtV // 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)); - 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) { + JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto(); + responseDto.setSignatureValid(signatureValid); + responseDto.setMessage(signatureValid ? SignatureConstant.VALIDATION_SUCCESSFUL : SignatureConstant.VALIDATION_FAILED); + responseDto.setTrustValid(validateTrust(jwtVerifyRequestDto, certToVerify)); + 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); @@ -611,190 +611,190 @@ private Certificate certificateExistsInHeader(String jwtHeader) { 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 reqCertToVerify) { - 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 trustCertData = null; - if (reqCertToVerify != null) { - trustCertData = keymanagerUtil.getPEMFormatedData(reqCertToVerify); - } - - if (!SignatureUtil.isDataValid(trustCertData)) - return SignatureConstant.TRUST_NOT_VERIFIED; - - CertificateTrustRequestDto trustRequestDto = new CertificateTrustRequestDto(); - trustRequestDto.setCertificateData(trustCertData); - trustRequestDto.setPartnerDomain(domain); - CertificateTrustResponeDto response = partnerCertManagerService.verifyCertificateTrust(trustRequestDto); - - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Verification Request - Trust Validation - Completed."); - return response.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."); - - 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()); - } - - 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()); - } - - 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 = DateUtils2.getUTCCurrentDateTimeString(); - String applicationId = jwsSignRequestDto.getApplicationId(); - String referenceId = jwsSignRequestDto.getReferenceId(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - 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; - final 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(), - DateUtils2.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(DateUtils2.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 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 reqCertToVerify) { + 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 trustCertData = null; + if (reqCertToVerify != null) { + trustCertData = keymanagerUtil.getPEMFormatedData(reqCertToVerify); + } + + if (!SignatureUtil.isDataValid(trustCertData)) + return SignatureConstant.TRUST_NOT_VERIFIED; + + CertificateTrustRequestDto trustRequestDto = new CertificateTrustRequestDto(); + trustRequestDto.setCertificateData(trustCertData); + trustRequestDto.setPartnerDomain(domain); + CertificateTrustResponeDto response = partnerCertManagerService.verifyCertificateTrust(trustRequestDto); + + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request - Trust Validation - Completed."); + return response.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."); + + 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()); + } + + 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()); + } + + 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 = DateUtils2.getUTCCurrentDateTimeString(); + String applicationId = jwsSignRequestDto.getApplicationId(); + String referenceId = jwsSignRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + 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; + final 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(), + DateUtils2.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(DateUtils2.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 { 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 @@ -803,66 +803,66 @@ 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 = DateUtils2.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(), - DateUtils2.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(DateUtils2.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: + @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 = DateUtils2.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(), + DateUtils2.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(DateUtils2.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; - } + 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) { @@ -969,357 +969,357 @@ private static String b64NoPad(byte[] bytes) { return B64_ENC.get().withoutPadding().encodeToString(bytes); } - @Override - public JWTSignatureResponseDto jwtSignV2(JWTSignatureRequestDtoV2 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 = DateUtils2.getUTCCurrentDateTimeString(); - String applicationId = jwtSignRequestDto.getApplicationId(); - String referenceId = jwtSignRequestDto.getReferenceId(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludePayload()); - boolean includeCertificateChain = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertificateChain()); - boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertHash()); - String certificateUrl = SignatureUtil.isDataValid( - jwtSignRequestDto.getCertificateUrl()) ? jwtSignRequestDto.getCertificateUrl(): null; - - Map additionalHeaders = jwtSignRequestDto.getAdditionalHeaders(); - - SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, - Optional.of(referenceId), timestamp); - keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), - DateUtils2.parseUTCToDate(timestamp)); - - String signedData = signV2(decodedDataToSign, certificateResponse, includePayload, includeCertificateChain, - includeCertHash, certificateUrl, referenceId, additionalHeaders); - JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); - responseDto.setJwtSignedData(signedData); - responseDto.setTimestamp(DateUtils2.getUTCCurrentDateTime()); - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Request - Completed"); - - return responseDto; - } - - private String signV2(String dataToSign, SignatureCertificate certificateResponse, boolean includePayload, - boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId, Map additionalHeaders) { - - JsonWebSignature jwSign = new JsonWebSignature(); - PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); - X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; - List certificateChain = signatureUtil.getCertificateTrustChain(x509Certificate); - if (includeCertificate) { - X509Certificate[] certArray = certificateChain.stream() - .filter(cert -> cert instanceof X509Certificate) - .map(cert -> (X509Certificate) cert) - .toArray(X509Certificate[]::new); - jwSign.setCertificateChainHeaderValue(certArray); - } - - if (includeCertHash) - jwSign.setX509CertSha256ThumbprintHeaderValue(x509Certificate); - - if (Objects.nonNull(certificateUrl)) - jwSign.setHeader("x5u", certificateUrl); - - // Add additional headers skip on error - if (additionalHeaders != null) { - for (Map.Entry entry : additionalHeaders.entrySet()) { - if (!"kid".equalsIgnoreCase(entry.getKey())) { - try { - jwSign.setHeader(entry.getKey(), entry.getValue()); - } catch (Exception e) { + @Override + public JWTSignatureResponseDto jwtSignV2(JWTSignatureRequestDtoV2 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 = DateUtils2.getUTCCurrentDateTimeString(); + String applicationId = jwtSignRequestDto.getApplicationId(); + String referenceId = jwtSignRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludePayload()); + boolean includeCertificateChain = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertificateChain()); + boolean includeCertHash = SignatureUtil.isIncludeAttrsValid(jwtSignRequestDto.getIncludeCertHash()); + String certificateUrl = SignatureUtil.isDataValid( + jwtSignRequestDto.getCertificateUrl()) ? jwtSignRequestDto.getCertificateUrl(): null; + + Map additionalHeaders = jwtSignRequestDto.getAdditionalHeaders(); + + SignatureCertificate certificateResponse = keymanagerService.getSignatureCertificate(applicationId, + Optional.of(referenceId), timestamp); + keymanagerUtil.isCertificateValid(certificateResponse.getCertificateEntry(), + DateUtils2.parseUTCToDate(timestamp)); + + String signedData = signV2(decodedDataToSign, certificateResponse, includePayload, includeCertificateChain, + includeCertHash, certificateUrl, referenceId, additionalHeaders); + JWTSignatureResponseDto responseDto = new JWTSignatureResponseDto(); + responseDto.setJwtSignedData(signedData); + responseDto.setTimestamp(DateUtils2.getUTCCurrentDateTime()); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Request - Completed"); + + return responseDto; + } + + private String signV2(String dataToSign, SignatureCertificate certificateResponse, boolean includePayload, + boolean includeCertificate, boolean includeCertHash, String certificateUrl, String referenceId, Map additionalHeaders) { + + JsonWebSignature jwSign = new JsonWebSignature(); + PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); + X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; + List certificateChain = signatureUtil.getCertificateTrustChain(x509Certificate); + if (includeCertificate) { + X509Certificate[] certArray = certificateChain.stream() + .filter(cert -> cert instanceof X509Certificate) + .map(cert -> (X509Certificate) cert) + .toArray(X509Certificate[]::new); + jwSign.setCertificateChainHeaderValue(certArray); + } + + if (includeCertHash) + jwSign.setX509CertSha256ThumbprintHeaderValue(x509Certificate); + + if (Objects.nonNull(certificateUrl)) + jwSign.setHeader("x5u", certificateUrl); + + // Add additional headers skip on error + if (additionalHeaders != null) { + for (Map.Entry entry : additionalHeaders.entrySet()) { + if (!"kid".equalsIgnoreCase(entry.getKey())) { + try { + jwSign.setHeader(entry.getKey(), entry.getValue()); + } catch (Exception e) { // Log the error but skip and continue processing LOGGER.warn(SignatureConstant.SESSIONID, SignatureConstant.JWS_SIGN, SignatureConstant.BLANK, "error occur while adding additional header: " + entry.getKey() + " value: " + entry.getValue()); - } - } - } - } - - 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)) { - if (additionalHeaders != null && additionalHeaders.containsKey("kid")) { - String mapKeyId = additionalHeaders.get("kid"); - if (mapKeyId.isEmpty() || mapKeyId.charAt(mapKeyId.length()-1) != SignatureConstant.KEY_ID_SEPARATOR.charAt(0)) { - mapKeyId = mapKeyId.concat(SignatureConstant.KEY_ID_SEPARATOR); - } - kidPrefix = mapKeyId; - } - 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); - } - } - - @Override - public JWTSignatureResponseDto jwsSignV2(JWSSignatureRequestDtoV2 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 = DateUtils2.getUTCCurrentDateTimeString(); - String applicationId = jwsSignRequestDto.getApplicationId(); - String referenceId = jwsSignRequestDto.getReferenceId(); - if (!keymanagerUtil.isValidApplicationId(applicationId)) { - applicationId = signApplicationid; - referenceId = signRefid; - } - - boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludePayload()); - boolean includeCertificateChain = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertificateChain()); - 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(), - DateUtils2.parseUTCToDate(timestamp)); - PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); - X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; - String providerName = certificateResponse.getProviderName(); - String uniqueIdentifier = certificateResponse.getUniqueIdentifier(); - Map additionalHeaders = jwsSignRequestDto.getAdditionalHeaders(); - - JWSHeader jwsHeader = signatureUtil.getJWSHeaderV2(signAlgorithm, b64JWSHeaderParam, includeCertificateChain, - includeCertHash, certificateUrl, x509Certificate, uniqueIdentifier, includeKeyId, kidPrefix, additionalHeaders); - - 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(DateUtils2.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; - } - - @Override - public JWTSignatureVerifyResponseDto jwtVerifyV2(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); + } + } + } + } + + 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)) { + if (additionalHeaders != null && additionalHeaders.containsKey("kid")) { + String mapKeyId = additionalHeaders.get("kid"); + if (mapKeyId.isEmpty() || mapKeyId.charAt(mapKeyId.length()-1) != SignatureConstant.KEY_ID_SEPARATOR.charAt(0)) { + mapKeyId = mapKeyId.concat(SignatureConstant.KEY_ID_SEPARATOR); + } + kidPrefix = mapKeyId; + } + 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); + } + } + + @Override + public JWTSignatureResponseDto jwsSignV2(JWSSignatureRequestDtoV2 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 = DateUtils2.getUTCCurrentDateTimeString(); + String applicationId = jwsSignRequestDto.getApplicationId(); + String referenceId = jwsSignRequestDto.getReferenceId(); + if (!keymanagerUtil.isValidApplicationId(applicationId)) { + applicationId = signApplicationid; + referenceId = signRefid; + } + + boolean includePayload = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludePayload()); + boolean includeCertificateChain = SignatureUtil.isIncludeAttrsValid(jwsSignRequestDto.getIncludeCertificateChain()); + 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(), + DateUtils2.parseUTCToDate(timestamp)); + PrivateKey privateKey = certificateResponse.getCertificateEntry().getPrivateKey(); + X509Certificate x509Certificate = certificateResponse.getCertificateEntry().getChain()[0]; + String providerName = certificateResponse.getProviderName(); + String uniqueIdentifier = certificateResponse.getUniqueIdentifier(); + Map additionalHeaders = jwsSignRequestDto.getAdditionalHeaders(); + + JWSHeader jwsHeader = signatureUtil.getJWSHeaderV2(signAlgorithm, b64JWSHeaderParam, includeCertificateChain, + includeCertHash, certificateUrl, x509Certificate, uniqueIdentifier, includeKeyId, kidPrefix, additionalHeaders); + + 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(DateUtils2.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; + } + + @Override + public JWTSignatureVerifyResponseDto jwtVerifyV2(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); reqCertData = keymanagerUtil.getPEMFormatedData(reqCertToVerify); - } - - List certChain = certificateExistsInHeaderV2(jwtTokens[0]); - - JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto(); - responseDto.setSignatureValid(signatureValid); - responseDto.setMessage(signatureValid ? SignatureConstant.VALIDATION_SUCCESSFUL : SignatureConstant.VALIDATION_FAILED); - responseDto.setTrustValid(validateTrustV2(jwtVerifyRequestDto, certChain, reqCertData)); - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "JWT Signature Verification Request - Completed."); - return responseDto; - } - - @SuppressWarnings("unchecked") - private List certificateExistsInHeaderV2(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); - List certChain = new ArrayList<>(); - for (String certData : certList) { - certChain.add(keymanagerUtil.convertToCertificate(Base64.decodeBase64(certData))); - } - return certChain; - } - LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, - "Certificate not found in JWT Header."); - return null; - } - - private String validateTrustV2(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, List headerCertificateChain, 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; - } - - List x509CertChain = headerCertificateChain.stream() - .map(cert -> (X509Certificate) cert) - .toList(); - - X509Certificate rootCert = x509CertChain.getLast(); - - Set intermediateCerts = new HashSet<>(); - intermediateCerts.addAll(x509CertChain.subList(0, x509CertChain.size() - 1)); + } + + List certChain = certificateExistsInHeaderV2(jwtTokens[0]); + + JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto(); + responseDto.setSignatureValid(signatureValid); + responseDto.setMessage(signatureValid ? SignatureConstant.VALIDATION_SUCCESSFUL : SignatureConstant.VALIDATION_FAILED); + responseDto.setTrustValid(validateTrustV2(jwtVerifyRequestDto, certChain, reqCertData)); + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request - Completed."); + return responseDto; + } + + @SuppressWarnings("unchecked") + private List certificateExistsInHeaderV2(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); + List certChain = new ArrayList<>(); + for (String certData : certList) { + certChain.add(keymanagerUtil.convertToCertificate(Base64.decodeBase64(certData))); + } + return certChain; + } + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "Certificate not found in JWT Header."); + return null; + } + + private String validateTrustV2(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, List headerCertificateChain, 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; + } + + List x509CertChain = headerCertificateChain.stream() + .map(cert -> (X509Certificate) cert) + .toList(); + + X509Certificate rootCert = x509CertChain.getLast(); + + Set intermediateCerts = new HashSet<>(); + intermediateCerts.addAll(x509CertChain.subList(0, x509CertChain.size() - 1)); String domain = jwtVerifyRequestDto.getDomain(); - if(!SignatureUtil.isDataValid(domain)) - return SignatureConstant.TRUST_NOT_VERIFIED_NO_DOMAIN; + if(!SignatureUtil.isDataValid(domain)) + return SignatureConstant.TRUST_NOT_VERIFIED_NO_DOMAIN; - X509Certificate leafCert = x509CertChain.getFirst(); + X509Certificate leafCert = x509CertChain.getFirst(); - X509Certificate trustCertData = leafCert == null ? (X509Certificate) keymanagerUtil.convertToCertificate(reqCertData) : leafCert; - if (trustCertData == null) - return SignatureConstant.TRUST_NOT_VERIFIED; + X509Certificate trustCertData = leafCert == null ? (X509Certificate) keymanagerUtil.convertToCertificate(reqCertData) : leafCert; + if (trustCertData == null) + return SignatureConstant.TRUST_NOT_VERIFIED; - boolean isTrustValid = partnerCertManagerService.validateCertificatePathWithInterCertTrust(trustCertData, domain, intermediateCerts); - if (isTrustValid) { - return SignatureConstant.TRUST_VALID; - } + boolean isTrustValid = partnerCertManagerService.validateCertificatePathWithInterCertTrust(trustCertData, domain, intermediateCerts); + if (isTrustValid) { + 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; - } -} + LOGGER.info(SignatureConstant.SESSIONID, SignatureConstant.JWT_SIGN, SignatureConstant.BLANK, + "JWT Signature Verification Request - Trust Validation - Completed."); + return SignatureConstant.TRUST_NOT_VALID; + } +} \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/controller/SignatureControllerTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/controller/SignatureControllerTest.java index 37e15b2d..7b931974 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/controller/SignatureControllerTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/controller/SignatureControllerTest.java @@ -144,23 +144,26 @@ public void testPdfSignStatusHandled() throws Exception { key.setReferenceId(""); keymanagerService.generateMasterKey("CSR", key); + String pdfData = "JVBERi0xLjQKMSAwIG9iago8PAovVHlwZSAvQ2F0YWxvZwovUGFnZXMgMiAwIFIKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL1BhZ2VzCi9LaWRzIFszIDAgUl0KL0NvdW50IDEKPD4KZW5kb2JqCjMgMCBvYmoKPDwKL1R5cGUgL1BhZ2UKL1BhcmVudCAyIDAgUgovTWVkaWFCb3ggWzAgMCA2MTIgNzkyXQo+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDQKL1Jvb3QgMSAwIFIKPj4Kc3RhcnR4cmVmCjE3NAolJUVPRg=="; RequestWrapper req = new RequestWrapper<>(); PDFSignatureRequestDto dto = new PDFSignatureRequestDto(); dto.setApplicationId("TEST"); dto.setReferenceId(""); - dto.setData("ZHVtbXkgcGRmIGNvbnRlbnQ="); + dto.setData(pdfData); dto.setTimeStamp(io.mosip.kernel.core.util.DateUtils.getUTCCurrentDateTimeString()); dto.setPageNumber(1); - dto.setLowerLeftX(10); - dto.setLowerLeftY(10); - dto.setUpperRightX(100); - dto.setUpperRightY(100); + dto.setLowerLeftX(100); + dto.setLowerLeftY(100); + dto.setUpperRightX(200); + dto.setUpperRightY(150); + dto.setReason("Test"); + dto.setPassword("1234"); req.setRequest(dto); String content = mockMvc.perform(post("/pdf/sign") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(req))) - .andExpect(status().is2xxSuccessful()) + .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); com.fasterxml.jackson.databind.JsonNode root = objectMapper.readTree(content); diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/service/SignatureServiceTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/service/SignatureServiceTest.java index 1544f00a..1ab37867 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/service/SignatureServiceTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/service/SignatureServiceTest.java @@ -1,19 +1,27 @@ package io.mosip.kernel.signature.test.service; -import io.mosip.kernel.core.crypto.exception.SignatureException; 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.DateUtils2; +import io.mosip.kernel.keymanagerservice.constant.KeymanagerErrorConstant; import io.mosip.kernel.keymanagerservice.dto.KeyPairGenerateRequestDto; import io.mosip.kernel.keymanagerservice.exception.KeymanagerServiceException; import io.mosip.kernel.keymanagerservice.repository.KeyAliasRepository; import io.mosip.kernel.keymanagerservice.service.KeymanagerService; import io.mosip.kernel.keymanagerservice.test.KeymanagerTestBootApplication; +import io.mosip.kernel.keymanagerservice.util.KeymanagerUtil; +import io.mosip.kernel.signature.constant.SignatureConstant; +import io.mosip.kernel.signature.constant.SignatureErrorCode; import io.mosip.kernel.signature.dto.*; -import io.mosip.kernel.signature.exception.PublicKeyParseException; import io.mosip.kernel.signature.exception.RequestException; +import io.mosip.kernel.signature.exception.SignatureFailureException; +import io.mosip.kernel.signature.service.SignatureProvider; import io.mosip.kernel.signature.service.SignatureService; import io.mosip.kernel.signature.service.SignatureServicev2; +import io.mosip.kernel.signature.service.impl.EC256SignatureProviderImpl; +import io.mosip.kernel.signature.service.impl.Ed25519SignatureProviderImpl; +import io.mosip.kernel.signature.service.impl.PS256SIgnatureProviderImpl; +import io.mosip.kernel.signature.service.impl.RS256SignatureProviderImpl; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -23,8 +31,13 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; -import java.util.HashMap; -import java.util.Map; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest(classes = { KeymanagerTestBootApplication.class }) @@ -43,6 +56,9 @@ public class SignatureServiceTest { @Autowired private KeyAliasRepository keyAliasRepository; + @Autowired + KeymanagerUtil keymanagerUtil; + @Before public void setUp() { KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); @@ -56,6 +72,16 @@ public void tearDown() { keyAliasRepository.deleteAll(); } + private static Map SIGNATURE_PROVIDER = new HashMap<>(); + + 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()); + } + @Test public void testJwtSign() { KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); @@ -199,6 +225,15 @@ public void testJwtVerify() { Assert.assertNotNull(verifyResponse); Assert.assertTrue(verifyResponse.isSignatureValid()); Assert.assertEquals("Validation Successful", verifyResponse.getMessage()); + + keyPairGenRequestDto.setReferenceId("ED25519_SIGN"); + keymanagerService.generateECSignKey("CSR", keyPairGenRequestDto); + jwtSignRequestDto.setReferenceId("ED25519_SIGN"); + signResponse = signatureService.jwtSign(jwtSignRequestDto); + verifyRequestDto.setJwtSignatureData(signResponse.getJwtSignedData()); + verifyResponse = signatureService.jwtVerify(verifyRequestDto); + + Assert.assertNotNull(verifyResponse); } @Test @@ -295,6 +330,18 @@ public void testJwsSign() { jwsSignRequestDto.setCertificateUrl("https:://test/certificate.com"); response = signatureService.jwsSign(jwsSignRequestDto); Assert.assertNotNull(response); + + jwsSignRequestDto.setApplicationId(""); + response = signatureService.jwsSign(jwsSignRequestDto); + Assert.assertNotNull(response); + + keyPairGenRequestDto.setApplicationId("TEST"); + keyPairGenRequestDto.setReferenceId("ED25519_SIGN"); + keymanagerService.generateECSignKey("CSR", keyPairGenRequestDto); + jwsSignRequestDto.setApplicationId("TEST"); + jwsSignRequestDto.setReferenceId("ED25519_SIGN"); + response = signatureService.jwsSign(jwsSignRequestDto); + Assert.assertNotNull(response); } @Test @@ -357,6 +404,15 @@ public void testSignv2() { Assert.assertNotNull(response); Assert.assertNotNull(response.getSignature()); Assert.assertNotNull(response.getTimestamp()); + + keyPairGenRequestDto.setApplicationId("KERNEL"); + keyPairGenRequestDto.setReferenceId("SIGN"); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + signRequestDto.setApplicationId(""); + response = signatureServicev2.signv2(signRequestDto); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getSignature()); } @Test @@ -435,6 +491,24 @@ public void testJwtSignV2() { JWTSignatureResponseDto response = signatureService.jwtSignV2(jwtSignRequestDto); Assert.assertNotNull(response); Assert.assertNotNull(response.getJwtSignedData()); + + keyPairGenRequestDto.setApplicationId("KERNEL"); + keyPairGenRequestDto.setReferenceId("SIGN"); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + jwtSignRequestDto.setApplicationId(null); + jwtSignRequestDto.setReferenceId(null); + response = signatureService.jwtSignV2(jwtSignRequestDto); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getJwtSignedData()); + + Map additionalHeaders2 = new HashMap<>(); + additionalHeaders2.put("test", "header"); + additionalHeaders2.put("kid", "test"); + additionalHeaders2.put("aud", "test"); + jwtSignRequestDto.setAdditionalHeaders(additionalHeaders2); + response = signatureService.jwtSignV2(jwtSignRequestDto); + Assert.assertNotNull(response); } @Test @@ -448,7 +522,7 @@ public void testSignPDF() { pdfSignRequestDto.setApplicationId("TEST"); pdfSignRequestDto.setReferenceId(""); pdfSignRequestDto.setData(CryptoUtil.encodeToURLSafeBase64("dummy pdf content".getBytes())); - pdfSignRequestDto.setTimeStamp(DateUtils.getUTCCurrentDateTimeString()); + pdfSignRequestDto.setTimeStamp(DateUtils2.getUTCCurrentDateTimeString()); pdfSignRequestDto.setReason("Test signing"); pdfSignRequestDto.setPageNumber(1); pdfSignRequestDto.setLowerLeftX(100); @@ -496,17 +570,335 @@ public void testSign() { Assert.assertNotNull(response); } - @Test(expected = PublicKeyParseException.class) - public void testValidateException() { +// @Test +// public void testValidate() { +// KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); +// keyPairGenRequestDto.setApplicationId("KERNEL"); +// keyPairGenRequestDto.setReferenceId("SIGN"); +// keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); +// +// SignRequestDto signRequestDto = new SignRequestDto(); +// signRequestDto.setData("eyAibW9kdWxlIjogImtleW1hbmFnZXIiLCAicHVycG9zZSI6ICJ0ZXN0IGNhc2UiIH0"); +// SignatureResponse signResponse = signatureService.sign(signRequestDto); +// +// TimestampRequestDto timestampRequestDto = new TimestampRequestDto(); +// timestampRequestDto.setSignature(signResponse.getData()); +// timestampRequestDto.setData("eyAibW9kdWxlIjogImtleW1hbmFnZXIiLCAicHVycG9zZSI6ICJ0ZXN0IGNhc2UiIH0"); +// timestampRequestDto.setTimestamp(DateUtils2.getUTCCurrentDateTime()); +// ValidatorResponseDto response = signatureService.validate(timestampRequestDto); +// Assert.assertNotNull(response); +// Assert.assertEquals("success", response.getStatus()); +// } + +// @Test(expected = SignatureException.class) +// public void testValidateException() { +// KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); +// keyPairGenRequestDto.setApplicationId("KERNEL"); +// keyPairGenRequestDto.setReferenceId("SIGN"); +// keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); +// +// TimestampRequestDto timestampRequestDto = new TimestampRequestDto(); +// timestampRequestDto.setData("eyAibW9kdWxlIjogImtleW1hbmFnZXIiLCAicHVycG9zZSI6ICJ0ZXN0IGNhc2UiIH0"); +// timestampRequestDto.setSignature("invalid signature"); +// timestampRequestDto.setTimestamp(DateUtils2.getUTCCurrentDateTime()); +// signatureService.validate(timestampRequestDto); +// } + + @Test(expected = SignatureFailureException.class) + public void testPS256Exception() { + SignatureProvider signatureProvider = SIGNATURE_PROVIDER.get("PS256"); + signatureProvider.sign(null, null, "Invalid Provider"); + } + + @Test(expected = SignatureFailureException.class) + public void testRS256Exception() { + SignatureProvider signatureProvider = SIGNATURE_PROVIDER.get("RS256"); + signatureProvider.sign(null, null, "Invalid Provider"); + } + + @Test(expected = SignatureFailureException.class) + public void testEC256Exception() { + SignatureProvider signatureProvider = SIGNATURE_PROVIDER.get("ES256"); + signatureProvider.sign(null, null, "Invalid Provider"); + } + + @Test(expected = SignatureFailureException.class) + public void testEd25519Exception() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + SignatureProvider signatureProvider = SIGNATURE_PROVIDER.get("EdDSA"); + signatureProvider.sign(keyPair.getPrivate(), null, "Invalid Provider"); + } + + @Test + public void testJwtVerifyV2() { KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); + keyPairGenRequestDto.setApplicationId("TEST"); + keyPairGenRequestDto.setReferenceId(""); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + // First sign + JWTSignatureRequestDtoV2 jwtSignRequestDtoV2 = new JWTSignatureRequestDtoV2(); + jwtSignRequestDtoV2.setApplicationId("TEST"); + jwtSignRequestDtoV2.setReferenceId(""); + jwtSignRequestDtoV2.setDataToSign(CryptoUtil.encodeToURLSafeBase64("{\"test\":\"data\"}".getBytes())); + jwtSignRequestDtoV2.setIncludePayload(true); + jwtSignRequestDtoV2.setIncludeCertificateChain(true); + jwtSignRequestDtoV2.setIncludeCertHash(true); + jwtSignRequestDtoV2.setCertificateUrl("https://test.com/cert"); + JWTSignatureResponseDto signResponse = signatureService.jwtSignV2(jwtSignRequestDtoV2); + + // Then verify + JWTSignatureVerifyRequestDto verifyRequestDto = new JWTSignatureVerifyRequestDto(); + verifyRequestDto.setApplicationId("TEST"); + verifyRequestDto.setReferenceId(""); + verifyRequestDto.setJwtSignatureData(signResponse.getJwtSignedData()); + JWTSignatureVerifyResponseDto verifyResponse = signatureService.jwtVerifyV2(verifyRequestDto); + + Assert.assertNotNull(verifyResponse); + Assert.assertTrue(verifyResponse.isSignatureValid()); + Assert.assertEquals("Validation Successful", verifyResponse.getMessage()); + keyPairGenRequestDto.setApplicationId("KERNEL"); keyPairGenRequestDto.setReferenceId("SIGN"); keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); - TimestampRequestDto timestampRequestDto = new TimestampRequestDto(); - timestampRequestDto.setData("eyAibW9kdWxlIjogImtleW1hbmFnZXIiLCAicHVycG9zZSI6ICJ0ZXN0IGNhc2UiIH0"); - timestampRequestDto.setSignature("invalid signature"); - timestampRequestDto.setTimestamp(DateUtils.getUTCCurrentDateTime()); - signatureService.validate(timestampRequestDto); + jwtSignRequestDtoV2.setApplicationId(""); + jwtSignRequestDtoV2.setReferenceId(""); + jwtSignRequestDtoV2.setIncludeCertificateChain(false); + signResponse = signatureService.jwtSignV2(jwtSignRequestDtoV2); + + verifyRequestDto.setJwtSignatureData(signResponse.getJwtSignedData()); + verifyRequestDto.setApplicationId(""); + verifyResponse = signatureService.jwtVerifyV2(verifyRequestDto); + Assert.assertNotNull(verifyResponse); + Assert.assertTrue(verifyResponse.isSignatureValid()); + Assert.assertEquals("Validation Successful", verifyResponse.getMessage()); + } + + @Test + public void testJwsSignV2() { + KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); + keyPairGenRequestDto.setApplicationId("TEST"); + keyPairGenRequestDto.setReferenceId(""); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + keyPairGenRequestDto.setApplicationId("KERNEL"); + keyPairGenRequestDto.setReferenceId("SIGN"); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + Map addtionalHeader = new HashMap<>(); + addtionalHeader.put("test", "header"); + addtionalHeader.put("test2", "header2"); + addtionalHeader.put("iss", "test"); + addtionalHeader.put("aud", "test"); + addtionalHeader.put("sub", "test"); + + JWSSignatureRequestDtoV2 jwsSignRequestDtoV2 = new JWSSignatureRequestDtoV2(); + jwsSignRequestDtoV2.setApplicationId("TEST"); + jwsSignRequestDtoV2.setReferenceId(""); + jwsSignRequestDtoV2.setDataToSign(CryptoUtil.encodeToURLSafeBase64("{\"test\":\"data\"}".getBytes())); + jwsSignRequestDtoV2.setIncludePayload(true); + jwsSignRequestDtoV2.setIncludeCertificateChain(true); + jwsSignRequestDtoV2.setB64JWSHeaderParam(false); + jwsSignRequestDtoV2.setValidateJson(true); + jwsSignRequestDtoV2.setAdditionalHeaders(addtionalHeader); + + JWTSignatureResponseDto response = signatureService.jwsSignV2(jwsSignRequestDtoV2); + Assert.assertNotNull(response); + Assert.assertNotNull(response.getJwtSignedData()); + + jwsSignRequestDtoV2.setApplicationId("KERNEL"); + jwsSignRequestDtoV2.setReferenceId("SIGN"); + jwsSignRequestDtoV2.setIncludePayload(false); + jwsSignRequestDtoV2.setIncludeCertificateChain(false); + jwsSignRequestDtoV2.setB64JWSHeaderParam(true); + jwsSignRequestDtoV2.setCertificateUrl("https:://test/certificate.com"); + response = signatureService.jwsSignV2(jwsSignRequestDtoV2); + Assert.assertNotNull(response); + + jwsSignRequestDtoV2.setApplicationId(""); + response = signatureService.jwsSignV2(jwsSignRequestDtoV2); + Assert.assertNotNull(response); + + keyPairGenRequestDto.setApplicationId("TEST"); + keyPairGenRequestDto.setReferenceId("ED25519_SIGN"); + keymanagerService.generateECSignKey("CSR", keyPairGenRequestDto); + + jwsSignRequestDtoV2.setApplicationId("TEST"); + jwsSignRequestDtoV2.setReferenceId("ED25519_SIGN"); + response = signatureService.jwsSignV2(jwsSignRequestDtoV2); + Assert.assertNotNull(response); + } + + @Test + public void testJwtVerifyDefaultAppIDAndRefID() { + KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); + keyPairGenRequestDto.setApplicationId("KERNEL"); + keyPairGenRequestDto.setReferenceId("SIGN"); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + // First sign + JWTSignatureRequestDto jwtSignRequestDto = new JWTSignatureRequestDto(); + jwtSignRequestDto.setDataToSign(CryptoUtil.encodeToURLSafeBase64("{\"test\":\"data\"}".getBytes())); + jwtSignRequestDto.setIncludePayload(true); + jwtSignRequestDto.setIncludeCertificate(false); + JWTSignatureResponseDto signResponse = signatureService.jwtSign(jwtSignRequestDto); + + // Then verify + JWTSignatureVerifyRequestDto verifyRequestDto = new JWTSignatureVerifyRequestDto(); + verifyRequestDto.setJwtSignatureData(signResponse.getJwtSignedData()); + JWTSignatureVerifyResponseDto verifyResponse = signatureService.jwtVerify(verifyRequestDto); + + Assert.assertNotNull(verifyResponse); + Assert.assertTrue(verifyResponse.isSignatureValid()); + Assert.assertEquals("Validation Successful", verifyResponse.getMessage()); + } + + @Test + public void testJsonParsingError() { + String signData = "ewogICJhbGciOiAiUlMyNTYiLAogIHg1YzogWwogICAgIlNmN21UV2pmOE91VWlVTksybXNYTXN2SEZhdjlmaGJJNkNvVlhyUlJPY0xPVFZrNk9lSSsrckZaQ0w4NDZsSk82MlpRTHZuZSs2IgogIF0sCiAgImtpZCI6ICJNQ1NTSjZBdjhiV0FZNzBXUk5nNHVPS04yLUhFMGRGOW1pWUI3Q2lqT1BzIgp9.eyAibW9kdWxlIjogIktleW1hbmFnZXIiLCAicHJvamVjdCI6ICJNb3NpcCIgfQ.bZIrGgpKoZAsL0NyKKshS78LzlvLp3xdlWiHtrB---UVL0cAenbMaxrjgWphQAzH4l2NCOz7BYeL1UN1sUvMOBCNfplRaG8aEDb4TTG6aQjMRXZg7LJJnuBQjuU4pdPLa8qYMBhW5nssc-WZ9DK4aLH2YW68FF4zUezvAsJWexftNkVE0n9Vf05sxI4olVh696t-xrNFsMDHlrHyOWVzkQOI6i9OMsyOqgBdo6hNJG7DXTzPRV_xKkiR3SGRP0AmF57zvS7kQm8SwkGQQE9rGYPqkLG1x_3pHL4P9NeqTT77kIcKR22lOyeWKcKR1NSzmDA_RKbJBD_w9kHF0hdytg"; + JWTSignatureVerifyRequestDto verifyRequestDto = new JWTSignatureVerifyRequestDto(); + verifyRequestDto.setJwtSignatureData(signData); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureService.jwtVerify(verifyRequestDto); + }); + assertEquals(SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorCode(), exception.getErrorCode()); + + exception = assertThrows(RequestException.class, () -> { + signatureService.jwtVerifyV2(verifyRequestDto); + }); + assertEquals(SignatureErrorCode.INVALID_VERIFY_INPUT.getErrorCode(), exception.getErrorCode()); + } + + @Test + public void testJWSsignEmptyDataException() { + JWTSignatureRequestDto requestDto = new JWTSignatureRequestDto(); + requestDto.setDataToSign(""); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureService.jwtSign(requestDto); + }); + + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); + } + + @Test + public void testSignV2EmptyDataException() { + SignRequestDtoV2 requestDto = new SignRequestDtoV2(); + requestDto.setApplicationId("INVALID_APP_ID"); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureServicev2.signv2(requestDto); + }); + + assertEquals(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), exception.getErrorCode()); + + requestDto.setApplicationId("TEST"); + exception = assertThrows(RequestException.class, () -> { + signatureServicev2.signv2(requestDto); + }); + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); + } + + @Test + public void rawSignException() { + SignRequestDtoV2 requestDto = new SignRequestDtoV2(); + requestDto.setApplicationId("INVALID_APP_ID"); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureServicev2.rawSign(requestDto); + }); + + assertEquals(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), exception.getErrorCode()); + + requestDto.setApplicationId("TEST"); + exception = assertThrows(RequestException.class, () -> { + signatureServicev2.rawSign(requestDto); + }); + + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); + + + KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); + keyPairGenRequestDto.setApplicationId("KERNEL"); + keyPairGenRequestDto.setReferenceId("SIGN"); + keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); + + requestDto.setApplicationId(null); + requestDto.setDataToSign("c2lnbiByYXcgZGF0YQ=="); + requestDto.setResponseEncodingFormat("INVALID_FORMAT"); + KeymanagerServiceException exception1 = assertThrows(KeymanagerServiceException.class, () -> { + signatureServicev2.rawSign(requestDto); + }); + assertEquals(KeymanagerErrorConstant.INVALID_FORMAT_ERROR.getErrorCode(), exception1.getErrorCode()); + + requestDto.setResponseEncodingFormat("base64url"); + SignResponseDtoV2 response = signatureServicev2.rawSign(requestDto); + Assert.assertNotNull(response); + } + + @Test + public void testJWTSignV2Exception() { + JWTSignatureRequestDtoV2 requestDtoV2 = new JWTSignatureRequestDtoV2(); + requestDtoV2.setApplicationId("INVALID_APP_ID"); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureService.jwtSignV2(requestDtoV2); + }); + + assertEquals(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), exception.getErrorCode()); + + requestDtoV2.setApplicationId("TEST"); + exception = assertThrows(RequestException.class, () -> { + signatureService.jwtSignV2(requestDtoV2); + }); + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); + + requestDtoV2.setDataToSign("c2lnbiByYXcgZGF0YQ=="); + exception = assertThrows(RequestException.class, () -> { + signatureService.jwtSignV2(requestDtoV2); + }); + assertEquals(SignatureErrorCode.INVALID_JSON.getErrorCode(), exception.getErrorCode()); + } + + @Test + public void testJWSsignV2Exception() { + JWSSignatureRequestDtoV2 requestDtoV2 = new JWSSignatureRequestDtoV2(); + requestDtoV2.setApplicationId("INVALID_APP_ID"); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureService.jwsSignV2(requestDtoV2); + }); + + assertEquals(SignatureErrorCode.SIGN_NOT_ALLOWED.getErrorCode(), exception.getErrorCode()); + + requestDtoV2.setApplicationId("TEST"); + exception = assertThrows(RequestException.class, () -> { + signatureService.jwsSignV2(requestDtoV2); + }); + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); + + requestDtoV2.setDataToSign("c2lnbiByYXcgZGF0YQ=="); + requestDtoV2.setValidateJson(true); + exception = assertThrows(RequestException.class, () -> { + signatureService.jwsSignV2(requestDtoV2); + }); + assertEquals(SignatureErrorCode.INVALID_JSON.getErrorCode(), exception.getErrorCode()); + } + + @Test + public void testJWTVerifyV2EmptySignData() { + JWTSignatureVerifyRequestDto verifyRequestDto = new JWTSignatureVerifyRequestDto(); + verifyRequestDto.setJwtSignatureData(""); + + RequestException exception = assertThrows(RequestException.class, () -> { + signatureService.jwtVerifyV2(verifyRequestDto); + }); + + assertEquals(SignatureErrorCode.INVALID_INPUT.getErrorCode(), exception.getErrorCode()); } } \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/util/SignatureUtilTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/util/SignatureUtilTest.java index d26bcf12..a14ae4cc 100644 --- a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/util/SignatureUtilTest.java +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/signature/test/util/SignatureUtilTest.java @@ -162,18 +162,15 @@ public void testGetIssuerFromPayload() { @Test public void testGetJWSHeaderV2WithNullHeaders() { KeyPairGenerateRequestDto keyPairGenRequestDto = new KeyPairGenerateRequestDto(); - keyPairGenRequestDto.setApplicationId("ROOT"); - keyPairGenRequestDto.setReferenceId(""); - keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); - keyPairGenRequestDto.setApplicationId("TEST"); keyPairGenRequestDto.setReferenceId(""); keymanagerService.generateMasterKey("CSR", keyPairGenRequestDto); KeyPairGenerateResponseDto certDeatils = keymanagerService.getCertificate("TEST", Optional.empty()); X509Certificate x509Certificate = (X509Certificate) keymanagerUtil.convertToCertificate(certDeatils.getCertificate()); + var header = signatureUtil.getJWSHeaderV2("PS256", false, false, false, - null, x509Certificate, testUniqueId, false, "", null); + "https://test.com/cert", x509Certificate, testUniqueId, false, "", null); Assert.assertNotNull(header); } } \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerControlerTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerControlerTest.java new file mode 100644 index 00000000..df821d1f --- /dev/null +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerControlerTest.java @@ -0,0 +1,160 @@ +package io.mosip.kernel.zkcryptoservice.test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.mosip.kernel.core.http.RequestWrapper; +import io.mosip.kernel.zkcryptoservice.controller.ZKCryptoManagerController; +import io.mosip.kernel.zkcryptoservice.dto.AuthorizedRolesDTO; +import io.mosip.kernel.zkcryptoservice.dto.CryptoDataDto; +import io.mosip.kernel.zkcryptoservice.dto.ReEncryptRandomKeyResponseDto; +import io.mosip.kernel.zkcryptoservice.dto.ZKCryptoRequestDto; +import io.mosip.kernel.zkcryptoservice.dto.ZKCryptoResponseDto; +import io.mosip.kernel.zkcryptoservice.service.spi.ZKCryptoManagerService; + +@RunWith(SpringRunner.class) +@WebMvcTest(ZKCryptoManagerController.class) +@ContextConfiguration(classes = ZKCryptoManagerControlerTest.TestConfig.class) +public class ZKCryptoManagerControlerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ZKCryptoManagerService zkCryptoManagerService; + + @MockBean(name = "zkAuthRoles") + private AuthorizedRolesDTO authorizedRolesDTO; + + @Autowired + private ObjectMapper objectMapper; + + @Test + @WithMockUser(roles = "ZONAL_ADMIN") + public void testZkEncryptSuccess() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + List list = new ArrayList<>(); + list.add(cryptoData); + requestDto.setZkDataAttributes(list); + + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequest(requestDto); + + ZKCryptoResponseDto responseDto = new ZKCryptoResponseDto(); + responseDto.setZkDataAttributes(list); + responseDto.setEncryptedRandomKey("encryptedKey"); + responseDto.setRankomKeyIndex("1"); + + when(authorizedRolesDTO.getPostzkencrypt()).thenReturn(List.of("ZONAL_ADMIN")); + when(zkCryptoManagerService.zkEncrypt(any(ZKCryptoRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/zkEncrypt") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestWrapper)) + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response.zkDataAttributes[0].identifier").value("name")) + .andExpect(jsonPath("$.response.zkDataAttributes[0].value").value("John Doe")) + .andExpect(jsonPath("$.response.encryptedRandomKey").value("encryptedKey")) + .andExpect(jsonPath("$.response.rankomKeyIndex").value("1")); + } + + @Test + @WithMockUser(roles = "ZONAL_ADMIN") + public void testZkDecryptSuccess() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("EncryptedValue"); + List list = new ArrayList<>(); + list.add(cryptoData); + requestDto.setZkDataAttributes(list); + + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequest(requestDto); + + ZKCryptoResponseDto responseDto = new ZKCryptoResponseDto(); + CryptoDataDto decryptedData = new CryptoDataDto(); + decryptedData.setIdentifier("name"); + decryptedData.setValue("DecryptedValue"); + List decryptedList = new ArrayList<>(); + decryptedList.add(decryptedData); + responseDto.setZkDataAttributes(decryptedList); + + when(authorizedRolesDTO.getPostzkdecrypt()).thenReturn(List.of("ZONAL_ADMIN")); + when(zkCryptoManagerService.zkDecrypt(any(ZKCryptoRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/zkDecrypt") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestWrapper)) + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response.zkDataAttributes[0].identifier").value("name")) + .andExpect(jsonPath("$.response.zkDataAttributes[0].value").value("DecryptedValue")); + } + + @Test + @WithMockUser(roles = "ZONAL_ADMIN") + public void testZkReEncryptRandomKeySuccess() throws Exception { + String encryptedKey = "encryptedKey"; + ReEncryptRandomKeyResponseDto responseDto = new ReEncryptRandomKeyResponseDto(); + responseDto.setEncryptedKey("reEncryptedKey"); + + when(authorizedRolesDTO.getPostzkreencryptrandomkey()).thenReturn(List.of("ZONAL_ADMIN")); + when(zkCryptoManagerService.zkReEncryptRandomKey(any(String.class))).thenReturn(responseDto); + + mockMvc.perform(post("/zkReEncryptRandomKey") + .param("encryptedKey", encryptedKey) + .with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.response.encryptedKey").value("reEncryptedKey")); + } + + @Test + @WithMockUser(roles = "ZONAL_ADMIN") + public void testZkEncryptValidationFailure() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + // Missing required fields to trigger validation error + + RequestWrapper requestWrapper = new RequestWrapper<>(); + requestWrapper.setRequest(requestDto); + + when(authorizedRolesDTO.getPostzkencrypt()).thenReturn(List.of("ZONAL_ADMIN")); + + mockMvc.perform(post("/zkEncrypt") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(requestWrapper)) + .with(csrf())) + .andExpect(status().isBadRequest()); + } + + @org.springframework.boot.autoconfigure.SpringBootApplication + @org.springframework.context.annotation.ComponentScan(basePackages = "io.mosip.kernel.zkcryptoservice.controller") + static class TestConfig { + } +} \ No newline at end of file diff --git a/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerServiceTest.java b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerServiceTest.java new file mode 100644 index 00000000..a03d2e69 --- /dev/null +++ b/kernel/kernel-keymanager-service/src/test/java/io/mosip/kernel/zkcryptoservice/test/ZKCryptoManagerServiceTest.java @@ -0,0 +1,1552 @@ +package io.mosip.kernel.zkcryptoservice.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.nio.ByteBuffer; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import io.mosip.kernel.core.crypto.spi.CryptoCoreSpec; +import io.mosip.kernel.core.keymanager.spi.ECKeyStore; +import io.mosip.kernel.core.util.CryptoUtil; +import io.mosip.kernel.cryptomanager.constant.CryptomanagerConstant; +import io.mosip.kernel.cryptomanager.util.CryptomanagerUtils; +import io.mosip.kernel.keymanagerservice.constant.KeymanagerConstant; +import io.mosip.kernel.keymanagerservice.dto.SymmetricKeyRequestDto; +import io.mosip.kernel.keymanagerservice.dto.SymmetricKeyResponseDto; +import io.mosip.kernel.keymanagerservice.entity.KeyAlias; +import io.mosip.kernel.keymanagerservice.entity.KeyStore; +import io.mosip.kernel.keymanagerservice.exception.NoUniqueAliasException; +import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; +import io.mosip.kernel.keymanagerservice.repository.DataEncryptKeystoreRepository; +import io.mosip.kernel.keymanagerservice.repository.KeyStoreRepository; +import io.mosip.kernel.keymanagerservice.service.KeymanagerService; +import io.mosip.kernel.keymanagerservice.util.KeymanagerUtil; +import io.mosip.kernel.zkcryptoservice.constant.ZKCryptoManagerConstants; +import io.mosip.kernel.zkcryptoservice.dto.CryptoDataDto; +import io.mosip.kernel.zkcryptoservice.dto.ReEncryptRandomKeyResponseDto; +import io.mosip.kernel.zkcryptoservice.dto.ZKCryptoRequestDto; +import io.mosip.kernel.zkcryptoservice.dto.ZKCryptoResponseDto; +import io.mosip.kernel.zkcryptoservice.exception.ZKCryptoException; +import io.mosip.kernel.zkcryptoservice.exception.ZKKeyDerivationException; +import io.mosip.kernel.zkcryptoservice.service.impl.ZKCryptoManagerServiceImpl; + +/** + * Test class for {@link ZKCryptoManagerServiceImpl} + * + * @author Test + * @since 1.1.2 + */ +@RunWith(MockitoJUnitRunner.class) +public class ZKCryptoManagerServiceTest { + + @InjectMocks + private ZKCryptoManagerServiceImpl zkCryptoManagerService; + + @Mock + private DataEncryptKeystoreRepository dataEncryptKeystoreRepository; + + @Mock + private KeymanagerDBHelper dbHelper; + + @Mock + private KeyStoreRepository keyStoreRepository; + + @Mock + private ECKeyStore keyStore; + + @Mock + private KeymanagerUtil keymanagerUtil; + + @Mock + private KeymanagerService keyManagerService; + + @Mock + private CryptomanagerUtils cryptomanagerUtil; + + @Mock + private CryptoCoreSpec cryptoCore; + + private SecretKey masterKey; + private SecretKey randomKey; + private KeyPair keyPair; + private X509Certificate mockCertificate; + + @Before + public void setUp() throws Exception { + // Set up configuration properties + ReflectionTestUtils.setField(zkCryptoManagerService, "aesGCMTransformation", "AES/GCM/NoPadding"); + ReflectionTestUtils.setField(zkCryptoManagerService, "masterKeyAppId", "KERNEL"); + ReflectionTestUtils.setField(zkCryptoManagerService, "masterKeyRefId", "IDENTITY_CACHE"); + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyApplicationId", "PUB_KEY_APP"); + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + ReflectionTestUtils.setField(zkCryptoManagerService, "aesECBTransformation", "AES/ECB/NoPadding"); + + // Generate test keys + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + + // Create AES keys (16 bytes for AES-128) + byte[] masterKeyBytes = new byte[16]; + new SecureRandom().nextBytes(masterKeyBytes); + masterKey = new SecretKeySpec(masterKeyBytes, "AES"); + + byte[] randomKeyBytes = new byte[16]; + new SecureRandom().nextBytes(randomKeyBytes); + randomKey = new SecretKeySpec(randomKeyBytes, "AES"); + + // Mock certificate + mockCertificate = org.mockito.Mockito.mock(X509Certificate.class); + when(mockCertificate.getPublicKey()).thenReturn(keyPair.getPublic()); + + zkCryptoManagerService.init(); + } + + @Test + public void testAfterPropertiesSetWithException() throws Exception { + // Use lenient stubbing since exception is caught and ignored + org.mockito.Mockito.lenient().when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenThrow(new RuntimeException("Test exception")); + + // Should not throw exception, just ignore + zkCryptoManagerService.afterPropertiesSet(); + } + + // ==================== zkEncrypt Tests ==================== + + @Test + public void testZkEncryptSuccess() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + // Mock repository calls + List indexes = Arrays.asList(0, 1, 2, 3, 4); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + // Mock key retrieval + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + // Mock cipher operations for master key decryption + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + assertNotNull(response.getZkDataAttributes()); + assertEquals(1, response.getZkDataAttributes().size()); + assertNotNull(response.getEncryptedRandomKey()); + assertNotNull(response.getRankomKeyIndex()); + verify(keymanagerUtil, times(1)).destoryKey(any(SecretKey.class)); + } + + @Test + public void testZkEncryptMultipleAttributes() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + List cryptoDataList = new ArrayList<>(); + CryptoDataDto cryptoData1 = new CryptoDataDto(); + cryptoData1.setIdentifier("name"); + cryptoData1.setValue("John Doe"); + cryptoDataList.add(cryptoData1); + CryptoDataDto cryptoData2 = new CryptoDataDto(); + cryptoData2.setIdentifier("email"); + cryptoData2.setValue("john@example.com"); + cryptoDataList.add(cryptoData2); + requestDto.setZkDataAttributes(cryptoDataList); + + List indexes = Arrays.asList(0, 1, 2, 3, 4); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + assertEquals(2, response.getZkDataAttributes().size()); + } + + // ==================== zkDecrypt Tests ==================== + + @Test + public void testZkDecryptSuccess() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + + // Create valid encrypted data + int keyIndex = 0; + byte[] indexBytes = ByteBuffer.allocate(4).putInt(keyIndex).array(); + byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + new SecureRandom().nextBytes(nonce); + new SecureRandom().nextBytes(aad); + + // Encrypt randomKey with masterKey for DB mock + Cipher ecbCipher = Cipher.getInstance("AES/ECB/NoPadding"); + ecbCipher.init(Cipher.ENCRYPT_MODE, masterKey); + byte[] encryptedRandomKeyBytes = ecbCipher.doFinal(randomKey.getEncoded()); + String encryptedRandomKeyString = Base64.getEncoder().encodeToString(encryptedRandomKeyBytes); + + when(dataEncryptKeystoreRepository.findKeyById(keyIndex)) + .thenReturn(encryptedRandomKeyString); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + // Calculate derived key + java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256"); + byte[] idBytes = "12345".getBytes(); + digest.update(idBytes); + byte[] hashBytes = digest.digest(); + + ecbCipher.init(Cipher.ENCRYPT_MODE, randomKey); + byte[] derivedKeyBytes = ecbCipher.doFinal(hashBytes); + SecretKey derivedKey = new SecretKeySpec(derivedKeyBytes, "AES"); + + // Encrypt data with derivedKey + Cipher gcmCipher = Cipher.getInstance("AES/GCM/NoPadding"); + javax.crypto.spec.GCMParameterSpec gcmSpec = new javax.crypto.spec.GCMParameterSpec(128, nonce); + gcmCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmSpec); + gcmCipher.updateAAD(aad); + byte[] encryptedData = gcmCipher.doFinal("John Doe".getBytes()); + + // Construct final payload + byte[] finalData = new byte[indexBytes.length + nonce.length + aad.length + encryptedData.length]; + System.arraycopy(indexBytes, 0, finalData, 0, indexBytes.length); + System.arraycopy(nonce, 0, finalData, indexBytes.length, nonce.length); + System.arraycopy(aad, 0, finalData, indexBytes.length + nonce.length, aad.length); + System.arraycopy(encryptedData, 0, finalData, indexBytes.length + nonce.length + aad.length, + encryptedData.length); + + cryptoData.setValue(CryptoUtil.encodeToURLSafeBase64(finalData)); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkDecrypt(requestDto); + + assertNotNull(response); + assertEquals(1, response.getZkDataAttributes().size()); + assertEquals("John Doe", response.getZkDataAttributes().get(0).getValue()); + } + + @Test + public void testZkDecryptMultipleAttributes() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + List cryptoDataList = new ArrayList<>(); + + // Attribute 1 + CryptoDataDto cryptoData1 = new CryptoDataDto(); + cryptoData1.setIdentifier("name"); + + int keyIndex = 0; + byte[] indexBytes = ByteBuffer.allocate(4).putInt(keyIndex).array(); + byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + new SecureRandom().nextBytes(nonce); + new SecureRandom().nextBytes(aad); + + Cipher ecbCipher = Cipher.getInstance("AES/ECB/NoPadding"); + ecbCipher.init(Cipher.ENCRYPT_MODE, masterKey); + byte[] encryptedRandomKeyBytes = ecbCipher.doFinal(randomKey.getEncoded()); + String encryptedRandomKeyString = Base64.getEncoder().encodeToString(encryptedRandomKeyBytes); + + when(dataEncryptKeystoreRepository.findKeyById(keyIndex)) + .thenReturn(encryptedRandomKeyString); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256"); + byte[] idBytes = "12345".getBytes(); + digest.update(idBytes); + byte[] hashBytes = digest.digest(); + + ecbCipher.init(Cipher.ENCRYPT_MODE, randomKey); + byte[] derivedKeyBytes = ecbCipher.doFinal(hashBytes); + SecretKey derivedKey = new SecretKeySpec(derivedKeyBytes, "AES"); + + Cipher gcmCipher = Cipher.getInstance("AES/GCM/NoPadding"); + javax.crypto.spec.GCMParameterSpec gcmSpec = new javax.crypto.spec.GCMParameterSpec(128, nonce); + gcmCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmSpec); + gcmCipher.updateAAD(aad); + byte[] encryptedData1 = gcmCipher.doFinal("John Doe".getBytes()); + + byte[] finalData1 = new byte[indexBytes.length + nonce.length + aad.length + encryptedData1.length]; + System.arraycopy(indexBytes, 0, finalData1, 0, indexBytes.length); + System.arraycopy(nonce, 0, finalData1, indexBytes.length, nonce.length); + System.arraycopy(aad, 0, finalData1, indexBytes.length + nonce.length, aad.length); + System.arraycopy(encryptedData1, 0, finalData1, indexBytes.length + nonce.length + aad.length, + encryptedData1.length); + + cryptoData1.setValue(CryptoUtil.encodeToURLSafeBase64(finalData1)); + cryptoDataList.add(cryptoData1); + + // Attribute 2 + CryptoDataDto cryptoData2 = new CryptoDataDto(); + cryptoData2.setIdentifier("email"); + + byte[] nonce2 = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad2 = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + new SecureRandom().nextBytes(nonce2); + new SecureRandom().nextBytes(aad2); + + gcmSpec = new javax.crypto.spec.GCMParameterSpec(128, nonce2); + gcmCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmSpec); + gcmCipher.updateAAD(aad2); + byte[] encryptedData2 = gcmCipher.doFinal("john@example.com".getBytes()); + + byte[] finalData2 = new byte[indexBytes.length + nonce2.length + aad2.length + encryptedData2.length]; + System.arraycopy(indexBytes, 0, finalData2, 0, indexBytes.length); + System.arraycopy(nonce2, 0, finalData2, indexBytes.length, nonce2.length); + System.arraycopy(aad2, 0, finalData2, indexBytes.length + nonce2.length, aad2.length); + System.arraycopy(encryptedData2, 0, finalData2, indexBytes.length + nonce2.length + aad2.length, + encryptedData2.length); + + cryptoData2.setValue(CryptoUtil.encodeToURLSafeBase64(finalData2)); + cryptoDataList.add(cryptoData2); + + requestDto.setZkDataAttributes(cryptoDataList); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkDecrypt(requestDto); + + assertNotNull(response); + assertEquals(2, response.getZkDataAttributes().size()); + assertEquals("John Doe", response.getZkDataAttributes().get(0).getValue()); + assertEquals("john@example.com", response.getZkDataAttributes().get(1).getValue()); + } + + @Test(expected = ZKCryptoException.class) + public void testZkDecryptInvalidLength() { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + + // Create data shorter than header length (4 + 12 + 16 = 32 bytes) + byte[] shortData = new byte[30]; + cryptoData.setValue(CryptoUtil.encodeToURLSafeBase64(shortData)); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + zkCryptoManagerService.zkDecrypt(requestDto); + } + + @Test + public void testShutdown() { + zkCryptoManagerService.shutdown(); + } + + @Test + public void testZkReEncryptRandomKeySuccess() throws Exception { + // Create encrypted key with thumbprint + byte[] thumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint); + byte[] encryptedKeyData = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData); + byte[] concatedData = new byte[thumbprint.length + encryptedKeyData.length]; + System.arraycopy(thumbprint, 0, concatedData, 0, thumbprint.length); + System.arraycopy(encryptedKeyData, 0, concatedData, thumbprint.length, encryptedKeyData.length); + String encryptedKey = CryptoUtil.encodeToURLSafeBase64(concatedData); + + // Create key alias with matching thumbprint + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint(org.bouncycastle.util.encoders.Hex.toHexString(thumbprint).toUpperCase()); + List keyAliases = Collections.singletonList(keyAlias); + + // Mock for pub key aliases (first call) + when(dbHelper.getKeyAliases(eq("PUB_KEY_APP"), eq("REF1,REF2"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(keyAliases)); + // Mock for master key (second call) + when(dbHelper.getKeyAliases(eq("KERNEL"), eq("IDENTITY_CACHE"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + + when(keyManagerService.decryptSymmetricKey(any(SymmetricKeyRequestDto.class))) + .thenReturn(createSymmetricKeyResponse(CryptoUtil.encodeToURLSafeBase64(new byte[16]))); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ReEncryptRandomKeyResponseDto response = zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + + assertNotNull(response); + assertNotNull(response.getEncryptedKey()); + } + + @Test + public void testZkReEncryptRandomKeyWithMultipleKeys() throws Exception { + byte[] thumbprint1 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint1); + byte[] encryptedKeyData1 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData1); + byte[] concatedData1 = new byte[thumbprint1.length + encryptedKeyData1.length]; + System.arraycopy(thumbprint1, 0, concatedData1, 0, thumbprint1.length); + System.arraycopy(encryptedKeyData1, 0, concatedData1, thumbprint1.length, encryptedKeyData1.length); + String encryptedKey1 = CryptoUtil.encodeToURLSafeBase64(concatedData1); + + byte[] thumbprint2 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint2); + byte[] encryptedKeyData2 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData2); + byte[] concatedData2 = new byte[thumbprint2.length + encryptedKeyData2.length]; + System.arraycopy(thumbprint2, 0, concatedData2, 0, thumbprint2.length); + System.arraycopy(encryptedKeyData2, 0, concatedData2, thumbprint2.length, encryptedKeyData2.length); + String encryptedKey2 = CryptoUtil.encodeToURLSafeBase64(concatedData2); + + String encryptedKey = encryptedKey1 + "." + encryptedKey2; + + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint(org.bouncycastle.util.encoders.Hex.toHexString(thumbprint1).toUpperCase()); + List keyAliases = Collections.singletonList(keyAlias); + + // Mock for pub key aliases (first call) + when(dbHelper.getKeyAliases(eq("PUB_KEY_APP"), eq("REF1,REF2"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(keyAliases)); + // Mock for master key (second call) + when(dbHelper.getKeyAliases(eq("KERNEL"), eq("IDENTITY_CACHE"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + + when(keyManagerService.decryptSymmetricKey(any(SymmetricKeyRequestDto.class))) + .thenReturn(createSymmetricKeyResponse(CryptoUtil.encodeToURLSafeBase64(new byte[16]))); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ReEncryptRandomKeyResponseDto response = zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + + assertNotNull(response); + } + + @Test(expected = ZKCryptoException.class) + public void testZkReEncryptRandomKeyNullInput() { + zkCryptoManagerService.zkReEncryptRandomKey(null); + } + + @Test(expected = ZKCryptoException.class) + public void testZkReEncryptRandomKeyEmptyInput() { + zkCryptoManagerService.zkReEncryptRandomKey(""); + } + + @Test(expected = ZKCryptoException.class) + public void testZkReEncryptRandomKeyWhitespaceInput() { + zkCryptoManagerService.zkReEncryptRandomKey(" "); + } + + @Test + public void testZkReEncryptRandomKeyNoMatchingThumbprint() throws Exception { + byte[] thumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint); + byte[] encryptedKeyData = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData); + byte[] concatedData = new byte[thumbprint.length + encryptedKeyData.length]; + System.arraycopy(thumbprint, 0, concatedData, 0, thumbprint.length); + System.arraycopy(encryptedKeyData, 0, concatedData, thumbprint.length, encryptedKeyData.length); + String encryptedKey = CryptoUtil.encodeToURLSafeBase64(concatedData); + + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint("DIFFERENT_THUMBPRINT"); + List keyAliases = Collections.singletonList(keyAlias); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(keyAliases)); + + try { + zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + org.junit.Assert.fail("Expected ZKCryptoException"); + } catch (ZKCryptoException e) { + // Expected + } + } + + @Test(expected = ZKCryptoException.class) + public void testZkReEncryptRandomKeyEmptyKeyAliases() throws Exception { + byte[] thumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint); + byte[] encryptedKeyData = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData); + byte[] concatedData = new byte[thumbprint.length + encryptedKeyData.length]; + System.arraycopy(thumbprint, 0, concatedData, 0, thumbprint.length); + System.arraycopy(encryptedKeyData, 0, concatedData, thumbprint.length, encryptedKeyData.length); + String encryptedKey = CryptoUtil.encodeToURLSafeBase64(concatedData); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(Collections.emptyList())); + + zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + } + + @Test(expected = NoUniqueAliasException.class) + public void testGetMasterKeyFromHSMNullAlias() { + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createEmptyKeyAliasMap()); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + zkCryptoManagerService.zkEncrypt(requestDto); + } + + @Test(expected = NoUniqueAliasException.class) + public void testGetMasterKeyFromHSMMultipleAliases() { + Map> keyAliasMap = new HashMap<>(); + KeyAlias keyAlias1 = new KeyAlias(); + keyAlias1.setAlias("alias1"); + KeyAlias keyAlias2 = new KeyAlias(); + keyAlias2.setAlias("alias2"); + keyAliasMap.put(KeymanagerConstant.CURRENTKEYALIAS, Arrays.asList(keyAlias1, keyAlias2)); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(keyAliasMap); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + zkCryptoManagerService.zkEncrypt(requestDto); + } + + @Test(expected = NoUniqueAliasException.class) + public void testEncryptRandomKeyNoKeyStore() throws Exception { + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(dbHelper.getKeyAliases(eq("PUB_KEY_APP"), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("pub-key-alias")); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(Optional.empty()); + + zkCryptoManagerService.zkEncrypt(requestDto); + } + + @Test + public void testEncryptRandomKeyWithEmptyReferenceId() throws Exception { + // Set pubKeyReferenceId to include empty/null values + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,,REF2, ,REF3"); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + // Mock for REF1, REF2, REF3 (empty ones should be skipped) + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + // Should have encrypted random key (only for non-empty ref IDs) + assertNotNull(response.getEncryptedRandomKey()); + + // Restore + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + } + + @Test + public void testZkReEncryptRandomKeyWithKeyAliasesNotNull() throws Exception { + // Set keyAliases to non-null to test the branch where keyAliases is already set + byte[] thumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + Arrays.fill(thumbprint, (byte) 0xAA); + String thumbprintHex = org.bouncycastle.util.encoders.Hex.toHexString(thumbprint).toUpperCase(); + + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint(thumbprintHex); // Set matching thumbprint first + List keyAliasesList = Collections.singletonList(keyAlias); + ReflectionTestUtils.setField(zkCryptoManagerService, "keyAliases", keyAliasesList); + + byte[] encryptedKeyData = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData); + byte[] concatedData = new byte[thumbprint.length + encryptedKeyData.length]; + System.arraycopy(thumbprint, 0, concatedData, 0, thumbprint.length); + System.arraycopy(encryptedKeyData, 0, concatedData, thumbprint.length, encryptedKeyData.length); + String encryptedKey = CryptoUtil.encodeToURLSafeBase64(concatedData); + + when(keyManagerService.decryptSymmetricKey(any(SymmetricKeyRequestDto.class))) + .thenReturn(createSymmetricKeyResponse(CryptoUtil.encodeToURLSafeBase64(new byte[16]))); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ReEncryptRandomKeyResponseDto response = zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + + assertNotNull(response); + assertNotNull(response.getEncryptedKey()); + + // Reset + ReflectionTestUtils.setField(zkCryptoManagerService, "keyAliases", null); + } + + @Test + public void testDoFinalIllegalArgumentException() { + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn("INVALID_BASE64"); // This will cause IllegalArgumentException in + // Base64.decode + + // Use lenient stubbing since exception happens before these are called + org.mockito.Mockito.lenient() + .when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + org.mockito.Mockito.lenient().when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected ZKKeyDerivationException"); + } catch (ZKKeyDerivationException e) { + // Expected + } + } + + @Test + public void testEncryptRandomKeyWithMultipleReferenceIds() throws Exception { + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + assertNotNull(response.getEncryptedRandomKey()); + // Should contain dot separator for multiple keys + assertTrue(response.getEncryptedRandomKey().contains(".") + || response.getEncryptedRandomKey().length() > 0); + } + + @Test + public void testZkReEncryptRandomKeyWithNullKeyAliasesInMap() throws Exception { + // Test when keyAliasMap.get returns null - this will set keyAliases to null + Map> keyAliasMap = new HashMap<>(); + keyAliasMap.put(KeymanagerConstant.KEYALIAS, null); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(keyAliasMap); + + byte[] thumbprint = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + new SecureRandom().nextBytes(thumbprint); + byte[] encryptedKeyData = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData); + byte[] concatedData = new byte[thumbprint.length + encryptedKeyData.length]; + System.arraycopy(thumbprint, 0, concatedData, 0, thumbprint.length); + System.arraycopy(encryptedKeyData, 0, concatedData, thumbprint.length, encryptedKeyData.length); + String encryptedKey = CryptoUtil.encodeToURLSafeBase64(concatedData); + + try { + zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + org.junit.Assert.fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected when keyAliases is null and we try to call .stream() + assertNotNull(e); + } + } + + @Test + public void testGetMasterKeyFromHSMWithNullKeyAlias() { + // Test the path where getKeyAlias returns null + // Mock getKeyAlias to return null by making currentKeyAliases empty + Map> emptyMap = new HashMap<>(); + emptyMap.put(KeymanagerConstant.CURRENTKEYALIAS, Collections.emptyList()); + when(dbHelper.getKeyAliases(eq("KERNEL"), eq("IDENTITY_CACHE"), any(LocalDateTime.class))) + .thenReturn(emptyMap); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected NoUniqueAliasException"); + } catch (NoUniqueAliasException e) { + // Expected + assertNotNull(e); + } + } + + // ==================== DTO Tests ==================== + + @Test + public void testZKCryptoRequestDto() { + ZKCryptoRequestDto dto = new ZKCryptoRequestDto(); + dto.setId("12345"); + dto.setZkDataAttributes(Collections.singletonList(new CryptoDataDto())); + + assertEquals("12345", dto.getId()); + assertNotNull(dto.getZkDataAttributes()); + assertEquals(1, dto.getZkDataAttributes().size()); + + // Test constructor + ZKCryptoRequestDto dto2 = new ZKCryptoRequestDto("67890", Collections.emptyList()); + assertEquals("67890", dto2.getId()); + assertNotNull(dto2.getZkDataAttributes()); + } + + @Test + public void testZKCryptoResponseDto() { + ZKCryptoResponseDto dto = new ZKCryptoResponseDto(); + dto.setZkDataAttributes(Collections.singletonList(new CryptoDataDto())); + dto.setEncryptedRandomKey("encrypted-key"); + dto.setRankomKeyIndex("0"); + + assertNotNull(dto.getZkDataAttributes()); + assertEquals("encrypted-key", dto.getEncryptedRandomKey()); + assertEquals("0", dto.getRankomKeyIndex()); + + // Test constructor + ZKCryptoResponseDto dto2 = new ZKCryptoResponseDto(); + assertNotNull(dto2); + } + + @Test + public void testCryptoDataDto() { + CryptoDataDto dto = new CryptoDataDto(); + dto.setIdentifier("name"); + dto.setValue("John Doe"); + + assertEquals("name", dto.getIdentifier()); + assertEquals("John Doe", dto.getValue()); + + // Test constructor + CryptoDataDto dto2 = new CryptoDataDto("email", "john@example.com"); + assertEquals("email", dto2.getIdentifier()); + assertEquals("john@example.com", dto2.getValue()); + } + + @Test + public void testReEncryptRandomKeyResponseDto() { + ReEncryptRandomKeyResponseDto dto = new ReEncryptRandomKeyResponseDto(); + dto.setEncryptedKey("encrypted-key"); + + assertEquals("encrypted-key", dto.getEncryptedKey()); + } + + @Test + public void testAuthorizedRolesDTO() { + io.mosip.kernel.zkcryptoservice.dto.AuthorizedRolesDTO dto = new io.mosip.kernel.zkcryptoservice.dto.AuthorizedRolesDTO(); + List roles = Arrays.asList("ZONAL_ADMIN", "GLOBAL_ADMIN"); + dto.setPostzkencrypt(roles); + dto.setPostzkdecrypt(roles); + dto.setPostzkreencryptrandomkey(roles); + + assertEquals(roles, dto.getPostzkencrypt()); + assertEquals(roles, dto.getPostzkdecrypt()); + assertEquals(roles, dto.getPostzkreencryptrandomkey()); + } + + @Test + public void testEncryptRandomKeyWithAllEmptyReferenceIds() throws Exception { + // Test when all reference IDs are empty/null - should return empty string + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", ", , ,"); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + // When all ref IDs are empty, encryptedRandomKey should be empty string + assertEquals("", response.getEncryptedRandomKey()); + + // Restore + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + } + + @Test + public void testEncryptRandomKeyWithNullReferenceId() throws Exception { + // Test when reference ID array contains null (though split won't produce null, + // but test the null check) + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1"); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + assertNotNull(response.getEncryptedRandomKey()); + + // Restore + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + } + + @Test + public void testDoFinalInvalidKeyException() { + // Test InvalidKeyException in doFinal - use a key that's too short + SecretKey shortKey = new SecretKeySpec(new byte[8], "AES"); // Too short for AES + + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(shortKey); // Return invalid key + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected ZKKeyDerivationException"); + } catch (ZKKeyDerivationException e) { + // Expected + assertNotNull(e); + } + } + + @Test + public void testDoFinalIllegalBlockSizeException() { + // This is hard to trigger directly, but we can test through invalid data length + // For ECB/NoPadding, data must be multiple of block size + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[15])); // Not multiple of 16 + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected ZKKeyDerivationException"); + } catch (ZKKeyDerivationException e) { + // Expected - IllegalBlockSizeException wrapped + assertNotNull(e); + } + } + + @Test + public void testGetDerivedKeyIllegalBlockSizeException() { + // Test IllegalBlockSizeException in getDerivedKey + // This is hard to trigger directly, but the exception path is covered by other + // tests + // This test verifies normal flow works + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + // This should work normally + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + assertNotNull(response); + } + + @Test + public void testDoCipherOpsBadPaddingException() throws Exception { + // Test BadPaddingException in doCipherOps - use wrong key for decryption + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + + // Create encrypted data with one key + byte[] indexBytes = ByteBuffer.allocate(4).putInt(0).array(); + byte[] nonce = new byte[ZKCryptoManagerConstants.GCM_NONCE_LENGTH]; + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + new SecureRandom().nextBytes(nonce); + new SecureRandom().nextBytes(aad); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + javax.crypto.spec.GCMParameterSpec gcmSpec = new javax.crypto.spec.GCMParameterSpec( + ZKCryptoManagerConstants.GCM_TAG_LENGTH * 8, nonce); + cipher.init(Cipher.ENCRYPT_MODE, randomKey, gcmSpec); + cipher.updateAAD(aad); + byte[] encryptedData = cipher.doFinal("John Doe".getBytes()); + + byte[] combined = new byte[indexBytes.length + nonce.length + aad.length + encryptedData.length]; + System.arraycopy(indexBytes, 0, combined, 0, indexBytes.length); + System.arraycopy(nonce, 0, combined, indexBytes.length, nonce.length); + System.arraycopy(aad, 0, combined, indexBytes.length + nonce.length, aad.length); + System.arraycopy(encryptedData, 0, combined, indexBytes.length + nonce.length + aad.length, + encryptedData.length); + + cryptoData.setValue(CryptoUtil.encodeToURLSafeBase64(combined)); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + // Use a different random key for decryption to cause BadPaddingException + byte[] wrongKeyBytes = new byte[16]; + new SecureRandom().nextBytes(wrongKeyBytes); + + when(dataEncryptKeystoreRepository.findKeyById(0)) + .thenReturn(Base64.getEncoder().encodeToString(wrongKeyBytes)); // Return wrong key + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + try { + zkCryptoManagerService.zkDecrypt(requestDto); + org.junit.Assert.fail("Expected ZKCryptoException due to BadPaddingException"); + } catch (ZKCryptoException e) { + // Expected - BadPaddingException wrapped + assertNotNull(e); + } + } + + @Test + public void testDoCipherOpsInvalidKeyException() { + // Test InvalidKeyException in doCipherOps + ReflectionTestUtils.setField(zkCryptoManagerService, "aesGCMTransformation", "AES/GCM/NoPadding"); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + // This should work normally - InvalidKeyException path is covered by other + // tests + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + assertNotNull(response); + } + + @Test + public void testGetRandomKeyIndexWithSingleIndex() { + // Test getRandomKeyIndex when there's only one index + List indexes = Collections.singletonList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + assertEquals("0", response.getRankomKeyIndex()); + } + + @Test + public void testGetKeyAliasWithNullInMap() { + // Test when keyAliasMap.get returns null + Map> keyAliasMap = new HashMap<>(); + keyAliasMap.put(KeymanagerConstant.CURRENTKEYALIAS, null); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(keyAliasMap); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected NullPointerException or NoUniqueAliasException"); + } catch (Exception e) { + // Expected - NPE when calling isEmpty() on null, or NoUniqueAliasException + assertNotNull(e); + } + } + + @Test + public void testZkReEncryptRandomKeyContinuePath() throws Exception { + // Test the continue path when keyAlias is not present (line 427) + byte[] thumbprint1 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + Arrays.fill(thumbprint1, (byte) 0xAA); + byte[] encryptedKeyData1 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData1); + byte[] concatedData1 = new byte[thumbprint1.length + encryptedKeyData1.length]; + System.arraycopy(thumbprint1, 0, concatedData1, 0, thumbprint1.length); + System.arraycopy(encryptedKeyData1, 0, concatedData1, thumbprint1.length, encryptedKeyData1.length); + String encryptedKey1 = CryptoUtil.encodeToURLSafeBase64(concatedData1); + + // Second key with different thumbprint that won't match + byte[] thumbprint2 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + Arrays.fill(thumbprint2, (byte) 0xBB); + byte[] encryptedKeyData2 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData2); + byte[] concatedData2 = new byte[thumbprint2.length + encryptedKeyData2.length]; + System.arraycopy(thumbprint2, 0, concatedData2, 0, thumbprint2.length); + System.arraycopy(encryptedKeyData2, 0, concatedData2, thumbprint2.length, encryptedKeyData2.length); + String encryptedKey2 = CryptoUtil.encodeToURLSafeBase64(concatedData2); + + String encryptedKey = encryptedKey1 + "." + encryptedKey2; + + // Create keyAlias with thumbprint that matches first key + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint(org.bouncycastle.util.encoders.Hex.toHexString(thumbprint1).toUpperCase()); + List keyAliases = Collections.singletonList(keyAlias); + + when(dbHelper.getKeyAliases(eq("PUB_KEY_APP"), eq("REF1,REF2"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(keyAliases)); + when(keyManagerService.decryptSymmetricKey(any(SymmetricKeyRequestDto.class))) + .thenReturn(createSymmetricKeyResponse(CryptoUtil.encodeToURLSafeBase64(new byte[16]))); + when(dbHelper.getKeyAliases(eq("KERNEL"), eq("IDENTITY_CACHE"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ReEncryptRandomKeyResponseDto response = zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + + assertNotNull(response); + // Should match first key (thumbprint1), second key should be skipped (continue) + assertNotNull(response.getEncryptedKey()); + } + + @Test + public void testZkReEncryptRandomKeyBreakPath() throws Exception { + // Test the break path when keyAlias is found on second iteration (line 430-431) + byte[] thumbprint1 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + Arrays.fill(thumbprint1, (byte) 0xAA); + byte[] encryptedKeyData1 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData1); + byte[] concatedData1 = new byte[thumbprint1.length + encryptedKeyData1.length]; + System.arraycopy(thumbprint1, 0, concatedData1, 0, thumbprint1.length); + System.arraycopy(encryptedKeyData1, 0, concatedData1, thumbprint1.length, encryptedKeyData1.length); + String encryptedKey1 = CryptoUtil.encodeToURLSafeBase64(concatedData1); + + // Second key with matching thumbprint + byte[] thumbprint2 = new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]; + Arrays.fill(thumbprint2, (byte) 0xBB); + byte[] encryptedKeyData2 = new byte[256]; + new SecureRandom().nextBytes(encryptedKeyData2); + byte[] concatedData2 = new byte[thumbprint2.length + encryptedKeyData2.length]; + System.arraycopy(thumbprint2, 0, concatedData2, 0, thumbprint2.length); + System.arraycopy(encryptedKeyData2, 0, concatedData2, thumbprint2.length, encryptedKeyData2.length); + String encryptedKey2 = CryptoUtil.encodeToURLSafeBase64(concatedData2); + + String encryptedKey = encryptedKey1 + "." + encryptedKey2; + + // Create keyAlias with thumbprint that matches second key (not first) + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setCertThumbprint(org.bouncycastle.util.encoders.Hex.toHexString(thumbprint2).toUpperCase()); + List keyAliases = Collections.singletonList(keyAlias); + + when(dbHelper.getKeyAliases(eq("PUB_KEY_APP"), eq("REF1,REF2"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMapWithKeyAliases(keyAliases)); + when(keyManagerService.decryptSymmetricKey(any(SymmetricKeyRequestDto.class))) + .thenReturn(createSymmetricKeyResponse(CryptoUtil.encodeToURLSafeBase64(new byte[16]))); + when(dbHelper.getKeyAliases(eq("KERNEL"), eq("IDENTITY_CACHE"), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ReEncryptRandomKeyResponseDto response = zkCryptoManagerService.zkReEncryptRandomKey(encryptedKey); + + assertNotNull(response); + // Should match second key (thumbprint2), first key should be skipped + // (continue), then break + assertNotNull(response.getEncryptedKey()); + } + + @Test + public void testDoFinalBadPaddingException() { + // Test BadPaddingException in doFinal - use wrong key or corrupted data + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + + // Use corrupted encrypted key data that will cause BadPaddingException + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[15])); // Wrong length for AES + + try { + zkCryptoManagerService.zkEncrypt(requestDto); + org.junit.Assert.fail("Expected ZKKeyDerivationException"); + } catch (ZKKeyDerivationException e) { + // Expected + assertNotNull(e); + } + } + + @Test + public void testDoCipherOpsIllegalBlockSizeException() throws Exception { + // Test IllegalBlockSizeException in doCipherOps + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + // This should work normally - IllegalBlockSizeException is hard to trigger in + // GCM mode + // but the exception path is covered by other tests + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + assertNotNull(response); + } + + @Test + public void testDoCipherOpsInvalidAlgorithmParameterException() throws Exception { + // Test InvalidAlgorithmParameterException - use invalid nonce length + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + // Create encrypted data with invalid nonce length + byte[] indexBytes = ByteBuffer.allocate(4).putInt(0).array(); + byte[] invalidNonce = new byte[8]; // Wrong length (should be 12) + byte[] aad = new byte[ZKCryptoManagerConstants.GCM_AAD_LENGTH]; + new SecureRandom().nextBytes(invalidNonce); + new SecureRandom().nextBytes(aad); + + // Try to create valid encrypted data but with wrong structure + byte[] encryptedData = new byte[32]; + new SecureRandom().nextBytes(encryptedData); + + // Create combined data with wrong nonce length + byte[] combined = new byte[indexBytes.length + invalidNonce.length + aad.length + encryptedData.length]; + System.arraycopy(indexBytes, 0, combined, 0, indexBytes.length); + System.arraycopy(invalidNonce, 0, combined, indexBytes.length, invalidNonce.length); + System.arraycopy(aad, 0, combined, indexBytes.length + invalidNonce.length, aad.length); + System.arraycopy(encryptedData, 0, combined, indexBytes.length + invalidNonce.length + aad.length, + encryptedData.length); + + cryptoData.setValue(CryptoUtil.encodeToURLSafeBase64(combined)); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + when(dataEncryptKeystoreRepository.findKeyById(0)) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + // Use lenient stubbing since exception might be thrown before destoryKey is + // called + org.mockito.Mockito.lenient().doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + // This will fail when trying to decrypt with wrong nonce length + try { + zkCryptoManagerService.zkDecrypt(requestDto); + org.junit.Assert.fail("Expected ZKCryptoException"); + } catch (ZKCryptoException e) { + // Expected - InvalidAlgorithmParameterException wrapped + assertNotNull(e); + } + } + + @Test + public void testGetMasterKeyFromHSMWithNonNullKeyAlias() throws Exception { + // Test the path where keyAlias is not null (line 293-294) + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(keymanagerUtil.convertToCertificate(anyString())).thenReturn(mockCertificate); + when(keyStoreRepository.findByAlias(anyString())).thenReturn(createKeyStoreOptional()); + when(cryptomanagerUtil.getCertificateThumbprint(any(X509Certificate.class))) + .thenReturn(new byte[CryptomanagerConstant.THUMBPRINT_LENGTH]); + when(cryptomanagerUtil.concatCertThumbprint(any(byte[].class), any(byte[].class))) + .thenReturn(new byte[100]); + when(cryptoCore.asymmetricEncrypt(any(PublicKey.class), any(byte[].class))) + .thenReturn(new byte[256]); + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + // This should work and test the Objects.nonNull(keyAlias) path + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + assertNotNull(response); + verify(keyStore, times(1)).getSymmetricKey("master-alias"); + } + + @Test + public void testEncryptRandomKeyWithNullPubKeyRefId() throws Exception { + // Test when pubKeyRefId array element is null (though split won't produce null) + // But test the Objects.isNull check + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", ""); + + ZKCryptoRequestDto requestDto = new ZKCryptoRequestDto(); + requestDto.setId("12345"); + CryptoDataDto cryptoData = new CryptoDataDto(); + cryptoData.setIdentifier("name"); + cryptoData.setValue("John Doe"); + requestDto.setZkDataAttributes(Collections.singletonList(cryptoData)); + + List indexes = Arrays.asList(0); + when(dataEncryptKeystoreRepository.getIdsByKeyStatus(ZKCryptoManagerConstants.ACTIVE_STATUS)) + .thenReturn(indexes); + when(dataEncryptKeystoreRepository.findKeyById(anyInt())) + .thenReturn(Base64.getEncoder().encodeToString(new byte[16])); + + when(dbHelper.getKeyAliases(anyString(), anyString(), any(LocalDateTime.class))) + .thenReturn(createKeyAliasMap("master-alias")); + when(keyStore.getSymmetricKey(anyString())).thenReturn(masterKey); + + doNothing().when(keymanagerUtil).destoryKey(any(SecretKey.class)); + + ZKCryptoResponseDto response = zkCryptoManagerService.zkEncrypt(requestDto); + + assertNotNull(response); + // When pubKeyReferenceId is empty, encryptedRandomKey should be empty + assertEquals("", response.getEncryptedRandomKey()); + + // Restore + ReflectionTestUtils.setField(zkCryptoManagerService, "pubKeyReferenceId", "REF1,REF2"); + } + + // ==================== Helper Methods ==================== + + private Map> createKeyAliasMap(String alias) { + Map> keyAliasMap = new HashMap<>(); + KeyAlias keyAlias = new KeyAlias(); + keyAlias.setAlias(alias); + keyAliasMap.put(KeymanagerConstant.CURRENTKEYALIAS, Collections.singletonList(keyAlias)); + return keyAliasMap; + } + + private Map> createKeyAliasMapWithKeyAliases(List keyAliases) { + Map> keyAliasMap = new HashMap<>(); + keyAliasMap.put(KeymanagerConstant.KEYALIAS, keyAliases); + return keyAliasMap; + } + + private Map> createEmptyKeyAliasMap() { + Map> keyAliasMap = new HashMap<>(); + keyAliasMap.put(KeymanagerConstant.CURRENTKEYALIAS, Collections.emptyList()); + return keyAliasMap; + } + + private Optional createKeyStoreOptional() { + KeyStore keyStore = new KeyStore(); + keyStore.setCertificateData("cert-data"); + return Optional.of(keyStore); + } + + private SymmetricKeyResponseDto createSymmetricKeyResponse(String symmetricKey) { + SymmetricKeyResponseDto response = new SymmetricKeyResponseDto(); + response.setSymmetricKey(symmetricKey); + return response; + } +}