What I often do is replace Tomcat with Jetty within the Spring, which does allow custom ssl configuration. With that kind of setup any kind of customization is possible such as handling pem files, but also especially encrypted pem files. Below is the setup which I use:
Below the pem files will be loaded and a KeyManager TrustManager will be created. This will be used to create a SSLFactory, which in return will be used to convert to a Jetty compatible ssl configuration.
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.JettySslUtils;
import nl.altindag.ssl.util.PemUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
@Configuration
public class SSLConfig {
@Bean
public SSLFactory sslFactory() {
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("chain.pem", "private-key.pem", "my-password".toCharArray());
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");
return SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.build();
}
@Bean
public SslContextFactory.Server sslContextFactory(SSLFactory sslFactory) {
return JettySslUtils.forServer(sslFactory);
}
}
The Jetty SSL configuration needs to be passed on to the server configuration:
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
@Configuration
public class ServerConfig {
@Bean
public ConfigurableServletWebServerFactory webServerFactory(SslContextFactory.Server sslContextFactory, @Value("${server.port}") int serverPort) {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
JettyServerCustomizer jettyServerCustomizer = server -> {
ServerConnector serverConnector = new ServerConnector(server, sslContextFactory);
serverConnector.setPort(serverPort);
server.setConnectors(new Connector[]{serverConnector});
};
factory.setServerCustomizers(Collections.singletonList(jettyServerCustomizer));
return factory;
}
}
After these two java configurations you also need to tell Spring not to use Tomcat, but it should use Jetty. This can be done with the following pom configuration:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.7.5</version>
</dependency>
I used my own library to read the pem files. See below for the dependencies and here for more regarding other usages: SSLContext-Kickstart
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-jetty</artifactId>
<version>8.1.2</version>
</dependency>
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-pem</artifactId>
<version>8.1.2</version>
</dependency>