CAMEL-6540: Added option to enrich Camel message with client certificate information for SSL consumers.
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/dc281f36 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/dc281f36 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/dc281f36 Branch: refs/heads/master Commit: dc281f360e1e5548057409a7bb9120c70e59f085 Parents: 6406914 Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Jul 11 18:04:18 2013 +0200 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Jul 11 18:04:39 2013 +0200 ---------------------------------------------------------------------- .../camel/component/netty/NettyConstants.java | 5 ++ .../camel/component/netty/NettyEndpoint.java | 48 ++++++++++++- .../NettyServerBootstrapConfiguration.java | 10 ++- .../netty/NettySSLClientCertHeadersTest.java | 74 ++++++++++++++++++++ 4 files changed, 134 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/dc281f36/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConstants.java ---------------------------------------------------------------------- diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConstants.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConstants.java index 1ea260c..4082c7d 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConstants.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyConstants.java @@ -29,6 +29,11 @@ public final class NettyConstants { public static final String NETTY_REMOTE_ADDRESS = "CamelNettyRemoteAddress"; public static final String NETTY_LOCAL_ADDRESS = "CamelNettyLocalAddress"; public static final String NETTY_SSL_SESSION = "CamelNettySSLSession"; + public static final String NETTY_SSL_CLIENT_CERT_SUBJECT_NAME = "CamelNettySSLClientCertSubjectName"; + public static final String NETTY_SSL_CLIENT_CERT_ISSUER_NAME = "CamelNettySSLClientCertIssuerName"; + public static final String NETTY_SSL_CLIENT_CERT_SERIAL_NO = "CamelNettySSLClientCertSerialNumber"; + public static final String NETTY_SSL_CLIENT_CERT_NOT_BEFORE = "CamelNettySSLClientCertNotBefore"; + public static final String NETTY_SSL_CLIENT_CERT_NOT_AFTER = "CamelNettySSLClientCertNotAfter"; private NettyConstants() { // Utility class http://git-wip-us.apache.org/repos/asf/camel/blob/dc281f36/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyEndpoint.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyEndpoint.java index a712b7e..7b464fa 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyEndpoint.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyEndpoint.java @@ -16,7 +16,11 @@ */ package org.apache.camel.component.netty; +import java.math.BigInteger; +import java.security.Principal; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; +import javax.security.cert.X509Certificate; import org.apache.camel.Consumer; import org.apache.camel.Exchange; @@ -106,7 +110,7 @@ public class NettyEndpoint extends DefaultEndpoint { } return sslSession; } - + protected void updateMessageHeader(Message in, ChannelHandlerContext ctx, MessageEvent messageEvent) { in.setHeader(NettyConstants.NETTY_CHANNEL_HANDLER_CONTEXT, ctx); in.setHeader(NettyConstants.NETTY_MESSAGE_EVENT, messageEvent); @@ -115,7 +119,47 @@ public class NettyEndpoint extends DefaultEndpoint { if (configuration.isSsl()) { // setup the SslSession header - in.setHeader(NettyConstants.NETTY_SSL_SESSION, getSSLSession(ctx)); + SSLSession sslSession = getSSLSession(ctx); + in.setHeader(NettyConstants.NETTY_SSL_SESSION, sslSession); + + // enrich headers with details from the client certificate if option is enabled + if (configuration.isSslClientCertHeaders()) { + enrichWithClientCertInformation(sslSession, in); + } + } + } + + /** + * Enriches the message with client certificate details such as subject name, serial number etc. + * <p/> + * If the certificate is unverified then the headers is not enriched. + * + * @param sslSession the SSL session + * @param message the message to enrich + */ + protected void enrichWithClientCertInformation(SSLSession sslSession, Message message) { + try { + X509Certificate[] certificates = sslSession.getPeerCertificateChain(); + if (certificates != null && certificates.length > 0) { + X509Certificate cert = certificates[0]; + + Principal subject = cert.getSubjectDN(); + if (subject != null) { + message.setHeader(NettyConstants.NETTY_SSL_CLIENT_CERT_SUBJECT_NAME, subject.getName()); + } + Principal issuer = cert.getIssuerDN(); + if (issuer != null) { + message.setHeader(NettyConstants.NETTY_SSL_CLIENT_CERT_ISSUER_NAME, issuer.getName()); + } + BigInteger serial = cert.getSerialNumber(); + if (serial != null) { + message.setHeader(NettyConstants.NETTY_SSL_CLIENT_CERT_SERIAL_NO, serial.toString()); + } + message.setHeader(NettyConstants.NETTY_SSL_CLIENT_CERT_NOT_BEFORE, cert.getNotBefore()); + message.setHeader(NettyConstants.NETTY_SSL_CLIENT_CERT_NOT_AFTER, cert.getNotAfter()); + } + } catch (SSLPeerUnverifiedException e) { + // ignore } } http://git-wip-us.apache.org/repos/asf/camel/blob/dc281f36/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyServerBootstrapConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyServerBootstrapConfiguration.java b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyServerBootstrapConfiguration.java index 7df49b0..e7972fb 100644 --- a/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyServerBootstrapConfiguration.java +++ b/components/camel-netty/src/main/java/org/apache/camel/component/netty/NettyServerBootstrapConfiguration.java @@ -18,7 +18,6 @@ package org.apache.camel.component.netty; import java.io.File; import java.util.Map; -import java.util.concurrent.ExecutorService; import org.apache.camel.util.jsse.SSLContextParameters; import org.jboss.netty.channel.socket.nio.BossPool; @@ -46,6 +45,7 @@ public class NettyServerBootstrapConfiguration implements Cloneable { protected Map<String, Object> options; // SSL options is also part of the server bootstrap as the server listener on port X is either plain or SSL protected boolean ssl; + protected boolean sslClientCertHeaders; protected SslHandler sslHandler; protected SSLContextParameters sslContextParameters; protected boolean needClientAuth; @@ -187,6 +187,14 @@ public class NettyServerBootstrapConfiguration implements Cloneable { this.ssl = ssl; } + public boolean isSslClientCertHeaders() { + return sslClientCertHeaders; + } + + public void setSslClientCertHeaders(boolean sslClientCertHeaders) { + this.sslClientCertHeaders = sslClientCertHeaders; + } + public SslHandler getSslHandler() { return sslHandler; } http://git-wip-us.apache.org/repos/asf/camel/blob/dc281f36/components/camel-netty/src/test/java/org/apache/camel/component/netty/NettySSLClientCertHeadersTest.java ---------------------------------------------------------------------- diff --git a/components/camel-netty/src/test/java/org/apache/camel/component/netty/NettySSLClientCertHeadersTest.java b/components/camel-netty/src/test/java/org/apache/camel/component/netty/NettySSLClientCertHeadersTest.java new file mode 100644 index 0000000..7469d1c --- /dev/null +++ b/components/camel-netty/src/test/java/org/apache/camel/component/netty/NettySSLClientCertHeadersTest.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.netty; + +import java.io.File; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.impl.JndiRegistry; +import org.junit.Test; + +public class NettySSLClientCertHeadersTest extends BaseNettyTest { + + @Override + protected JndiRegistry createRegistry() throws Exception { + JndiRegistry registry = super.createRegistry(); + registry.bind("ksf", new File("src/test/resources/keystore.jks")); + registry.bind("tsf", new File("src/test/resources/keystore.jks")); + return registry; + } + + @Override + public boolean isUseRouteBuilder() { + return false; + } + + @Test + public void testSSLInOutWithNettyConsumer() throws Exception { + // ibm jdks dont have sun security algorithms + if (isJavaVendor("ibm")) { + return; + } + + getMockEndpoint("mock:input").expectedMessageCount(1); + + getMockEndpoint("mock:input").expectedHeaderReceived(NettyConstants.NETTY_SSL_CLIENT_CERT_SUBJECT_NAME, + "CN=arlu15, OU=Sun Java System Application Server, O=Sun Microsystems, L=Santa Clara, ST=California, C=US"); + getMockEndpoint("mock:input").expectedHeaderReceived(NettyConstants.NETTY_SSL_CLIENT_CERT_ISSUER_NAME, + "CN=arlu15, OU=Sun Java System Application Server, O=Sun Microsystems, L=Santa Clara, ST=California, C=US"); + getMockEndpoint("mock:input").expectedHeaderReceived(NettyConstants.NETTY_SSL_CLIENT_CERT_SERIAL_NO, "1210701502"); + + context.addRoutes(new RouteBuilder() { + public void configure() { + // needClientAuth=true so we can get the client certificate details + from("netty:tcp://localhost:{{port}}?sync=true&ssl=true&passphrase=changeit&keyStoreFile=#ksf&trustStoreFile=#tsf" + + "&needClientAuth=true&sslClientCertHeaders=true") + .to("mock:input") + .transform().constant("Bye World"); + } + }); + context.start(); + + String response = template.requestBody( + "netty:tcp://localhost:{{port}}?sync=true&ssl=true&passphrase=changeit&keyStoreFile=#ksf&trustStoreFile=#tsf", + "Hello World", String.class); + assertEquals("Bye World", response); + + assertMockEndpointsSatisfied(); + } + +}