From bba7510e05e7e1b8bfcbac4501db12324a230630 Mon Sep 17 00:00:00 2001 From: "igor.balog" Date: Mon, 19 Feb 2024 10:09:06 +0100 Subject: [PATCH] Encryption updates --- CHANGELOG.md | 4 +- README.md | 1 + .../.env | 2 + ci/docker/test-cases/https-https-form/.env | 2 + .../.env | 2 + ci/docker/test-cases/https-https-header/.env | 2 + .../.env | 2 + ci/docker/test-cases/https-https-mixed/.env | 2 + ci/docker/test-cases/https-idscp2-form/.env | 2 + ci/docker/test-cases/https-idscp2-header/.env | 2 + ci/docker/test-cases/https-idscp2-mixed/.env | 2 + ci/docker/test-cases/ws-idscp2/.env | 2 + ci/docker/test-cases/ws-ws/.env | 2 + doc/AUDIT.md | 4 +- .../idsa/businesslogic/entity/AuditLog.java | 4 +- .../service/AuditEventService.java | 20 ++- .../eng/idsa/businesslogic/util/AES256.java | 140 ++++++++++++++++++ 17 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 src/main/java/it/eng/idsa/businesslogic/util/AES256.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 791570fa..4418b5bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ All notable changes to this project will be documented in this file. ### Added - Audit logs stored in database + - Column encrypted using AES/GCM/NoPadding - New endpoint for fetching audit events - + - requires to set environment variable with AES256-SECRET-KEY used in encryption algorithm + ### Changed - Using JFrog for IDS dependency management diff --git a/README.md b/README.md index f3a6856c..cde20b9f 100644 --- a/README.md +++ b/README.md @@ -462,6 +462,7 @@ application.connectorUUID= application.dapsJWKSUrl= ``` * Choose one of 2 profiles: SENDER or RECEIVER. + * Set environment variable with following name: *AES256-SECRET-KEY* with any value (MyPassword for example). This will be used for encrypting AuditLog column in DB. * Start application ### Creating docker image diff --git a/ci/docker/test-cases/https-https-form-contract-negotiation/.env b/ci/docker/test-cases/https-https-form-contract-negotiation/.env index 27eea64c..25064742 100644 --- a/ci/docker/test-cases/https-https-form-contract-negotiation/.env +++ b/ci/docker/test-cases/https-https-form-contract-negotiation/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-https-form/.env b/ci/docker/test-cases/https-https-form/.env index 72dbb345..ec476955 100644 --- a/ci/docker/test-cases/https-https-form/.env +++ b/ci/docker/test-cases/https-https-form/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-https-header-contract-negotiation/.env b/ci/docker/test-cases/https-https-header-contract-negotiation/.env index cdd2a0d3..37d03485 100644 --- a/ci/docker/test-cases/https-https-header-contract-negotiation/.env +++ b/ci/docker/test-cases/https-https-header-contract-negotiation/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-https-header/.env b/ci/docker/test-cases/https-https-header/.env index bc34eb7e..f585ed46 100644 --- a/ci/docker/test-cases/https-https-header/.env +++ b/ci/docker/test-cases/https-https-header/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-https-mixed-contract-negotiation/.env b/ci/docker/test-cases/https-https-mixed-contract-negotiation/.env index c4f0d229..4006a7dc 100644 --- a/ci/docker/test-cases/https-https-mixed-contract-negotiation/.env +++ b/ci/docker/test-cases/https-https-mixed-contract-negotiation/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-https-mixed/.env b/ci/docker/test-cases/https-https-mixed/.env index 03c5c353..6cc01ad7 100644 --- a/ci/docker/test-cases/https-https-mixed/.env +++ b/ci/docker/test-cases/https-https-mixed/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-idscp2-form/.env b/ci/docker/test-cases/https-idscp2-form/.env index ecaf6e49..aab61bd3 100644 --- a/ci/docker/test-cases/https-idscp2-form/.env +++ b/ci/docker/test-cases/https-idscp2-form/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-idscp2-header/.env b/ci/docker/test-cases/https-idscp2-header/.env index 730893db..2b5faeaf 100644 --- a/ci/docker/test-cases/https-idscp2-header/.env +++ b/ci/docker/test-cases/https-idscp2-header/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/https-idscp2-mixed/.env b/ci/docker/test-cases/https-idscp2-mixed/.env index 84ff37a6..81beeb60 100644 --- a/ci/docker/test-cases/https-idscp2-mixed/.env +++ b/ci/docker/test-cases/https-idscp2-mixed/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/ws-idscp2/.env b/ci/docker/test-cases/ws-idscp2/.env index cb2dcf4d..43bbf7a8 100644 --- a/ci/docker/test-cases/ws-idscp2/.env +++ b/ci/docker/test-cases/ws-idscp2/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/ci/docker/test-cases/ws-ws/.env b/ci/docker/test-cases/ws-ws/.env index 1bef8a91..0308b7f6 100644 --- a/ci/docker/test-cases/ws-ws/.env +++ b/ci/docker/test-cases/ws-ws/.env @@ -1,5 +1,7 @@ BROKER_URL=https://broker.ids.isst.fraunhofer.de/infrastructure +AES256-SECRET-KEY=TRUEConnectorAESSecretKey123 + #SSL settings KEYSTORE_NAME=ssl-server.jks KEY_PASSWORD=changeit diff --git a/doc/AUDIT.md b/doc/AUDIT.md index 134884ac..0a0e201a 100644 --- a/doc/AUDIT.md +++ b/doc/AUDIT.md @@ -1,6 +1,8 @@ # Audit events in TRUE Connector -Audit events are stored in database (H2 with default configuration, possible to replace with PostgreSQL), this way tampering of the logs is prohibited. Entries in database are done only by the Execution Core Container, and ECC exposes protected endpoint, for API user, to fetch all audit logs, or audit logs for specific date: +Audit events are stored in database (H2 with default configuration, possible to replace with PostgreSQL), this way tampering of the logs is prohibited. Entries in database are done only by the Execution Core Container. Column for storing auditLog entry is encrypted using *AES/GCM/NoPadding* algorithm which requires user to set valid password. It must be done using environment variable with following name: *AES256-SECRET-KEY*.
+When ECC inserts audit entry into Database, AuditLog value will be encrypted using provided algorithm, and when data is requested, it will be decrypted.
+ECC exposes protected endpoint, for API user, to fetch all audit logs, or audit logs for specific date: ``` https://localhost:8090/api/audit/ diff --git a/src/main/java/it/eng/idsa/businesslogic/entity/AuditLog.java b/src/main/java/it/eng/idsa/businesslogic/entity/AuditLog.java index dfce2825..72a2804f 100644 --- a/src/main/java/it/eng/idsa/businesslogic/entity/AuditLog.java +++ b/src/main/java/it/eng/idsa/businesslogic/entity/AuditLog.java @@ -11,6 +11,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import it.eng.idsa.businesslogic.util.AES256Static; + @Entity @Table(name = "AuditLogs") public class AuditLog { @@ -28,7 +30,7 @@ public AuditLog() { } public AuditLog(String event) { - this.event = event; + this.event = AES256Static.encrypt(event); this.timestamp = LocalDateTime.now(); } diff --git a/src/main/java/it/eng/idsa/businesslogic/service/AuditEventService.java b/src/main/java/it/eng/idsa/businesslogic/service/AuditEventService.java index 9b5c6a0d..f931020f 100644 --- a/src/main/java/it/eng/idsa/businesslogic/service/AuditEventService.java +++ b/src/main/java/it/eng/idsa/businesslogic/service/AuditEventService.java @@ -4,12 +4,14 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import it.eng.idsa.businesslogic.entity.AuditLog; import it.eng.idsa.businesslogic.repository.AuditEventRepository; +import it.eng.idsa.businesslogic.util.AES256Static; @Service public class AuditEventService { @@ -21,13 +23,27 @@ public AuditLog saveAuditEvent(AuditLog auditEvent) { } public List getAllAuditEvents() { - return auditRepository.findAll(); + return auditRepository.findAll() + .parallelStream() + .map(this::decryptAuditLog) + .collect(Collectors.toList()); } public List getAuditEventsForDate(LocalDate date) { LocalDateTime startOfDay = date.atStartOfDay(); // Start of the day LocalDateTime endOfDay = date.atTime(LocalTime.MAX); // End of the day - return auditRepository.findByTimestampBetween(startOfDay, endOfDay); + return auditRepository.findByTimestampBetween(startOfDay, endOfDay) + .parallelStream() + .map(this::decryptAuditLog) + .collect(Collectors.toList()); + } + + private AuditLog decryptAuditLog(AuditLog auditLog) { + AuditLog a = new AuditLog(); + a.setId(auditLog.getId()); + a.setEvent(AES256Static.decrypt(auditLog.getEvent())); + a.setTimestamp(auditLog.getTimestamp()); + return a; } } diff --git a/src/main/java/it/eng/idsa/businesslogic/util/AES256.java b/src/main/java/it/eng/idsa/businesslogic/util/AES256.java new file mode 100644 index 00000000..c19a93f8 --- /dev/null +++ b/src/main/java/it/eng/idsa/businesslogic/util/AES256.java @@ -0,0 +1,140 @@ +package it.eng.idsa.businesslogic.util; + +import java.security.SecureRandom; +import java.security.spec.KeySpec; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.lang3.ObjectUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AES256 { + + private static final Logger logger = LoggerFactory.getLogger(AES256.class); + + private static final int KEY_LENGTH = 256; + private static final int ITERATION_COUNT = 65536; + private static String secretKey; + private static final String salt = "hd2y3vxlLv"; + private static String algorithm = "AES/GCM/NoPadding"; + + static { + secretKey = ObjectUtils.isNotEmpty(System.getenv("AES256-SECRET-KEY")) ? + System.getenv("AES256-SECRET-KEY") : "FPrnUtKJIGX1EMs"; + } + + public static String encrypt(String strToEncrypt) { + try { + SecureRandom secureRandom = new SecureRandom(); + byte[] ivBytes = new byte[16]; + secureRandom.nextBytes(ivBytes); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES"); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes); + + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); + + byte[] cipherText = cipher.doFinal(strToEncrypt.getBytes("UTF-8")); + byte[] encryptedData = new byte[ivBytes.length + cipherText.length]; + System.arraycopy(ivBytes, 0, encryptedData, 0, ivBytes.length); + System.arraycopy(cipherText, 0, encryptedData, ivBytes.length, cipherText.length); + + return Base64.getEncoder().encodeToString(encryptedData); + } catch (Exception e) { + logger.error("Error while encrypting", e); + return null; + } + } + + public static String decrypt(String strToDecrypt) { + try { + byte[] encryptedData = Base64.getDecoder().decode(strToDecrypt); + byte[] ivBytes = new byte[16]; + System.arraycopy(encryptedData, 0, ivBytes, 0, ivBytes.length); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES"); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, ivBytes); + + Cipher cipher = Cipher.getInstance(algorithm); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); + + byte[] cipherText = new byte[encryptedData.length - 16]; + System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length); + + byte[] decryptedText = cipher.doFinal(cipherText); + return new String(decryptedText, "UTF-8"); + } catch (Exception e) { + logger.error("Error while decrypting", e); + return null; + } + } + + /* + public static String encrypt(String strToEncrypt) { + try { + SecureRandom secureRandom = new SecureRandom(); + byte[] iv = new byte[16]; + secureRandom.nextBytes(iv); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES"); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = cipher.doFinal(strToEncrypt.getBytes("UTF-8")); + byte[] encryptedData = new byte[iv.length + cipherText.length]; + System.arraycopy(iv, 0, encryptedData, 0, iv.length); + System.arraycopy(cipherText, 0, encryptedData, iv.length, cipherText.length); + + return Base64.getEncoder().encodeToString(encryptedData); + } catch (Exception e) { + logger.error("Error while encrypting", e); + return null; + } + } + + public static String decrypt(String strToDecrypt) { + try { + byte[] encryptedData = Base64.getDecoder().decode(strToDecrypt); + byte[] iv = new byte[16]; + System.arraycopy(encryptedData, 0, iv, 0, iv.length); + IvParameterSpec ivspec = new IvParameterSpec(iv); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH); + SecretKey tmp = factory.generateSecret(spec); + SecretKeySpec secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES"); + + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivspec); + + byte[] cipherText = new byte[encryptedData.length - 16]; + System.arraycopy(encryptedData, 16, cipherText, 0, cipherText.length); + + byte[] decryptedText = cipher.doFinal(cipherText); + return new String(decryptedText, "UTF-8"); + } catch (Exception e) { + logger.error("Error while decrypting", e); + return null; + } + } + */ +}