diff --git a/certify-service/pom.xml b/certify-service/pom.xml index 0a9dc52e1..c52b7b154 100644 --- a/certify-service/pom.xml +++ b/certify-service/pom.xml @@ -26,6 +26,19 @@ danubetech-maven-public https://repo.danubetech.com/repository/maven-public/ + + ossrh-inji + CentralRepositoryInji + https://central.sonatype.com/repository/maven-snapshots + + true + + + + google + GoogleMaven + https://maven.google.com + @@ -236,6 +249,17 @@ kernel-biometrics-api 1.3.0 + + io.inji.verify + verify-service + 0.18.2-SNAPSHOT + + + io.inji + pixelpass-jar + + + diff --git a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java index 5462c0821..fb2144b95 100644 --- a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java +++ b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java @@ -8,18 +8,20 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.cache.annotation.EnableCaching; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.context.annotation.Import; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync @EnableCaching -@SpringBootApplication(scanBasePackages = "io.mosip.certify,"+ +@Import(io.inji.verify.config.AppConfig.class) +@SpringBootApplication(scanBasePackages = "io.mosip.certify," + "io.mosip.kernel.crypto," + "io.mosip.kernel.keymanager.hsm," + "io.mosip.kernel.cryptomanager," + - "io.mosip.kernel.keymanagerservice.validator,"+ - "io.mosip.kernel.keymanager,"+ + "io.mosip.kernel.keymanagerservice.validator," + + "io.mosip.kernel.keymanager," + "io.mosip.kernel.cryptomanager.util," + "io.mosip.kernel.keymanagerservice.helper," + "io.mosip.kernel.keymanagerservice.repository," + @@ -30,11 +32,14 @@ "io.mosip.kernel.signature.util," + "io.mosip.kernel.signature.builder," + "io.mosip.kernel.signature.*," + - "io.mosip.kernel.pdfgenerator.*,"+ + "io.mosip.kernel.pdfgenerator.*," + "io.mosip.kernel.partnercertservice.service," + - "io.mosip.kernel.keymanagerservice.repository,"+ - "io.mosip.kernel.keymanagerservice.entity,"+ + "io.mosip.kernel.keymanagerservice.repository," + + "io.mosip.kernel.keymanagerservice.entity," + "io.mosip.kernel.partnercertservice.helper," + + "io.inji.verify.services," + + "io.inji.verify.key.impl," + + "io.inji.verify.repository," + "${mosip.certify.integration.scan-base-package}") public class CertifyServiceApplication { public static void main(String[] args) { diff --git a/certify-service/src/main/java/io/mosip/certify/config/AppConfig.java b/certify-service/src/main/java/io/mosip/certify/config/AppConfig.java index 7125b394d..50aa2c498 100644 --- a/certify-service/src/main/java/io/mosip/certify/config/AppConfig.java +++ b/certify-service/src/main/java/io/mosip/certify/config/AppConfig.java @@ -34,8 +34,16 @@ import lombok.extern.slf4j.Slf4j; @Configuration -@EnableJpaRepositories(basePackages = {"io.mosip.kernel.keymanagerservice.repository", "io.mosip.certify.repository"}) -@EntityScan(basePackages = {"io.mosip.kernel.keymanagerservice.entity, io.mosip.certify.entity"}) +@EnableJpaRepositories(basePackages = { + "io.mosip.kernel.keymanagerservice.repository", + "io.mosip.certify.repository", + "io.inji.verify.repository" +}) +@EntityScan(basePackages = { + "io.mosip.kernel.keymanagerservice.entity", + "io.mosip.certify.entity", + "io.inji.verify.models" +}) @Slf4j public class AppConfig implements ApplicationRunner { @@ -79,10 +87,11 @@ public RestTemplate restTemplate() { return new RestTemplate(requestFactory); } - @Bean + @Bean("certifyPixelPass") public PixelPass pixelPass() { return new PixelPass(); } + @Override public void run(ApplicationArguments args) throws Exception { initKeys(); diff --git a/certify-service/src/main/java/io/mosip/certify/config/CertifyContextPathOverride.java b/certify-service/src/main/java/io/mosip/certify/config/CertifyContextPathOverride.java new file mode 100644 index 000000000..0b9897363 --- /dev/null +++ b/certify-service/src/main/java/io/mosip/certify/config/CertifyContextPathOverride.java @@ -0,0 +1,19 @@ +/* + * 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.config; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CertifyContextPathOverride { + + @Bean + public WebServerFactoryCustomizer webServerFactoryCustomizer() { + return factory -> factory.setContextPath(""); + } +} diff --git a/certify-service/src/main/java/io/mosip/certify/services/CertifyIssuanceServiceImpl.java b/certify-service/src/main/java/io/mosip/certify/services/CertifyIssuanceServiceImpl.java index b4c88c82d..9e9ddde5e 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/CertifyIssuanceServiceImpl.java +++ b/certify-service/src/main/java/io/mosip/certify/services/CertifyIssuanceServiceImpl.java @@ -39,6 +39,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; @@ -88,6 +89,7 @@ public class CertifyIssuanceServiceImpl implements VCIssuanceService { private AuditPlugin auditWrapper; @Autowired + @Qualifier("certifyPixelPass") private PixelPass pixelPass; private Map didDocument; diff --git a/certify-service/src/main/java/io/mosip/certify/services/IarPresentationService.java b/certify-service/src/main/java/io/mosip/certify/services/IarPresentationService.java index ca5faccf5..6cc905fad 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/IarPresentationService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/IarPresentationService.java @@ -6,6 +6,11 @@ package io.mosip.certify.services; import com.fasterxml.jackson.databind.JsonNode; +import io.inji.verify.dto.result.CredentialResultsDto; +import io.inji.verify.dto.result.VPVerificationResultDto; +import io.inji.verify.dto.result.VerificationRequestDto; +import io.inji.verify.services.VerifiablePresentationRequestService; +import io.inji.verify.services.VerifiablePresentationSubmissionService; import io.mosip.certify.core.constants.ErrorConstants; import io.mosip.certify.core.constants.IarStatus; import io.mosip.certify.core.dto.IarAuthorizationRequest; @@ -17,15 +22,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.type.TypeReference; @@ -34,8 +33,7 @@ import java.util.*; /** - * Service for handling VP Presentation processing - * Handles VP submission to verifier and verification result processing + * Service for handling VP Presentation processing using the embedded inji-verify library. */ @Slf4j @Service @@ -43,12 +41,11 @@ public class IarPresentationService { private final IarSessionRepository iarSessionRepository; - private final RestTemplate restTemplate; - private final ObjectMapper objectMapper; - @Value("${mosip.certify.verify.service.vp-result-endpoint:}") - private String verifyServiceVpResultEndpoint; + private final VerifiablePresentationSubmissionService vpSubmissionService; + + private final VerifiablePresentationRequestService vpRequestService; @Value("${mosip.certify.iar.authorization-code.length:24}") private int authorizationCodeLength; @@ -58,11 +55,13 @@ public class IarPresentationService { @Autowired public IarPresentationService(IarSessionRepository iarSessionRepository, - RestTemplate restTemplate, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + VerifiablePresentationSubmissionService vpSubmissionService, + VerifiablePresentationRequestService vpRequestService) { this.iarSessionRepository = iarSessionRepository; - this.restTemplate = restTemplate; this.objectMapper = objectMapper; + this.vpSubmissionService = vpSubmissionService; + this.vpRequestService = vpRequestService; } /** @@ -79,9 +78,9 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr } IarSession session = sessionOpt.get(); - submitVpToVerifier(session.getResponseUri(), + submitVpToVerifier(session.getResponseUri(), presentationRequest.getOpenid4vpPresentation(), - session.getRequestId(), + session.getRequestId(), session.getTransactionId()); VpVerificationResponse verificationResponse = getVpVerificationResult(session.getTransactionId()); @@ -99,12 +98,12 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr String authorizationCode = generateAndStoreAuthorizationCode(session); response.setStatus(IarStatus.OK); response.setCode(authorizationCode); - log.info("Authorization code generated after successful VP cryptographic verification for auth_session: {}, request_id: {}", + log.info("Authorization code generated after successful VP cryptographic verification for auth_session: {}, request_id: {}", presentationRequest.getAuthSession(), session.getRequestId()); } else { response.setStatus(IarStatus.ERROR); - log.warn("Authorization denied - VP cryptographic verification failed for auth_session: {}, request_id: {}, error: {}", - presentationRequest.getAuthSession(), session.getRequestId(), + log.warn("Authorization denied - VP cryptographic verification failed for auth_session: {}, request_id: {}, error: {}", + presentationRequest.getAuthSession(), session.getRequestId(), verificationResponse.getError()); } @@ -122,29 +121,28 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr } /** - * Submit VP presentation to verifier service + * Submit VP presentation to the embedded inji-verify library. + * responseUri is retained in the signature for session compatibility but is not used for HTTP. */ private void submitVpToVerifier(String responseUri, String vpPresentationJson, String requestId, String transactionId) throws CertifyException { - log.info("Submitting VP to Verify at {} with state(requestId)={}, transactionId={}", - responseUri, requestId, transactionId); - + log.info("Submitting VP to vpSubmissionService with state(requestId)={}, transactionId={}", + requestId, transactionId); + try { Map vpPresentationData = objectMapper.readValue(vpPresentationJson, new TypeReference>() {}); - + Object vpTokenObj = vpPresentationData.get("vp_token"); if (vpTokenObj == null) { log.error("Missing vp_token in wallet's VP presentation"); throw new CertifyException("vp_submission_failed", "Missing vp_token in VP presentation"); } - + Object presentationSubmissionObj = vpPresentationData.get("presentation_submission"); if (presentationSubmissionObj == null) { log.error("Missing presentation_submission in wallet's VP presentation"); throw new CertifyException("vp_submission_failed", "Missing presentation_submission in VP presentation"); } - - log.debug("Using requestId: {} for Verify service", requestId); - + String vpTokenJson; if (vpTokenObj instanceof String vpTokenString) { vpTokenJson = vpTokenString; @@ -153,116 +151,105 @@ private void submitVpToVerifier(String responseUri, String vpPresentationJson, S vpTokenJson = objectMapper.writeValueAsString(vpTokenObj); log.debug("vp_token serialized to JSON, length: {}", vpTokenJson.length()); } - + String presentationSubmissionJson; if (presentationSubmissionObj instanceof String presentationSubmissionString) { presentationSubmissionJson = presentationSubmissionString; } else { - presentationSubmissionJson = objectMapper.writeValueAsString(presentationSubmissionObj); + presentationSubmissionJson = objectMapper.writeValueAsString(presentationSubmissionObj); log.debug("presentation_submission serialized to JSON, length: {}", presentationSubmissionJson.length()); } - MultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("vp_token", vpTokenJson); - formData.add("presentation_submission", presentationSubmissionJson); - formData.add("state", requestId); - - log.debug("Form data prepared - vp_token length: {}, presentation_submission length: {}, state(requestId): {}", - vpTokenJson.length(), presentationSubmissionJson.length(), requestId); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - HttpEntity> requestEntity = new HttpEntity<>(formData, headers); - - var response = restTemplate.exchange( - responseUri, - HttpMethod.POST, - requestEntity, - String.class - ); - - if (response.getStatusCode().is2xxSuccessful()) { - log.info("Successfully submitted VP presentation to verify service, response status: {}", - response.getStatusCode()); + log.debug("Calling vpSubmissionService.submit with state(requestId): {}", requestId); + ResponseEntity submissionResponse = vpSubmissionService.submit(vpTokenJson, presentationSubmissionJson, requestId, null, null); + + if (submissionResponse.getStatusCode().is2xxSuccessful()) { + log.info("VP submission accepted, status: {}", submissionResponse.getStatusCode()); } else { - log.warn("VP submission returned non-success status: {}, body: {}", - response.getStatusCode(), response.getBody()); - throw new CertifyException("vp_submission_failed", - "VP submission failed with status: " + response.getStatusCode()); + log.warn("VP submission returned non-success status: {}", submissionResponse.getStatusCode()); + throw new CertifyException("vp_submission_failed", + "VP submission failed with status: " + submissionResponse.getStatusCode()); } - + } catch (CertifyException e) { throw e; } catch (Exception e) { - log.error("Failed to submit VP presentation to verify service at: {}, requestId: {}, transactionId: {}", - responseUri, requestId, transactionId, e); - throw new CertifyException("vp_submission_failed", - "Failed to submit VP presentation to verify service: " + e.getMessage(), e); + log.error("Failed to submit VP presentation, requestId: {}, transactionId: {}", + requestId, transactionId, e); + throw new CertifyException("vp_submission_failed", + "Failed to submit VP presentation: " + e.getMessage(), e); } } /** - * Get VP verification result from verifier service + * Get VP verification result from the embedded inji-verify library. */ private VpVerificationResponse getVpVerificationResult(String transactionId) throws CertifyException { - if (!StringUtils.hasText(verifyServiceVpResultEndpoint)) { - throw new IllegalStateException("mosip.certify.verify.service.vp-result-endpoint must be configured"); - } try { - String vpResultUrl = verifyServiceVpResultEndpoint + "/" + transactionId; - log.debug("Getting verification results from: {}", vpResultUrl); - - var resultResponse = restTemplate.exchange( - vpResultUrl, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), - new org.springframework.core.ParameterizedTypeReference>() {} - ); - - Map verificationResult = resultResponse.getBody(); - log.info("VP verification result received for transaction_id: {}", transactionId); - + List requestIds = vpRequestService.getLatestRequestIdFor(transactionId); + log.debug("Fetching VP result for transactionId: {}, requestIds: {}", transactionId, requestIds); + + VerificationRequestDto verificationRequest = new VerificationRequestDto(); + verificationRequest.setIncludeClaims(true); + VPVerificationResultDto vpResult = vpSubmissionService.getVPResultV2(verificationRequest, requestIds, transactionId); + VpVerificationResponse response = new VpVerificationResponse(); response.setRequestId(transactionId); - - if (verificationResult != null) { - String vpResultStatus = (String) verificationResult.get("vpResultStatus"); - log.debug("Verify service vpResultStatus: {}", vpResultStatus); - if ("SUCCESS".equals(vpResultStatus)) { + if (vpResult != null) { + Map verificationDetails = buildVerificationDetails(vpResult); + response.setVerificationDetails(verificationDetails); + + if (vpResult.isAllChecksSuccessful()) { response.setStatus(IarStatus.OK.getValue()); - response.setVerificationDetails(verificationResult); - log.info("VP cryptographic verification successful for transaction_id: {}, vpResultStatus: {}", transactionId, vpResultStatus); + log.info("VP cryptographic verification successful for transaction_id: {}", transactionId); } else { response.setStatus(IarStatus.ERROR.getValue()); response.setError("verification_failed"); - response.setErrorDescription("VP cryptographic verification failed: " + verificationResult.get("errorDescription")); - response.setVerificationDetails(verificationResult); - log.warn("VP cryptographic verification failed for transaction_id: {}, vpResultStatus: {}", transactionId, vpResultStatus); + response.setErrorDescription("VP cryptographic verification failed"); + log.warn("VP cryptographic verification failed for transaction_id: {}", + transactionId); } } else { response.setStatus(IarStatus.ERROR.getValue()); response.setError("no_verification_result"); - response.setErrorDescription("No verification result available from verify service"); + response.setErrorDescription("No VP verification result available"); log.error("No verification result available for transaction_id: {}", transactionId); } - + return response; - + } catch (Exception e) { log.error("Failed to get VP verification results for transaction_id: {}", transactionId, e); - + VpVerificationResponse errorResponse = new VpVerificationResponse(); errorResponse.setStatus(IarStatus.ERROR.getValue()); errorResponse.setRequestId(transactionId); errorResponse.setError("verification_result_error"); errorResponse.setErrorDescription("Failed to retrieve verification results: " + e.getMessage()); - + return errorResponse; } } + private Map buildVerificationDetails(VPVerificationResultDto vpResult) { + Map details = new HashMap<>(); + details.put("allChecksSuccessful", vpResult.isAllChecksSuccessful()); + List> credentialResultsList = new ArrayList<>(); + if (vpResult.getCredentialResults() != null) { + for (CredentialResultsDto credentialResult : vpResult.getCredentialResults()) { + Map credentialEntry = new HashMap<>(); + credentialEntry.put("vc", credentialResult.getVerifiableCredential()); + credentialEntry.put("allChecksSuccessful", credentialResult.isAllChecksSuccessful()); + credentialResultsList.add(credentialEntry); + } + } + details.put("vcResults", credentialResultsList); + return details; + } + /** - * Generate and store authorization code + * Generate and store authorization code. */ private String generateAndStoreAuthorizationCode(IarSession session) throws CertifyException { // Idempotency guard: if an authorization code already exists for this session @@ -270,26 +257,24 @@ private String generateAndStoreAuthorizationCode(IarSession session) throws Cert // Check if the existing code has already been used - if so, this is an error if (Boolean.TRUE.equals(session.getIsCodeUsed())) { log.error("Authorization code already used for auth_session: {}, cannot reuse", session.getAuthSession()); - throw new CertifyException("invalid_request", + throw new CertifyException("invalid_request", "Authorization code for this session has already been used. Please start a new authorization flow."); } log.info("Authorization code already exists for auth_session: {}, returning existing code", session.getAuthSession()); return session.getAuthorizationCode(); } - // Validate minimum length requirement + if (authorizationCodeLength < 24) { - throw new CertifyException("invalid_configuration", + throw new CertifyException("invalid_configuration", "Authorization code length must be at least 24 characters. Current value: " + authorizationCodeLength); } - // Generate random bytes based on configured length // For Base64 URL encoding, we need approximately 3/4 of the target length in bytes int byteLength = (int) Math.ceil(authorizationCodeLength * 0.75); byte[] randomBytes = new byte[byteLength]; new java.security.SecureRandom().nextBytes(randomBytes); String encoded = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); - // Ensure we have enough characters, pad if necessary if (encoded.length() < authorizationCodeLength) { byte[] additionalBytes = new byte[16]; @@ -297,44 +282,37 @@ private String generateAndStoreAuthorizationCode(IarSession session) throws Cert String additionalEncoded = java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(additionalBytes); encoded = (encoded + additionalEncoded).substring(0, authorizationCodeLength); } - + String authCode = "iar_auth_" + encoded.substring(0, authorizationCodeLength); log.debug("Generated authorization code for auth_session: {} (length: {})", session.getAuthSession(), authCode.length()); - + session.setAuthorizationCode(authCode); session.setCodeIssuedAt(LocalDateTime.now()); iarSessionRepository.save(session); log.info("Authorization code stored for auth_session: {}", session.getAuthSession()); - + return authCode; } private String extractIdentity(VpVerificationResponse verificationResponse) { - Object detailsObj = verificationResponse.getVerificationDetails(); if (!(detailsObj instanceof Map)) { return null; } Map details = (Map) detailsObj; - Object vcResultsObj = details.get("vcResults"); if (!(vcResultsObj instanceof List)) { return null; } List> vcResults = (List>) vcResultsObj; - for (Map vcEntry : vcResults) { - Object vcStringObj = vcEntry.get("vc"); if (!(vcStringObj instanceof String)) { continue; } - - String vcJson = (String) vcStringObj; - String extracted = extractIdentityFromVc(vcJson); - + String extracted = extractIdentityFromVc((String) vcStringObj); if (extracted != null && !extracted.isEmpty()) { return extracted; } @@ -342,30 +320,23 @@ private String extractIdentity(VpVerificationResponse verificationResponse) { return null; } - private String extractIdentityFromVc(String vcJson) { try { JsonNode root = objectMapper.readTree(vcJson); - JsonNode cs = root.path("credentialSubject"); if (cs.isMissingNode()) { return null; } - Iterator> fields = cs.fields(); while (fields.hasNext()) { Map.Entry entry = fields.next(); - if (identityKeys.contains(entry.getKey())) { return entry.getValue().asText(); } } - } catch (Exception ex) { log.warn("Failed to parse VC JSON for identity extraction", ex); } - return null; } - } diff --git a/certify-service/src/main/java/io/mosip/certify/services/IarVpRequestService.java b/certify-service/src/main/java/io/mosip/certify/services/IarVpRequestService.java index 30a82a692..4d0d99db0 100644 --- a/certify-service/src/main/java/io/mosip/certify/services/IarVpRequestService.java +++ b/certify-service/src/main/java/io/mosip/certify/services/IarVpRequestService.java @@ -6,31 +6,30 @@ package io.mosip.certify.services; import com.fasterxml.jackson.databind.ObjectMapper; +import io.inji.verify.dto.authorizationrequest.AuthorizationRequestResponseDto; +import io.inji.verify.dto.authorizationrequest.VPRequestCreateDto; +import io.inji.verify.dto.authorizationrequest.VPRequestResponseDto; +import io.inji.verify.dto.presentation.*; +import io.inji.verify.services.VerifiablePresentationRequestService; import io.mosip.certify.config.VerifyServiceConfig; -import io.mosip.certify.core.dto.InteractiveAuthorizationRequest; -import io.mosip.certify.core.dto.VerifyVpRequest; -import io.mosip.certify.core.dto.VerifyVpResponse; +import io.mosip.certify.core.dto.*; import io.mosip.certify.core.exception.CertifyException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; import java.io.IOException; -import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; /** - * Service for creating VP requests with verify service - * Handles communication with VP Verifier service + * Service for creating VP requests via the embedded inji-verify library. */ @Slf4j @Service @@ -40,12 +39,11 @@ public class IarVpRequestService { private final ObjectMapper objectMapper; + private final VerifiablePresentationRequestService vpRequestService; + @Value("${mosip.certify.vp-request.config-file-url:}") private String vpRequestConfigUrl; - @Value("${mosip.certify.verify.service.vp-request-endpoint:}") - private String verifyServiceVpRequestEndpoint; - @Value("${mosip.certify.verify.service.verifier-client-id:}") private String verifierClientId; @@ -62,30 +60,31 @@ public class IarVpRequestService { private String activeProfile; @Autowired - public IarVpRequestService(RestTemplate restTemplate, ObjectMapper objectMapper) { + public IarVpRequestService(RestTemplate restTemplate, + ObjectMapper objectMapper, + VerifiablePresentationRequestService vpRequestService) { this.restTemplate = restTemplate; this.objectMapper = objectMapper; + this.vpRequestService = vpRequestService; } + /** - * Create VP request with verify service - * - * @param iarRequest The interactive authorization request - * @return VerifyVpResponse from verify service - * @throws CertifyException if request fails + * Create VP request using the embedded inji-verify library. */ public VerifyVpResponse createVpRequest(InteractiveAuthorizationRequest iarRequest) throws CertifyException { - log.info("Calling verify service for VP request generation for wallet client_id: {} using verifier client_id: {}", + log.info("Creating VP request via library for wallet client_id: {} using verifier client_id: {}", iarRequest.getClientId(), verifierClientId); validateConfiguration(); + VerifyServiceConfig verifyServiceConfig; try { - log.info("Fetching VP Request Config from : {}", vpRequestConfigUrl); + log.info("Fetching VP Request Config from: {}", vpRequestConfigUrl); String vpRequestConfig; if (activeProfile != null && activeProfile.contains("local")) { Resource resource = new ClassPathResource(vpRequestConfigUrl); try (var inputStream = resource.getInputStream()) { - vpRequestConfig = new String(inputStream.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); + vpRequestConfig = new String(inputStream.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8); } } else { vpRequestConfig = restTemplate.getForObject(vpRequestConfigUrl, String.class); @@ -100,96 +99,101 @@ public VerifyVpResponse createVpRequest(InteractiveAuthorizationRequest iarReque } try { - VerifyVpRequest verifyRequest = new VerifyVpRequest(); - verifyRequest.setClientId(verifierClientId); - log.debug("Using verifier client_id: {} for VP request (wallet client_id: {})", - verifierClientId, iarRequest.getClientId()); - verifyRequest.setResponseModesSupported(Arrays.asList( - "direct_post", - "direct_post.jwt" - )); - verifyRequest.setEncryptionRequired(true); - - verifyRequest.setPresentationDefinition(verifyServiceConfig.getPresentationDefinition()); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity requestEntity = new HttpEntity<>(verifyRequest, headers); - - String verifyServiceUrl = verifyServiceVpRequestEndpoint; - log.debug("Calling verify service at: {}", verifyServiceUrl); - - VerifyVpResponse verifyResponse = restTemplate.postForObject( - verifyServiceUrl, - requestEntity, - VerifyVpResponse.class + VPDefinitionResponseDto vpDefinition = mapToVPDefinitionResponseDto( + verifyServiceConfig.getPresentationDefinition()); + + VPRequestCreateDto vpRequestCreateDto = new VPRequestCreateDto( + verifierClientId, + null, + null, + null, + vpDefinition, + false, + false ); - if (verifyResponse == null) { - throw new CertifyException("unknown_error", "Empty response from verify service"); + + log.debug("Calling inji-verify library createAuthorizationRequest for verifier client_id: {}", verifierClientId); + VPRequestResponseDto vpAuthRequest = vpRequestService.createAuthorizationRequest(vpRequestCreateDto); + + if (vpAuthRequest == null) { + throw new CertifyException("unknown_error", "Empty response from inji-verify library"); } - log.info("Successfully received VP request from verify service for client_id: {}, transactionId: {}", - iarRequest.getClientId(), verifyResponse.getTransactionId()); + log.info("VP request created via library for client_id: {}, transactionId: {}", + iarRequest.getClientId(), vpAuthRequest.getTransactionId()); - return verifyResponse; + return mapToVerifyVpResponse(vpAuthRequest); } catch (CertifyException e) { throw e; } catch (Exception e) { - log.error("Failed to call verify service for client_id: {}", iarRequest.getClientId(), e); - throw new CertifyException("unknown_error", "Failed to call verify service", e); + log.error("Failed to create VP request via library for client_id: {}", iarRequest.getClientId(), e); + throw new CertifyException("unknown_error", "Failed to create VP request via library", e); } } /** - * Convert verify response to OpenID4VP request + * Convert verify response to OpenID4VP request. */ public Object convertToOpenId4VpRequest(VerifyVpResponse verifyResponse, InteractiveAuthorizationRequest iarRequest) { - // Create a Map to represent the OpenID4VP request structure Map openId4VpRequest = new HashMap<>(); - + VerifyVpResponse.AuthorizationDetails authDetails = verifyResponse.getAuthorizationDetails(); if (authDetails == null) { - log.error("No authorization details found in verify service response - this is required for production"); - throw new CertifyException("unknown_error", "Invalid response from verify service: missing authorization details"); + log.error("No authorization details found in library response"); + throw new CertifyException("unknown_error", "Invalid response from inji-verify library: missing authorization details"); } openId4VpRequest.put("response_type", authDetails.getResponseType()); openId4VpRequest.put("client_id", authDetails.getClientId() != null ? authDetails.getClientId() : iarRequest.getClientId()); - openId4VpRequest.put("nonce", authDetails.getNonce()); - log.info("Forwarding VP request nonce from Verify: {}", authDetails.getNonce()); - + log.info("Forwarding VP request nonce from library: {}", authDetails.getNonce()); + openId4VpRequest.put("presentation_definition", authDetails.getPresentationDefinition()); - + String responseMode = authDetails.getResponseMode(); if (!StringUtils.hasText(responseMode)) { throw new CertifyException("unknown_error", "Response mode is required"); } - - // Map verifier response_mode to OpenID4VCI 1.1 IAE modes. if ("direct_post".equals(responseMode)) { responseMode = iaePostResponseMode; } else if ("direct_post.jwt".equals(responseMode)) { responseMode = iaePostJwtResponseMode; } openId4VpRequest.put("response_mode", responseMode); - + openId4VpRequest.put("response_uri", certifyIaeEndpoint); log.info("Using certify /iae endpoint for wallet VP submission: {}", certifyIaeEndpoint); - log.info("Successfully converted verify service response to OpenId4VpRequest for client_id: {}", iarRequest.getClientId()); - log.debug("OpenId4VpRequest - responseType: {}, responseMode: {}, responseUri: {}, nonce: {}", - openId4VpRequest.get("response_type"), openId4VpRequest.get("response_mode"), - openId4VpRequest.get("response_uri"), openId4VpRequest.get("nonce")); - + log.info("Successfully converted library response to OpenId4VpRequest for client_id: {}", iarRequest.getClientId()); return openId4VpRequest; } + private VerifyVpResponse mapToVerifyVpResponse(VPRequestResponseDto dto) { + VerifyVpResponse response = new VerifyVpResponse(); + response.setTransactionId(dto.getTransactionId()); + response.setRequestId(dto.getRequestId()); + response.setExpiresAt(dto.getExpiresAt()); + + AuthorizationRequestResponseDto authorizationRequestResponse = dto.getAuthorizationDetails(); + if (authorizationRequestResponse != null) { + VerifyVpResponse.AuthorizationDetails authorizationDetails = new VerifyVpResponse.AuthorizationDetails(); + authorizationDetails.setClientId(authorizationRequestResponse.getClientId()); + authorizationDetails.setNonce(authorizationRequestResponse.getNonce()); + authorizationDetails.setResponseUri(authorizationRequestResponse.getResponseUri()); + authorizationDetails.setResponseType(authorizationRequestResponse.getResponseType()); + authorizationDetails.setResponseMode(authorizationRequestResponse.getResponseMode()); + authorizationDetails.setIssuedAt(authorizationRequestResponse.getIssuedAt()); + authorizationDetails.setPresentationDefinition( + objectMapper.convertValue(authorizationRequestResponse.getPresentationDefinition(), PresentationDefinition.class)); + response.setAuthorizationDetails(authorizationDetails); + } + return response; + } + private void validateConfiguration() { - if (!StringUtils.hasText(verifyServiceVpRequestEndpoint)) { - throw new IllegalStateException("mosip.certify.verify.service.vp-request-endpoint must be configured"); + if (!StringUtils.hasText(vpRequestConfigUrl)) { + throw new IllegalStateException("mosip.certify.vp-request.config-file-url must be configured"); } if (!StringUtils.hasText(verifierClientId)) { throw new IllegalStateException("mosip.certify.verify.service.verifier-client-id must be configured"); @@ -199,4 +203,78 @@ private void validateConfiguration() { } log.info("IarVpRequestService configuration validation successful"); } + + /** + * Manually maps PresentationDefinition to VPDefinitionResponseDto + * because VPDefinitionResponseDto has no default constructor (Lombok @Value). + */ + private VPDefinitionResponseDto mapToVPDefinitionResponseDto(PresentationDefinition pd) { + if (pd == null) return null; + + List inputDescriptorDtos = null; + if (pd.getInputDescriptors() != null) { + inputDescriptorDtos = pd.getInputDescriptors().stream() + .map(this::mapToInputDescriptorDto) + .toList(); + } + + return new VPDefinitionResponseDto( + pd.getId(), + inputDescriptorDtos, + pd.getName(), + pd.getPurpose(), + null, // format — not present in PresentationDefinition + null // submissionRequirements — not present in PresentationDefinition + ); + } + + private InputDescriptorDto mapToInputDescriptorDto(InputDescriptor id) { + if (id == null) return null; + + ConstraintsDTO constraintsDto = null; + if (id.getConstraints() != null) { + constraintsDto = mapToConstraintsDTO(id.getConstraints()); + } + + return new InputDescriptorDto( + id.getId(), + null, // name — not present in InputDescriptor + null, // purpose — not present in InputDescriptor + null, // group — not present in InputDescriptor + id.getFormat(), + constraintsDto + ); + } + + private ConstraintsDTO mapToConstraintsDTO(InputConstraints constraints) { + if (constraints == null) return null; + + FieldDTO[] fieldDtos = null; + if (constraints.getFields() != null) { + fieldDtos = constraints.getFields().stream() + .map(this::mapToFieldDTO) + .toArray(FieldDTO[]::new); + } + + return new ConstraintsDTO(fieldDtos); + } + + private FieldDTO mapToFieldDTO(FieldConstraint fc) { + if (fc == null) return null; + + String[] pathArray = null; + if (fc.getPath() != null) { + pathArray = fc.getPath().toArray(new String[0]); + } + + io.inji.verify.dto.presentation.FilterDTO filterDto = null; + if (fc.getFilter() != null) { + filterDto = new io.inji.verify.dto.presentation.FilterDTO( + fc.getFilter().getType(), + fc.getFilter().getPattern() + ); + } + + return new FieldDTO(pathArray, filterDto); + } } diff --git a/certify-service/src/main/resources/application-local.properties b/certify-service/src/main/resources/application-local.properties index fad683b0b..e715ff552 100644 --- a/certify-service/src/main/resources/application-local.properties +++ b/certify-service/src/main/resources/application-local.properties @@ -312,15 +312,18 @@ mosip.certify.oauth.access-token.audience=${mosip.certify.authorization.url}${se mosip.certify.iar.authorization-code.expires-minutes=10 mosip.certify.iar.authorization-code.length=24 -## Verify service integration -# Only configure the base URL, VP request and VP result endpoints - other endpoints are obtained dynamically from verify service -mosip.certify.verify.service.base-url=http://localhost:8080 -mosip.certify.verify.service.vp-request-endpoint=${mosip.certify.verify.service.base-url}/v1/verify/vp-request -mosip.certify.verify.service.vp-result-endpoint=${mosip.certify.verify.service.base-url}/v1/verify/vp-result +## inji-verify library integration (embedded in-process — no external service required) mosip.certify.verify.service.verifier-client-id=certify-verifier-client mosip.certify.vp-request.config-file-url=vp_request_config.json +## inji-verify library properties +inji.vp-request.long-polling-timeout=55000 +inji.vp-submission.base-url=${mosip.certify.oauth.interactive-authorization-endpoint} +inji.did.verify.public.key.uri=did:web:localhost:v1:certify#key-0 +inji.verify.keystore.file.path=classpath:sample-keystore/test.p12 +inji.verify.keystore.file.pass=mosip + # Cache mosip.certify.jsonld.cache.enabled=true mosip.certify.jsonld.cache.maxEntries=512 diff --git a/certify-service/src/main/resources/sample-keystore/test.p12 b/certify-service/src/main/resources/sample-keystore/test.p12 new file mode 100644 index 000000000..63cd45e3f Binary files /dev/null and b/certify-service/src/main/resources/sample-keystore/test.p12 differ diff --git a/certify-service/src/test/resources/application-test.properties b/certify-service/src/test/resources/application-test.properties index 5dfbd3d39..7bc64cf7a 100644 --- a/certify-service/src/test/resources/application-test.properties +++ b/certify-service/src/test/resources/application-test.properties @@ -140,3 +140,14 @@ spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true mosip.certify.vp-request.config-file-url=vp_request_config.json mosip.certify.credential-offer-url=${mosip.certify.domain.url}${server.servlet.path}/credential-offer-data/ + +## inji-verify library integration +mosip.certify.verify.service.verifier-client-id=certify-verifier-client + +## inji-verify library properties +inji.vp-request.long-polling-timeout=55000 +inji.vp-submission.base-url=${mosip.certify.oauth.interactive-authorization-endpoint} +inji.did.verify.public.key.uri=did:web:localhost:v1:certify#key-0 +inji.verify.keystore.file.path=classpath:sample-keystore/test.p12 +inji.verify.keystore.file.pass=mosip + diff --git a/db_scripts/inji_certify/ddl.sql b/db_scripts/inji_certify/ddl.sql index 46518db5e..79f9e8071 100644 --- a/db_scripts/inji_certify/ddl.sql +++ b/db_scripts/inji_certify/ddl.sql @@ -12,3 +12,7 @@ \ir ddl/certify-status_list_available_indices.sql \ir ddl/certify-shedlock.sql \ir ddl/certify-iar_session.sql +\ir ddl/verify-authorization_request_details.sql +\ir ddl/verify-presentation_definition.sql +\ir ddl/verify-vc_submission.sql +\ir ddl/verify-vp_submission.sql \ No newline at end of file diff --git a/db_scripts/inji_certify/ddl/verify-authorization_request_details.sql b/db_scripts/inji_certify/ddl/verify-authorization_request_details.sql new file mode 100644 index 000000000..d26d94be7 --- /dev/null +++ b/db_scripts/inji_certify/ddl/verify-authorization_request_details.sql @@ -0,0 +1,22 @@ +-- 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/. +-- +-- Table required by the embedded inji-verify library. +-- Stores VP authorization requests created during the presentation-during-issuance flow. + +CREATE TABLE IF NOT EXISTS authorization_request_details ( + request_id character varying(40) NOT NULL, + transaction_id character varying(40) NOT NULL, + authorization_details text NOT NULL, + expires_at bigint NOT NULL, + CONSTRAINT pk_authorization_request_details PRIMARY KEY (request_id) +); + +COMMENT ON TABLE authorization_request_details IS 'Stores VP authorization request details created by the embedded inji-verify library'; +COMMENT ON COLUMN authorization_request_details.request_id IS 'Unique request ID for the VP authorization request'; +COMMENT ON COLUMN authorization_request_details.transaction_id IS 'Transaction ID linking this request to the issuance session'; +COMMENT ON COLUMN authorization_request_details.authorization_details IS 'JSON blob containing full OpenID4VP authorization request details'; +COMMENT ON COLUMN authorization_request_details.expires_at IS 'Epoch-millis expiry of the authorization request'; + +CREATE INDEX IF NOT EXISTS idx_ard_transaction_id ON authorization_request_details (transaction_id); diff --git a/db_scripts/inji_certify/ddl/verify-presentation_definition.sql b/db_scripts/inji_certify/ddl/verify-presentation_definition.sql new file mode 100644 index 000000000..21793f248 --- /dev/null +++ b/db_scripts/inji_certify/ddl/verify-presentation_definition.sql @@ -0,0 +1,24 @@ +-- 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/. +-- +-- Table required by the embedded inji-verify library. +-- Stores predefined Presentation Definitions used in OpenID4VP sharing. + +CREATE TABLE IF NOT EXISTS presentation_definition ( + id character varying(36) NOT NULL, + input_descriptors text NOT NULL, + name character varying(500), + purpose character varying(500), + vp_format text, + submission_requirements text, + CONSTRAINT pk_presentation_definition PRIMARY KEY (id) +); + +COMMENT ON TABLE presentation_definition IS 'Stores Presentation Definitions used by the embedded inji-verify library'; +COMMENT ON COLUMN presentation_definition.id IS 'Unique identifier for the presentation definition'; +COMMENT ON COLUMN presentation_definition.input_descriptors IS 'JSON array of input descriptor objects defining required credential types'; +COMMENT ON COLUMN presentation_definition.name IS 'Human-readable name for the presentation definition'; +COMMENT ON COLUMN presentation_definition.purpose IS 'Human-readable purpose for which the presentation definition is used'; +COMMENT ON COLUMN presentation_definition.vp_format IS 'JSON describing supported VP formats and algorithms'; +COMMENT ON COLUMN presentation_definition.submission_requirements IS 'JSON describing submission requirement constraints'; diff --git a/db_scripts/inji_certify/ddl/verify-vc_submission.sql b/db_scripts/inji_certify/ddl/verify-vc_submission.sql new file mode 100644 index 000000000..d3a268ace --- /dev/null +++ b/db_scripts/inji_certify/ddl/verify-vc_submission.sql @@ -0,0 +1,17 @@ +-- 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/. +-- +-- Table required by the embedded inji-verify library. +-- Stores individual VC results extracted during VP verification. + +CREATE TABLE IF NOT EXISTS vc_submission ( + transaction_id character varying(40) NOT NULL, + vc text NOT NULL +); + +COMMENT ON TABLE vc_submission IS 'Stores individual VC results from VP verification by the embedded inji-verify library'; +COMMENT ON COLUMN vc_submission.transaction_id IS 'Transaction ID linking the VC result to the issuance session'; +COMMENT ON COLUMN vc_submission.vc IS 'Base64-encoded or JSON VC extracted from the verified VP token'; + +CREATE INDEX IF NOT EXISTS idx_vc_submission_transaction_id ON vc_submission (transaction_id); diff --git a/db_scripts/inji_certify/ddl/verify-vp_submission.sql b/db_scripts/inji_certify/ddl/verify-vp_submission.sql new file mode 100644 index 000000000..28821c593 --- /dev/null +++ b/db_scripts/inji_certify/ddl/verify-vp_submission.sql @@ -0,0 +1,31 @@ +-- 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/. +-- +-- Table required by the embedded inji-verify library. +-- Stores VP submissions from the wallet during the presentation-during-issuance flow. + +CREATE TABLE IF NOT EXISTS vp_submission ( + request_id character varying(40) NOT NULL, + vp_token VARCHAR NULL, + presentation_submission text NULL, + error character varying(100) NULL, + error_description character varying(200) NULL, + response_code character varying(200) NULL, + response_code_expiry_at TIMESTAMP WITH TIME ZONE NULL, + response_code_used boolean DEFAULT false, + CONSTRAINT pk_vp_submission PRIMARY KEY (request_id), + CONSTRAINT uq_vp_submission_response_code UNIQUE (response_code) +); + +COMMENT ON TABLE vp_submission IS 'Stores VP token submissions received by the embedded inji-verify library'; +COMMENT ON COLUMN vp_submission.request_id IS 'Foreign key to authorization_request_details.request_id'; +COMMENT ON COLUMN vp_submission.vp_token IS 'The VP token submitted by the wallet'; +COMMENT ON COLUMN vp_submission.presentation_submission IS 'JSON presentation_submission descriptor from the wallet'; +COMMENT ON COLUMN vp_submission.error IS 'Error code if the wallet reported an error'; +COMMENT ON COLUMN vp_submission.error_description IS 'Human-readable error description from the wallet'; +COMMENT ON COLUMN vp_submission.response_code IS 'Response code for same-device flows (not used in certify)'; +COMMENT ON COLUMN vp_submission.response_code_expiry_at IS 'Expiry of the response code'; +COMMENT ON COLUMN vp_submission.response_code_used IS 'Whether the response code has been consumed'; + +CREATE INDEX IF NOT EXISTS idx_vp_submission_response_code ON vp_submission (response_code); diff --git a/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_rollback.sql b/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_rollback.sql index 83d883f3f..dc9c8621c 100644 --- a/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_rollback.sql +++ b/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_rollback.sql @@ -28,6 +28,15 @@ UPDATE certify.credential_config SET credential_format = 'vc+sd-jwt' WHERE credential_format = 'dc+sd-jwt'; +DROP INDEX IF EXISTS certify.idx_vp_submission_response_code; +DROP INDEX IF EXISTS certify.idx_vc_submission_transaction_id; +DROP INDEX IF EXISTS certify.idx_ard_transaction_id; + +DROP TABLE IF EXISTS certify.vp_submission; +DROP TABLE IF EXISTS certify.vc_submission; +DROP TABLE IF EXISTS certify.presentation_definition; +DROP TABLE IF EXISTS certify.authorization_request_details; + UPDATE certify.credential_config SET proof_types_supported = '{}'::jsonb WHERE proof_types_supported = '{"jwt": {"proof_signing_alg_values_supported": ["RS256", "ES256", "PS256", "EdDSA"]}}'::jsonb; diff --git a/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_upgrade.sql b/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_upgrade.sql index 34eb95bb6..ff06fcae4 100644 --- a/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_upgrade.sql +++ b/db_upgrade_script/inji_certify/sql/0.14.0_to_1.0.0_upgrade.sql @@ -25,6 +25,76 @@ UPDATE certify.credential_config SET credential_format = 'dc+sd-jwt' WHERE credential_format = 'vc+sd-jwt'; +CREATE TABLE IF NOT EXISTS certify.authorization_request_details ( + request_id character varying(40) NOT NULL, + transaction_id character varying(40) NOT NULL, + authorization_details text NOT NULL, + expires_at bigint NOT NULL, + CONSTRAINT pk_authorization_request_details PRIMARY KEY (request_id) +); + +COMMENT ON TABLE certify.authorization_request_details IS 'Stores VP authorization request details created by the embedded inji-verify library'; +COMMENT ON COLUMN certify.authorization_request_details.request_id IS 'Unique request ID for the VP authorization request'; +COMMENT ON COLUMN certify.authorization_request_details.transaction_id IS 'Transaction ID linking this request to the issuance session'; +COMMENT ON COLUMN certify.authorization_request_details.authorization_details IS 'JSON blob containing full OpenID4VP authorization request details'; +COMMENT ON COLUMN certify.authorization_request_details.expires_at IS 'Epoch-millis expiry of the authorization request'; + +CREATE INDEX IF NOT EXISTS idx_ard_transaction_id ON certify.authorization_request_details (transaction_id); + +CREATE TABLE IF NOT EXISTS certify.presentation_definition ( + id character varying(36) NOT NULL, + input_descriptors text NOT NULL, + name character varying(500), + purpose character varying(500), + vp_format text, + submission_requirements text, + CONSTRAINT pk_presentation_definition PRIMARY KEY (id) +); + +COMMENT ON TABLE certify.presentation_definition IS 'Stores Presentation Definitions used by the embedded inji-verify library'; +COMMENT ON COLUMN certify.presentation_definition.id IS 'Unique identifier for the presentation definition'; +COMMENT ON COLUMN certify.presentation_definition.input_descriptors IS 'JSON array of input descriptor objects defining required credential types'; +COMMENT ON COLUMN certify.presentation_definition.name IS 'Human-readable name for the presentation definition'; +COMMENT ON COLUMN certify.presentation_definition.purpose IS 'Human-readable purpose for which the presentation definition is used'; +COMMENT ON COLUMN certify.presentation_definition.vp_format IS 'JSON describing supported VP formats and algorithms'; +COMMENT ON COLUMN certify.presentation_definition.submission_requirements IS 'JSON describing submission requirement constraints'; + +CREATE TABLE IF NOT EXISTS certify.vc_submission ( + transaction_id character varying(40) NOT NULL, + vc text NOT NULL +); + +COMMENT ON TABLE certify.vc_submission IS 'Stores individual VC results from VP verification by the embedded inji-verify library'; +COMMENT ON COLUMN certify.vc_submission.transaction_id IS 'Transaction ID linking the VC result to the issuance session'; +COMMENT ON COLUMN certify.vc_submission.vc IS 'Base64-encoded or JSON VC extracted from the verified VP token'; + +CREATE INDEX IF NOT EXISTS idx_vc_submission_transaction_id ON certify.vc_submission (transaction_id); + +CREATE TABLE IF NOT EXISTS certify.vp_submission ( + request_id character varying(40) NOT NULL, + vp_token VARCHAR NULL, + presentation_submission text NULL, + error character varying(100) NULL, + error_description character varying(200) NULL, + response_code character varying(200) NULL, + response_code_expiry_at TIMESTAMP WITH TIME ZONE NULL, + response_code_used boolean DEFAULT false, + CONSTRAINT pk_vp_submission PRIMARY KEY (request_id), + CONSTRAINT uq_vp_submission_response_code UNIQUE (response_code) +); + +COMMENT ON TABLE certify.vp_submission IS 'Stores VP token submissions received by the embedded inji-verify library'; +COMMENT ON COLUMN certify.vp_submission.request_id IS 'Foreign key to authorization_request_details.request_id'; +COMMENT ON COLUMN certify.vp_submission.vp_token IS 'The VP token submitted by the wallet'; +COMMENT ON COLUMN certify.vp_submission.presentation_submission IS 'JSON presentation_submission descriptor from the wallet'; +COMMENT ON COLUMN certify.vp_submission.error IS 'Error code if the wallet reported an error'; +COMMENT ON COLUMN certify.vp_submission.error_description IS 'Human-readable error description from the wallet'; +COMMENT ON COLUMN certify.vp_submission.response_code IS 'Response code for same-device flows (not used in certify)'; +COMMENT ON COLUMN certify.vp_submission.response_code_expiry_at IS 'Expiry of the response code'; +COMMENT ON COLUMN certify.vp_submission.response_code_used IS 'Whether the response code has been consumed'; + +CREATE INDEX IF NOT EXISTS idx_vp_submission_response_code ON certify.vp_submission (response_code); + UPDATE certify.credential_config SET proof_types_supported = '{"jwt": {"proof_signing_alg_values_supported": ["RS256", "ES256", "PS256", "EdDSA"]}}'::jsonb WHERE proof_types_supported = '{}'::jsonb; diff --git a/docker-compose/docker-compose-injistack/certify_init.sql b/docker-compose/docker-compose-injistack/certify_init.sql index a12d28f00..6a199e59c 100644 --- a/docker-compose/docker-compose-injistack/certify_init.sql +++ b/docker-compose/docker-compose-injistack/certify_init.sql @@ -344,4 +344,80 @@ CREATE INDEX IF NOT EXISTS idx_iar_session_request_id ON certify.iar_session(req CREATE INDEX IF NOT EXISTS idx_iar_session_expires_at ON certify.iar_session(expires_at); CREATE INDEX IF NOT EXISTS idx_iar_session_authorization_code_used ON certify.iar_session(authorization_code, is_code_used) WHERE authorization_code IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_iar_session_scope ON certify.iar_session(scope); -CREATE INDEX IF NOT EXISTS idx_iar_session_transaction_id ON certify.iar_session(transaction_id); \ No newline at end of file +CREATE INDEX IF NOT EXISTS idx_iar_session_transaction_id ON certify.iar_session(transaction_id); + +-- ===================================================== +-- inji-verify library tables (embedded in certify-service) +-- These tables support Presentation During Issuance (VP request/submission) +-- ===================================================== + +CREATE TABLE IF NOT EXISTS certify.authorization_request_details ( + request_id character varying(40) NOT NULL, + transaction_id character varying(40) NOT NULL, + authorization_details text NOT NULL, + expires_at bigint NOT NULL, + CONSTRAINT pk_authorization_request_details PRIMARY KEY (request_id) +); + +COMMENT ON TABLE certify.authorization_request_details IS 'Stores VP authorization request details created by the embedded inji-verify library'; +COMMENT ON COLUMN certify.authorization_request_details.request_id IS 'Unique request ID for the VP authorization request'; +COMMENT ON COLUMN certify.authorization_request_details.transaction_id IS 'Transaction ID linking this request to the issuance session'; +COMMENT ON COLUMN certify.authorization_request_details.authorization_details IS 'JSON blob containing full OpenID4VP authorization request details'; +COMMENT ON COLUMN certify.authorization_request_details.expires_at IS 'Epoch-millis expiry of the authorization request'; + +CREATE INDEX IF NOT EXISTS idx_ard_transaction_id ON certify.authorization_request_details (transaction_id); + +CREATE TABLE IF NOT EXISTS certify.presentation_definition ( + id character varying(36) NOT NULL, + input_descriptors text NOT NULL, + name character varying(500), + purpose character varying(500), + vp_format text, + submission_requirements text, + CONSTRAINT pk_presentation_definition PRIMARY KEY (id) +); + +COMMENT ON TABLE certify.presentation_definition IS 'Stores Presentation Definitions used by the embedded inji-verify library'; +COMMENT ON COLUMN certify.presentation_definition.id IS 'Unique identifier for the presentation definition'; +COMMENT ON COLUMN certify.presentation_definition.input_descriptors IS 'JSON array of input descriptor objects defining required credential types'; +COMMENT ON COLUMN certify.presentation_definition.name IS 'Human-readable name for the presentation definition'; +COMMENT ON COLUMN certify.presentation_definition.purpose IS 'Human-readable purpose for which the presentation definition is used'; +COMMENT ON COLUMN certify.presentation_definition.vp_format IS 'JSON describing supported VP formats and algorithms'; +COMMENT ON COLUMN certify.presentation_definition.submission_requirements IS 'JSON describing submission requirement constraints'; + +CREATE TABLE IF NOT EXISTS certify.vc_submission ( + transaction_id character varying(40) NOT NULL, + vc text NOT NULL +); + +COMMENT ON TABLE certify.vc_submission IS 'Stores individual VC results from VP verification by the embedded inji-verify library'; +COMMENT ON COLUMN certify.vc_submission.transaction_id IS 'Transaction ID linking the VC result to the issuance session'; +COMMENT ON COLUMN certify.vc_submission.vc IS 'Base64-encoded or JSON VC extracted from the verified VP token'; + +CREATE INDEX IF NOT EXISTS idx_vc_submission_transaction_id ON certify.vc_submission (transaction_id); + +CREATE TABLE IF NOT EXISTS certify.vp_submission ( + request_id character varying(40) NOT NULL, + vp_token VARCHAR NULL, + presentation_submission text NULL, + error character varying(100) NULL, + error_description character varying(200) NULL, + response_code character varying(200) NULL, + response_code_expiry_at TIMESTAMP WITH TIME ZONE NULL, + response_code_used boolean DEFAULT false, + CONSTRAINT pk_vp_submission PRIMARY KEY (request_id), + CONSTRAINT uq_vp_submission_response_code UNIQUE (response_code) +); + +COMMENT ON TABLE certify.vp_submission IS 'Stores VP token submissions received by the embedded inji-verify library'; +COMMENT ON COLUMN certify.vp_submission.request_id IS 'Foreign key to authorization_request_details.request_id'; +COMMENT ON COLUMN certify.vp_submission.vp_token IS 'The VP token submitted by the wallet'; +COMMENT ON COLUMN certify.vp_submission.presentation_submission IS 'JSON presentation_submission descriptor from the wallet'; +COMMENT ON COLUMN certify.vp_submission.error IS 'Error code if the wallet reported an error'; +COMMENT ON COLUMN certify.vp_submission.error_description IS 'Human-readable error description from the wallet'; +COMMENT ON COLUMN certify.vp_submission.response_code IS 'Response code for same-device flows (not used in certify)'; +COMMENT ON COLUMN certify.vp_submission.response_code_expiry_at IS 'Expiry of the response code'; +COMMENT ON COLUMN certify.vp_submission.response_code_used IS 'Whether the response code has been consumed'; + +CREATE INDEX IF NOT EXISTS idx_vp_submission_response_code ON certify.vp_submission (response_code); + diff --git a/docker-compose/docker-compose-injistack/config/certify-default.properties b/docker-compose/docker-compose-injistack/config/certify-default.properties index d1fcc5c10..a57b5f814 100644 --- a/docker-compose/docker-compose-injistack/config/certify-default.properties +++ b/docker-compose/docker-compose-injistack/config/certify-default.properties @@ -235,16 +235,19 @@ mosip.certify.oauth.interactive-authorization-endpoint=${mosip.certify.authoriza mosip.certify.oauth.access-token.audience=${mosip.certify.identifier}/v1/certify/issuance/credential -## ---------------------------------------- Verify Service Configuration -------------------------------------------------------- +## ---------------------------------------- inji-verify Library Configuration (embedded in-process) -------------------------------------------------------- -# Self-hosted VP Verifier service (integrated with Inji Certify) -mosip.certify.verify.service.base-url=http://verify-service:8080 -mosip.certify.verify.service.vp-request-endpoint=${mosip.certify.verify.service.base-url}/v1/verify/vp-request -mosip.certify.verify.service.vp-result-endpoint=${mosip.certify.verify.service.base-url}/v1/verify/vp-result mosip.certify.verify.service.verifier-client-id=certify-verifier-client mosip.certify.vp-request.config-file-url=http://certify-nginx/vp_request_config.json +## inji-verify library properties +inji.vp-request.long-polling-timeout=55000 +inji.vp-submission.base-url=${mosip.certify.oauth.interactive-authorization-endpoint} +inji.did.verify.public.key.uri=${mosip.certify.data-provider-plugin.did-url}#key-0 +inji.verify.keystore.file.path=classpath:sample-keystore/test.p12 +inji.verify.keystore.file.pass=mosip + ## ------------------------------------------- Pre-Authorized Code Configuration ------------------------------------------- mosip.certify.pre-auth-code.default-expiry-seconds=600 mosip.certify.pre-auth-code.min-expiry-seconds=60 diff --git a/docker-compose/docker-compose-injistack/docker-compose.yaml b/docker-compose/docker-compose-injistack/docker-compose.yaml index d30c0915d..e3c02863e 100644 --- a/docker-compose/docker-compose-injistack/docker-compose.yaml +++ b/docker-compose/docker-compose-injistack/docker-compose.yaml @@ -9,7 +9,6 @@ services: volumes: - ./certify_init.sql:/docker-entrypoint-initdb.d/certify_init.sql - ./mimoto_init.sql:/docker-entrypoint-initdb.d/mimoto_init.sql - - ./verify_init.sql:/docker-entrypoint-initdb.d/verify_init.sql networks: - network ports: @@ -108,32 +107,6 @@ services: networks: - network -## Enable this to test Presentation during Issuance locally -# verify-service: -# container_name: verify-service -# image: injistack/inji-verify-service:0.17.0 -# user: root -# ports: -# - "8095:8080" -# environment: -# - active_profile_env=default -# - DATABASE_HOST=database -# - DATABASE_PORT=5432 -# - DATABASE_NAME=inji_verify -# - DATABASE_SCHEMA=verify -# - DATABASE_USERNAME=postgres -# - DATABASE_PASSWORD=postgres -# - INJI_VP_REQUEST_LONG_POLLING_TIMEOUT=55000 -# - INJI_VP_SUBMISSION_BASE_URL=http://verify-service:8080/v1/verify -# - INJI_DID_VERIFY_URI=did:web:verify-service:v1:verify -# - INJI_DID_VERIFY_PUBLIC_KEY_URI=did:web:verify-service:v1:verify#key-0 -# - INJI_KEYSTORE_FILE_PASS=mosip -# - INJI_VERIFY_REDIRECT_URI=http://verify-service:8080 -# depends_on: -# - database -# networks: -# - network - networks: network: name: mosip_network