diff --git a/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java b/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java index 1d14cc3..ae79022 100644 --- a/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java +++ b/src/main/java/net/siegeln/cameleer/saas/config/JwtConfig.java @@ -1,27 +1,71 @@ package net.siegeln.cameleer.saas.config; import jakarta.annotation.PostConstruct; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; @Component public class JwtConfig { + private static final Logger log = LoggerFactory.getLogger(JwtConfig.class); + @Value("${cameleer.jwt.expiration:86400}") private long expirationSeconds = 86400; + @Value("${cameleer.jwt.private-key-path:}") + private String privateKeyPath = ""; + + @Value("${cameleer.jwt.public-key-path:}") + private String publicKeyPath = ""; + private KeyPair keyPair; @PostConstruct - public void init() throws NoSuchAlgorithmException { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519"); - this.keyPair = keyGen.generateKeyPair(); + public void init() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { + if (privateKeyPath.isEmpty() || publicKeyPath.isEmpty()) { + log.warn("No Ed25519 key files configured — generating ephemeral keys (dev mode)"); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519"); + this.keyPair = keyGen.generateKeyPair(); + } else { + log.info("Loading Ed25519 keys from {} and {}", privateKeyPath, publicKeyPath); + PrivateKey privateKey = loadPrivateKey(Path.of(privateKeyPath)); + PublicKey publicKey = loadPublicKey(Path.of(publicKeyPath)); + this.keyPair = new KeyPair(publicKey, privateKey); + } + } + + private PrivateKey loadPrivateKey(Path path) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + String pem = Files.readString(path) + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + byte[] decoded = Base64.getDecoder().decode(pem); + return KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(decoded)); + } + + private PublicKey loadPublicKey(Path path) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + String pem = Files.readString(path) + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s+", ""); + byte[] decoded = Base64.getDecoder().decode(pem); + return KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(decoded)); } public PrivateKey getPrivateKey() { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 41fb8b3..6b2a4d8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -21,3 +21,5 @@ management: cameleer: jwt: expiration: 86400 # 24 hours in seconds + private-key-path: ${CAMELEER_JWT_PRIVATE_KEY_PATH:} + public-key-path: ${CAMELEER_JWT_PUBLIC_KEY_PATH:}