diff --git a/sunbird-rc-certify-integration-impl/pom.xml b/sunbird-rc-certify-integration-impl/pom.xml index c045ea8..3728988 100644 --- a/sunbird-rc-certify-integration-impl/pom.xml +++ b/sunbird-rc-certify-integration-impl/pom.xml @@ -49,6 +49,7 @@ 3.0.1 0.8.11 3.6.3 + 1.3.0-beta.1 @@ -58,6 +59,29 @@ provided + + io.mosip.esignet + esignet-core + 1.4.1 + + + * + * + + + + + io.mosip.esignet + esignet-integration-api + 1.4.1 + + + * + * + + + + org.projectlombok lombok @@ -65,6 +89,24 @@ 1.18.30 + + io.mosip.kernel + kernel-keymanager-service + ${kernel-keymanager-service.version} + provided + lib + + + org.springframework.cloud + spring-cloud-starter-sleuth + + + org.springframework.security + spring-security-test + + + + org.apache.velocity velocity diff --git a/sunbird-rc-certify-integration-impl/src/main/java/io/mosip/certify/sunbirdrc/integration/service/SunbirdRCVCIssuancePlugin.java b/sunbird-rc-certify-integration-impl/src/main/java/io/mosip/certify/sunbirdrc/integration/service/SunbirdRCVCIssuancePlugin.java index 68b51df..0bc7a70 100644 --- a/sunbird-rc-certify-integration-impl/src/main/java/io/mosip/certify/sunbirdrc/integration/service/SunbirdRCVCIssuancePlugin.java +++ b/sunbird-rc-certify-integration-impl/src/main/java/io/mosip/certify/sunbirdrc/integration/service/SunbirdRCVCIssuancePlugin.java @@ -37,6 +37,11 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.cache.CacheManager; +import org.springframework.http.HttpHeaders; + +import io.mosip.esignet.core.dto.OIDCTransaction; + import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; @@ -46,8 +51,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - - +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.io.PrintWriter; +import java.util.Base64; +import java.util.Objects; + +import java.time.ZoneOffset; +import java.time.LocalDateTime; + +import javax.crypto.Cipher; +import java.security.Key; +import io.mosip.kernel.core.keymanager.spi.KeyStore; +import io.mosip.kernel.keymanagerservice.constant.KeymanagerConstant; +import io.mosip.kernel.keymanagerservice.entity.KeyAlias; +import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; @ConditionalOnProperty(value = "mosip.certify.integration.vci-plugin", havingValue = "SunbirdRCVCIssuancePlugin") @Component @@ -74,23 +92,55 @@ public class SunbirdRCVCIssuancePlugin implements VCIssuancePlugin { private final String FILTER_EQUALS_OPERATOR = "eq"; + public static final String AES_CIPHER_FAILED = "aes_cipher_failed"; + + public static final String OIDC_SERVICE_APP_ID = "CERTIFY_SERVICE"; + private final String PSUT_TOKEN="psut"; + public static final String NO_UNIQUE_ALIAS = "no_unique_alias"; + + private Base64.Decoder urlSafeDecoder = Base64.getUrlDecoder(); + @Autowired Environment env; @Autowired ObjectMapper mapper; + @Autowired + private KeyStore keyStore; + @Autowired private RestTemplate restTemplate; + @Autowired + private KeymanagerDBHelper dbHelper; + @Value("${mosip.certify.vciplugin.sunbird-rc.issue-credential-url}") String issueCredentialUrl; + @Value("${io.credissuer.com.get-credential-url}") + String getCredentialUrl; + + @Value("${mosip.certify.cache.secure.individual-id}") + private boolean secureIndividualId; + + @Value("${mosip.certify.cache.store.individual-id}") + private boolean storeIndividualId; + + @Value("${mosip.certify.cache.security.secretkey.reference-id}") + private String cacheSecretKeyRefId; + @Value("${mosip.certify.vciplugin.sunbird-rc.enable-psut-based-registry-search:false}") private boolean enablePSUTBasedRegistrySearch; + @Value("${mosip.esignet.authenticator.credissuer.bearer-token}") + private String credIssuerBeaerToken; + + @Value("${mosip.certify.cache.security.algorithm-name}") + private String aesECBTransformation; + @Value("#{'${mosip.certify.vciplugin.sunbird-rc.supported-credential-types}'.split(',')}") List supportedCredentialTypes; @@ -100,6 +150,12 @@ public class SunbirdRCVCIssuancePlugin implements VCIssuancePlugin { private VelocityEngine vEngine; + @Autowired + CacheManager cacheManager; + @Value("${mosip.esignet.ida.vci-user-info-cache}") + private String userinfoCache; + private static final String ACCESS_TOKEN_HASH = "accessTokenHash"; + @PostConstruct public void initialize() throws VCIExchangeException { @@ -121,57 +177,108 @@ public InputStream getResourceStream(String name) throws ResourceNotFoundExcepti vEngine.init(); //Validate all the supported VC for (String credentialType : supportedCredentialTypes) { - validateAndCachePropertiesForCredentialType(credentialType.trim()); + //validateAndCachePropertiesForCredentialType(credentialType.trim()); } } @Override - public VCResult getVerifiableCredentialWithLinkedDataProof(VCRequestDto vcRequestDto, String holderId, Map identityDetails) throws VCIExchangeException { - if (vcRequestDto == null || vcRequestDto.getType() == null) { - throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); + public VCResult getVerifiableCredentialWithLinkedDataProof(VCRequestDto vcRequestDto, String holderId, + Map identityDetails) throws VCIExchangeException { + JsonLDObject vcJsonLdObject = null; + String individualId = null; + try { + OIDCTransaction transaction = getOAuthTransaction(identityDetails.get(ACCESS_TOKEN_HASH).toString()); + individualId = getIndividualId(transaction.getIndividualId()); + System.out.println("individualId : "+individualId); + } catch (Exception e) { + throw new RuntimeException(e); } - List types = vcRequestDto.getType(); - if (types.isEmpty() || !types.get(0).equals("VerifiableCredential")) { - log.error("Invalid request: first item in type is not VerifiableCredential"); - throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); + try { + VCResult vcResult = new VCResult(); + Map vcResponseMap = fetchCredential(getCredentialUrl + individualId); + vcJsonLdObject = JsonLDObject.fromJsonObject((Map)vcResponseMap.get(CREDENTIAL_OBJECT_KEY)); + vcResult.setCredential(vcJsonLdObject); + vcResult.setFormat("ldp_vc"); + return vcResult; + } catch (Exception e) { + log.error("Failed to build credissuer response", e); } - types.remove(0); - String requestedCredentialType = String.join("-", types); - //Check if the key is in the supported-credential-types - if (!supportedCredentialTypes.contains(requestedCredentialType)) { - log.error("Credential type is not supported"); + throw new VCIExchangeException(); + } + + private Map fetchCredential(String entityUrl) throws VCIExchangeException { + RequestEntity requestEntity = RequestEntity + .get(UriComponentsBuilder.fromUriString(entityUrl).build().toUri()) + .header("Authorization", "Bearer " + credIssuerBeaerToken) // Set the headers + .build(); + ResponseEntity> responseEntity = restTemplate.exchange(requestEntity, + new ParameterizedTypeReference>() {}); + if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) { + return responseEntity.getBody(); + }else { + log.error("Credissuer service is not running. Status Code: " , responseEntity.getStatusCode()); throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); } - //Validate context of vcrequestdto with template - List contextList=vcRequestDto.getContext(); - for(String supportedType:supportedCredentialTypes){ - Template template= credentialTypeTemplates.get(supportedType); - validateContextUrl(template,contextList); + } + + @SuppressWarnings("unchecked") + public OIDCTransaction getOAuthTransaction(String accessTokenHash) throws Exception { + if (cacheManager.getCache(userinfoCache) != null) { + try { + OIDCTransaction oIDCTransaction = cacheManager.getCache(userinfoCache).get(accessTokenHash, OIDCTransaction.class); //NOSONAR getCache() will not be returning null here. + return oIDCTransaction; + } catch (Exception e) { + log.error("Exception occured while fetching the cache", e); + throw new Exception(e); + } + } + throw new Exception("cache_missing>>>>>"); + } - String registrySearchField = (identityDetails.containsKey("sub")) ? (String) identityDetails.get("sub") : null; - if (registrySearchField == null) { - log.error("Invalid request: registrySearchField is null"); - throw new VCIExchangeException(ErrorConstants.VCI_EXCHANGE_FAILED); + protected String getIndividualId(String encryptedIndividualId) throws Exception { + if (!storeIndividualId) + return null; + return secureIndividualId ? decryptIndividualId(encryptedIndividualId) : encryptedIndividualId; + } + + private String decryptIndividualId(String encryptedIndividualId) throws Exception { + try { + Cipher cipher = Cipher.getInstance(aesECBTransformation); + byte[] decodedBytes = b64Decode(encryptedIndividualId); + cipher.init(Cipher.DECRYPT_MODE, getSecretKeyFromHSM()); + return new String(cipher.doFinal(decodedBytes, 0, decodedBytes.length)); + } catch (Exception e) { + log.error("Error Cipher Operations of provided secret data.", e); + throw new Exception(AES_CIPHER_FAILED); } - Map responseRegistryMap; - if(enablePSUTBasedRegistrySearch){ - String registrySearchUrl=credentialTypeConfigMap.get(requestedCredentialType).get(REGISTRY_SEARCH_URL); - responseRegistryMap= fetchRegistryObjectByPSUT(registrySearchUrl,registrySearchField); - }else { - String registryUrl=credentialTypeConfigMap.get(requestedCredentialType).get(REGISTRY_GET_URL); - responseRegistryMap =fetchRegistryObject(registryUrl+ registrySearchField); + } + + private Key getSecretKeyFromHSM() throws Exception { + String keyAlias = getKeyAlias(OIDC_SERVICE_APP_ID, cacheSecretKeyRefId); + if (Objects.nonNull(keyAlias)) { + return keyStore.getSymmetricKey(keyAlias); } - Map credentialRequestMap = createCredentialIssueRequest(requestedCredentialType, responseRegistryMap,vcRequestDto,holderId); - Map vcResponseMap =sendCredentialIssueRequest(credentialRequestMap); - - VCResult vcResult = new VCResult(); - JsonLDObject vcJsonLdObject = JsonLDObject.fromJsonObject((Map)vcResponseMap.get(CREDENTIAL_OBJECT_KEY)); - vcResult.setCredential(vcJsonLdObject); - vcResult.setFormat(LINKED_DATA_PROOF_VC_FORMAT); - return vcResult; + throw new Exception(NO_UNIQUE_ALIAS); } + private String getKeyAlias(String keyAppId, String keyRefId) throws Exception { + Map> keyAliasMap = dbHelper.getKeyAliases(keyAppId, keyRefId, + LocalDateTime.now(ZoneOffset.UTC)); + List currentKeyAliases = keyAliasMap.get(KeymanagerConstant.CURRENTKEYALIAS); + if (!currentKeyAliases.isEmpty() && currentKeyAliases.size() == 1) { + return currentKeyAliases.get(0).getAlias(); + } + log.error("CurrentKeyAlias is not unique. KeyAlias count: {}", currentKeyAliases.size()); + throw new Exception(NO_UNIQUE_ALIAS); + } + + + + private byte[] b64Decode(String value) { + return urlSafeDecoder.decode(value); + }; + @Override public VCResult getVerifiableCredential(VCRequestDto vcRequestDto, String holderId, Map identityDetails) throws VCIExchangeException { throw new VCIExchangeException(ErrorConstants.NOT_IMPLEMENTED);