feat: externalize Ed25519 keys with file-based loading
Keys are loaded from PEM files when CAMELEER_JWT_PRIVATE_KEY_PATH and CAMELEER_JWT_PUBLIC_KEY_PATH are set. Falls back to ephemeral key generation for development. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,27 +1,71 @@
|
|||||||
package net.siegeln.cameleer.saas.config;
|
package net.siegeln.cameleer.saas.config;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
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.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class JwtConfig {
|
public class JwtConfig {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(JwtConfig.class);
|
||||||
|
|
||||||
@Value("${cameleer.jwt.expiration:86400}")
|
@Value("${cameleer.jwt.expiration:86400}")
|
||||||
private long expirationSeconds = 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;
|
private KeyPair keyPair;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() throws NoSuchAlgorithmException {
|
public void init() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("Ed25519");
|
if (privateKeyPath.isEmpty() || publicKeyPath.isEmpty()) {
|
||||||
this.keyPair = keyGen.generateKeyPair();
|
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() {
|
public PrivateKey getPrivateKey() {
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ management:
|
|||||||
cameleer:
|
cameleer:
|
||||||
jwt:
|
jwt:
|
||||||
expiration: 86400 # 24 hours in seconds
|
expiration: 86400 # 24 hours in seconds
|
||||||
|
private-key-path: ${CAMELEER_JWT_PRIVATE_KEY_PATH:}
|
||||||
|
public-key-path: ${CAMELEER_JWT_PUBLIC_KEY_PATH:}
|
||||||
|
|||||||
Reference in New Issue
Block a user