All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.mosip.certify.services.VCIssuanceServiceImpl Maven / Gradle / Ivy
/*
* 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.services;
import foundation.identity.jsonld.JsonLDObject;
import io.mosip.certify.api.dto.VCRequestDto;
import io.mosip.certify.api.dto.VCResult;
import io.mosip.certify.api.exception.VCIExchangeException;
import io.mosip.certify.api.spi.AuditPlugin;
import io.mosip.certify.api.spi.VCIssuancePlugin;
import io.mosip.certify.api.util.Action;
import io.mosip.certify.api.util.ActionStatus;
import io.mosip.certify.core.dto.CredentialMetadata;
import io.mosip.certify.core.dto.CredentialRequest;
import io.mosip.certify.core.dto.CredentialResponse;
import io.mosip.certify.core.dto.ParsedAccessToken;
import io.mosip.certify.core.dto.VCIssuanceTransaction;
import io.mosip.certify.core.constants.Constants;
import io.mosip.certify.core.constants.ErrorConstants;
import io.mosip.certify.core.exception.CertifyException;
import io.mosip.certify.core.exception.InvalidRequestException;
import io.mosip.certify.core.exception.NotAuthenticatedException;
import io.mosip.certify.core.spi.VCIssuanceService;
import io.mosip.certify.core.util.AuditHelper;
import io.mosip.certify.core.util.SecurityHelperService;
import io.mosip.certify.exception.InvalidNonceException;
import io.mosip.certify.proof.ProofValidator;
import io.mosip.certify.proof.ProofValidatorFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Service
public class VCIssuanceServiceImpl implements VCIssuanceService {
private static final String TYPE_VERIFIABLE_CREDENTIAL = "VerifiableCredential";
@Value("#{${mosip.certify.key-values}}")
private LinkedHashMap> issuerMetadata;
@Value("${mosip.certify.cnonce-expire-seconds:300}")
private int cNonceExpireSeconds;
@Autowired
private ParsedAccessToken parsedAccessToken;
@Autowired
private VCIssuancePlugin vcIssuancePlugin;
@Autowired
private ProofValidatorFactory proofValidatorFactory;
@Autowired
private VCICacheService vciCacheService;
@Autowired
private SecurityHelperService securityHelperService;
@Autowired
private AuditPlugin auditWrapper;
@Override
public CredentialResponse getCredential(CredentialRequest credentialRequest) {
if(!parsedAccessToken.isActive())
throw new NotAuthenticatedException();
String scopeClaim = (String) parsedAccessToken.getClaims().getOrDefault("scope", "");
CredentialMetadata credentialMetadata = null;
for(String scope : scopeClaim.split(Constants.SPACE)) {
Optional result = getScopeCredentialMapping(scope);
if(result.isPresent()) {
credentialMetadata = result.get(); //considering only first credential scope
break;
}
}
if(credentialMetadata == null) {
log.error("No credential mapping found for the provided scope {}", scopeClaim);
throw new CertifyException(ErrorConstants.INVALID_SCOPE);
}
ProofValidator proofValidator = proofValidatorFactory.getProofValidator(credentialRequest.getProof().getProof_type());
if(!proofValidator.validate((String)parsedAccessToken.getClaims().get(Constants.CLIENT_ID), getValidClientNonce(),
credentialRequest.getProof())) {
throw new CertifyException(ErrorConstants.INVALID_PROOF);
}
//Get VC from configured plugin implementation
VCResult> vcResult = getVerifiableCredential(credentialRequest, credentialMetadata,
proofValidator.getKeyMaterial(credentialRequest.getProof()));
auditWrapper.logAudit(Action.VC_ISSUANCE, ActionStatus.SUCCESS,
AuditHelper.buildAuditDto(parsedAccessToken.getAccessTokenHash(), "accessTokenHash"), null);
return getCredentialResponse(credentialRequest.getFormat(), vcResult);
}
@Override
public Map getCredentialIssuerMetadata(String version) {
if(issuerMetadata.containsKey(version))
return issuerMetadata.get(version);
return issuerMetadata.get("latest");
}
private VCResult> getVerifiableCredential(CredentialRequest credentialRequest, CredentialMetadata credentialMetadata,
String holderId) {
parsedAccessToken.getClaims().put("accessTokenHash", parsedAccessToken.getAccessTokenHash());
VCRequestDto vcRequestDto = new VCRequestDto();
vcRequestDto.setFormat(credentialRequest.getFormat());
vcRequestDto.setContext(credentialRequest.getCredential_definition().getContext());
vcRequestDto.setType(credentialRequest.getCredential_definition().getType());
vcRequestDto.setCredentialSubject(credentialRequest.getCredential_definition().getCredentialSubject());
VCResult> vcResult = null;
try {
switch (credentialRequest.getFormat()) {
case "ldp_vc" :
validateLdpVcFormatRequest(credentialRequest, credentialMetadata);
vcResult = vcIssuancePlugin.getVerifiableCredentialWithLinkedDataProof(vcRequestDto, holderId,
parsedAccessToken.getClaims());
break;
// jwt_vc_json & jwt_vc_json-ld cases are merged
case "jwt_vc_json-ld" :
case "jwt_vc_json" :
vcResult = vcIssuancePlugin.getVerifiableCredential(vcRequestDto, holderId,
parsedAccessToken.getClaims());
break;
default:
throw new CertifyException(ErrorConstants.UNSUPPORTED_VC_FORMAT);
}
} catch (VCIExchangeException e) {
throw new CertifyException(e.getErrorCode());
}
if(vcResult != null && vcResult.getCredential() != null)
return vcResult;
log.error("Failed to generate VC : {}", vcResult);
auditWrapper.logAudit(Action.VC_ISSUANCE, ActionStatus.ERROR,
AuditHelper.buildAuditDto(parsedAccessToken.getAccessTokenHash(), "accessTokenHash"), null);
throw new CertifyException(ErrorConstants.VC_ISSUANCE_FAILED);
}
private CredentialResponse> getCredentialResponse(String format, VCResult> vcResult) {
switch (format) {
case "ldp_vc":
CredentialResponse ldpVcResponse = new CredentialResponse<>();
ldpVcResponse.setCredential((JsonLDObject)vcResult.getCredential());
return ldpVcResponse;
case "jwt_vc_json-ld":
case "jwt_vc_json":
CredentialResponse jsonResponse = new CredentialResponse<>();
jsonResponse.setCredential((String)vcResult.getCredential());
return jsonResponse;
}
throw new CertifyException(ErrorConstants.UNSUPPORTED_VC_FORMAT);
}
private Optional getScopeCredentialMapping(String scope) {
Map vciMetadata = getCredentialIssuerMetadata("latest");
LinkedHashMap supportedCredentials = (LinkedHashMap) vciMetadata.get("credential_configurations_supported");
Optional> result = supportedCredentials.entrySet().stream()
.filter(cm -> ((LinkedHashMap) cm.getValue()).get("scope").equals(scope)).findFirst();
if(result.isPresent()) {
LinkedHashMap metadata = (LinkedHashMap)result.get().getValue();
CredentialMetadata credentialMetadata = new CredentialMetadata();
credentialMetadata.setFormat((String) metadata.get("format"));
credentialMetadata.setScope((String) metadata.get("scope"));
credentialMetadata.setId(result.get().getKey());
LinkedHashMap credentialDefinition = (LinkedHashMap) metadata.get("credential_definition");
credentialMetadata.setTypes((List) credentialDefinition.get("type"));
return Optional.of(credentialMetadata);
}
return Optional.empty();
}
private void validateLdpVcFormatRequest(CredentialRequest credentialRequest,
CredentialMetadata credentialMetadata) {
if(!credentialRequest.getCredential_definition().getType().containsAll(credentialMetadata.getTypes()))
throw new InvalidRequestException(ErrorConstants.UNSUPPORTED_VC_TYPE);
//TODO need to validate Credential_definition as JsonLD document, if invalid throw exception
}
private String getValidClientNonce() {
VCIssuanceTransaction transaction = vciCacheService.getVCITransaction(parsedAccessToken.getAccessTokenHash());
//If the transaction is null, it means that VCI service never created cNonce, its authorization server issued cNonce
String cNonce = (transaction == null) ?
(String) parsedAccessToken.getClaims().get(Constants.C_NONCE) :
transaction.getCNonce();
Object nonceExpireSeconds = parsedAccessToken.getClaims().getOrDefault(Constants.C_NONCE_EXPIRES_IN, 0);
int cNonceExpire = (transaction == null) ?
nonceExpireSeconds instanceof Long ? (int)(long)nonceExpireSeconds : (int)nonceExpireSeconds :
transaction.getCNonceExpireSeconds();
long issuedEpoch = (transaction == null) ?
((Instant) parsedAccessToken.getClaims().getOrDefault(JwtClaimNames.IAT, Instant.MIN)).getEpochSecond():
transaction.getCNonceIssuedEpoch();
if( cNonce == null ||
cNonceExpire <= 0 ||
(issuedEpoch+cNonceExpire) < LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC) ) {
log.error("Client Nonce not found / expired in the access token, generate new cNonce");
transaction = createVCITransaction();
throw new InvalidNonceException(transaction.getCNonce(), transaction.getCNonceExpireSeconds());
}
return cNonce;
}
private VCIssuanceTransaction createVCITransaction() {
VCIssuanceTransaction transaction = new VCIssuanceTransaction();
transaction.setCNonce(securityHelperService.generateSecureRandomString(20));
transaction.setCNonceIssuedEpoch(LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC));
transaction.setCNonceExpireSeconds(cNonceExpireSeconds);
return vciCacheService.setVCITransaction(parsedAccessToken.getAccessTokenHash(), transaction);
}
}