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:
- Using jmx
- Using manager service
- 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:
- Make a class extending the protocol of your choice for eg. Http11NioProtocol
- Override the required methods and just call super in them to keep default behavior
- Make a thread in this class to call reloadSslHostConfigs method time to time
- Package this class in a jar and put that jar in tomcat's lib folder
- 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());
}
精彩评论