Skip to content

Commit

Permalink
Added a validation method to check the revocation during the verifiab…
Browse files Browse the repository at this point in the history
…le presentation validation.
  • Loading branch information
rubenmodamioin2 committed Nov 5, 2024
1 parent d661296 commit 5579acb
Show file tree
Hide file tree
Showing 21 changed files with 402 additions and 369 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v1.0.3]
### Added
- Added a validation method to check the revocation during the verifiable presentation validation.

## [v1.0.2]
### Fixed
- Added functionality to redirect to the home page when clicking the logo in the login page.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = 'es.in2'
version = '1.0.2'
version = '1.0.3'

java {
toolchain {
Expand Down
1 change: 0 additions & 1 deletion src/main/java/es/in2/vcverifier/VcVerifierApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ public static void main(String[] args) {
public ObjectMapper objectMapper() {
return OBJECT_MAPPER;
}

}
10 changes: 3 additions & 7 deletions src/main/java/es/in2/vcverifier/config/ClientLoaderConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package es.in2.vcverifier.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import es.in2.vcverifier.exception.ClientLoadingException;
import es.in2.vcverifier.model.ClientData;
import es.in2.vcverifier.model.ExternalTrustedListYamlData;
import es.in2.vcverifier.service.AllowedClientsService;
import es.in2.vcverifier.service.TrustFrameworkService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -25,8 +23,7 @@
@RequiredArgsConstructor
public class ClientLoaderConfig {

private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());
private final AllowedClientsService allowedClientsService;
private final TrustFrameworkService trustFrameworkService;

@Bean
public RegisteredClientRepository registeredClientRepository() {
Expand All @@ -37,8 +34,7 @@ public RegisteredClientRepository registeredClientRepository() {
private List<RegisteredClient> loadClients() {
try {
// Leer el archivo YAML
String clientsYaml = allowedClientsService.fetchAllowedClient();
ExternalTrustedListYamlData clientsYamlData = yamlMapper.readValue(clientsYaml, ExternalTrustedListYamlData.class);
ExternalTrustedListYamlData clientsYamlData = trustFrameworkService.fetchAllowedClient();
List<RegisteredClient> registeredClients = new ArrayList<>();
// Convertir cada ClientData a RegisteredClient y agregarlo a la lista
for (ClientData clientData : clientsYamlData.clients()) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package es.in2.vcverifier.config.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

import java.util.Optional;

@ConfigurationProperties(prefix = "trust-framework")
public record TrustFrameworkProperties(
@NestedConfigurationProperty TrustedIssuerListProperties trustedIssuerList,
@NestedConfigurationProperty ClientsRepositoryProperties clientsRepository,
@NestedConfigurationProperty RevocationListProperties revocationList) {

@ConstructorBinding
public TrustFrameworkProperties(
TrustedIssuerListProperties trustedIssuerList,
ClientsRepositoryProperties clientsRepository,
RevocationListProperties revocationList) {
this.trustedIssuerList = Optional.ofNullable(trustedIssuerList).orElse(new TrustedIssuerListProperties(""));
this.clientsRepository = Optional.ofNullable(clientsRepository).orElse(new ClientsRepositoryProperties(""));
this.revocationList = Optional.ofNullable(revocationList).orElse(new RevocationListProperties(""));
}

public record TrustedIssuerListProperties(String uri) {
}

public record ClientsRepositoryProperties(String uri) {
}

public record RevocationListProperties(String uri) {
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.in2.vcverifier.exception;

public class CredentialRevokedException extends RuntimeException {
public CredentialRevokedException(String message) {
super(message);
}
}

13 changes: 13 additions & 0 deletions src/main/java/es/in2/vcverifier/model/RevokedCredentialIds.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package es.in2.vcverifier.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

@JsonIgnoreProperties
public record RevokedCredentialIds (
@JsonProperty("revoked_credentials")
List<String> revokedCredentials
){
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package es.in2.vcverifier.service;

import es.in2.vcverifier.model.ExternalTrustedListYamlData;
import es.in2.vcverifier.model.issuer.IssuerCredentialsCapabilities;

import java.util.List;

public interface TrustFrameworkService {
List<IssuerCredentialsCapabilities> getTrustedIssuerListData(String id);
List<String> getRevokedCredentialIds();
ExternalTrustedListYamlData fetchAllowedClient();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package es.in2.vcverifier.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import es.in2.vcverifier.config.properties.TrustedIssuerListProperties;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import es.in2.vcverifier.config.properties.TrustFrameworkProperties;
import es.in2.vcverifier.exception.FailedCommunicationException;
import es.in2.vcverifier.exception.IssuerNotAuthorizedException;
import es.in2.vcverifier.exception.JsonConversionException;
import es.in2.vcverifier.exception.RemoteFileFetchException;
import es.in2.vcverifier.model.ExternalTrustedListYamlData;
import es.in2.vcverifier.model.RevokedCredentialIds;
import es.in2.vcverifier.model.issuer.IssuerAttribute;
import es.in2.vcverifier.model.issuer.IssuerCredentialsCapabilities;
import es.in2.vcverifier.model.issuer.IssuerResponse;
Expand All @@ -27,16 +31,28 @@
@Slf4j
public class TrustFrameworkServiceImpl implements TrustFrameworkService {

private final TrustedIssuerListProperties trustedIssuerListProperties;
private final ObjectMapper objectMapper;
private final TrustFrameworkProperties trustFrameworkProperties;
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory());

@Override
public ExternalTrustedListYamlData fetchAllowedClient() {
try {
String clientsYaml = fetchRemoteFile(trustFrameworkProperties.clientsRepository().uri());
return yamlMapper.readValue(clientsYaml, ExternalTrustedListYamlData.class);
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
throw new RemoteFileFetchException("Error reading clients list from GitHub.", e);
}
}

@Override
public List<IssuerCredentialsCapabilities> getTrustedIssuerListData(String id) {
try {
// Step 1: Send HTTP request to fetch issuer data
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(trustedIssuerListProperties.uri() + id))
.uri(URI.create(trustFrameworkProperties.trustedIssuerList().uri() + id))
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Expand Down Expand Up @@ -64,6 +80,19 @@ public List<IssuerCredentialsCapabilities> getTrustedIssuerListData(String id) {
}
}

@Override
public List<String> getRevokedCredentialIds() {
try {
String revokedCredentialIdsYaml = fetchRemoteFile(trustFrameworkProperties.revocationList().uri());
RevokedCredentialIds revokedCredentialIds = yamlMapper.readValue(revokedCredentialIdsYaml, RevokedCredentialIds.class);
return revokedCredentialIds.revokedCredentials();
} catch (IOException | InterruptedException e) {
log.error("Error fetching revoked credential IDs from URI {}: {}", trustFrameworkProperties.revocationList().uri(), e.getMessage());
Thread.currentThread().interrupt();
throw new FailedCommunicationException("Error fetching revoked credential IDs: " + e.getMessage());
}
}

// Helper method to decode Base64 and map to IssuerCredentialsCapabilities
private IssuerCredentialsCapabilities decodeAndMapIssuerAttributeBody(IssuerAttribute issuerAttribute) {
try {
Expand All @@ -77,6 +106,19 @@ private IssuerCredentialsCapabilities decodeAndMapIssuerAttributeBody(IssuerAttr
throw new JsonConversionException("Failed to decode and map issuer attribute body");
}
}

private String fetchRemoteFile(String fileUrl) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(fileUrl))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return response.body();
} else {
throw new RemoteFileFetchException("Failed to fetch file from GitHub. Status code: " + response.statusCode());
}
}
}


34 changes: 27 additions & 7 deletions src/main/java/es/in2/vcverifier/service/impl/VpServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,33 @@ public boolean validateVerifiablePresentation(String verifiablePresentation) {
SignedJWT jwtCredential = extractFirstVerifiableCredential(verifiablePresentation);
Payload payload = jwtService.getPayloadFromSignedJWT(jwtCredential);

// Step 2: Validate the issuer
// Step 2: Validate the credential id is not in the revoked list
validateCredentialNotRevoked(payload);

// Step 3: Validate the issuer
String credentialIssuerDid = jwtService.getClaimFromPayload(payload, "iss");

// Step 3: Extract and validate credential types
// Step 4: Extract and validate credential types
List<String> credentialTypes = getCredentialTypes(payload);

// Step 4: Retrieve the list of issuer capabilities
// Step 5: Retrieve the list of issuer capabilities
List<IssuerCredentialsCapabilities> issuerCapabilitiesList = trustFrameworkService.getTrustedIssuerListData(credentialIssuerDid);

// Step 5: Validate credential type against issuer capabilities
// Step 6: Validate credential type against issuer capabilities
validateCredentialTypeWithIssuerCapabilities(issuerCapabilitiesList, credentialTypes);
log.info("Issuer DID {} is a trusted participant", credentialIssuerDid);

// Step 5: Extract the mandateId from the Verifiable Credential
// Step 7: Extract the mandateId from the Verifiable Credential
String mandatorOrganizationIdentifier = extractMandatorOrganizationIdentifier(credentialTypes, payload);

//TODO this must be validated against the participants list, not the issuer list

// Validate the mandatee ID with trusted issuer service, if is not present the trustedIssuerListService throws an exception
// Validate the mandator with trusted issuer service, if is not present the trustedIssuerListService throws an exception
trustFrameworkService.getTrustedIssuerListData(DID_ELSI_PREFIX + mandatorOrganizationIdentifier);

log.info("Mandator OrganizationIdentifier {} is valid and allowed", mandatorOrganizationIdentifier);

// Step 6: Validate the VP's signature with the DIDService (the DID of the holder of the VP)
// Step 8: Validate the VP's signature with the DIDService (the DID of the holder of the VP)
String mandateeId = extractMandateeId(credentialTypes, payload);
PublicKey holderPublicKey = didService.getPublicKeyFromDid(mandateeId); // Get the holder's public key in bytes
jwtService.verifyJWTSignature(verifiablePresentation, holderPublicKey, KeyType.EC); // Validate the VP was signed by the holder DID
Expand Down Expand Up @@ -171,6 +174,23 @@ private void validateCredentialTypeWithIssuerCapabilities(List<IssuerCredentials
throw new InvalidCredentialTypeException("Credential types " + credentialTypes + " are not supported by the issuer.");
}

private void validateCredentialNotRevoked(Payload payload) {
Object vcFromPayload = jwtService.getVCFromPayload(payload);

if (vcFromPayload instanceof LinkedTreeMap<?, ?> vcObject) {
// Use a wildcard generic type to avoid unchecked cast warning
Object credentialId = vcObject.get("id").toString();
List<String> revokedIds = trustFrameworkService.getRevokedCredentialIds();
if (revokedIds.contains(credentialId)) {
throw new CredentialRevokedException("Credential ID " + credentialId + " is revoked.");
}
}
else {
throw new InvalidCredentialTypeException("VC from payload is not a LinkedTreeMap.");
}
}


private JsonNode convertObjectToJSONNode(Object vcObject) throws JsonConversionException {
JsonNode jsonNode;

Expand Down
12 changes: 7 additions & 5 deletions src/main/resources/application-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ security:
cronUnit:
expiration:

clientsRepository:
uri:

trustedIssuerList:
uri:
trustFramework:
trustedIssuerList:
uri:
clientsRepository:
uri:
revocationList:
uri:

crypto:
privateKey:
Expand Down
12 changes: 7 additions & 5 deletions src/main/resources/application-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ security:
expiration: "1"
cronUnit: "DAYS"

clientsRepository:
uri: "https://raw.githubusercontent.com/DOME-Marketplace/dome-services-directory/refs/heads/main/trusted-service-list-"

trustedIssuerList:
uri: "http://localhost:8080/v4/issuers/"
trustFramework:
trustedIssuerList:
uri: "http://localhost:8080/v4/issuers/"
clientsRepository:
uri: "https://raw.githubusercontent.com/DOME-Marketplace/dome-services-directory/refs/heads/main/trusted-service-list-"
revocationList:
uri: ""

crypto:
privateKey: "73e509a7681d4a395b1ced75681c4dc4020dbab02da868512276dd766733d5b5" # for test purposes
Expand Down
Loading

0 comments on commit 5579acb

Please sign in to comment.