Skip to content

Commit

Permalink
RCF-248: Added jwtVerify implementation (#215)
Browse files Browse the repository at this point in the history
* RCF-226: Changed consent screen fetch logic

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>

* RCF-226: Changed decryption logic

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>

* Removed unused code

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>

* Removed logs

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>

* RCF-248: Modified as per review comments

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>

---------

Signed-off-by: Himaja Dhanyamraju <Dhanyamraju.Himaja2@mindtree.com>
  • Loading branch information
HimajaDhanyamraju2 authored Nov 30, 2023
1 parent ae3880f commit cb69ac3
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ public Context provideApplicationContext() {

@Singleton
@Provides
public ClientCryptoManagerService provideClientCryptoManagerService() {
return new LocalClientCryptoServiceImpl(appContext);
public ClientCryptoManagerService provideClientCryptoManagerService(CertificateManagerService certificateManagerService) {
return new LocalClientCryptoServiceImpl(appContext, certificateManagerService);
}

@Singleton
Expand Down
9 changes: 8 additions & 1 deletion android/keymanager/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,16 @@ dependencies {
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'

implementation 'org.bouncycastle:bcprov-jdk15on:1.59'
// https://mvnrepository.com/artifact/com.madgag.spongycastle/core
implementation 'org.bouncycastle:bcpkix-jdk15on:1.47'

implementation 'com.madgag.spongycastle:core:1.58.0.0'

implementation 'com.fasterxml.jackson.core:jackson-core:2.13.2'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3'
implementation 'com.fasterxml.jackson.module:jackson-module-afterburner:2.13.3'
implementation 'org.bitbucket.b_c:jose4j:0.6.5'

}

sonarqube {
Expand Down
3 changes: 3 additions & 0 deletions android/keymanager/src/main/assets/config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ mosip.kernel.crypto.sign-algorithm-padding-scheme=PKCS1
mosip.kernel.certificate.sign.algorithm=SHA256withRSA
#CA certificate allowed partner domains
mosip.kernel.partner.allowed.domains=DEVICE

mosip.sign.applicationid=KERNEL
mosip.sign.refid=SIGN
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.mosip.registration.keymanager.dto;

import javax.validation.constraints.NotBlank;

import lombok.Data;

/**
* Partner Certificates Verify Trust Request DTO.
*/
@Data
public class CertificateTrustRequestDto {

/**
* Certificate Data of Partner.
*/
@NotBlank(message = "Invalid Request")
private String certificateData;

/**
* Partner Type.
*/
@NotBlank(message = "Invalid Request")
private String partnerDomain;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.mosip.registration.keymanager.dto;

import lombok.Data;

/**
* DTO class for certificate verification response.
*/
@Data
public class CertificateTrustResponseDto {

/**
* Status of certificate verification.
*/
private Boolean status;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.mosip.registration.keymanager.dto;

import javax.validation.constraints.NotBlank;

import lombok.Data;

@Data
public class JWTSignatureVerifyRequestDto {

@NotBlank
private String jwtSignatureData;

private String actualData;

/**
* Application id of decrypting module
*/
private String applicationId;

/**
* Refrence Id
*/
private String referenceId;

/**
* Certificate to be use in JWT Signature verification.
*/
private String certificateData;

/**
* Flag to validate against trust store.
*/
private Boolean validateTrust;

/**
* Domain to be considered to validate trust store
*/
private String domain;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.mosip.registration.keymanager.dto;

import lombok.Data;

@Data
public class JWTSignatureVerifyResponseDto {

/**
* The Signature verification status.
*/
private boolean signatureValid;

/**
* The Signature validation message.
*/
private String message;

/**
* The Trust validation status.
*/
private String trustValid;

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import io.mosip.registration.keymanager.dto.CACertificateRequestDto;
import io.mosip.registration.keymanager.dto.CACertificateResponseDto;
import io.mosip.registration.keymanager.dto.CertificateRequestDto;
import io.mosip.registration.keymanager.dto.CertificateTrustRequestDto;
import io.mosip.registration.keymanager.dto.CertificateTrustResponseDto;
import io.mosip.registration.keymanager.exception.KeymanagerServiceException;
import io.mosip.registration.keymanager.repository.KeyStoreRepository;
import io.mosip.registration.keymanager.spi.CertificateManagerService;
Expand Down Expand Up @@ -54,6 +56,7 @@ public CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCe

String certSubject = CertificateManagerUtil.formatCertificateDN(reqX509Cert.getSubjectX500Principal().getName());
String certIssuer = CertificateManagerUtil.formatCertificateDN(reqX509Cert.getIssuerX500Principal().getName());

boolean selfSigned = CertificateManagerUtil.isSelfSignedCertificate(reqX509Cert);

if (selfSigned) {
Expand Down Expand Up @@ -81,6 +84,26 @@ public CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCe
return responseDto;
}

public CertificateTrustResponseDto verifyCertificateTrust(CertificateTrustRequestDto certificateTrustRequestDto) {
Log.i(TAG, "Certificate Trust Path Validation.");

String certificateData = certificateTrustRequestDto.getCertificateData();
if (!CertificateManagerUtil.isValidCertificateData(certificateData)) {
Log.e(TAG, "Invalid Certificate Data provided to verify partner certificate trust.");
throw new KeymanagerServiceException(KeyManagerErrorCode.INVALID_CERTIFICATE.getErrorCode(),
KeyManagerErrorCode.INVALID_CERTIFICATE.getErrorMessage());
}
X509Certificate reqX509Cert = (X509Certificate) CertificateManagerUtil.convertToCertificate(certificateData);
String partnerDomain = validateAllowedDomains(certificateTrustRequestDto.getPartnerDomain());

Log.i(TAG, "Certificate Trust Path Validation for domain: " + partnerDomain);

boolean certValid = validateCertificatePath(reqX509Cert, partnerDomain);
CertificateTrustResponseDto responseDto = new CertificateTrustResponseDto();
responseDto.setStatus(certValid);
return responseDto;
}

@Override
public void uploadOtherDomainCertificate(CertificateRequestDto certificateRequestDto) {
Log.i(TAG, "Uploading other domain Certificate.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,22 @@
import android.security.keystore.KeyProperties;
import android.util.Log;
import io.mosip.registration.keymanager.dto.*;
import io.mosip.registration.keymanager.exception.KeymanagerServiceException;
import io.mosip.registration.keymanager.spi.CertificateManagerService;
import io.mosip.registration.keymanager.spi.ClientCryptoManagerService;
import io.mosip.registration.keymanager.util.CertificateManagerUtil;
import io.mosip.registration.keymanager.util.ConfigService;
import io.mosip.registration.keymanager.util.CryptoUtil;
import io.mosip.registration.keymanager.util.JsonUtils;
import io.mosip.registration.keymanager.util.KeyManagerConstant;
import io.mosip.registration.keymanager.util.KeyManagerErrorCode;
import java.security.cert.Certificate;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwx.CompactSerializer;
import org.json.JSONObject;
import org.spongycastle.crypto.InvalidCipherTextException;
import org.spongycastle.crypto.digests.SHA256Digest;
Expand All @@ -32,13 +42,15 @@
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import java.util.Objects;
import static io.mosip.registration.keymanager.util.KeyManagerConstant.*;


Expand Down Expand Up @@ -76,17 +88,21 @@ public class LocalClientCryptoServiceImpl implements ClientCryptoManagerService

private static String CERTIFICATE_SIGN_ALGORITHM;

private static String SIGN_APPLICATION_ID;
private static String SIGN_REFERENCE_ID;

private static SecureRandom secureRandom = new SecureRandom();
private KeyStore keyStore = null;

private CertificateManagerService certificateManagerService;

@Inject
public LocalClientCryptoServiceImpl(Context appContext) {
public LocalClientCryptoServiceImpl(Context appContext, CertificateManagerService certificateManagerService) {
Log.i(TAG, "LocalClientCryptoServiceImpl: Constructor call successful");
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
initLocalClientCryptoService(appContext);
this.certificateManagerService = certificateManagerService;
} catch (Exception e) {
Log.e(TAG, "LocalClientCryptoServiceImpl: Failed Initialization", e);
}
Expand Down Expand Up @@ -122,6 +138,8 @@ private void initializeClientSecurity() {
ConfigService.getProperty("mosip.kernel.crypto.gcm-tag-length",context));
KEYGEN_ASYMMETRIC_ALGO_SIGN_PAD = ConfigService.getProperty("mosip.kernel.crypto.sign-algorithm-padding-scheme",context);
CERTIFICATE_SIGN_ALGORITHM = ConfigService.getProperty("mosip.kernel.certificate.sign.algorithm",context);
SIGN_APPLICATION_ID = ConfigService.getProperty("mosip.sign.applicationid", context);
SIGN_REFERENCE_ID = ConfigService.getProperty("mosip.sign.refid", context);
}

private void genSignKey() {
Expand Down Expand Up @@ -231,6 +249,125 @@ public SignVerifyResponseDto verifySign(SignVerifyRequestDto signVerifyRequestDt
return null;
}

public JWTSignatureVerifyResponseDto jwtVerify(JWTSignatureVerifyRequestDto jwtVerifyRequestDto) throws Exception {
String signedData = jwtVerifyRequestDto.getJwtSignatureData();
if (!CertificateManagerUtil.isDataValid(signedData)) {
Log.e(TAG,"Provided Signed Data value is invalid.");
throw new Exception();
}

String encodedActualData = CertificateManagerUtil.isDataValid(jwtVerifyRequestDto.getActualData())
? jwtVerifyRequestDto.getActualData() : null;

String reqCertData = CertificateManagerUtil.isValidCertificateData(jwtVerifyRequestDto.getCertificateData())
? jwtVerifyRequestDto.getCertificateData(): null;
String applicationId = jwtVerifyRequestDto.getApplicationId();
String referenceId = jwtVerifyRequestDto.getReferenceId();
if (!CertificateManagerUtil.isValidApplicationId(applicationId)) {
applicationId = SIGN_APPLICATION_ID;
referenceId = SIGN_REFERENCE_ID;
}

String[] jwtTokens = signedData.split(PERIOD, -1);

boolean signatureValid = false;
Certificate certToVerify = certificateExistsInHeader(jwtTokens[0]);
if (Objects.nonNull(certToVerify)){
signatureValid = verifySignature(jwtTokens, encodedActualData, certToVerify);
} else {
Certificate reqCertToVerify = getCertificateToVerify(reqCertData, applicationId, referenceId);
signatureValid = verifySignature(jwtTokens, encodedActualData, reqCertToVerify);
}

JWTSignatureVerifyResponseDto responseDto = new JWTSignatureVerifyResponseDto();
responseDto.setSignatureValid(signatureValid);
responseDto.setMessage(signatureValid ? KeyManagerConstant.VALIDATION_SUCCESSFUL : KeyManagerConstant.VALIDATION_FAILED);
responseDto.setTrustValid(validateTrust(jwtVerifyRequestDto, certToVerify, reqCertData));
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 CertificateManagerUtil.convertToCertificate(reqCertData);

// 3rd precedence to consider certificate to use in signature verification. (based on AppId & RefId)
String certificateData = certificateManagerService.getCertificate(applicationId, referenceId);
return CertificateManagerUtil.convertToCertificate(certificateData);
}

private boolean verifySignature(String[] jwtTokens, String actualData, Certificate certToVerify) {
JsonWebSignature jws = new JsonWebSignature();
try {
boolean validCert = CertificateManagerUtil.isCertificateDatesValid((X509Certificate) certToVerify);
if (!validCert) {
Log.e(TAG, "Error certificate dates are not valid.");
throw new KeymanagerServiceException(KeyManagerErrorCode.CERT_NOT_VALID.getErrorCode(),
KeyManagerErrorCode.CERT_NOT_VALID.getErrorMessage());
}

PublicKey publicKey = certToVerify.getPublicKey();
if (Objects.nonNull(actualData))
jwtTokens[1] = actualData;

jws.setCompactSerialization(CompactSerializer.serialize(jwtTokens));
if (Objects.nonNull(publicKey))
jws.setKey(publicKey);

return jws.verifySignature();
} catch (Exception e) {
Log.e(TAG, "Provided Signed Data value is invalid.");
throw new KeymanagerServiceException(KeyManagerErrorCode.VERIFY_ERROR.getErrorCode(),
KeyManagerErrorCode.VERIFY_ERROR.getErrorMessage(), e);
}
}

private Certificate certificateExistsInHeader(String jwtHeader) {
String jwtTokenHeader = new String(CryptoUtil.decodeBase64(jwtHeader));
Map<String, Object> jwtTokenHeadersMap = JsonUtils.jsonStringToJavaMap(jwtTokenHeader);
if (jwtTokenHeadersMap == null){
Log.e(TAG, "Provided Signed Data value is invalid.");
return null;
}
// 1st precedence to consider certificate to use in signature verification (JWT Header).
if (jwtTokenHeadersMap.containsKey(JWT_HEADER_CERT_KEY)) {
Log.i(TAG, "Certificate found in JWT Header.");
List<String> certList = (List<String>) jwtTokenHeadersMap.get(JWT_HEADER_CERT_KEY);
return CertificateManagerUtil.convertToCertificate(Base64.decodeBase64(certList.get(0)));
}
Log.i(TAG, "Certificate not found in JWT Header.");
return null;
}

private String validateTrust(JWTSignatureVerifyRequestDto jwtVerifyRequestDto, Certificate headerCertificate, String reqCertData) {
if (jwtVerifyRequestDto.getValidateTrust() == null || !jwtVerifyRequestDto.getValidateTrust()) {
return KeyManagerConstant.TRUST_NOT_VERIFIED;
}

String domain = jwtVerifyRequestDto.getDomain();
if(!CertificateManagerUtil.isDataValid(domain))
return KeyManagerConstant.TRUST_NOT_VERIFIED_NO_DOMAIN;

String certData = null;
if (Objects.nonNull(headerCertificate)) {
certData = CertificateManagerUtil.getPEMFormatedData(headerCertificate);
}
String trustCertData = certData == null ? reqCertData : certData;

if (trustCertData == null)
return KeyManagerConstant.TRUST_NOT_VERIFIED;

CertificateTrustRequestDto trustRequestDto = new CertificateTrustRequestDto();
trustRequestDto.setCertificateData(trustCertData);
trustRequestDto.setPartnerDomain(domain);
CertificateTrustResponseDto responseDto = certificateManagerService.verifyCertificateTrust(trustRequestDto);

if (responseDto.getStatus()){
return KeyManagerConstant.TRUST_VALID;
}
return KeyManagerConstant.TRUST_NOT_VALID;
}

@Override
public CryptoResponseDto encrypt(CryptoRequestDto cryptoRequestDto) {
CryptoResponseDto cryptoResponseDto = new CryptoResponseDto();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import io.mosip.registration.keymanager.dto.CACertificateRequestDto;
import io.mosip.registration.keymanager.dto.CACertificateResponseDto;
import io.mosip.registration.keymanager.dto.CertificateRequestDto;
import io.mosip.registration.keymanager.dto.CertificateTrustRequestDto;
import io.mosip.registration.keymanager.dto.CertificateTrustResponseDto;

public interface CertificateManagerService {

CACertificateResponseDto uploadCACertificate(CACertificateRequestDto caCertificateRequestDto);
void uploadOtherDomainCertificate(CertificateRequestDto certificateRequestDto);
String getCertificate(String applicationId, String referenceId);
CertificateTrustResponseDto verifyCertificateTrust(CertificateTrustRequestDto certificateTrustRequestDto);
}
Loading

0 comments on commit cb69ac3

Please sign in to comment.