/*
* This file is part of ReadonlyREST.
*
* ReadonlyREST is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ReadonlyREST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ReadonlyREST. If not, see http://www.gnu.org/licenses/
*/
package org.elasticsearch.plugin.readonlyrest.ssl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.plugin.readonlyrest.ESContext;
import org.elasticsearch.plugin.readonlyrest.settings.SettingsMalformedException;
import org.elasticsearch.plugin.readonlyrest.settings.ssl.SslSettings;
import org.elasticsearch.plugin.readonlyrest.settings.ssl.EnabledSslSettings;
import javax.net.ssl.SSLException;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.Key;
import java.security.PrivilegedAction;
import java.security.cert.Certificate;
import java.util.Base64;
import java.util.Optional;
public class SSLEngineProvider {
private final Logger logger;
private SslContext context;
public SSLEngineProvider(SslSettings settings, ESContext esContext) {
this.logger = esContext.logger(getClass());
if (settings instanceof EnabledSslSettings) {
createContext((EnabledSslSettings) settings);
}
}
public Optional<SslContext> getContext() {
return Optional.ofNullable(context);
}
private void createContext(EnabledSslSettings settings) {
if (settings.getCertchainPem().isPresent() && settings.getPrivkeyPem().isPresent()) {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
try {
logger.info("Loading SSL context with certChain=" + settings.getCertchainPem().get().getName() +
", privKey=" + settings.getPrivkeyPem().get().getName());
// #TODO expose configuration of sslPrivKeyPem password? Letsencrypt never sets one..
context = SslContextBuilder.forServer(
settings.getCertchainPem().get(),
settings.getPrivkeyPem().get(),
null
).build();
} catch (SSLException e) {
logger.error("Failed to load SSL CertChain & private key!");
e.printStackTrace();
}
return null;
});
// Everything is configured
logger.info("SSL configured through cert_chain and privkey");
return;
} else {
logger.info("SSL cert_chain and privkey not configured, attempting with JKS keystore..");
try {
char[] keyStorePassBa = null;
if (settings.getKeystorePass().isPresent()) {
keyStorePassBa = settings.getKeystorePass().get().toCharArray();
}
// Load the JKS keystore
java.security.KeyStore ks = java.security.KeyStore.getInstance("JKS");
ks.load(new java.io.FileInputStream(settings.getKeystoreFile()), keyStorePassBa);
char[] keyPassBa = null;
if (settings.getKeyPass().isPresent()) {
keyPassBa = settings.getKeyPass().get().toCharArray();
}
// Get PrivKey from keystore
String sslKeyAlias;
if (!settings.getKeyAlias().isPresent()) {
if (ks.aliases().hasMoreElements()) {
String inferredAlias = ks.aliases().nextElement();
logger.info("SSL ssl.key_alias not configured, took first alias in keystore: " + inferredAlias);
sslKeyAlias = inferredAlias;
} else {
throw new SettingsMalformedException("No alias found, therefore key found in keystore!");
}
} else {
sslKeyAlias = settings.getKeyAlias().get();
}
Key key = ks.getKey(sslKeyAlias, keyPassBa);
if (key == null) {
throw new SettingsMalformedException("Private key not found in keystore for alias: " + sslKeyAlias);
}
// Create a PEM of the private key
StringBuilder sb = new StringBuilder();
sb.append("---BEGIN PRIVATE KEY---\n");
sb.append(Base64.getEncoder().encodeToString(key.getEncoded()));
sb.append("\n");
sb.append("---END PRIVATE KEY---");
String privateKey = sb.toString();
logger.info("Discovered key from JKS");
// Get CertChain from keystore
Certificate[] cchain = ks.getCertificateChain(sslKeyAlias);
// Create a PEM of the certificate chain
sb = new StringBuilder();
for (Certificate c : cchain) {
sb.append("-----BEGIN CERTIFICATE-----\n");
sb.append(Base64.getEncoder().encodeToString(c.getEncoded()));
sb.append("\n");
sb.append("-----END CERTIFICATE-----\n");
}
String certChain = sb.toString();
logger.info("Discovered cert chain from JKS");
AccessController.doPrivileged(
new PrivilegedAction<Void>() {
@Override
public Void run() {
try {
// #TODO expose configuration of sslPrivKeyPem password? Letsencrypt never sets one..
context = SslContextBuilder.forServer(
new ByteArrayInputStream(certChain.getBytes(StandardCharsets.UTF_8)),
new ByteArrayInputStream(privateKey.getBytes(StandardCharsets.UTF_8)),
null
).build();
} catch (Exception e) {
logger.error("Failed to load SSL CertChain & private key from Keystore!");
e.printStackTrace();
}
return null;
}
});
} catch (Throwable t) {
logger.error("Failed to load SSL certs and keys from JKS Keystore!");
t.printStackTrace();
}
}
}
}