Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 036924e4 authored by Marco Amoia's avatar Marco Amoia
Browse files

Merge branch 'release' into 'main'

Release

See merge request !25
parents 8db0801e 2016b518
No related branches found
No related tags found
3 merge requests!47Update version to 1.2.0,!28Documentation,!25Release
Pipeline #261529 passed with warnings
Showing
with 445 additions and 43 deletions
{
"sonarQubeUri": "https://sonarqube.tools.simpl-europe.eu",
"projectKey": "simpl-simpl-open-development-iaa-authentication-provider-939"
}
\ No newline at end of file
# Tier2 Authentication Provider
Manages tier 2 authentication of an SIMPL Agent
## Logical Data Model
![image](docs/logical_data_model.png)
\ No newline at end of file
{
"openapi": "3.0.1",
"info": {
"title": "OpenAPI definition",
"version": "v0"
},
"servers": [
{
"url": "https://authentication-provider.authority.svc.cluster.local",
"description": "Generated server url"
}
],
"paths": {
"/keypair": {
"get": {
"tags": [
"keypair-controller"
],
"summary": "Get installed Key Pair",
"description": "ONBOARDER_M user get the public/private Key Pair",
"operationId": "getInstalledKeyPair",
"responses": {
"409": {
"description": "Conflict",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ErrorDTO"
}
}
}
},
"200": {
"description": "Key Pair successfully retrieved. The response body contains the details of the requested KeyPair.",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/KeyPairDTO"
}
}
}
},
"404": {
"description": "Key Pair not found. The requested KeyPair does not exist or is not accessible.",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/KeyPairDTO"
}
}
}
},
"401": {
"description": "Unauthorized",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/KeyPairDTO"
}
}
}
}
}
},
"post": {
"tags": [
"keypair-controller"
],
"summary": "Import Key Pair",
"description": "ONBOARDER_M user import a public/private Key Pair and store it to database",
"operationId": "importKeyPair",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImportKeyPairDTO"
}
}
},
"required": true
},
"responses": {
"409": {
"description": "Conflict",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ErrorDTO"
}
}
}
},
"204": {
"description": "Key Pair generate and stored successfully"
},
"401": {
"description": "Unauthorized"
}
}
},
"head": {
"tags": [
"keypair-controller"
],
"summary": "Keypair Exists",
"description": "ONBOARDER_M checks whether a public/private Key Pair is stored in the database",
"operationId": "existsKeypair",
"responses": {
"409": {
"description": "Conflict",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ErrorDTO"
}
}
}
},
"200": {
"description": "KeyPair is present"
},
"404": {
"description": "KeyPair is not present"
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/keypair/generate": {
"post": {
"tags": [
"keypair-controller"
],
"summary": "Generate Key Pair",
"description": "ONBOARDER_M user generate a public/private Key Pair and store it to database",
"operationId": "generateKeyPair",
"responses": {
"409": {
"description": "Conflict",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ErrorDTO"
}
}
}
},
"204": {
"description": "Key Pair generate and stored successfully"
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/csr/generate": {
"post": {
"tags": [
"csr-controller"
],
"operationId": "generateCSR",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DistinguishedNameDTO"
}
}
},
"required": true
},
"responses": {
"409": {
"description": "Conflict",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/ErrorDTO"
}
}
}
},
"200": {
"description": "OK",
"content": {
"*/*": {
"schema": {
"$ref": "#/components/schemas/StreamingResponseBody"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ErrorDTO": {
"type": "object",
"properties": {
"error": {
"type": "string"
},
"elementName": {
"type": "string"
}
}
},
"ImportKeyPairDTO": {
"required": [
"privateKey",
"publicKey"
],
"type": "object",
"properties": {
"publicKey": {
"type": "string"
},
"privateKey": {
"type": "string"
}
}
},
"DistinguishedNameDTO": {
"required": [
"commonName",
"country",
"organization",
"organizationalUnit"
],
"type": "object",
"properties": {
"commonName": {
"type": "string"
},
"organization": {
"type": "string"
},
"organizationalUnit": {
"type": "string"
},
"country": {
"type": "string"
}
}
},
"StreamingResponseBody": {
"type": "object"
},
"KeyPairDTO": {
"type": "object",
"properties": {
"publicKey": {
"type": "string",
"format": "byte"
},
"privateKey": {
"type": "string",
"format": "byte"
}
}
}
}
}
}
PROJECT_VERSION_NUMBER="0.8.1"
PROJECT_VERSION_NUMBER="1.0.0"
......@@ -3,9 +3,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.aruba.simpl</groupId>
<groupId>eu.europa.ec.simpl</groupId>
<artifactId>simpl-parent</artifactId>
<version>0.8.0</version>
<version>1.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
......@@ -20,7 +20,7 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.aruba.simpl</groupId>
<groupId>eu.europa.ec.simpl</groupId>
<artifactId>simpl-spring-boot-starter</artifactId>
</dependency>
<dependency>
......@@ -45,7 +45,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.aruba.simpl</groupId>
<groupId>eu.europa.ec.simpl</groupId>
<artifactId>simpl-test-lib</artifactId>
<scope>test</scope>
</dependency>
......
package com.aruba.simpl.authenticationprovider;
package eu.europa.ec.simpl.authenticationprovider;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
......
package com.aruba.simpl.authenticationprovider.configurations;
package eu.europa.ec.simpl.authenticationprovider.configurations;
import com.aruba.simpl.common.exchanges.CredentialExchange;
import eu.europa.ec.simpl.common.exchanges.usersroles.CredentialExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
......
package com.aruba.simpl.authenticationprovider.configurations;
package eu.europa.ec.simpl.authenticationprovider.configurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
......
package com.aruba.simpl.authenticationprovider.configurations;
package eu.europa.ec.simpl.authenticationprovider.configurations;
import com.aruba.simpl.common.model.csr.AlgorithmConfig;
import eu.europa.ec.simpl.common.model.csr.AlgorithmConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "keypair")
......
package com.aruba.simpl.authenticationprovider.configurations;
package eu.europa.ec.simpl.authenticationprovider.configurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
......
package com.aruba.simpl.authenticationprovider.configurations;
package eu.europa.ec.simpl.authenticationprovider.configurations;
import org.springframework.boot.context.properties.ConfigurationProperties;
......
package com.aruba.simpl.authenticationprovider.controllers;
package eu.europa.ec.simpl.authenticationprovider.controllers;
import com.aruba.simpl.authenticationprovider.services.CSRService;
import com.aruba.simpl.common.exchanges.authenticationprovider.CSRExchange;
import com.aruba.simpl.common.model.dto.DistinguishedNameDTO;
import eu.europa.ec.simpl.authenticationprovider.services.CSRService;
import eu.europa.ec.simpl.common.exchanges.authenticationprovider.CSRExchange;
import eu.europa.ec.simpl.common.model.dto.DistinguishedNameDTO;
import jakarta.validation.Valid;
import java.util.Optional;
import org.springframework.http.HttpHeaders;
......
package com.aruba.simpl.authenticationprovider.controllers;
package eu.europa.ec.simpl.authenticationprovider.controllers;
import com.aruba.simpl.authenticationprovider.exceptions.CipherException;
import com.aruba.simpl.authenticationprovider.model.dto.ImportKeyPairDTO;
import com.aruba.simpl.authenticationprovider.services.KeyPairService;
import com.aruba.simpl.authenticationprovider.utils.PemUtils;
import com.aruba.simpl.common.exchanges.authenticationprovider.KeyPairExchange;
import com.aruba.simpl.common.model.dto.KeyPairDTO;
import eu.europa.ec.simpl.authenticationprovider.exceptions.CipherException;
import eu.europa.ec.simpl.authenticationprovider.model.dto.ImportKeyPairDTO;
import eu.europa.ec.simpl.authenticationprovider.services.KeyPairService;
import eu.europa.ec.simpl.authenticationprovider.utils.PemUtils;
import eu.europa.ec.simpl.common.exchanges.authenticationprovider.KeyPairExchange;
import eu.europa.ec.simpl.common.model.dto.KeyPairDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
......@@ -29,6 +30,7 @@ public class KeypairController implements KeyPairExchange {
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@ResponseStatus(HttpStatus.NO_CONTENT)
@Override
public void generateKeyPair() throws CipherException {
keyPairService.generateAndStoreKeyPair();
}
......@@ -42,8 +44,10 @@ public class KeypairController implements KeyPairExchange {
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@Override
public Boolean existsKeypair() {
return keyPairService.existsKeyPair();
public ResponseEntity<Void> existsKeypair() {
return keyPairService.existsKeyPair()
? new ResponseEntity<>(HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
@Operation(
......
package com.aruba.simpl.authenticationprovider.exceptions;
package eu.europa.ec.simpl.authenticationprovider.exceptions;
public class CipherException extends RuntimeException {
public CipherException(String message) {
this(message, null);
}
public CipherException(String message, Throwable throwable) {
super(message, throwable);
}
......
package com.aruba.simpl.authenticationprovider.exceptions;
package eu.europa.ec.simpl.authenticationprovider.exceptions;
import com.aruba.simpl.common.exceptions.StatusException;
import eu.europa.ec.simpl.common.exceptions.StatusException;
import org.springframework.http.HttpStatus;
public class InvalidKeyException extends StatusException {
......
package com.aruba.simpl.authenticationprovider.exceptions;
package eu.europa.ec.simpl.authenticationprovider.exceptions;
import com.aruba.simpl.common.exceptions.StatusException;
import eu.europa.ec.simpl.common.exceptions.StatusException;
import org.springframework.http.HttpStatus;
public class KeyPairNotFoundException extends StatusException {
......
package eu.europa.ec.simpl.authenticationprovider.liquibase.migration_2024_12_11;
import eu.europa.ec.simpl.common.exceptions.RuntimeWrapperException;
import java.io.ByteArrayInputStream;
import java.io.SequenceInputStream;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.CustomChangeException;
import liquibase.exception.SetupException;
import liquibase.exception.ValidationErrors;
import liquibase.resource.ResourceAccessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.Assert;
@Slf4j
public class CipherMigration implements CustomTaskChange {
@Override
public String getConfirmationMessage() {
return "";
}
@Override
public void setFileOpener(ResourceAccessor arg0) {
// Overridden method. No logic implemented.
}
@Override
public void setUp() throws SetupException {
// Overridden method. No logic implemented.
}
@Override
public ValidationErrors validate(Database arg0) {
// Overridden method. No logic implemented.
return null;
}
@Override
public void execute(Database database) throws CustomChangeException {
log.info("Start migration");
var conn = ((JdbcConnection) database.getConnection()).getUnderlyingConnection();
var getAllKeysQuery = "SELECT id, public_key, private_key FROM keypair";
var updateKeysQuery = "UPDATE keypair SET public_key = ?, private_key = ? WHERE id = ?";
try (var statement = conn.createStatement()) {
var resultSet = statement.executeQuery(getAllKeysQuery);
while (resultSet.next()) {
var id = resultSet.getObject("id");
log.info("Migrate key {}", id);
var publicKey = convertEncryption(resultSet.getBytes("public_key"));
var privateKey = convertEncryption(resultSet.getBytes("private_key"));
try (var updateStatement = conn.prepareStatement(updateKeysQuery)) {
updateStatement.setBytes(1, publicKey);
updateStatement.setBytes(2, privateKey);
updateStatement.setObject(3, id);
updateStatement.executeUpdate();
}
}
} catch (Exception e) {
throw new RuntimeWrapperException(e);
}
log.info("End migration");
}
public byte[] convertEncryption(byte[] cipherMessage) {
var message = decryptUsingAESECB256(cipherMessage);
return encryptUsingAESGCMNoPagging(message);
}
@SuppressWarnings("java:S5542")
public byte[] decryptUsingAESECB256(byte[] cipherMessage) {
final String CIPHER_ALGORITHM = "AES";
try {
var cipher = Cipher.getInstance(CIPHER_ALGORITHM, new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(CIPHER_ALGORITHM));
return cipher.doFinal(cipherMessage);
} catch (Exception e) {
throw new RuntimeWrapperException(e);
}
}
public byte[] encryptUsingAESGCMNoPagging(byte[] message) {
final int IV_SIZE = 12;
final int TAG_LENGTH = 128;
final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
try {
// GCM Parameter spec generation
var iv = new byte[IV_SIZE];
var secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
var parameterSpec = new GCMParameterSpec(TAG_LENGTH, iv);
var cipher = Cipher.getInstance(CIPHER_ALGORITHM, new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(CIPHER_ALGORITHM), parameterSpec);
// Join IV and Cipher message
var inputStream = new ByteArrayInputStream(message);
var ivStream = new ByteArrayInputStream(iv);
try (var cipherInputStream = new CipherInputStream(inputStream, cipher)) {
var stream = new SequenceInputStream(ivStream, cipherInputStream);
return IOUtils.toByteArray(stream);
}
} catch (Exception e) {
throw new RuntimeWrapperException(e);
}
}
public String getBase64SecretKeyFromEnv() {
return System.getenv("CRYPTO_SECRETKEYBASE64");
}
private SecretKey getSecretKey(String cipherAlgorithm) {
String secretKeyBase64 = getBase64SecretKeyFromEnv();
Assert.notNull(secretKeyBase64, "Property crypto.secretKeyBase64 is mandatory");
var keyBytes = Base64.getDecoder().decode(secretKeyBase64);
return new SecretKeySpec(keyBytes, cipherAlgorithm);
}
}
package com.aruba.simpl.authenticationprovider.model.dto;
package eu.europa.ec.simpl.authenticationprovider.model.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
......
package com.aruba.simpl.authenticationprovider.model.entities;
package eu.europa.ec.simpl.authenticationprovider.model.entities;
import com.aruba.simpl.common.model.entity.annotations.UUIDv7Generator;
import eu.europa.ec.simpl.common.model.entity.annotations.UUIDv7Generator;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
......
package com.aruba.simpl.authenticationprovider.model.repositories;
package eu.europa.ec.simpl.authenticationprovider.model.repositories;
import com.aruba.simpl.authenticationprovider.model.entities.ApplicantKeyPair;
import eu.europa.ec.simpl.authenticationprovider.model.entities.ApplicantKeyPair;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment