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;
|
||||
|
||||
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() {
|
||||
|
||||
@@ -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:}
|
||||
|
||||
Reference in New Issue
Block a user