Skip to content

Commit

Permalink
feature/verifier-h2m-m2m-flows (#4)
Browse files Browse the repository at this point in the history
* New DOME login page

* New Crypto feature (generate ECKey + did:key from a Private Key)
New Security Config with scaffolding for OpenID Connect

* WIP in custom OpenID Connect Authorization Code Flow for Verifiable Credentials

* Update info

* Update did:key creation and decode
New JWT service

* add validation fot the auth request of the OPENID CORE

* add verifiactions, and fix problem during did generation

* Add conditional for H2M and M2M

* Update CustomAuthorizationRequestConverter.java

* Filters for security

* h2m flow

added qr generation page and jwt request retrival.

* Added auth response logic to validate the vp

* vp validation chnges

* merge h2m m2m

* wip: refactor ClientLoaderConfig

* wip: add test

* wip: add check structure

* Finish JWT Claims checks

* refactor didResolver implementation

* adapt verifications

the certificate validation signature is failing

* add some tests

* Update VpValidationServiceImpl.java

* add some tests

* Update VpValidationServiceImpl.java

* wip: VP certificated input error

* Vo validation

* wip: VP certificated input error

* change settings gradle rootproject name

* wip: check vp from vp_token in assertion

* fix: check vp from vp_token in assertion

* New changes

* solve merge conflicts

* wip: Custom token

* Test for generate the access token

* add jwtcustomizer

* wip: Custom token

* return custom token

* delete jwtCustomizer bean

* add JWT type into header

* uncomment validation

* WIP: TODOs

* Add logic to retrieve the token response

* Delete the OAuthAuthorization after consum the request

* Add TODOs checks

* Add logic for verificate client in the auth code flow

* Remove Controller for tests

* Add new client

* solve merge conflicts

* getScope method

* getScope method

* refactor LEARCredentialMachine

* add same device login

* remove localhost for testing

* adjust padding for qr image background

* Retrieval of the trustFramework via GitHub repository

* Correct error on remote url

* update application profiles

* add url of github repository as config variable

* change application.yaml names

* add info to CHANGELOG.md

* delete dependency

* change header image

* solve pr comments

* add custom exception

* add custom exception handler

* delete ResponseEntity response from Oid4vp controller

* delete import

* add exception for the CustomExceptionHandler to manage cache retrieval

* refactor global exception handler

* refactor Controller to RestController

* rollback RestController

* change client loader config implementation to external yaml config file

* add interface against the trusted issuer list

* add interface against the trusted issuer list and validations on vp service

* solve PR comments

* fix default env profile implementation

* wip: logs to deployment

* expose health endpoint

* add management dependency

* add local configs

* change LEARCredentialMachine dto attributes names

* fix some reference to LearCredentialMachine

* fix some attributes from LearCredentialEmployee

* add logs for debugging

* update scope for learcredential

* add logs for debugging

* change scope name

* fix exp access token

* fix fixme

* add websocket logic for redirection

* update scope

* remove origin retrieval from the token request

* remove state validation for testing

* add some tests

* add some tests

* add token log

* add state validation on token request as optional

* add CUstom Token Request Converter M2M tests

* add Auth Provider Tests

* add H2M Converter Tests

* update scope

* add Custom Error Response Handler unit tests

* init CustomAuthorizationRequestConverterTest

---------

Co-authored-by: Oriol Canadés <oriol.canades@in2.es>
Co-authored-by: Oriol Canadés <83498869+oriolcanadesin2@users.noreply.github.com>
Co-authored-by: RubenModamioGarcia <ruben.modamio@in2.es>
Co-authored-by: albertrodriguezin2 <166031280+albertrodriguezin2@users.noreply.github.com>
  • Loading branch information
5 people authored Oct 8, 2024
1 parent a772fae commit bc75e6b
Show file tree
Hide file tree
Showing 118 changed files with 6,852 additions and 16 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Pre-release](https://github.com/in2workspace/in2-vc-verifier/releases/tag/v0.1.0): v0.1.0
- Project scaffolding and setup

### Added
- Spring Security with Authorization Server configuration, supporting OIDC flows (OID4VP flow).
- Custom authentication filters to handle token requests and authorization code flows.
- Custom token filters for OIDC token validation and processing.
- Cryptographic components for ECKey generation (P-256) and managing cryptographic properties
- LoginQrController for generating and displaying a QR code to initiate authentication requests.
- Oid4vpController for handling authentication requests and responses for Verifiable Presentations (VPs).
- AuthorizationResponseProcessorService: Added service to supports the processing of the presentation submission in compliance with the OID4VP flow.
- ClientAssertionValidationService to validate JWT claims for client assertions.
- ResolverController for resolving Decentralized Identifiers (DIDs) to JWKs.
- DIDService to fetch public keys associated with Decentralized Identifiers (DIDs).
- JWTService for JWT generation, parsing, signature verification, and payload extraction.
- TrustFrameworkService for validating allowed clients, issuers, and participants within the trust framework.
- VpService for validate and extract credentials from Verifiable Presentations (VPs).
- JtiTokenCache. In-memory JTI token to track and prevent token reuse.
- CacheStore. Implemented cache management
- ClientLoaderConfig. Loads OAuth2 clients dynamically from a JSON file
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,17 @@
[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=in2workspace_in2-verifier-api)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=in2workspace_in2-verifier-api&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=in2workspace_in2-verifier-api)
</div>

