Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 23 additions & 43 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,13 @@
<maven.javadoc.version>3.6.3</maven.javadoc.version>

<!-- spring -->
<spring.boot.version>3.2.3</spring.boot.version>
<spring.boot.version>3.3.10</spring.boot.version>
<session.redis.version>3.4.0</session.redis.version>
<starter.redis.version>3.4.1</starter.redis.version>

<!-- data -->
<h2.version>2.1.210</h2.version>

<!-- test -->
<junit.version>4.13.1</junit.version>
<mockito.version>5.15.2</mockito.version>
<!-- logger -->
<logback.version>1.2.3</logback.version>

<!-- json -->
<jackson.version>2.13.2</jackson.version>
Expand Down Expand Up @@ -117,6 +112,16 @@
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-core</artifactId>
<version>1.3.0-beta.1</version>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
Expand All @@ -131,16 +136,6 @@
<artifactId>identity-credential</artifactId>
<version>20231002</version>
</dependency>
<dependency>
<groupId>info.weboftrust</groupId>
<artifactId>ld-signatures-java</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>decentralized-identity</groupId>
<artifactId>jsonld-common-java</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>io.mosip</groupId>
<artifactId>vcverifier-jar</artifactId>
Expand Down Expand Up @@ -223,10 +218,6 @@
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${starter.redis.version}</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</dependency>

