From b2ce5b228eccc3d9d678a819e9b5c1fad7c472fb Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Tue, 23 Jun 2026 16:02:19 +0530 Subject: [PATCH 01/10] [INJICERT-#902]: Integrate verify as embedded library Signed-off-by: Sakshiijk --- certify-service/pom.xml | 6 + .../certify/CertifyServiceApplication.java | 24 +- .../io/mosip/certify/config/AppConfig.java | 7 +- .../config/CertifyContextPathOverride.java | 19 ++ .../services/IarPresentationService.java | 210 +++++++---------- .../certify/services/IarVpRequestService.java | 219 ++++++++++++------ .../resources/application-local.properties | 13 +- .../verify-authorization_request_details.sql | 22 ++ .../ddl/verify-presentation_definition.sql | 24 ++ .../inji_certify/ddl/verify-vc_submission.sql | 17 ++ .../inji_certify/ddl/verify-vp_submission.sql | 31 +++ .../docker-compose-injistack/certify_init.sql | 36 ++- .../config/certify-default.properties | 13 +- .../docker-compose.yaml | 30 +-- 14 files changed, 429 insertions(+), 242 deletions(-) create mode 100644 certify-service/src/main/java/io/mosip/certify/config/CertifyContextPathOverride.java create mode 100644 db_scripts/inji_certify/ddl/verify-authorization_request_details.sql create mode 100644 db_scripts/inji_certify/ddl/verify-presentation_definition.sql create mode 100644 db_scripts/inji_certify/ddl/verify-vc_submission.sql create mode 100644 db_scripts/inji_certify/ddl/verify-vp_submission.sql diff --git a/certify-service/pom.xml b/certify-service/pom.xml index 0a9dc52e1..569c1acaa 100644 --- a/certify-service/pom.xml +++ b/certify-service/pom.xml @@ -236,6 +236,12 @@ kernel-biometrics-api 1.3.0 + + io.inji.verify + verify-service + 0.18.1 + lib + 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..d9973a6fc 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,25 @@ 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) +@EntityScan(basePackages = { + "io.mosip.certify.entity", + "io.mosip.kernel.keymanagerservice.entity", + "io.inji.verify.entity" +}) +@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 +37,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," + + // Inji Verify library — service and repository beans only (not controllers/filters) + "io.inji.verify.services," + + "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..544c2b111 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 @@ -33,9 +33,9 @@ import io.mosip.kernel.keymanagerservice.service.KeymanagerService; 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"}) +@Configuration("certifyAppConfig") +@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.entity"}) @Slf4j public class AppConfig implements ApplicationRunner { @@ -83,6 +83,7 @@ public RestTemplate restTemplate() { 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/IarPresentationService.java b/certify-service/src/main/java/io/mosip/certify/services/IarPresentationService.java index ca5faccf5..1d9b35b0c 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.VCResultDto; +import io.inji.verify.dto.submission.VPTokenResultDto; +import io.inji.verify.enums.VPResultStatus; +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,188 +151,161 @@ 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); + + VPTokenResultDto vpResult = vpSubmissionService.getVPResult(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 (VPResultStatus.SUCCESS == vpResult.getVpResultStatus()) { 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: {}, status: {}", + transactionId, vpResult.getVpResultStatus()); } } 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(VPTokenResultDto vpResult) { + Map details = new HashMap<>(); + details.put("vpResultStatus", vpResult.getVpResultStatus().name()); + List> vcResultsList = new ArrayList<>(); + if (vpResult.getVcResults() != null) { + for (VCResultDto vcResult : vpResult.getVcResults()) { + Map vcEntry = new HashMap<>(); + vcEntry.put("vc", vcResult.getVc()); + vcResultsList.add(vcEntry); + } + } + details.put("vcResults", vcResultsList); + 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 if (StringUtils.hasText(session.getAuthorizationCode())) { - // 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]; new java.security.SecureRandom().nextBytes(additionalBytes); 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 +313,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..bbf16e6fc 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,97 +99,99 @@ 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 libraryResponse = vpRequestService.createAuthorizationRequest(vpRequestCreateDto); + + if (libraryResponse == 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(), libraryResponse.getTransactionId()); - return verifyResponse; + return mapToVerifyVpResponse(libraryResponse); } 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 void validateConfiguration() { - if (!StringUtils.hasText(verifyServiceVpRequestEndpoint)) { - throw new IllegalStateException("mosip.certify.verify.service.vp-request-endpoint must be configured"); + private VerifyVpResponse mapToVerifyVpResponse(VPRequestResponseDto dto) { + VerifyVpResponse response = new VerifyVpResponse(); + response.setTransactionId(dto.getTransactionId()); + response.setRequestId(dto.getRequestId()); + response.setExpiresAt(dto.getExpiresAt()); + + AuthorizationRequestResponseDto la = dto.getAuthorizationDetails(); + if (la != null) { + VerifyVpResponse.AuthorizationDetails ad = new VerifyVpResponse.AuthorizationDetails(); + ad.setClientId(la.getClientId()); + ad.setNonce(la.getNonce()); + ad.setResponseUri(la.getResponseUri()); + ad.setResponseType(la.getResponseType()); + ad.setResponseMode(la.getResponseMode()); + ad.setIssuedAt(la.getIssuedAt()); + ad.setPresentationDefinition( + objectMapper.convertValue(la.getPresentationDefinition(), PresentationDefinition.class)); + response.setAuthorizationDetails(ad); } + return response; + } + + private void validateConfiguration() { if (!StringUtils.hasText(verifierClientId)) { throw new IllegalStateException("mosip.certify.verify.service.verifier-client-id must be configured"); } @@ -199,4 +200,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/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/docker-compose/docker-compose-injistack/certify_init.sql b/docker-compose/docker-compose-injistack/certify_init.sql index a12d28f00..76851f37e 100644 --- a/docker-compose/docker-compose-injistack/certify_init.sql +++ b/docker-compose/docker-compose-injistack/certify_init.sql @@ -344,4 +344,38 @@ 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 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 +); + +CREATE TABLE certify.presentation_definition( + id character varying(36) NOT NULL, + input_descriptors jsonb NOT NULL, + name character varying(500), + purpose character varying(500), + vp_format text, + submission_requirements text +); + +CREATE TABLE certify.vc_submission( + transaction_id character varying(40) NOT NULL, + vc text NOT NULL +); + +CREATE TABLE certify.vp_submission( + request_id character varying(40) NOT NULL, + vp_token VARCHAR NOT NULL, + presentation_submission text NOT NULL, + error character varying(100) NULL, + error_description character varying(200) NULL +); diff --git a/docker-compose/docker-compose-injistack/config/certify-default.properties b/docker-compose/docker-compose-injistack/config/certify-default.properties index f35e329fe..637239af6 100644 --- a/docker-compose/docker-compose-injistack/config/certify-default.properties +++ b/docker-compose/docker-compose-injistack/config/certify-default.properties @@ -236,16 +236,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..ce4d98b34 100644 --- a/docker-compose/docker-compose-injistack/docker-compose.yaml +++ b/docker-compose/docker-compose-injistack/docker-compose.yaml @@ -9,7 +9,8 @@ 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 + # verify_init.sql removed — verify tables are now created inside inji_certify DB + # as the inji-verify library is embedded in certify-service (shares the same datasource) networks: - network ports: @@ -108,31 +109,8 @@ 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 +## verify-service container REMOVED — inji-verify is now embedded as a library inside certify-service. +## Verify tables are created in inji_certify DB (certify schema) via certify_init.sql. networks: network: From 6f942f6fe621b9f379f65cab2473879af0cabb9f Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Wed, 24 Jun 2026 15:45:19 +0530 Subject: [PATCH 02/10] [INJICERT-#902]: Address review changes Signed-off-by: Sakshiijk --- .../certify/CertifyServiceApplication.java | 2 +- .../io/mosip/certify/config/AppConfig.java | 14 ++++-- .../services/IarPresentationService.java | 43 +++++++++++-------- .../certify/services/IarVpRequestService.java | 32 +++++++------- .../docker-compose.yaml | 5 --- 5 files changed, 53 insertions(+), 43 deletions(-) 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 d9973a6fc..797ac3b2f 100644 --- a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java +++ b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java @@ -42,8 +42,8 @@ "io.mosip.kernel.keymanagerservice.repository," + "io.mosip.kernel.keymanagerservice.entity," + "io.mosip.kernel.partnercertservice.helper," + - // Inji Verify library — service and repository beans only (not controllers/filters) "io.inji.verify.services," + + "io.inji.verify.key.impl," + "io.inji.verify.repository," + "${mosip.certify.integration.scan-base-package}") public class CertifyServiceApplication { 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 544c2b111..63c61d39a 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 @@ -33,9 +33,17 @@ import io.mosip.kernel.keymanagerservice.service.KeymanagerService; import lombok.extern.slf4j.Slf4j; -@Configuration("certifyAppConfig") -@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.entity"}) +@Configuration +@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 { 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 1d9b35b0c..7f72906ed 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,9 +6,9 @@ package io.mosip.certify.services; import com.fasterxml.jackson.databind.JsonNode; -import io.inji.verify.dto.result.VCResultDto; -import io.inji.verify.dto.submission.VPTokenResultDto; -import io.inji.verify.enums.VPResultStatus; +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; @@ -189,7 +189,9 @@ private VpVerificationResponse getVpVerificationResult(String transactionId) thr List requestIds = vpRequestService.getLatestRequestIdFor(transactionId); log.debug("Fetching VP result for transactionId: {}, requestIds: {}", transactionId, requestIds); - VPTokenResultDto vpResult = vpSubmissionService.getVPResult(requestIds, transactionId); + VerificationRequestDto verificationRequest = new VerificationRequestDto(); + verificationRequest.setIncludeClaims(true); + VPVerificationResultDto vpResult = vpSubmissionService.getVPResultV2(verificationRequest, requestIds, transactionId); VpVerificationResponse response = new VpVerificationResponse(); response.setRequestId(transactionId); @@ -198,15 +200,15 @@ private VpVerificationResponse getVpVerificationResult(String transactionId) thr Map verificationDetails = buildVerificationDetails(vpResult); response.setVerificationDetails(verificationDetails); - if (VPResultStatus.SUCCESS == vpResult.getVpResultStatus()) { + if (vpResult.isAllChecksSuccessful()) { response.setStatus(IarStatus.OK.getValue()); 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"); - log.warn("VP cryptographic verification failed for transaction_id: {}, status: {}", - transactionId, vpResult.getVpResultStatus()); + log.warn("VP cryptographic verification failed for transaction_id: {}", + transactionId); } } else { response.setStatus(IarStatus.ERROR.getValue()); @@ -230,18 +232,19 @@ private VpVerificationResponse getVpVerificationResult(String transactionId) thr } } - private Map buildVerificationDetails(VPTokenResultDto vpResult) { + private Map buildVerificationDetails(VPVerificationResultDto vpResult) { Map details = new HashMap<>(); - details.put("vpResultStatus", vpResult.getVpResultStatus().name()); - List> vcResultsList = new ArrayList<>(); - if (vpResult.getVcResults() != null) { - for (VCResultDto vcResult : vpResult.getVcResults()) { - Map vcEntry = new HashMap<>(); - vcEntry.put("vc", vcResult.getVc()); - vcResultsList.add(vcEntry); + details.put("allChecksSuccessful", vpResult.isAllChecksSuccessful()); + List> credentialResultsList = new ArrayList<>(); + if (vpResult.getCredentialResults() != null) { + for (CredentialResultsDto credentialResult : vpResult.getCredentialResults()) { + Map credentialEntry = new HashMap<>(); + credentialEntry.put("verifiableCredential", credentialResult.getVerifiableCredential()); + credentialEntry.put("allChecksSuccessful", credentialResult.isAllChecksSuccessful()); + credentialResultsList.add(credentialEntry); } } - details.put("vcResults", vcResultsList); + details.put("credentialResults", credentialResultsList); return details; } @@ -249,7 +252,9 @@ private Map buildVerificationDetails(VPTokenResultDto vpResult) * Generate and store authorization code. */ private String generateAndStoreAuthorizationCode(IarSession session) throws CertifyException { + // Idempotency guard: if an authorization code already exists for this session if (StringUtils.hasText(session.getAuthorizationCode())) { + // 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", @@ -258,17 +263,19 @@ private String generateAndStoreAuthorizationCode(IarSession session) throws Cert 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", "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]; new java.security.SecureRandom().nextBytes(additionalBytes); 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 bbf16e6fc..2c6eaac6a 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 @@ -113,16 +113,16 @@ public VerifyVpResponse createVpRequest(InteractiveAuthorizationRequest iarReque ); log.debug("Calling inji-verify library createAuthorizationRequest for verifier client_id: {}", verifierClientId); - VPRequestResponseDto libraryResponse = vpRequestService.createAuthorizationRequest(vpRequestCreateDto); + VPRequestResponseDto vpAuthRequest = vpRequestService.createAuthorizationRequest(vpRequestCreateDto); - if (libraryResponse == null) { + if (vpAuthRequest == null) { throw new CertifyException("unknown_error", "Empty response from inji-verify library"); } log.info("VP request created via library for client_id: {}, transactionId: {}", - iarRequest.getClientId(), libraryResponse.getTransactionId()); + iarRequest.getClientId(), vpAuthRequest.getTransactionId()); - return mapToVerifyVpResponse(libraryResponse); + return mapToVerifyVpResponse(vpAuthRequest); } catch (CertifyException e) { throw e; @@ -175,18 +175,18 @@ private VerifyVpResponse mapToVerifyVpResponse(VPRequestResponseDto dto) { response.setRequestId(dto.getRequestId()); response.setExpiresAt(dto.getExpiresAt()); - AuthorizationRequestResponseDto la = dto.getAuthorizationDetails(); - if (la != null) { - VerifyVpResponse.AuthorizationDetails ad = new VerifyVpResponse.AuthorizationDetails(); - ad.setClientId(la.getClientId()); - ad.setNonce(la.getNonce()); - ad.setResponseUri(la.getResponseUri()); - ad.setResponseType(la.getResponseType()); - ad.setResponseMode(la.getResponseMode()); - ad.setIssuedAt(la.getIssuedAt()); - ad.setPresentationDefinition( - objectMapper.convertValue(la.getPresentationDefinition(), PresentationDefinition.class)); - response.setAuthorizationDetails(ad); + 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; } diff --git a/docker-compose/docker-compose-injistack/docker-compose.yaml b/docker-compose/docker-compose-injistack/docker-compose.yaml index ce4d98b34..e3c02863e 100644 --- a/docker-compose/docker-compose-injistack/docker-compose.yaml +++ b/docker-compose/docker-compose-injistack/docker-compose.yaml @@ -9,8 +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 removed — verify tables are now created inside inji_certify DB - # as the inji-verify library is embedded in certify-service (shares the same datasource) networks: - network ports: @@ -109,9 +107,6 @@ services: networks: - network -## verify-service container REMOVED — inji-verify is now embedded as a library inside certify-service. -## Verify tables are created in inji_certify DB (certify schema) via certify_init.sql. - networks: network: name: mosip_network From 109723e3376eb7f65f01a43f8d01266784883948 Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Wed, 24 Jun 2026 16:06:49 +0530 Subject: [PATCH 03/10] [INJICERT-#902]: Update db_scripts Signed-off-by: Sakshiijk --- db_scripts/inji_certify/ddl.sql | 4 ++ .../sql/0.14.0_to_1.0.0_rollback.sql | 11 ++- .../sql/0.14.0_to_1.0.0_upgrade.sql | 72 ++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) 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_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 c5a320351..059c2115d 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 @@ -26,4 +26,13 @@ COMMENT ON COLUMN certify.credential_config.credential_subject IS 'Credential Su UPDATE certify.credential_config SET credential_format = 'vc+sd-jwt' -WHERE credential_format = 'dc+sd-jwt'; \ No newline at end of file +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; \ No newline at end of file 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 62a68b4f5..88b1b0bc0 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 @@ -23,4 +23,74 @@ COMMENT ON COLUMN certify.credential_config.claims IS 'Claims: JSON object conta UPDATE certify.credential_config SET credential_format = 'dc+sd-jwt' -WHERE credential_format = 'vc+sd-jwt'; \ No newline at end of file +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); \ No newline at end of file From 6cb687cffb086fec2f42e36b95ecdaac33ff0d04 Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Wed, 24 Jun 2026 16:46:36 +0530 Subject: [PATCH 04/10] [INJICERT-#902]: Added test.p12 file Signed-off-by: Sakshiijk --- .../src/main/resources/sample-keystore/test.p12 | Bin 0 -> 974 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 certify-service/src/main/resources/sample-keystore/test.p12 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 0000000000000000000000000000000000000000..63cd45e3fa46ca1f67a7956d251485ff19f29ca0 GIT binary patch literal 974 zcmXqLVm`&h$ZXKWT*Ahw)#lOmotKfFaX}MvDoYb{B2YNipovigMT(J?rHPTzpz)tU z<8L-@s2(0JMy3UgYYiG#8Kl8gv+^258dxB>JOvZ0NEINWiZB8FUY91JB4sSL#oB?gKJ2~k547NL;L+*Aca zb5mn;0|OIF0~1pdgC-_NxFUA81x-vwKp{OQMg}0Ij*wv_6d>&p3%nY+-^oAnGBf-a zv+c?rzoRz91jxcW%Zjq~4~ zzUg>=f5hf}*{!ygtAR zoxV2Ba8;X#;>@+38P~*_1ETF#x-!1oeA)8;(zE727BuVjY}~c(;*#`)5Z}dq@jjQ9 z+iqF&+~TjZPSRIf4@rZnUA)_vnlv)bi=4k-nG><>d#-vkgXAg3Ag%nz`JbIOFgTSS zyv@@Y{C)Dr%guVuRhCX?XKz+JW8F|Ndv9p+huifmvmRNVu<4EaEO+i#YwqXpT;bUn zg(~@h;u%IKMAjzV3h1 z@b*H4&&Ej)c8UlHmwumptoD8JwFN(1m+V>of6?+i2d6L9S= Date: Thu, 25 Jun 2026 11:48:24 +0530 Subject: [PATCH 05/10] [INJICERT-#902]: Address codeRabbit comments Signed-off-by: Sakshiijk --- .../services/IarPresentationService.java | 12 +-- .../certify/services/IarVpRequestService.java | 3 + .../docker-compose-injistack/certify_init.sql | 80 ++++++++++++++----- 3 files changed, 71 insertions(+), 24 deletions(-) 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 7f72906ed..9368ba6b0 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 @@ -81,7 +81,9 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr submitVpToVerifier(session.getResponseUri(), presentationRequest.getOpenid4vpPresentation(), session.getRequestId(), - session.getTransactionId()); + session.getTransactionId(), + session.getClientId(), + session.getVerifyNonce()); VpVerificationResponse verificationResponse = getVpVerificationResult(session.getTransactionId()); @@ -124,7 +126,7 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr * 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 { + private void submitVpToVerifier(String responseUri, String vpPresentationJson, String requestId, String transactionId, String clientId, String nonce) throws CertifyException { log.info("Submitting VP to vpSubmissionService with state(requestId)={}, transactionId={}", requestId, transactionId); @@ -161,7 +163,7 @@ private void submitVpToVerifier(String responseUri, String vpPresentationJson, S } log.debug("Calling vpSubmissionService.submit with state(requestId): {}", requestId); - ResponseEntity submissionResponse = vpSubmissionService.submit(vpTokenJson, presentationSubmissionJson, requestId, null, null); + ResponseEntity submissionResponse = vpSubmissionService.submit(vpTokenJson, presentationSubmissionJson, requestId, clientId, nonce); if (submissionResponse.getStatusCode().is2xxSuccessful()) { log.info("VP submission accepted, status: {}", submissionResponse.getStatusCode()); @@ -239,12 +241,12 @@ private Map buildVerificationDetails(VPVerificationResultDto vpR if (vpResult.getCredentialResults() != null) { for (CredentialResultsDto credentialResult : vpResult.getCredentialResults()) { Map credentialEntry = new HashMap<>(); - credentialEntry.put("verifiableCredential", credentialResult.getVerifiableCredential()); + credentialEntry.put("vc", credentialResult.getVerifiableCredential()); credentialEntry.put("allChecksSuccessful", credentialResult.isAllChecksSuccessful()); credentialResultsList.add(credentialEntry); } } - details.put("credentialResults", credentialResultsList); + details.put("vcResults", credentialResultsList); return details; } 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 2c6eaac6a..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 @@ -192,6 +192,9 @@ private VerifyVpResponse mapToVerifyVpResponse(VPRequestResponseDto dto) { } private void validateConfiguration() { + 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"); } diff --git a/docker-compose/docker-compose-injistack/certify_init.sql b/docker-compose/docker-compose-injistack/certify_init.sql index 76851f37e..6a199e59c 100644 --- a/docker-compose/docker-compose-injistack/certify_init.sql +++ b/docker-compose/docker-compose-injistack/certify_init.sql @@ -351,31 +351,73 @@ CREATE INDEX IF NOT EXISTS idx_iar_session_transaction_id ON certify.iar_session -- These tables support Presentation During Issuance (VP request/submission) -- ===================================================== -CREATE TABLE certify.authorization_request_details ( - request_id character varying(40) NOT NULL, - transaction_id character varying(40) NOT NULL, +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 + expires_at bigint NOT NULL, + CONSTRAINT pk_authorization_request_details PRIMARY KEY (request_id) ); -CREATE TABLE certify.presentation_definition( - id character varying(36) NOT NULL, - input_descriptors jsonb NOT NULL, - name character varying(500), - purpose character varying(500), - vp_format text, - submission_requirements text +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) ); -CREATE TABLE certify.vc_submission( +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 + vc text NOT NULL ); -CREATE TABLE certify.vp_submission( - request_id character varying(40) NOT NULL, - vp_token VARCHAR NOT NULL, - presentation_submission text NOT NULL, - error character varying(100) NULL, - error_description character varying(200) 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); + From 6118da89ec4f35c27312f472f55e680d4933d44d Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Wed, 1 Jul 2026 14:17:51 +0530 Subject: [PATCH 06/10] Update pom.xml Signed-off-by: Sakshiijk --- certify-service/pom.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/certify-service/pom.xml b/certify-service/pom.xml index 569c1acaa..7d3f45960 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 + @@ -239,8 +252,7 @@ io.inji.verify verify-service - 0.18.1 - lib + 0.18.2-SNAPSHOT From d508fbb4fc63a333540009a7a94cba17f503848f Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Sun, 5 Jul 2026 22:05:04 +0530 Subject: [PATCH 07/10] [INJICERT-#902]: resolve verify-service integration issues Signed-off-by: Sakshiijk --- .../io/mosip/certify/CertifyServiceApplication.java | 2 +- .../main/java/io/mosip/certify/config/AppConfig.java | 2 +- .../certify/services/IarPresentationService.java | 2 +- .../src/test/resources/application-test.properties | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) 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 797ac3b2f..3a42d2493 100644 --- a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java +++ b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java @@ -19,7 +19,7 @@ @EntityScan(basePackages = { "io.mosip.certify.entity", "io.mosip.kernel.keymanagerservice.entity", - "io.inji.verify.entity" + "io.inji.verify.models" }) @SpringBootApplication(scanBasePackages = "io.mosip.certify," + "io.mosip.kernel.crypto," + 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 63c61d39a..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 @@ -87,7 +87,7 @@ public RestTemplate restTemplate() { return new RestTemplate(requestFactory); } - @Bean + @Bean("certifyPixelPass") public PixelPass pixelPass() { return new PixelPass(); } 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 9368ba6b0..d2f0d41df 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 @@ -163,7 +163,7 @@ private void submitVpToVerifier(String responseUri, String vpPresentationJson, S } log.debug("Calling vpSubmissionService.submit with state(requestId): {}", requestId); - ResponseEntity submissionResponse = vpSubmissionService.submit(vpTokenJson, presentationSubmissionJson, requestId, clientId, nonce); + ResponseEntity submissionResponse = vpSubmissionService.submit(vpTokenJson, presentationSubmissionJson, requestId, null, null); if (submissionResponse.getStatusCode().is2xxSuccessful()) { log.info("VP submission accepted, status: {}", submissionResponse.getStatusCode()); 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 + From 321920b41903e3a0a6a8a78a2087d52ad271ce9b Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Mon, 6 Jul 2026 12:28:24 +0530 Subject: [PATCH 08/10] [INJICERT-#902]: Address review changes Signed-off-by: Sakshiijk --- certify-service/pom.xml | 7 +++++++ .../java/io/mosip/certify/CertifyServiceApplication.java | 3 +-- .../io/mosip/certify/services/IarPresentationService.java | 6 ++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/certify-service/pom.xml b/certify-service/pom.xml index 7d3f45960..f941c24c1 100644 --- a/certify-service/pom.xml +++ b/certify-service/pom.xml @@ -253,6 +253,13 @@ io.inji.verify verify-service 0.18.2-SNAPSHOT + lib + + + 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 3a42d2493..b1a298a13 100644 --- a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java +++ b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java @@ -18,8 +18,7 @@ @Import(io.inji.verify.config.AppConfig.class) @EntityScan(basePackages = { "io.mosip.certify.entity", - "io.mosip.kernel.keymanagerservice.entity", - "io.inji.verify.models" + "io.mosip.kernel.keymanagerservice.entity" }) @SpringBootApplication(scanBasePackages = "io.mosip.certify," + "io.mosip.kernel.crypto," + 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 d2f0d41df..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 @@ -81,9 +81,7 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr submitVpToVerifier(session.getResponseUri(), presentationRequest.getOpenid4vpPresentation(), session.getRequestId(), - session.getTransactionId(), - session.getClientId(), - session.getVerifyNonce()); + session.getTransactionId()); VpVerificationResponse verificationResponse = getVpVerificationResult(session.getTransactionId()); @@ -126,7 +124,7 @@ public IarAuthorizationResponse processVpPresentation(IarAuthorizationRequest pr * 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, String clientId, String nonce) throws CertifyException { + private void submitVpToVerifier(String responseUri, String vpPresentationJson, String requestId, String transactionId) throws CertifyException { log.info("Submitting VP to vpSubmissionService with state(requestId)={}, transactionId={}", requestId, transactionId); From 738f1860f33bd3f4d8846a03271115c4d31cf2bb Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Mon, 6 Jul 2026 15:00:39 +0530 Subject: [PATCH 09/10] [INJICERT-#902]: Remove duplicate code and update pixelPass bean Signed-off-by: Sakshiijk --- .../main/java/io/mosip/certify/CertifyServiceApplication.java | 4 ---- .../io/mosip/certify/services/CertifyIssuanceServiceImpl.java | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) 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 b1a298a13..fb2144b95 100644 --- a/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java +++ b/certify-service/src/main/java/io/mosip/certify/CertifyServiceApplication.java @@ -16,10 +16,6 @@ @EnableAsync @EnableCaching @Import(io.inji.verify.config.AppConfig.class) -@EntityScan(basePackages = { - "io.mosip.certify.entity", - "io.mosip.kernel.keymanagerservice.entity" -}) @SpringBootApplication(scanBasePackages = "io.mosip.certify," + "io.mosip.kernel.crypto," + "io.mosip.kernel.keymanager.hsm," + 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; From 36403305aae7d5511b904f941697a4bbc4db3895 Mon Sep 17 00:00:00 2001 From: Sakshiijk Date: Mon, 6 Jul 2026 17:11:16 +0530 Subject: [PATCH 10/10] [INJICERT_902_release-1.0.x]:update inji-verify dependency Signed-off-by: Sakshiijk --- certify-service/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/certify-service/pom.xml b/certify-service/pom.xml index f941c24c1..c52b7b154 100644 --- a/certify-service/pom.xml +++ b/certify-service/pom.xml @@ -253,7 +253,6 @@ io.inji.verify verify-service 0.18.2-SNAPSHOT - lib io.inji