开发者

How do I force tomcat to reload trusted certificates?

开发者 https://www.devze.com 2023-03-01 11:18 出处:网络
My WebApp uses a Connector for 2-Way SSL (aka \"Client Authentication\"): <Connector port=\"8084\" SSLEnabled=\"true\" maxThreads=\"10\" minSpareThreads=\"3\" maxSpareThreads=\"5\"

My WebApp uses a Connector for 2-Way SSL (aka "Client Authentication"):

<Connector port="8084" SSLEnabled="true" maxThreads="10" minSpareThreads="3" maxSpareThreads="5"
             enableLookups="false" disableUploadTimeout="true" acceptCount="100" scheme="https" secure="true"
             clientAuth="true" truststoreFile="conf/keystore.kst" truststoreType="JCEKS" sslProtocol="TLS" URIEncoding="UTF-8"
             keystoreFile="conf/keystore.kst" keystoreType="JCEKS" keyAlias="myAlias"
             ciphers="TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA"/>

My problem is that while the Tomcat 开发者_运维知识库server is running and I update the keystore with new trusted certifictaes , or even delete trusted certificates from it , the connector doesn't notice the changes.

What I've tried so far:

1) Stopping , Re-Initializing (reflection) and starting the Connector - didn't work.

2) Implementing my own SSLContext that reloads the certificates from the keystore. Well , here I'm missing the part of registering this SSLContext with tomcat (so that tomcat will use it in the connector for new incoming connections)

There are many posts on this matter but no real solution:

http://www.delphifaq.com/faq/f5003.shtml

http://jcalcote.wordpress.com/tag/truststore

(This article describes only how to recreate SSLcontext from the client side (missing the server side))

Any Ideas?

There's another related question :

How do I force a tomcat web application reload the trust store after I update it

but the answer there is not sufficient since I don't want to build a new ClassLoader.

Thanks.


There is now a solution to this starting with Tomcat v8.5.24.

They introduced 2 methods named: reloadSslHostConfig(String hostName) - to reload a specific host reloadSslHostConfigs() - reload all

They can be called in various ways:

  1. Using jmx
  2. Using manager service
  3. By making custom protocol - I found this way during my research

Details of way 1 and way 2 are easily available online.

Details of how to go about using way 3:

  1. Make a class extending the protocol of your choice for eg. Http11NioProtocol
  2. Override the required methods and just call super in them to keep default behavior
  3. Make a thread in this class to call reloadSslHostConfigs method time to time
  4. Package this class in a jar and put that jar in tomcat's lib folder
  5. Edit protocol in connector in server.xml to use this custom defined protocol

Find sample code below:

Main protocol class:

    package com.myown.connector;

    import java.io.File;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    import java.net.URL;
    import java.net.URLConnection;
    import java.nio.file.StandardCopyOption;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ConcurrentMap;

    import javax.management.MalformedObjectNameException;
    import javax.management.ObjectName;
    import javax.net.ssl.SSLSessionContext;

    import org.apache.coyote.http11.Http11NioProtocol;
    import org.apache.juli.logging.Log;
    import org.apache.juli.logging.LogFactory;
    import org.apache.tomcat.util.modeler.Registry;
    import org.apache.tomcat.util.net.AbstractEndpoint;
    import org.apache.tomcat.util.net.AbstractJsseEndpoint;
    import org.apache.tomcat.util.net.GetSslConfig;
    import org.apache.tomcat.util.net.SSLContext;
    import org.apache.tomcat.util.net.SSLHostConfig;
    import org.apache.tomcat.util.net.SSLHostConfigCertificate;
    import org.apache.tomcat.util.net.SSLImplementation;
    import org.apache.tomcat.util.net.SSLUtil;

    public class ReloadProtocol extends Http11NioProtocol {

        private static final Log log = LogFactory.getLog(Http12ProtocolSSL.class);

        public ReloadProtocol() {
            super();
            RefreshSslConfigThread refresher = new 
                  RefreshSslConfigThread(this.getEndpoint(), this);
            refresher.start();
        }

        @Override
        public void setKeystorePass(String s) {
            super.setKeystorePass(s);
        }

        @Override
        public void setKeyPass(String s) {
            super.setKeyPass(s);
        }

        @Override
        public void setTruststorePass(String p) {
            super.setTruststorePass(p);
        }

        class RefreshSslConfigThread extends Thread {

            AbstractJsseEndpoint<?> abstractJsseEndpoint = null;
            Http11NioProtocol protocol = null;

            public RefreshSslConfigThread(AbstractJsseEndpoint<?> abstractJsseEndpoint, Http11NioProtocol protocol) {
                this.abstractJsseEndpoint = abstractJsseEndpoint;
                this.protocol = protocol;
            }

            public void run() {
                int timeBetweenRefreshesInt = 1000000; // time in milli-seconds
                while (true) {
                    try {
                            abstractJsseEndpoint.reloadSslHostConfigs();
                            System.out.println("Config Updated");
                    } catch (Exception e) {
                        System.out.println("Problem while reloading.");
                    }
                    try {
                        Thread.sleep(timeBetweenRefreshesInt);
                    } catch (InterruptedException e) {
                        System.out.println("Error while sleeping");
                    }
                }
            }
       }
}

