Search code examples
javatomcatsslhsm

HSM usage with Apache Tomcat for HTTPS


My HSM (Hardware Security Module) stores (or allows to use) private key, however, it does not support PKCS#11 and similar method. In turn, Apache Tomcat might work with certificate and keys either via JKS, PKCS#11 or programmatically. My goal is to enable HTTPS support on a Web server, but I see no way how to achieve that with changes in configuration files only.

I imagine an option that I could store certificate in JKS, and get private key associated with it via HSM vendor provided API. For that purpose, if I am right, I will need to re-implement JSSEImplementation and corresponding factories. As well, I will need to implement specific Key and Trust Managers.

Is that the only way to solve such problem?

Is it safe to replace JSSEImplementation in a running standalone instance of Apache Tomcat, for instance, right after it started.


Solution

  • Finally, I came up only to the solution below based on this example. I add <Connector> instance to the Tomcat configuration with sslImplementationName property pointing to the custom JSSEImplementation class name, and extend JSSEImplementation with custom JSSESocketFactory and X509KeyManager classes.

    Tomcat configuration looks like:

    <Connector
           protocol="org.apache.coyote.http11.Http11Protocol"
           port="8443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           clientAuth="true" sslProtocol="TLS" SSLEnabled="true"
           sslImplementationName="x.y.z.CustomJSSEImplementation"
           keyAlias="alias_of_key_in_HSM_and_cert_in_JKS"
    />
    

    CustomJSSEImplementation class is:

    public class CustomJSSEImplementation extends JSSEImplementation {
       @Override
       public ServerSocketFactory getServerSocketFactory(AbstractEndpoint endpoint) {
          return new CustomSslContextSocketFactory(endpoint);
       }
    
       @Override
       public SSLUtil getSSLUtil(AbstractEndpoint endpoint) {
          return new CustomSslContextSocketFactory(endpoint);
       }
    }
    

    CustomSslContextSocketFactory class is:

    public class CustomSslContextSocketFactory extends JSSESocketFactory {
    
        public static final AtomicReference<CustomSslContext> customSslContext =
            new AtomicReference<CustomSslContext>();
    
        public CustomSslContextSocketFactory(AbstractEndpoint endpoint) {
            super(endpoint);
        }
    
        @Override
        public KeyManager[] getKeyManagers() throws Exception {
            return (customSslContext.get() == null ? super.getKeyManagers() : customSslContext.get().getKeyManagers(this));
        }
    }
    

    CustomSslContext interface is:

    interface CustomSslContext {
        KeyManager[] getKeyManagers(JSSESocketFactory factory) throws Exception;
    }
    

    HsmKeyManagerImpl which reference private key in the HSM by an keyAlias property looks like:

    public class HsmKeyManagerImpl implements X509KeyManager {
        ...
    
        @Override
        public PrivateKey getPrivateKey(String alias) {
            // HSM Vendor specific API calls
        }
    }
    

    I didn't show the code how to obtain certificate which corresponds to the private, but the same alias defined by the keyAlias property of the <Connector> is used to get it from the JKS.