From fb2889e32a72b9fbb2659973defa6cf2d02fbd95 Mon Sep 17 00:00:00 2001 From: Dimitrije Gasic Date: Tue, 30 Sep 2025 19:06:04 +0200 Subject: [PATCH] Add wrapping, entity and repository --- MiniKms/pom.xml | 4 ++ .../minikms/entity/WrappedKeyEntity.java | 49 +++++++++++++++++ .../minikms/enumeration/KeyOperation.java | 5 ++ .../security/minikms/enumeration/KeyType.java | 5 ++ .../repository/WrappedKeyRepository.java | 11 ++++ .../minikms/service/RootKeyManager.java | 52 +++++++++++++++++++ .../src/main/resources/application.properties | 2 + 7 files changed, 128 insertions(+) create mode 100644 MiniKms/src/main/java/ftn/security/minikms/entity/WrappedKeyEntity.java create mode 100644 MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyOperation.java create mode 100644 MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyType.java create mode 100644 MiniKms/src/main/java/ftn/security/minikms/repository/WrappedKeyRepository.java create mode 100644 MiniKms/src/main/java/ftn/security/minikms/service/RootKeyManager.java diff --git a/MiniKms/pom.xml b/MiniKms/pom.xml index 49d9c7f..4a815af 100644 --- a/MiniKms/pom.xml +++ b/MiniKms/pom.xml @@ -51,6 +51,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-data-jpa + org.springframework.security spring-security-test diff --git a/MiniKms/src/main/java/ftn/security/minikms/entity/WrappedKeyEntity.java b/MiniKms/src/main/java/ftn/security/minikms/entity/WrappedKeyEntity.java new file mode 100644 index 0000000..35639c3 --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/entity/WrappedKeyEntity.java @@ -0,0 +1,49 @@ +package ftn.security.minikms.entity; + +import ftn.security.minikms.enumeration.KeyOperation; +import ftn.security.minikms.enumeration.KeyType; +import jakarta.persistence.*; +import jakarta.persistence.Id; +import lombok.Data; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; + +@Data +@Entity +public class WrappedKeyEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, updatable = false) + private UUID logicalKey; + + private String alias; + private Integer version; + + @Enumerated(EnumType.STRING) + private KeyType keyType; + + @Lob + private byte[] wrappedKey; + + @ElementCollection(fetch = FetchType.EAGER) + @Enumerated(EnumType.STRING) + private List allowedOperations; + + private Instant createdAt; + + public static WrappedKeyEntity of(UUID logicalKey, String alias, Integer version, KeyType keyType, byte[] wrappedKey, List allowedOperations) { + var entity = new WrappedKeyEntity(); + entity.logicalKey = logicalKey; + entity.alias = alias; + entity.version = version; + entity.keyType = keyType; + entity.wrappedKey = wrappedKey; + entity.allowedOperations = allowedOperations; + entity.createdAt = Instant.now(); + return entity; + } +} \ No newline at end of file diff --git a/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyOperation.java b/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyOperation.java new file mode 100644 index 0000000..75e3e6d --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyOperation.java @@ -0,0 +1,5 @@ +package ftn.security.minikms.enumeration; + +public enum KeyOperation { + ENCRYPT, SIGN +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyType.java b/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyType.java new file mode 100644 index 0000000..8a6d2eb --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/enumeration/KeyType.java @@ -0,0 +1,5 @@ +package ftn.security.minikms.enumeration; + +public enum KeyType { + SYMMETRIC, ASYMMETRIC, HMAC +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/repository/WrappedKeyRepository.java b/MiniKms/src/main/java/ftn/security/minikms/repository/WrappedKeyRepository.java new file mode 100644 index 0000000..208ff8b --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/repository/WrappedKeyRepository.java @@ -0,0 +1,11 @@ +package ftn.security.minikms.repository; + +import ftn.security.minikms.entity.WrappedKeyEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface WrappedKeyRepository extends JpaRepository { + Optional findFirstByLogicalKeyOrderByVersionDesc(UUID logicalKey); +} diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/RootKeyManager.java b/MiniKms/src/main/java/ftn/security/minikms/service/RootKeyManager.java new file mode 100644 index 0000000..c99ee82 --- /dev/null +++ b/MiniKms/src/main/java/ftn/security/minikms/service/RootKeyManager.java @@ -0,0 +1,52 @@ +package ftn.security.minikms.service; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.GeneralSecurityException; +import java.util.Base64; + +@Service +public class RootKeyManager { + private static final java.security.SecureRandom RNG = new java.security.SecureRandom(); + private final SecretKey rootKey; + + public RootKeyManager(@Value("${ROOT_KEY}") String base64Key) { + if (base64Key == null || base64Key.isEmpty()) { + throw new IllegalArgumentException("ROOT_KEY environment variable is not set or empty"); + } + + byte[] raw = Base64.getDecoder().decode(base64Key); + this.rootKey = new SecretKeySpec(raw, "AES"); + } + + public byte[] wrap(byte[] plaintextKey, byte[] aad) throws GeneralSecurityException { + var iv = new byte[12]; + RNG.nextBytes(iv); + + var c = Cipher.getInstance("AES/GCM/NoPadding"); + c.init(Cipher.ENCRYPT_MODE, rootKey, new GCMParameterSpec(128, iv)); + if (aad != null && aad.length > 0) c.updateAAD(aad); + var ct = c.doFinal(plaintextKey); + + var out = new byte[iv.length + ct.length]; + System.arraycopy(iv, 0, out, 0, iv.length); + System.arraycopy(ct, 0, out, iv.length, ct.length); + return out; + } + + public byte[] unwrap(byte[] blob, byte[] aad) throws GeneralSecurityException { + if (blob.length < 12 + 16) throw new GeneralSecurityException("Blob too short"); + var iv = java.util.Arrays.copyOfRange(blob, 0, 12); + var ct = java.util.Arrays.copyOfRange(blob, 12, blob.length); + + var c = Cipher.getInstance("AES/GCM/NoPadding"); + c.init(Cipher.DECRYPT_MODE, rootKey, new GCMParameterSpec(128, iv)); + if (aad != null && aad.length > 0) c.updateAAD(aad); + return c.doFinal(ct); + } +} diff --git a/MiniKms/src/main/resources/application.properties b/MiniKms/src/main/resources/application.properties index 60417b3..d3bcf13 100644 --- a/MiniKms/src/main/resources/application.properties +++ b/MiniKms/src/main/resources/application.properties @@ -1 +1,3 @@ spring.application.name=MiniKms + +ROOT_KEY=your-base64-encoded-key-here