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