All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.mosip.certify.mosipid.integration.service.IdaVCIssuancePluginImpl Maven / Gradle / Ivy

There is a newer version: 0.2.0
Show newest version
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */
package io.mosip.certify.mosipid.integration.service;

import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

import javax.crypto.Cipher;

import io.mosip.certify.api.dto.VCRequestDto;
import io.mosip.certify.api.dto.VCResult;
import io.mosip.certify.api.spi.VCIssuancePlugin;
import io.mosip.certify.api.exception.VCIExchangeException;
import io.mosip.certify.api.util.ErrorConstants;
import io.mosip.certify.mosipid.integration.dto.CredentialDefinitionDTO;
import io.mosip.certify.mosipid.integration.dto.IdaResponseWrapper;
import io.mosip.certify.mosipid.integration.dto.IdaVcExchangeRequest;
import io.mosip.certify.mosipid.integration.dto.IdaVcExchangeResponse;
import io.mosip.certify.mosipid.integration.helper.VCITransactionHelper;
import io.mosip.esignet.core.dto.OIDCTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import com.fasterxml.jackson.databind.ObjectMapper;

import foundation.identity.jsonld.JsonLDObject;
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;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
@ConditionalOnProperty(value = "mosip.certify.integration.vci-plugin", havingValue = "IdaVCIssuancePluginImpl")
public class IdaVCIssuancePluginImpl implements VCIssuancePlugin {
	private static final String CLIENT_ID = "client_id";
	private static final String ACCESS_TOKEN_HASH = "accessTokenHash";
	public static final String SIGNATURE_HEADER_NAME = "signature";
	public static final String AUTHORIZATION_HEADER_NAME = "Authorization";
	public static final String OIDC_SERVICE_APP_ID = "CERTIFY_SERVICE";
	public static final String AES_CIPHER_FAILED = "aes_cipher_failed";
	public static final String NO_UNIQUE_ALIAS = "no_unique_alias";

	@Autowired
	private ObjectMapper objectMapper;

	@Autowired
	private RestTemplate restTemplate;

	@Autowired
	HelperService helperService;

	@Autowired
	private KeyStore keyStore;

	@Autowired
	private KeymanagerDBHelper dbHelper;

	@Autowired
	VCITransactionHelper vciTransactionHelper;

	@Value("${mosip.certify.ida.vci-exchange-url}")
	private String vciExchangeUrl;

	@Value("${mosip.certify.ida.vci-exchange-id}")
	private String vciExchangeId;

	@Value("${mosip.certify.ida.vci-exchange-version}")
	private String vciExchangeVersion;

	@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.algorithm-name}")
	private String aesECBTransformation;

	@Value("${mosip.certify.cache.security.secretkey.reference-id}")
	private String cacheSecretKeyRefId;

	private Base64.Decoder urlSafeDecoder = Base64.getUrlDecoder();


	@Override
	public VCResult getVerifiableCredentialWithLinkedDataProof(VCRequestDto vcRequestDto, String holderId,
																			 Map identityDetails) throws VCIExchangeException {
		log.info("Started to created the VCIssuance");
		try {
			OIDCTransaction transaction = vciTransactionHelper
					.getOAuthTransaction(identityDetails.get(ACCESS_TOKEN_HASH).toString());
			String individualId = getIndividualId(transaction.getIndividualId());
			IdaVcExchangeRequest idaVciExchangeRequest = new IdaVcExchangeRequest();
			CredentialDefinitionDTO vciCred = new CredentialDefinitionDTO();
			idaVciExchangeRequest.setId(vciExchangeId);// Configuration
			idaVciExchangeRequest.setVersion(vciExchangeVersion);// Configuration
			idaVciExchangeRequest.setRequestTime(HelperService.getUTCDateTime());
			idaVciExchangeRequest.setTransactionID(transaction.getAuthTransactionId());// Cache input
			idaVciExchangeRequest.setVcAuthToken(transaction.getKycToken()); // Cache input
			idaVciExchangeRequest.setIndividualId(individualId);
			idaVciExchangeRequest.setCredSubjectId(holderId);
			idaVciExchangeRequest.setVcFormat(vcRequestDto.getFormat());
			idaVciExchangeRequest.setLocales(convertLangCodesToISO3LanguageCodes(transaction.getClaimsLocales()));
			vciCred.setCredentialSubject(vcRequestDto.getCredentialSubject());
			vciCred.setType(vcRequestDto.getType());
			vciCred.setContext(vcRequestDto.getContext());
			idaVciExchangeRequest.setCredentialsDefinition(vciCred);

			String requestBody = objectMapper.writeValueAsString(idaVciExchangeRequest);
			RequestEntity requestEntity = RequestEntity
					.post(UriComponentsBuilder.fromUriString(vciExchangeUrl)
							.pathSegment(transaction.getRelyingPartyId(),
									identityDetails.get(CLIENT_ID).toString())
							.build().toUri())
					.contentType(MediaType.APPLICATION_JSON_UTF8)
					.header(SIGNATURE_HEADER_NAME, helperService.getRequestSignature(requestBody))
					.header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_NAME).body(requestBody);

			ResponseEntity>> responseEntity = restTemplate.exchange(
					requestEntity, new ParameterizedTypeReference>>() {});
			if (responseEntity.getStatusCode().is2xxSuccessful() && responseEntity.getBody() != null) {
				IdaResponseWrapper> responseWrapper = responseEntity.getBody();
				if (responseWrapper != null && responseWrapper.getResponse() != null)
				{
					VCResult vCResult = new VCResult();
					vCResult.setCredential(responseWrapper.getResponse().getVerifiableCredentials());
					vCResult.setFormat(vcRequestDto.getFormat());
					return vCResult;
				}
				log.error("Errors in response received from IDA VCI Exchange: {}", responseWrapper.getErrors());  //NOSONAR responseWrapper is already evaluated to be not null
				throw new VCIExchangeException(CollectionUtils.isEmpty(responseWrapper.getErrors()) ?
						ErrorConstants.VCI_EXCHANGE_FAILED : responseWrapper.getErrors().get(0).getErrorCode());
			}
			log.error("Error response received from IDA (VCI-exchange) with status : {}", responseEntity.getStatusCode());
		} catch (VCIExchangeException e) { throw e; } catch (Exception e) {
			log.error("IDA Vci-exchange failed ", e);
		}
		throw new VCIExchangeException();
	}

	@Override
	public VCResult getVerifiableCredential(VCRequestDto vcRequestDto, String holderId,
													Map identityDetails) throws VCIExchangeException {
		throw new VCIExchangeException(ErrorConstants.NOT_IMPLEMENTED);
	}

	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);
		}
	}

	private Key getSecretKeyFromHSM() throws Exception {
		String keyAlias = getKeyAlias(OIDC_SERVICE_APP_ID, cacheSecretKeyRefId);
		if (Objects.nonNull(keyAlias)) {
			return keyStore.getSymmetricKey(keyAlias);
		}
		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);
	};

	//Converts an array of two-letter language codes to their corresponding ISO 639-2/T language codes.
	private List convertLangCodesToISO3LanguageCodes(String[] langCodes) {
		if(langCodes == null || langCodes.length == 0 || (langCodes.length == 1 && langCodes[0].isEmpty()))
			return List.of();
		return Arrays.stream(langCodes)
				.map(langCode -> {
					try {
						return new Locale(langCode).getISO3Language();
					} catch (MissingResourceException ex) {}
					return null;
				})
				.filter(Objects::nonNull)
				.collect(Collectors.toList());
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy