7

Does Spring Boot support Server Name Indication (SNI)? Specifically, is it possible for a Spring Boot (2.2.2.RELEASE) application running an embedded Tomcat server and packaged as an executable jar file to support multiple SSL certificates/domains based on the hostname of the incoming request?

It appears Tomcat supports SNI (as of Tomcat 8.5), but I'm not sure how to implement SNI in my Spring Boot app.

Patrick Mercer
  • 141
  • 1
  • 7

2 Answers2

6

I was able to verify that Spring Boot running an embedded Tomcat server supports Server Name Indication (SNI) using the following config:

application.properties

abc.com.key-store=${user.dir}/abc.com.p12
xyz.com.key-store=${user.dir}/xyz.com.p12

ApplicationConfig.java

import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration
public class ApplicationConfig {

    @Autowired
    private Environment env;

    @Bean
    public ServletWebServerFactory servletContainer() throws Exception {

        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
            // Override TomcatServletWebServerFactory methods (if needed)
        };

        // add SSL Connector
        tomcat.addAdditionalTomcatConnectors(createSSLConnectorForMultipleHosts());

        return tomcat;
    }


    private Connector createSSLConnectorForMultipleHosts() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");

        /**
         * Tomcat 9.0.x server SSL Connector for multiple hosts
         * 
        <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
                   maxThreads="150" SSLEnabled="true"
                   defaultSSLHostConfigName="*.abc.com">
            <SSLHostConfig hostName="*.abc.com">
                <Certificate certificateKeystoreFile="conf/abc.com.p12"
                             type="RSA" />
            </SSLHostConfig>
            <SSLHostConfig hostName="*.xyz.com">
                <Certificate certificateKeystoreFile="conf/xyz.com.p12"
                             type="RSA" />
            </SSLHostConfig>
        </Connector>
         */

        try {
            connector.setScheme("https");
            connector.setSecure(true);
            connector.setPort(8443);
            connector.setAttribute("SSLEnabled", "true");
            connector.setAttribute("defaultSSLHostConfigName", "*.abc.com");

            // *.abc.com
            SSLHostConfig sslHostConfig = new SSLHostConfig();
            sslHostConfig.setHostName("*.abc.com");

            SSLHostConfigCertificate sslHostConfigCertificate = new SSLHostConfigCertificate(sslHostConfig, Type.RSA);
            sslHostConfigCertificate.setCertificateKeystoreFile(env.getProperty("abc.com.key-store"));

            sslHostConfig.addCertificate(sslHostConfigCertificate);
            connector.addSslHostConfig(sslHostConfig);

            // *.xyz.com
            sslHostConfig = new SSLHostConfig();
            sslHostConfig.setHostName("*.xyz.com");

            sslHostConfigCertificate = new SSLHostConfigCertificate(sslHostConfig, Type.RSA);
            sslHostConfigCertificate.setCertificateKeystoreFile(env.getProperty("xyz.com.key-store"));

            sslHostConfig.addCertificate(sslHostConfigCertificate);
            connector.addSslHostConfig(sslHostConfig);

            return connector;
        }
        catch (Exception ex) {
            throw new IllegalStateException("Exception creating SSL Connector: ", ex);
        }
    }

}
Patrick Mercer
  • 141
  • 1
  • 7
2

Spring supports tomcat connector configuration. I did not run this code but this will give you some idea. You can try something like this:

@Bean
public ServletWebServerFactory servletContainer() throws Exception {
    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    tomcat.addAdditionalTomcatConnectors(createConnector());
    return tomcat;
}

public Connector createConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11AprProtocol");
    connector.setScheme("https");
    connector.setSecure(true);
    connector.setPort(8443);
    connector.addSslHostConfig(getSSLHostConfig());
    return connector;
}

private SSLHostConfig getSSLHostConfig() {
    SSLHostConfig sslHostConfig = new SSLHostConfig();
    sslHostConfig.setHostName("abc.com");
    sslHostConfig.setCertificateFile("abc.crt");
    sslHostConfig.setCaCertificateFile("xyz.crt");
    return sslHostConfig;
}
Ajinkya
  • 544
  • 4
  • 9
  • Thanks for the helpful code tip as it got me on the right track for being able to verify that Spring Boot does indeed support Server Name Indication (SNI). – Patrick Mercer Mar 05 '20 at 22:50