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, 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() { return keyPair.getPrivate(); } public PublicKey getPublicKey() { return keyPair.getPublic(); } public long getExpirationSeconds() { return expirationSeconds; } }