I have been trying to get a 2-way ssl/https proxy working with Camel. I have been able to set up the Jetty Component using 2-way ssl and am now attempting to get it working with the Http4 component to complete the client side of the proxy.
When I route the jetty traffic to a log component, all is well and the 2 way ssl trust chain is fine. When I throw in the Http4 component, it blows up with a peer not authenticated exception. I am using Camel 2.7.0
Here is what I have so far
public static void main(String[] args) throws Exception {
CamelContext context = new DefaultCamelContext();
JettyHttpComponent jetty = context.getComponent("jetty", JettyHttpComponent.class);
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
sslConnector.setPort(9443);
sslConnector.setKeystore("/home/brian/jboss.keystore");
sslConnector.setKeyPassword("changeit");
sslConnector.setTruststore("/home/brian/jboss.truststore");
sslConnector.setTrustPassword("changeit");
sslConnector.setPassword("changeit");
sslConnector.setNeedClientAuth(true);
Map<Integer, SslSelectChannelConnector> connectors = new HashMap<Integer, SslSelectChannelConnector>();
connectors.put(9443, sslConnector);
jetty.setSslSocketConnectors(connectors);
final Endpoint jettyEndpoint = jetty.createEndpoint("jetty:https://localhost:9443/service");
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(new File("/home/brian/User2.p12")), "Password1234!".toCharArray());
X509KeyManager keyManager = new CTSKeyManager(keystore, "user2", "Password1234!".toCharArray());
KeyManager[] keyManagers = new KeyManager[] { keyManager };
X509TrustManager trustManager = new EasyTrustManager();
TrustManager[] trustManagers = new TrustManager[] { trustManager };
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(keyManagers, trustManagers, null);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", 443, new SSLSocketFactory(sslcontext,
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)));
HttpComponent http4 = context.getComponent("http4", HttpComponent.class);
http4.setClientConnectionManager(new ThreadSafeClientConnManager(registry));
final Endpoint https4Endpoint = http4
.createEndpoint("https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure=false");
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
from(jettyEndpoint).to(https4Endpoint);
}
});
context.start();
context.stop();
}
private static class EasyTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
private static class CTSKeyManager extends X509ExtendedKeyManager {
private final KeyStore keystore;
private final char[] privateKeyPassword;
private final String privateKeyAlias;
public CTSKeyManager(KeyStore keystore, String privateKeyAlias, char[] privateKeyPassword) {
this.keystore = keystore;
this.privateKeyAlias = privateKeyAlias;
this.privateKeyPassword = privateKeyPassword;
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
String[] serverAliases = null;
try {
List<String> aliasList = new ArrayList<String>();
int count = 0;
Enumeration<String> aliases = keystore.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
aliasList.add(alias);
count++;
}
serverAliases = aliasList.toArray(new String[count]);
} catch (Exception e) {
}
return serverAliases;
}
@Override
public PrivateKey getPrivateKey(String alias) {
PrivateKey privateKey = null;
try {
privateKey = (PrivateKey) keystore.getKey(alias, privateKeyPassword);
} catch (Exception e) {
}
return privateKey;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return privateKeyAlias == null ? null : new String[] { privateKeyAlias };
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
X509Certificate[] x509 = null;
try {
Certificate[] certs = keystore.getCertificateChain(alias);
if (certs == null || certs.length == 0) {
return null;
}
x509 = new X509Certificate[certs.length];
for (int i = 0; i < certs.length; i++) {
x509[i] = (X509Certificate) certs[i];
}
} catch (Exception e) {
}
return x509;
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
return privateKeyAlias;
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return privateKeyAlias;
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
return privateKeyAlias;
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
return privateKeyAlias;
}
}
}
As far as I can tell, the trust should be fine between all keystores/truststores used on both sides of the proxy connections.
Here is my stack trace
[ qtp25584663-14] HttpProducer DEBUG Executing http POST method: https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure;=false [ qtp25584663-14] DefaultErrorHandler DEBUG Failed delivery for exchangeId: ID-ubuntu-46528-1303140195358-0-1. On delivery attempt: 0 caught: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated [ qtp25584663-14] DefaultErrorHandler ERROR Failed delivery for exchangeId: ID-ubuntu-46528-1303140195358-0-1. Exhausted after delivery attempt: 1 caught: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at com.sun.net.ssl.internal.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:352) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:390) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121) at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:561) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732) at org.apache.camel.component.http4.HttpProducer.executeMethod(HttpProducer.java:187) at org.apache.camel.component.http4.HttpProducer.process(HttpProducer.java:101) at org.apache.camel.impl.converter.AsyncProcessorTypeConverter$ProcessorToAsyncProcessorBridge.process(AsyncProcessorTypeConverter.java:50) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.SendProcessor$2.doInAsyncProducer(SendProcessor.java:104) at org.apache.camel.impl.ProducerCache.doInAsyncProducer(ProducerCache.java:272) at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:98) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.DelegateAsyncProcessor.processNext(DelegateAsyncProcessor.java:98) at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:89) at org.apache.camel.processor.interceptor.TraceInterceptor.process(TraceInterceptor.java:99) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.RedeliveryErrorHandler.processErrorHandler(RedeliveryErrorHandler.java:299) at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:208) at org.apache.camel.processor.DefaultChannel.process(DefaultChannel.java:269) at org.apache.camel.processor.UnitOfWorkProcessor.process(UnitOfWorkProcessor.java:109) at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:77) at org.apache.camel.processor.DelegateAsyncProcessor.processNext(DelegateAsyncProcessor.java:98) at org.apache.camel.processor.DelegateAsyncProcessor.process(DelegateAsyncProcessor.java:89) at org.apache.camel.management.InstrumentationProcessor.process(InstrumentationProcessor.java:68) at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:109) at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:534) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1351) at org.eclipse.jetty.servlets.MultiPartFilter.doFilter(MultiPartFilter.java:97) at org.apache.camel.component.jetty.CamelMultipartFilter.doFilter(CamelMultipartFilter.java:41) at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1322) at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:473) at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:929) at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:403) at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:864) at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117) at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:114) at org.eclipse.jetty.server.Server.handle(Server.java:352) at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:596) at org.eclipse.jetty.server.HttpConnection$RequestHandler.content(HttpConnection.java:1068) at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:805) at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:218) at org.eclipse.jetty.server.HttpConnection.handle(HttpConnection.java:426) at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:508) at org.eclipse.jetty.io.nio.SelectChannelEndPoint.access$000(SelectChannelEndPoint.java:34) at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:40) at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:451) at java.lang.Thread.run(Thread.java:662)
Ok working now, as it turns out, I had a fundamental misunderstanding of endpoints and protocols within Camel. I should have been registering a scheme with the the https4 protocol and setting my SSLSocketFactory/SSLContext on it. Since it Was registering a scheme with https it was never been using by the Http4 component.
Here is my working solution with 2 caveats.
Why can't I pass in a SchemeRegistry to the ThreadSafeClientConnManager and it is not used when constructing the HttpClient? I have to the HttpClientConfigurer instead
Jetty has an issue where the Keystore and Truststore must be set by path on the SslSelectChannelConnector instead of via SSLContext (bug is in at least jetty 7.2.2 and 7.4.0 ->latest)
Code:
public class CamelProxy {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
CamelContext context = new DefaultCamelContext();
final Endpoint jettyEndpoint = configureJetty(context);
final Endpoint https4Endpoint = configureHttpClient(context);
context.addRoutes(new RouteBuilder() {
@Override
public void configure() {
from(jettyEndpoint).to("log:com.smithforge.request?showAll=true").to(https4Endpoint);
}
});
context.start();
context.stop();
}
private static Endpoint configureHttpClient(CamelContext context) throws Exception {
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream(new File("/home/brian/User2.p12")), "Password1234!".toCharArray());
KeyStore truststore = KeyStore.getInstance("JKS");
truststore.load(new FileInputStream(new File("/home/brian/jboss.truststore")), "changeit".toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance("SunX509");
keyFactory.init(keystore, "Password1234!".toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance("SunX509");
trustFactory.init(truststore);
SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(keyFactory.getKeyManagers(), trustFactory.getTrustManagers(), null);
SSLSocketFactory factory = new SSLSocketFactory(sslcontext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
SchemeRegistry registry = new SchemeRegistry();
final Scheme scheme = new Scheme("https4", 443, factory);
registry.register(scheme);
HttpComponent http4 = context.getComponent("http4", HttpComponent.class);
http4.setHttpClientConfigurer(new HttpClientConfigurer() {
@Override
public void configureHttpClient(HttpClient client) {
client.getConnectionManager().getSchemeRegistry().register(scheme);
}
});
http4.setClientConnectionManager(new ThreadSafeClientConnManager());
return http4
.createEndpoint("https4://soafa-lite-staging:443/axis2/services/SigActService?bridgeEndpoint=true&throwExceptionOnFailure=false");
}
private static Endpoint configureJetty(CamelContext context) throws Exception {
JettyHttpComponent jetty = context.getComponent("jetty", JettyHttpComponent.class);
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
sslConnector.setPort(4443);
sslConnector.setKeystore("/home/brian/jboss.keystore");
sslConnector.setKeyPassword("changeit");
sslConnector.setTruststore("/home/brian/jboss.truststore");
sslConnector.setTrustPassword("changeit");
sslConnector.setPassword("changeit");
sslConnector.setNeedClientAuth(true);
sslConnector.setAllowRenegotiate(true);
Map<Integer, SslSelectChannelConnector> connectors = new HashMap<Integer, SslSelectChannelConnector>();
connectors.put(4443, sslConnector);
jetty.setSslSocketConnectors(connectors);
return jetty.createEndpoint("jetty:https://localhost:4443/service");
}
// .to("log:com.smithforge.response?showHeaders=true");
}