<!-- MOSIP -->
<dependency>
Expand Down Expand Up @@ -284,27 +275,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
Expand Down Expand Up @@ -380,14 +356,6 @@
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
Expand Down Expand Up @@ -460,6 +428,12 @@
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-biometrics-api</artifactId>
<version>${kernel.biometrics.api.version}</version>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
Expand All @@ -471,6 +445,12 @@
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-cbeffutil-api</artifactId>
<version>${kernel.cbeffutil.version}</version>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public class CredentialDefinitionResponseDto {
@Schema(description = "Type of the Credential Definition")
private List<@NotEmpty String> type;

@NotEmpty
@Valid
@SerializedName("credentialSubject")
@JsonProperty("credentialSubject")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
@Data
public class CredentialIssuerDisplayResponse {

@NotBlank
@SerializedName("name")
@JsonProperty("name")
@Schema(description = "Name of the Credential Issuer")
private String name;

@NotBlank
@SerializedName("locale")
@JsonProperty("locale")
@Schema(description = "Locale of the Credential Issuer")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
public class PresentationDefinitionDTO {

String id;
@JsonProperty("inputDescriptors")
@JsonProperty("input_descriptors")
List<InputDescriptorDTO> inputDescriptors;
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ public VCCredentialResponse downloadCredentialFromDataShare(PresentationRequest
}
VCCredentialResponse vcCredentialResponse = objectMapper.readValue(vcCredentialResponseString, VCCredentialResponse.class);

log.info("Completed Mapping the Credential to Object => ");
if(vcCredentialResponse.getCredential() == null){
DataShareResponseDto dataShareResponse = objectMapper.readValue(vcCredentialResponseString, DataShareResponseDto.class);
String errorCode = dataShareResponse.getErrors().get(0).getErrorCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,24 @@ public LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> loadD
String userLocale) {

LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> displayProperties = new LinkedHashMap<>();
Set<String> orderedKeys = Optional.ofNullable(credentialsSupportedResponse.getOrder())
.map(LinkedHashSet::new) // preserve order
.orElse(new LinkedHashSet<>());

// Add remaining keys from credentialProperties that are not already in orderedKeys
for (String key : credentialProperties.keySet()) {
orderedKeys.add(key);
}

// LDP VC format — display config is in "credential_definition.credential_subject"
if (credentialsSupportedResponse.getCredentialDefinition() == null ||
credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject() == null) {
log.warn("Missing credential definition or credential subject for LDP VC format");
return displayProperties;
log.info("Issuer well-known has no credential definition or credential subject for LDP VC format; falling back to claim-based display properties");
return buildFallbackDisplayProperties(credentialProperties, new ArrayList<>(orderedKeys));
}

Map<String, CredentialDisplayResponseDto> displayConfigMap =
credentialsSupportedResponse.getCredentialDefinition().getCredentialSubject();
List<String> orderedKeys = credentialsSupportedResponse.getOrder();

if (displayConfigMap == null) {
log.warn("No display configuration found for LDP VC format");
return displayProperties;
}

String resolvedLocale = LocaleUtils.resolveLocaleWithFallback(displayConfigMap, userLocale);

Expand All @@ -107,7 +109,7 @@ public LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> loadD
addFallbackDisplayProperties(credentialProperties, localizedDisplayMap, resolvedLocale);

List<String> fieldKeys = (orderedKeys != null && !orderedKeys.isEmpty())
? orderedKeys
? new ArrayList<>(orderedKeys)
: new ArrayList<>(localizedDisplayMap.keySet());

for (String key : fieldKeys) {
Expand All @@ -120,4 +122,35 @@ public LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> loadD

return displayProperties;
}

private LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> buildFallbackDisplayProperties(
Map<String, Object> credentialProperties,
List<String> orderedKeys) {

LinkedHashMap<String, Map<CredentialIssuerDisplayResponse, Object>> displayProperties = new LinkedHashMap<>();

// Determine field order (prefer issuer-provided 'order' if any)
List<String> fieldKeys = (orderedKeys != null && !orderedKeys.isEmpty())
? new ArrayList<>(orderedKeys)
: new ArrayList<>(credentialProperties.keySet());

// Exclude non-claim metadata
fieldKeys.remove("id");

// Build default display entries from claims
for (String key : fieldKeys) {
Object value = credentialProperties.get(key);
if (value == null) {
continue;
}

// Generate fallback display using vc keys
CredentialIssuerDisplayResponse display = new CredentialIssuerDisplayResponse();
display.setName(camelToTitleCase(key));
display.setLocale("en");

displayProperties.put(key, Map.of(display, value));
}
return displayProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.io.IOException;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -132,6 +134,7 @@ public String authorizePresentation(PresentationRequestDTO presentationRequestDT
try {
return processInputDescriptor(vcCredentialResponse, inputDescriptorDTO, presentationRequestDTO, presentationDefinitionDTO);
} catch (JsonProcessingException e) {
log.error("Exception occured during processInputDesciptor: {}", e.getMessage());
throw new VPNotCreatedException(ErrorConstants.INVALID_REQUEST.getErrorMessage());
}
})
Expand Down Expand Up @@ -168,13 +171,15 @@ private String processInputDescriptor(VCCredentialResponse vcCredentialResponse,

// Create VP Token
String vpToken = createVpToken(vpDTO);

// Create PresentationSubmission
String presentationSubmission = constructPresentationSubmission(format, vpDTO, presentationDefinitionDTO, inputDescriptorDTO);

// If response_uri is present, POST the response
if (presentationRequestDTO.getResponseMode() != null && "direct_post".equals(presentationRequestDTO.getResponseMode())) {
return postVpToResponseUri(
presentationRequestDTO.getResponseUri(),
presentationRequestDTO.getRedirectUri(),
vpToken,
presentationSubmission,
presentationRequestDTO.getState(),
Expand Down Expand Up @@ -207,33 +212,39 @@ private String buildRedirectString(String vpToken, String redirectUri, String pr
URLEncoder.encode(presentationSubmission, StandardCharsets.UTF_8));
}

private String postVpToResponseUri(String responseUri, String vpToken, String presentationSubmission, String state, String nonce) throws JsonProcessingException {
Map<String, Object> postRequest = new HashMap<>();
postRequest.put("vp_token", vpToken);
postRequest.put("presentation_submission", objectMapper.readTree(presentationSubmission));
private String postVpToResponseUri(String responseUri, String redirectUri, String vpToken, String presentationSubmission, String state, String nonce) throws JsonProcessingException {
MultiValueMap<String, String> postRequest = new LinkedMultiValueMap<>();
postRequest.add("vp_token", Base64.getUrlEncoder().encodeToString(vpToken.getBytes(StandardCharsets.UTF_8)));
postRequest.add("presentation_submission", presentationSubmission);
Comment on lines +217 to +218
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not Base64-encode vp_token for direct_post
OID4VP’s direct_post mode requires the wallet to form-post the actual VP token (JSON/JWT) as a regular form field. Base64-wrapping it here means verifiers receive a different payload than the spec calls for, so existing integrations will fail (or have to guess whether to decode). Please send the raw token and let the HTTP client handle percent-encoding.(ewc-consortium.github.io)

-        postRequest.add("vp_token", Base64.getUrlEncoder().encodeToString(vpToken.getBytes(StandardCharsets.UTF_8)));
+        postRequest.add("vp_token", vpToken);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
postRequest.add("vp_token", Base64.getUrlEncoder().encodeToString(vpToken.getBytes(StandardCharsets.UTF_8)));
postRequest.add("presentation_submission", presentationSubmission);
postRequest.add("vp_token", vpToken);
postRequest.add("presentation_submission", presentationSubmission);
🤖 Prompt for AI Agents
In src/main/java/io/mosip/mimoto/service/impl/PresentationServiceImpl.java
around lines 217 to 218, the code Base64-encodes the vp_token before adding it
to the form which breaks OID4VP direct_post expectations; remove the Base64
encoding and add the raw vpToken string as the vp_token form field (let the HTTP
client handle any percent-encoding), so the wallet posts the actual VP token
(JSON/JWT) per the spec.


if (state != null) {
postRequest.put("state", state);
}

if (nonce != null) {
postRequest.put("nonce", nonce);
postRequest.add("state", state);
}

log.info("Posting VP to response_uri: {}", responseUri);
try {
Map<String, Object> postResponse = restApiClient.postApi(
responseUri,
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_FORM_URLENCODED,
postRequest,
Map.class
);
log.info("Response from verifier after POST: {}", postResponse);
// Check for redirect_uri in response
String redirectUri = (String) postResponse.get("redirect_uri");
if (redirectUri != null && !redirectUri.isEmpty()) {

// Check for redirect_uri in response first
if (postResponse != null && postResponse.containsKey("redirect_uri")) {
String responseRedirectUri = (String) postResponse.get("redirect_uri");
if (responseRedirectUri != null && !responseRedirectUri.isEmpty()) {
return responseRedirectUri;
}
}

// Use request's redirectUri if it's non-blank
if (redirectUri != null && !redirectUri.isBlank()) {
log.info("Using redirectUri from request: {}", redirectUri);
return redirectUri;
}

// Fallback behavior if redirect_uri is not provided
log.warn("No redirect_uri received from verifier in POST response. Falling back to response_uri.");
return responseUri + "?status=vp_sent";
Expand Down
Loading