Connector in server.xml should mention this as the protocol:

<Connector protocol="com.myown.connector.ReloadProtocol"
 ..........

Hope this helps.


In case your connector has the bindOnInit property set to false (exist starting Tomcat 6.x), which:

Controls when the socket used by the connector is bound. By default it is bound when the connector is initiated and unbound when the connector is destroyed. If set to false, the socket will be bound when the connector is started and unbound when it is stopped.

Code snippets from org.apache.tomcat.util.net.AbstractEndpoint Tomcat 8.0.29:

public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

public final void stop() throws Exception {
    stopInternal();
    if (bindState == BindState.BOUND_ON_START) {
        unbind();
        bindState = BindState.UNBOUND;
    }
}

Then you can stop & start the connector through JMX after updates to your keys and trust stores.


The Tomcat HTTP/1.1 protocol handler can reload keystores.

If you are using embedded Tomcat or have some way of accessing the Tomcat Connector, then then you ask the protocol hander to reload the keystores and trust stores on demand without restarting the connector.

void addConnector(TomcatEmbedded tomcatEmbedded) {
    // Create a connector using the HTTP/1.1 protocol handler
    Connector connector = new Connector("HTTP/1.1");
    connector.setPort(8080);

    tomcatEmbedded.addConnector(connector);
}


void reload(Connector connector) {
    ProtocolHandler protocolHandler = connector.getProtocolHandler();
    if (protocolHandler instanceof Http11NioProtocol) {
        Http11NioProtocol http11NioProtocol = (Http11NioProtocol)protocolHandler;
        // reload the key and trust stores
        http11NioProtocol.reloadSslHostConfigs();
    }
}


The easiest way is read the keystore programmatically, get a SSL context from that and use it to make connection.

private SSLContext buildSslSocketContext() {

        logger.info("Started checking for certificates and if it finds the certificates will be loaded…..");

        String keyStoreLoc = //KEYSTORE LOCATION;
        String password = //KEYSTORE_PASSWORD;
        SSLContext context = null;
        
        try {
            // Create a KeyStore containing our trusted CAs
            KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
            InputStream in = null;

            try {
                in = new FileInputStream(keyStoreLoc);
                keystore.load(in,password.toCharArray());
            }catch(Exception e) {
                logger.error("Unable to load keystore "+e.getMessage());
            }finally {
                if(in != null) {
                    in.close(); 
                }               
            }

            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keystore);

            // Create an SSLContext that uses our TrustManager
            context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            logger.info("Completed loading of certificates.");

        } catch (Exception e) {
            logger.error("unable to create ssl context "+e.getMessage());
        }
        return context;
    } 

    ClientBuilder clientBuilder =  null;

        try {
            SSLContext sslContext = buildSslSocketContext();
            clientBuilder = ClientBuilder.newBuilder();
            if (sslContext != null) {
                clientBuilder.sslContext(sslContext);
            } else {
                logger.info("SSL conext is missing");
            }
            client = clientBuilder.build(); //use this client to make  http connection
        }catch(Exception e) {
            logger.error("unable to get ssl conext for client :"+e.getMessage());
        }
0

精彩评论

暂无评论...
验证码 换一张
取 消