Skip to content

Commit

Permalink
Full SSL Cert Chain (#848)
Browse files Browse the repository at this point in the history
* fix: consider full cert chain here

* fix: made sslContextFactory singleton... avoidind duplicate cert loading
  • Loading branch information
ohager authored Dec 22, 2024
1 parent 3486565 commit 1ee9373
Showing 1 changed file with 54 additions and 40 deletions.
94 changes: 54 additions & 40 deletions src/brs/web/server/AbstractServerConnectorBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.security.*;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand All @@ -23,59 +25,66 @@ public abstract class AbstractServerConnectorBuilder {

private static final Logger logger = LoggerFactory.getLogger(AbstractServerConnectorBuilder.class);
protected final WebServerContext context;
private static SslContextFactory.Server sslContextFactory;

public AbstractServerConnectorBuilder(WebServerContext context) {
this.context = context;
}

abstract ServerConnector build(Server server);

protected ServerConnector createSSLConnector(Server server) {
logger.info("Creating SSL Connector");
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSecureScheme("https");
httpsConfig.setSecurePort(context.getPropertyService().getInt(Props.API_PORT));
httpsConfig.addCustomizer(new SecureRequestCustomizer());
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();

sslContextFactory.setKeyStorePath(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH));
sslContextFactory.setKeyStorePassword(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD));

// Handle optional Let's Encrypt Certificates...
String letsencryptPath = context.getPropertyService().getString(Props.API_SSL_LETSENCRYPT_PATH);
if (letsencryptPath != null && !letsencryptPath.isEmpty()) {
try {
loadLetsEncryptCertsAsPkcs12(letsencryptPath, context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH), context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD));
} catch (Exception e) {
logger.error(e.getMessage());
}
private SslContextFactory.Server getSslContextFactory() {
if (sslContextFactory == null) {
sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH));
sslContextFactory.setKeyStorePassword(context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD));

// Reload the certificate every week, in case it was renewed
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
Runnable reloadCert = () -> {
// Handle optional Let's Encrypt Certificates...
String letsencryptPath = context.getPropertyService().getString(Props.API_SSL_LETSENCRYPT_PATH);
if (letsencryptPath != null && !letsencryptPath.isEmpty()) {
try {
loadLetsEncryptCertsAsPkcs12(letsencryptPath, context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH), context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD));
sslContextFactory.reload(consumer -> logger.info("SSL keystore from letsencrypt reloaded."));
} catch (Exception e) {
logger.error(e.getMessage());
}

// Reload the certificate every week, in case it was renewed
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
Runnable reloadCert = () -> {
try {
loadLetsEncryptCertsAsPkcs12(letsencryptPath, context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PATH), context.getPropertyService().getString(Props.API_SSL_KEY_STORE_PASSWORD));
sslContextFactory.reload(consumer -> logger.info("SSL keystore from letsencrypt reloaded."));
} catch (Exception e) {
logger.error(e.getMessage());
}
};
scheduler.scheduleWithFixedDelay(reloadCert, 7, 7, TimeUnit.DAYS);
}

String[] strongCiphers = {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
// Add more strong ciphers as needed
};
scheduler.scheduleWithFixedDelay(reloadCert, 7, 7, TimeUnit.DAYS);
sslContextFactory.setIncludeCipherSuites(strongCiphers);
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.3");
sslContextFactory.setExcludeProtocols("SSLv3");
}
return sslContextFactory;
}

protected ServerConnector createSSLConnector(Server server) {
logger.info("Creating SSL Connector");
HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSecureScheme("https");
httpsConfig.setSecurePort(context.getPropertyService().getInt(Props.API_PORT));
httpsConfig.addCustomizer(new SecureRequestCustomizer());

String[] strongCiphers = {
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
// Add more strong ciphers as needed
};
sslContextFactory.setIncludeCipherSuites(strongCiphers);
sslContextFactory.setIncludeProtocols("TLSv1.2", "TLSv1.3");
sslContextFactory.setExcludeProtocols("SSLv3");
return new ServerConnector(server, new SslConnectionFactory(sslContextFactory, "http/1.1"),
return new ServerConnector(server, new SslConnectionFactory(getSslContextFactory(), "http/1.1"),
new HttpConnectionFactory(httpsConfig));
}

public void loadLetsEncryptCertsAsPkcs12(String letsencryptPath, String p12Filename, String password) throws Exception {
public static void loadLetsEncryptCertsAsPkcs12(String letsencryptPath, String p12Filename, String password) throws Exception {

logger.info("Converting Let's Encrypt Certificate to PKCS12...");
Security.addProvider(new BouncyCastleProvider());
Expand All @@ -92,16 +101,21 @@ public void loadLetsEncryptCertsAsPkcs12(String letsencryptPath, String p12Filen
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded()));

X509CertificateHolder certObj = (X509CertificateHolder) certParser.readObject();
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
certConverter.setProvider("BC");
X509Certificate certificate = certConverter.getCertificate(certObj);
X509Certificate[] certificates = new X509Certificate[]{certificate};
// Convert all certificates in fullchain.pem
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter().setProvider("BC");
List<X509Certificate> certList = new ArrayList<>();
Object certObj;
while ((certObj = certParser.readObject()) != null) {
if (certObj instanceof X509CertificateHolder) {
certList.add(certConverter.getCertificate((X509CertificateHolder) certObj));
}
}
X509Certificate[] certificates = certList.toArray(new X509Certificate[0]);

// Add the private key and certificates to the keystore
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.setKeyEntry("SIGNUM_NODE_CERT", privateKey, password.toCharArray(), certificates);
keyStore.setKeyEntry("lets-encrypt", privateKey, password.toCharArray(), certificates);

// Finally, save as PKCS12 file...
try (OutputStream out = new FileOutputStream(p12Filename)) {
Expand Down

0 comments on commit 1ee9373

Please sign in to comment.