diff --git a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java index e150a5b230..4495f3f45d 100644 --- a/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java +++ b/client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.concurrent.ThreadFactory; -import org.asynchttpclient.channel.SSLEngineFactory; import org.asynchttpclient.channel.pool.KeepAliveStrategy; import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.filter.RequestFilter; @@ -249,7 +248,7 @@ public interface AsyncHttpClientConfig { int getHandshakeTimeout(); - SSLEngineFactory getSslEngineFactory(); + SslEngineFactory getSslEngineFactory(); int getChunkedFileChunkSize(); diff --git a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java index 9befbcf902..43490b364a 100644 --- a/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java +++ b/client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java @@ -31,7 +31,6 @@ import java.util.Properties; import java.util.concurrent.ThreadFactory; -import org.asynchttpclient.channel.SSLEngineFactory; import org.asynchttpclient.channel.pool.KeepAliveStrategy; import org.asynchttpclient.filter.IOExceptionFilter; import org.asynchttpclient.filter.RequestFilter; @@ -100,7 +99,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig { private final int sslSessionCacheSize; private final int sslSessionTimeout; private final SslContext sslContext; - private final SSLEngineFactory sslEngineFactory; + private final SslEngineFactory sslEngineFactory; // filters private final List requestFilters; @@ -163,7 +162,7 @@ private DefaultAsyncHttpClientConfig(// int sslSessionCacheSize,// int sslSessionTimeout,// SslContext sslContext,// - SSLEngineFactory sslEngineFactory,// + SslEngineFactory sslEngineFactory,// // filters List requestFilters,// @@ -416,7 +415,7 @@ public SslContext getSslContext() { } @Override - public SSLEngineFactory getSslEngineFactory() { + public SslEngineFactory getSslEngineFactory() { return sslEngineFactory; } @@ -557,7 +556,7 @@ public static class Builder { private int sslSessionCacheSize = defaultSslSessionCacheSize(); private int sslSessionTimeout = defaultSslSessionTimeout(); private SslContext sslContext; - private SSLEngineFactory sslEngineFactory; + private SslEngineFactory sslEngineFactory; // filters private final List requestFilters = new LinkedList<>(); @@ -831,7 +830,7 @@ public Builder setSslContext(final SslContext sslContext) { return this; } - public Builder setSslEngineFactory(SSLEngineFactory sslEngineFactory) { + public Builder setSslEngineFactory(SslEngineFactory sslEngineFactory) { this.sslEngineFactory = sslEngineFactory; return this; } diff --git a/client/src/main/java/org/asynchttpclient/SslEngineFactory.java b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java new file mode 100644 index 0000000000..21b7376702 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/SslEngineFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2014 AsyncHttpClient Project. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient; + +import javax.net.ssl.SSLEngine; + +public interface SslEngineFactory { + + /** + * Creates new {@link SSLEngine}. + * + * @param config the client config + * @param peerHost the peer hostname + * @param peerPort the peer port + * @return new engine + */ + SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort); +} diff --git a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java index 19d6cf84dd..238b32b349 100755 --- a/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java +++ b/client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java @@ -49,7 +49,7 @@ import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.SSLEngineFactory; +import org.asynchttpclient.SslEngineFactory; import org.asynchttpclient.channel.pool.ConnectionPoolPartitioning; import org.asynchttpclient.handler.AsyncHandlerExtensions; import org.asynchttpclient.netty.Callback; @@ -61,6 +61,7 @@ import org.asynchttpclient.netty.handler.Processor; import org.asynchttpclient.netty.handler.WebSocketProtocol; import org.asynchttpclient.netty.request.NettyRequestSender; +import org.asynchttpclient.netty.ssl.DefaultSslEngineFactory; import org.asynchttpclient.proxy.ProxyServer; import org.asynchttpclient.uri.Uri; import org.slf4j.Logger; @@ -81,7 +82,7 @@ public class ChannelManager { public static final String WS_ENCODER_HANDLER = "ws-encoder"; private final AsyncHttpClientConfig config; - private final SSLEngineFactory sslEngineFactory; + private final SslEngineFactory sslEngineFactory; private final EventLoopGroup eventLoopGroup; private final boolean allowReleaseEventLoopGroup; private final Class socketChannelClass; @@ -107,7 +108,7 @@ public ChannelManager(final AsyncHttpClientConfig config, Timer nettyTimer) { this.config = config; try { - this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new SSLEngineFactory.DefaultSSLEngineFactory(config); + this.sslEngineFactory = config.getSslEngineFactory() != null ? config.getSslEngineFactory() : new DefaultSslEngineFactory(config); } catch (SSLException e) { throw new ExceptionInInitializerError(e); } @@ -392,7 +393,7 @@ private HttpClientCodec newHttpClientCodec() { } private SslHandler createSslHandler(String peerHost, int peerPort) { - SSLEngine sslEngine = sslEngineFactory.newSSLEngine(peerHost, peerPort); + SSLEngine sslEngine = sslEngineFactory.newSslEngine(config, peerHost, peerPort); SslHandler sslHandler = new SslHandler(sslEngine); if (handshakeTimeout > 0) sslHandler.setHandshakeTimeoutMillis(handshakeTimeout); diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java new file mode 100644 index 0000000000..4658289e1c --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/DefaultSslEngineFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.netty.ssl; + +import io.netty.buffer.ByteBufAllocator; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; + +import org.asynchttpclient.AsyncHttpClientConfig; + +public class DefaultSslEngineFactory extends SslEngineFactoryBase { + + private final SslContext sslContext; + + public DefaultSslEngineFactory(AsyncHttpClientConfig config) throws SSLException { + this.sslContext = getSslContext(config); + } + + private SslContext getSslContext(AsyncHttpClientConfig config) throws SSLException { + if (config.getSslContext() != null) + return config.getSslContext(); + + SslContextBuilder sslContextBuilder = SslContextBuilder.forClient()// + .sslProvider(config.isUseOpenSsl() ? SslProvider.OPENSSL : SslProvider.JDK)// + .sessionCacheSize(config.getSslSessionCacheSize())// + .sessionTimeout(config.getSslSessionTimeout()); + + if (config.isAcceptAnyCertificate()) + sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); + + return sslContextBuilder.build(); + } + + @Override + public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) { + // FIXME should be using ctx allocator + SSLEngine sslEngine = sslContext.newEngine(ByteBufAllocator.DEFAULT, peerHost, peerPort); + configureSslEngine(sslEngine, config); + return sslEngine; + } +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java b/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java new file mode 100644 index 0000000000..6e5b190f37 --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/JsseSslEngineFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.netty.ssl; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.asynchttpclient.AsyncHttpClientConfig; + +public class JsseSslEngineFactory extends SslEngineFactoryBase { + + private final SSLContext sslContext; + + public JsseSslEngineFactory(SSLContext sslContext) { + this.sslContext = sslContext; + } + + @Override + public SSLEngine newSslEngine(AsyncHttpClientConfig config, String peerHost, int peerPort) { + SSLEngine sslEngine = sslContext.createSSLEngine(peerHost, peerPort); + configureSslEngine(sslEngine, config); + return sslEngine; + } + +} diff --git a/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java b/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java new file mode 100644 index 0000000000..3b2fd1bcea --- /dev/null +++ b/client/src/main/java/org/asynchttpclient/netty/ssl/SslEngineFactoryBase.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2015 AsyncHttpClient Project. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at + * http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ +package org.asynchttpclient.netty.ssl; + +import static org.asynchttpclient.util.MiscUtils.isNonEmpty; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; + +import org.asynchttpclient.AsyncHttpClientConfig; +import org.asynchttpclient.SslEngineFactory; + +public abstract class SslEngineFactoryBase implements SslEngineFactory { + + protected void configureSslEngine(SSLEngine sslEngine, AsyncHttpClientConfig config) { + sslEngine.setUseClientMode(true); + if (!config.isAcceptAnyCertificate()) { + SSLParameters params = sslEngine.getSSLParameters(); + params.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(params); + } + + if (isNonEmpty(config.getEnabledProtocols())) + sslEngine.setEnabledProtocols(config.getEnabledProtocols()); + + if (isNonEmpty(config.getEnabledCipherSuites())) + sslEngine.setEnabledCipherSuites(config.getEnabledCipherSuites()); + } +} diff --git a/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java index da540badbb..542c841e0e 100644 --- a/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java +++ b/client/src/test/java/org/asynchttpclient/BasicHttpsTest.java @@ -42,7 +42,7 @@ protected String getTargetUrl() { @Test(groups = "standalone") public void zeroCopyPostTest() throws Exception { - try (AsyncHttpClient client = asyncHttpClient(config().setSslContext(createSslContext(new AtomicBoolean(true))))) { + try (AsyncHttpClient client = asyncHttpClient(config().setSslEngineFactory(createSslEngineFactory(new AtomicBoolean(true))))) { Response resp = client.preparePost(getTargetUrl()).setBody(SIMPLE_TEXT_FILE).setHeader("Content-Type", "text/html").execute().get(); assertNotNull(resp); assertEquals(resp.getStatusCode(), HttpServletResponse.SC_OK); @@ -52,7 +52,7 @@ public void zeroCopyPostTest() throws Exception { @Test(groups = "standalone") public void multipleSSLRequestsTest() throws Exception { - try (AsyncHttpClient c = asyncHttpClient(config().setSslContext(createSslContext(new AtomicBoolean(true))))) { + try (AsyncHttpClient c = asyncHttpClient(config().setSslEngineFactory(createSslEngineFactory(new AtomicBoolean(true))))) { String body = "hello there"; // once @@ -78,7 +78,7 @@ public boolean keepAlive(Request ahcRequest, HttpRequest nettyRequest, HttpRespo } }; - try (AsyncHttpClient c = asyncHttpClient(config().setSslContext(createSslContext(new AtomicBoolean(true))).setKeepAliveStrategy(keepAliveStrategy))) { + try (AsyncHttpClient c = asyncHttpClient(config().setSslEngineFactory(createSslEngineFactory(new AtomicBoolean(true))).setKeepAliveStrategy(keepAliveStrategy))) { String body = "hello there"; c.preparePost(getTargetUrl()).setBody(body).setHeader("Content-Type", "text/html").execute(); @@ -94,7 +94,7 @@ public boolean keepAlive(Request ahcRequest, HttpRequest nettyRequest, HttpRespo public void reconnectsAfterFailedCertificationPath() throws Exception { AtomicBoolean trust = new AtomicBoolean(false); - try (AsyncHttpClient client = asyncHttpClient(config().setSslEngineFactory(createSSLEngineFactory(trust)))) { + try (AsyncHttpClient client = asyncHttpClient(config().setSslEngineFactory(createSslEngineFactory(trust)))) { String body = "hello there"; // first request fails because server certificate is rejected @@ -128,7 +128,7 @@ public void failInstantlyIfNotAllowedSelfSignedCertificate() throws Throwable { @Test(groups = "standalone") public void testNormalEventsFired() throws Exception { - try (AsyncHttpClient client = asyncHttpClient(config().setSslContext(createSslContext(new AtomicBoolean(true))))) { + try (AsyncHttpClient client = asyncHttpClient(config().setSslEngineFactory(createSslEngineFactory(new AtomicBoolean(true))))) { EventCollectingHandler handler = new EventCollectingHandler(); client.preparePost(getTargetUrl()).setBody("whatever").execute(handler).get(3, TimeUnit.SECONDS); handler.waitForCompletion(3, TimeUnit.SECONDS); diff --git a/client/src/test/java/org/asynchttpclient/test/TestUtils.java b/client/src/test/java/org/asynchttpclient/test/TestUtils.java index 56ecfb4d68..3c0d95be25 100644 --- a/client/src/test/java/org/asynchttpclient/test/TestUtils.java +++ b/client/src/test/java/org/asynchttpclient/test/TestUtils.java @@ -2,7 +2,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.testng.Assert.assertEquals; -import io.netty.handler.ssl.SslContext; import java.io.File; import java.io.FileNotFoundException; @@ -32,15 +31,14 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.commons.io.FileUtils; -import org.asynchttpclient.AsyncHttpClientConfig; -import org.asynchttpclient.channel.SSLEngineFactory; +import org.asynchttpclient.SslEngineFactory; +import org.asynchttpclient.netty.ssl.JsseSslEngineFactory; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.HashLoginService; @@ -274,37 +272,21 @@ private static TrustManager[] createTrustManagers() throws GeneralSecurityExcept return tmf.getTrustManagers(); } - public static SSLEngineFactory createSSLEngineFactory(AsyncHttpClientConfig config, AtomicBoolean trust) throws SSLException { - - return new SSLEngineFactory.DefaultSSLEngineFactory(config) { - - private SSLContext sslContext = newSSLContext(); - - private SSLContext newSSLContext() { - try { - KeyManager[] keyManagers = createKeyManagers(); - TrustManager[] trustManagers = new TrustManager[] { dummyTrustManager(trust, (X509TrustManager) createTrustManagers()[0]) }; - SecureRandom secureRandom = new SecureRandom(); - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, trustManagers, secureRandom); - - return sslContext; - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } - - @Override - protected SslContext getSslContext() throws SSLException { - return null; - } - - @Override - protected SSLEngine instanciateSslEngine(String peerHost, int peerPort) { - return sslContext.createSSLEngine(peerHost, peerPort); - } - }; + public static SslEngineFactory createSslEngineFactory(AtomicBoolean trust) throws SSLException { + + try { + KeyManager[] keyManagers = createKeyManagers(); + TrustManager[] trustManagers = new TrustManager[] { dummyTrustManager(trust, (X509TrustManager) createTrustManagers()[0]) }; + SecureRandom secureRandom = new SecureRandom(); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(keyManagers, trustManagers, secureRandom); + + return new JsseSslEngineFactory(sslContext); + + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } } public static class DummyTrustManager implements X509TrustManager { diff --git a/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java b/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java index 236561ece6..3ea07836b9 100644 --- a/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java +++ b/extras/simple/src/main/java/org/asynchttpclient/extras/simple/SimpleAsyncHttpClient.java @@ -15,6 +15,7 @@ import static org.asynchttpclient.Dsl.*; import static org.asynchttpclient.util.MiscUtils.closeSilently; import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.ssl.SslContext; import java.io.Closeable; import java.io.IOException; @@ -24,8 +25,6 @@ import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; -import javax.net.ssl.SSLContext; - import org.asynchttpclient.AsyncCompletionHandlerBase; import org.asynchttpclient.AsyncHandler; import org.asynchttpclient.AsyncHttpClient; @@ -40,6 +39,7 @@ import org.asynchttpclient.Request; import org.asynchttpclient.RequestBuilder; import org.asynchttpclient.Response; +import org.asynchttpclient.SslEngineFactory; import org.asynchttpclient.cookie.Cookie; import org.asynchttpclient.handler.ProgressAsyncHandler; import org.asynchttpclient.handler.resumable.ResumableAsyncHandler; @@ -556,10 +556,15 @@ public Builder setThreadFactory(ThreadFactory threadFactory) { return this; } - public Builder setSSLContext(final SSLContext sslContext) { + public Builder setSslContext(SslContext sslContext) { configBuilder.setSslContext(sslContext); return this; } + + public Builder setSslEngineFactory(SslEngineFactory sslEngineFactory) { + configBuilder.setSslEngineFactory(sslEngineFactory); + return this; + } public Builder setRealm(Realm realm) { configBuilder.setRealm(realm);