# Introduction

Spring Authorization Server is a framework that provides implementations of the OAuth 2.1 and OpenID Connect 1.0 specifications and other related specifications.
It is built on top of Spring Security to provide a secure, light-weight,
and customizable foundation for building OpenID Connect 1.0 Identity Providers and OAuth2 Authorization Server products.

# Testing

We test the first call by sending a GET request to the '/oauth2/authorize' endpoint.

```text
http://localhost:9000/oauth2/authorize?response_type=code&client_id=did:key:wejkdew87fwhef9833f4&request_uri=https://dome-marketplace.org/api/v1/request.jwt%23GkurKxf5T0Y-mnPFCHqWOMiZi4VS138cQO_V7PZHAdM&state=af0ifjsldkj&nonce=n-0S6_WzA2Mj
```
17 changes: 16 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,30 @@ sonar {

repositories {
mavenCentral()
maven { url "https://jitpack.io" }
}

dependencies {
// Starters
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// Cryptography
implementation 'com.nimbusds:nimbus-jose-jwt:9.40'
implementation 'org.bitcoinj:bitcoinj-core:0.17-alpha5'
implementation 'io.github.novacrypto:Base58:2022.01.17'
//Jackson
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2'
// JSON
implementation 'org.json:json:20230227'
// QR GENERATOR
implementation 'com.github.kenglxn.QRGen:javase:3.0.1'
// DevTools
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// Websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = 'in2-verifier-api'
rootProject.name = 'in2-verifier-api'
21 changes: 21 additions & 0 deletions src/main/java/es/in2/vcverifier/VcVerifierApplication.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
package es.in2.vcverifier;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableConfigurationProperties
@ConfigurationPropertiesScan
public class VcVerifierApplication {

private static final ObjectMapper OBJECT_MAPPER =
JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.serializationInclusion(JsonInclude.Include.NON_NULL)
.build();
public static void main(String[] args) {
SpringApplication.run(VcVerifierApplication.class, args);
}

@Bean
public ObjectMapper objectMapper() {
return OBJECT_MAPPER;
}

}
34 changes: 34 additions & 0 deletions src/main/java/es/in2/vcverifier/config/ApiConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package es.in2.vcverifier.config;

import es.in2.vcverifier.exception.InvalidSpringProfile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import java.util.List;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class ApiConfig {

private final Environment environment;

public String getCurrentEnvironment() {
List<String> profiles = List.of(environment.getActiveProfiles());
if (profiles.isEmpty()) {
log.debug(environment.getDefaultProfiles()[0]);
if (environment.getDefaultProfiles()[0] != null && !environment.getDefaultProfiles()[0].isBlank()){
return environment.getDefaultProfiles()[0];
}
} else {
log.debug(environment.getActiveProfiles()[0]);
if (profiles.get(0) != null && !profiles.get(0).isBlank()){
return profiles.get(0);
}
}
throw new InvalidSpringProfile("An error occurred while trying to retrieve the current Spring Profile");
}

}
43 changes: 43 additions & 0 deletions src/main/java/es/in2/vcverifier/config/CacheStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package es.in2.vcverifier.config;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.RequiredArgsConstructor;

import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

@RequiredArgsConstructor
public class CacheStore<T> {

private final Cache<String, T> cache;

public CacheStore(long expiryDuration, TimeUnit timeUnit) {
this.cache = CacheBuilder.newBuilder()
.expireAfterWrite(expiryDuration, timeUnit)
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
.build();
}

public T get(String key) {
T value = cache.getIfPresent(key);
if (value != null) {
return value;
} else {
throw new NoSuchElementException("Value is not present.");
}
}

public void delete(String key) {
cache.invalidate(key);
}

public String add(String key, T value) {
if (key != null && !key.isBlank() && value != null) {
cache.put(key, value);
return key;
}
return null; // Retornar null para indicar que no se agregó nada
}
}

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

import es.in2.vcverifier.model.AuthorizationCodeData;
import es.in2.vcverifier.model.AuthorizationRequestJWT;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Configuration
@RequiredArgsConstructor
public class CacheStoreConfig {
@Bean
public CacheStore<AuthorizationRequestJWT> cacheStoreForAuthorizationRequestJWT() {
return new CacheStore<>(10, TimeUnit.MINUTES);
}
@Bean
public CacheStore<OAuth2AuthorizationRequest> cacheStoreForOAuth2AuthorizationRequest() {
return new CacheStore<>(10, TimeUnit.MINUTES);
}
@Bean
public CacheStore<AuthorizationCodeData> cacheStoreForAuthorizationCodeData() {
return new CacheStore<>(10, TimeUnit.MINUTES);
}

@Bean
public Set<String> jtiCache() {
return new HashSet<>();
}
}
89 changes: 89 additions & 0 deletions src/main/java/es/in2/vcverifier/config/ClientLoaderConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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 lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Configuration
@RequiredArgsConstructor
public class ClientLoaderConfig {

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

@Bean
public RegisteredClientRepository registeredClientRepository() {
List<RegisteredClient> clients = loadClients(); // Cargar los clientes
return new InMemoryRegisteredClientRepository(clients); // Pasar los clientes al repositorio
}

private List<RegisteredClient> loadClients() {
try {
// Leer el archivo YAML
String clientsYaml = allowedClientsService.fetchAllowedClient();
ExternalTrustedListYamlData clientsYamlData = yamlMapper.readValue(clientsYaml,ExternalTrustedListYamlData.class);

List<RegisteredClient> registeredClients = new ArrayList<>();

// Convertir cada ClientData a RegisteredClient y agregarlo a la lista
for (ClientData clientData : clientsYamlData.clients()) {
RegisteredClient.Builder registeredClientBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(clientData.clientId())
.clientAuthenticationMethods(authMethods -> clientData.clientAuthenticationMethods().forEach(method ->
authMethods.add(new ClientAuthenticationMethod(method))))
.authorizationGrantTypes(grantTypes -> clientData.authorizationGrantTypes().forEach(grantType ->
grantTypes.add(new AuthorizationGrantType(grantType))))
.redirectUris(uris -> uris.addAll(clientData.redirectUris()))
.postLogoutRedirectUris(uris -> uris.addAll(clientData.postLogoutRedirectUris()))
.scopes(scopes -> scopes.addAll(clientData.scopes()));

if (clientData.clientSecret() != null && !clientData.clientSecret().isBlank()) {
registeredClientBuilder.clientSecret(clientData.clientSecret());
}
// Configurar ClientSettings
ClientSettings.Builder clientSettingsBuilder = ClientSettings.builder()
.requireAuthorizationConsent(clientData.requireAuthorizationConsent());

// Configurar valores opcionales si están presentes en el JSON
if (clientData.jwkSetUrl() != null) {
clientSettingsBuilder.jwkSetUrl(clientData.jwkSetUrl());
}

if (clientData.tokenEndpointAuthenticationSigningAlgorithm() != null) {
clientSettingsBuilder.tokenEndpointAuthenticationSigningAlgorithm(
SignatureAlgorithm.from(clientData.tokenEndpointAuthenticationSigningAlgorithm()));
}

if (clientData.requireProofKey() != null) {
clientSettingsBuilder.requireProofKey(clientData.requireProofKey());
}

registeredClientBuilder.clientSettings(clientSettingsBuilder.build());

registeredClients.add(registeredClientBuilder.build());
}
return registeredClients;
} catch (Exception e) {
throw new ClientLoadingException("Error loading clients from Yaml", e);
}
}
}


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

import lombok.Getter;
import org.springframework.stereotype.Component;

import java.util.HashSet;

@Getter
@Component
public class JtiTokenCache {

private final HashSet<String> jtiTokenCache;

public JtiTokenCache(HashSet<String> jtiCache) {
this.jtiTokenCache = jtiCache;
}

public boolean addJti(String jti) {
return jtiTokenCache.add(jti);
}

public boolean isJtiPresent(String jti) {
return jtiTokenCache.contains(jti);
}

}
25 changes: 25 additions & 0 deletions src/main/java/es/in2/vcverifier/config/WebSocketConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package es.in2.vcverifier.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// Habilitar el broker solo para el canal /oidc
config.enableSimpleBroker("/oidc");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// Registrar el endpoint de WebSocket para que los clientes se conecten
registry.addEndpoint("/qr-socket").withSockJS();
}
}

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

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "clients-repository")
public record ClientRepositoryProperties(
String uri
)
{
}
Loading

0 comments on commit bc75e6b

Please sign in to comment.