ALPN should allow handshake failure if no compatible protocols found

Motivation:
If there are no common protocols in the ALPN protocol exchange we still compete the handshake successfully.  This handshake should fail according to http://tools.ietf.org/html/rfc7301#section-3.2 with a status of no_application_protocol.  The specification also allows for the server to "play dumb" and not advertise that it supports ALPN in this case (see MAY clauses in http://tools.ietf.org/html/rfc7301#section-3.1)

Modifications:
-Upstream project used for ALPN (alpn-boot) does not support this.  So a PR https://github.com/jetty-project/jetty-alpn/pull/3 was submitted.
-The netty code using alpn-boot should support the new interface (return null on existing method).
-Version number of alpn-boot must be updated in pom.xml files

Result:
-Netty fails the SSL handshake if ALPN is used and there are no common protocols.
This commit is contained in:
Scott Mitchell 2014-10-03 18:16:04 -04:00
parent 41d9830e07
commit f8af84d599
31 changed files with 1807 additions and 613 deletions

View File

@ -29,14 +29,16 @@ import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.CharsetUtil;
import java.net.URI;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
/**
@ -63,8 +65,12 @@ public final class Http2Client {
/* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
* Please refer to the HTTP/2 specification for cipher requirements. */
SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyAlpnSslEngineWrapper.instance(),
new ApplicationProtocolConfig(
Protocol.ALPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.HTTP_2.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
0, 0);
} else {
sslCtx = null;

View File

@ -26,13 +26,14 @@ import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.util.Arrays;
/**
* A HTTP/2 Server that responds to requests with a Hello World. Once started, you can test the
* server with the example client.
@ -53,10 +54,12 @@ public final class Http2Server {
/* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
* Please refer to the HTTP/2 specification for cipher requirements. */
SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(
new ApplicationProtocolConfig(
Protocol.ALPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.HTTP_2.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
JettyAlpnSslEngineWrapper.instance(),
0, 0);
} else {
sslCtx = null;

View File

@ -24,18 +24,20 @@ import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.codec.http2.Http2OrHttpChooser.SelectedProtocol;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheObjectAggregator;
import io.netty.handler.ssl.JettyAlpnSslEngineWrapper;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
/**
* Simple memcache client that demonstrates get and set commands against a memcache server.
@ -55,8 +57,12 @@ public final class MemcacheClient {
/* NOTE: the following filter may not include all ciphers required by the HTTP/2 specification
* Please refer to the HTTP/2 specification for cipher requirements. */
SupportedCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.HTTP_2.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyAlpnSslEngineWrapper.instance(),
new ApplicationProtocolConfig(
Protocol.ALPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.HTTP_2.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
0, 0);
} else {
sslCtx = null;

View File

@ -27,13 +27,14 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import java.util.Arrays;
/**
* An SPDY client that allows you to send HTTP GET to a SPDY server.
* <p>
@ -57,8 +58,12 @@ public final class SpdyClient {
// Configure SSL.
final SslContext sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE, null, IdentityCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyNpnSslEngineWrapper.instance(),
new ApplicationProtocolConfig(
Protocol.NPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.SPDY_3_1.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
0, 0);
HttpResponseClientHandler httpResponseHandler = new HttpResponseClientHandler();

View File

@ -24,13 +24,14 @@ import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.spdy.SpdyOrHttpChooser.SelectedProtocol;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.IdentityCipherSuiteFilter;
import io.netty.handler.ssl.JettyNpnSslEngineWrapper;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.util.Arrays;
/**
* A SPDY Server that responds to a GET request with a Hello World.
* <p>
@ -58,8 +59,12 @@ public final class SpdyServer {
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContext.newServerContext(
ssc.certificate(), ssc.privateKey(), null, null, IdentityCipherSuiteFilter.INSTANCE,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
JettyNpnSslEngineWrapper.instance(),
new ApplicationProtocolConfig(
Protocol.NPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.SPDY_3_1.protocolName(),
SelectedProtocol.HTTP_1_1.protocolName()),
0, 0);
// Configure the server.

View File

@ -0,0 +1,122 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import static io.netty.handler.ssl.ApplicationProtocolUtil.toList;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
/**
* Provides an {@link SSLEngine} agnostic way to configure a {@link ApplicationProtocolNegotiator}.
*/
public final class ApplicationProtocolConfig {
private final List<String> supportedProtocols;
private final Protocol protocol;
private final SelectorFailureBehavior selectorBehavior;
private final SelectedListenerFailureBehavior selectedBehavior;
/**
* Create a new instance.
* @param protocol The application protocol functionality to use.
* @param selectorBehavior How the peer selecting the protocol should behave.
* @param selectedBehavior How the peer being notified of the selected protocol should behave.
* @param supportedProtocols The order of iteration determines the preference of support for protocols.
*/
public ApplicationProtocolConfig(Protocol protocol, SelectorFailureBehavior selectorBehavior,
SelectedListenerFailureBehavior selectedBehavior, Iterable<String> supportedProtocols) {
this(protocol, selectorBehavior, selectedBehavior, toList(supportedProtocols));
}
/**
* Create a new instance.
* @param protocol The application protocol functionality to use.
* @param selectorBehavior How the peer selecting the protocol should behave.
* @param selectedBehavior How the peer being notified of the selected protocol should behave.
* @param supportedProtocols The order of iteration determines the preference of support for protocols.
*/
public ApplicationProtocolConfig(Protocol protocol, SelectorFailureBehavior selectorBehavior,
SelectedListenerFailureBehavior selectedBehavior, String... supportedProtocols) {
this(protocol, selectorBehavior, selectedBehavior, toList(supportedProtocols));
}
/**
* Create a new instance.
* @param protocol The application protocol functionality to use.
* @param selectorBehavior How the peer selecting the protocol should behave.
* @param selectedBehavior How the peer being notified of the selected protocol should behave.
* @param supportedProtocols The order of iteration determines the preference of support for protocols.
*/
private ApplicationProtocolConfig(Protocol protocol, SelectorFailureBehavior selectorBehavior,
SelectedListenerFailureBehavior selectedBehavior, List<String> supportedProtocols) {
this.supportedProtocols = Collections.unmodifiableList(checkNotNull(supportedProtocols, "supportedProtocols"));
this.protocol = checkNotNull(protocol, "protocol");
this.selectorBehavior = checkNotNull(selectorBehavior, "selectorBehavior");
this.selectedBehavior = checkNotNull(selectedBehavior, "selectedBehavior");
}
/**
* Defines which application level protocol negotiation to use.
*/
public enum Protocol {
NONE, NPN, ALPN, NPN_AND_ALPN
}
/**
* Defines the most common behaviors for the peer that selects the application protocol.
*/
public enum SelectorFailureBehavior {
FATAL_ALERT, NO_ADVERTISE, CHOOSE_MY_LAST_PROTOCOL
}
/**
* Defines the most common behaviors for the peer which is notified of the selected protocol.
*/
public enum SelectedListenerFailureBehavior {
ACCEPT, FATAL_ALERT, CHOOSE_MY_LAST_PROTOCOL
}
/**
* The application level protocols supported.
*/
public List<String> supportedProtocols() {
return supportedProtocols;
}
/**
* Get which application level protocol negotiation to use.
*/
public Protocol protocol() {
return protocol;
}
/**
* Get the desired behavior for the peer who selects the application protocol.
*/
public SelectorFailureBehavior selectorFailureBehavior() {
return selectorBehavior;
}
/**
* Get the desired behavior for the peer who is notified of the selected protocol.
*/
public SelectedListenerFailureBehavior selectedListenerFailureBehavior() {
return selectedBehavior;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.List;
/**
* Interface to support Application Protocol Negotiation.
* <p>
* Default implementations are provided for:
* <ul>
* <li><a href="https://technotes.googlecode.com/git/nextprotoneg.html">Next Protocol Negotiation</a></li>
* <li><a href="http://tools.ietf.org/html/rfc7301">Application-Layer Protocol Negotiation</a></li>
* </ul>
*/
public interface ApplicationProtocolNegotiator {
/**
* Get the collection of application protocols supported by this application (in preference order).
*/
List<String> protocols();
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class for application protocol common operations.
*/
final class ApplicationProtocolUtil {
private static final int DEFAULT_LIST_SIZE = 2;
private ApplicationProtocolUtil() {
}
static List<String> toList(Iterable<String> protocols) {
return toList(DEFAULT_LIST_SIZE, protocols);
}
static List<String> toList(int initialListSize, Iterable<String> protocols) {
if (protocols == null) {
return null;
}
List<String> result = new ArrayList<String>(initialListSize);
for (String p : protocols) {
if (p == null || p.isEmpty()) {
throw new IllegalArgumentException("protocol cannot be null or empty");
}
result.add(p);
}
if (result.isEmpty()) {
throw new IllegalArgumentException("protocols cannot empty");
}
return result;
}
static List<String> toList(String... protocols) {
return toList(DEFAULT_LIST_SIZE, protocols);
}
static List<String> toList(int initialListSize, String... protocols) {
if (protocols == null) {
return null;
}
List<String> result = new ArrayList<String>(initialListSize);
for (String p : protocols) {
if (p == null || p.isEmpty()) {
throw new IllegalArgumentException("protocol cannot be null or empty");
}
result.add(p);
}
if (result.isEmpty()) {
throw new IllegalArgumentException("protocols cannot empty");
}
return result;
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import javax.net.ssl.SSLEngine;
/**
* The {@link JdkApplicationProtocolNegotiator} to use if you need ALPN and are using {@link SslProvider#JDK}.
*/
public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicationProtocolNegotiator {
private static final SslEngineWrapperFactory ALPN_WRAPPER = new SslEngineWrapperFactory() {
{
if (!JdkAlpnSslEngine.isAvailable()) {
throw new RuntimeException("ALPN unsupported. Is your classpatch configured correctly?"
+ " See http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-starting");
}
}
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator,
boolean isServer) {
return new JdkAlpnSslEngine(engine, applicationNegotiator, isServer);
}
};
/**
* Create a new instance.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(Iterable<String> protocols) {
this(false, protocols);
}
/**
* Create a new instance.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(String... protocols) {
this(false, protocols);
}
/**
* Create a new instance.
* @param failIfNoCommonProtocols Fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(boolean failIfNoCommonProtocols, Iterable<String> protocols) {
this(failIfNoCommonProtocols, failIfNoCommonProtocols, protocols);
}
/**
* Create a new instance.
* @param failIfNoCommonProtocols Fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(boolean failIfNoCommonProtocols, String... protocols) {
this(failIfNoCommonProtocols, failIfNoCommonProtocols, protocols);
}
/**
* Create a new instance.
* @param clientFailIfNoCommonProtocols Client side fail with a fatal alert if not common protocols are detected.
* @param serverFailIfNoCommonProtocols Server side fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(boolean clientFailIfNoCommonProtocols,
boolean serverFailIfNoCommonProtocols, Iterable<String> protocols) {
this(serverFailIfNoCommonProtocols ? FAIL_SELECTOR_FACTORY : NO_FAIL_SELECTOR_FACTORY,
clientFailIfNoCommonProtocols ? FAIL_SELECTION_LISTENER_FACTORY : NO_FAIL_SELECTION_LISTENER_FACTORY,
protocols);
}
/**
* Create a new instance.
* @param clientFailIfNoCommonProtocols Client side fail with a fatal alert if not common protocols are detected.
* @param serverFailIfNoCommonProtocols Server side fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(boolean clientFailIfNoCommonProtocols,
boolean serverFailIfNoCommonProtocols, String... protocols) {
this(serverFailIfNoCommonProtocols ? FAIL_SELECTOR_FACTORY : NO_FAIL_SELECTOR_FACTORY,
clientFailIfNoCommonProtocols ? FAIL_SELECTION_LISTENER_FACTORY : NO_FAIL_SELECTION_LISTENER_FACTORY,
protocols);
}
/**
* Create a new instance.
* @param selectorFactory The factory which provides classes responsible for selecting the protocol.
* @param listenerFactory The factory which provides to be notified of which protocol was selected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(ProtocolSelectorFactory selectorFactory,
ProtocolSelectionListenerFactory listenerFactory, Iterable<String> protocols) {
super(ALPN_WRAPPER, selectorFactory, listenerFactory, protocols);
}
/**
* Create a new instance.
* @param selectorFactory The factory which provides classes responsible for selecting the protocol.
* @param listenerFactory The factory which provides to be notified of which protocol was selected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkAlpnApplicationProtocolNegotiator(ProtocolSelectorFactory selectorFactory,
ProtocolSelectionListenerFactory listenerFactory, String... protocols) {
super(ALPN_WRAPPER, selectorFactory, listenerFactory, protocols);
}
}

View File

@ -15,6 +15,12 @@
*/
package io.netty.handler.ssl;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
import io.netty.util.internal.PlatformDependent;
import java.util.HashSet;
import java.util.List;
import javax.net.ssl.SSLEngine;
@ -24,7 +30,7 @@ import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.alpn.ALPN.ClientProvider;
import org.eclipse.jetty.alpn.ALPN.ServerProvider;
final class JettyAlpnSslEngine extends JettySslEngine {
final class JdkAlpnSslEngine extends JdkSslEngine {
private static boolean available;
static boolean isAvailable() {
@ -52,52 +58,51 @@ final class JettyAlpnSslEngine extends JettySslEngine {
}
}
JettyAlpnSslEngine(SSLEngine engine, final List<String> nextProtocols, boolean server) {
super(engine, nextProtocols, server);
JdkAlpnSslEngine(SSLEngine engine, final JdkApplicationProtocolNegotiator applicationNegotiator, boolean server) {
super(engine);
checkNotNull(applicationNegotiator, "applicationNegotiator");
if (server) {
final String[] array = nextProtocols.toArray(new String[nextProtocols.size()]);
final String fallback = array[array.length - 1];
final ProtocolSelector protocolSelector = checkNotNull(applicationNegotiator.protocolSelectorFactory()
.newSelector(this, new HashSet<String>(applicationNegotiator.protocols())), "protocolSelector");
ALPN.put(engine, new ServerProvider() {
@Override
public String select(List<String> protocols) {
for (int i = 0; i < array.length; ++i) {
String p = array[i];
if (protocols.contains(p)) {
session.setApplicationProtocol(p);
return p;
}
try {
return protocolSelector.select(protocols);
} catch (Throwable t) {
PlatformDependent.throwException(t);
return null;
}
session.setApplicationProtocol(fallback);
return fallback;
}
@Override
public void unsupported() {
session.setApplicationProtocol(null);
protocolSelector.unsupported();
}
});
} else {
final ProtocolSelectionListener protocolListener = checkNotNull(applicationNegotiator
.protocolListenerFactory().newListener(this, applicationNegotiator.protocols()),
"protocolListener");
ALPN.put(engine, new ClientProvider() {
@Override
public List<String> protocols() {
return nextProtocols;
return applicationNegotiator.protocols();
}
@Override
public void selected(String protocol) {
getSession().setApplicationProtocol(protocol);
}
@Override
public boolean supports() {
return true;
try {
protocolListener.selected(protocol);
} catch (Throwable t) {
PlatformDependent.throwException(t);
}
}
@Override
public void unsupported() {
getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1));
protocolListener.unsupported();
}
});
}
@ -105,13 +110,13 @@ final class JettyAlpnSslEngine extends JettySslEngine {
@Override
public void closeInbound() throws SSLException {
ALPN.remove(engine);
ALPN.remove(getWrappedEngine());
super.closeInbound();
}
@Override
public void closeOutbound() {
ALPN.remove(engine);
ALPN.remove(getWrappedEngine());
super.closeOutbound();
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLEngine;
/**
* JDK extension methods to support {@link ApplicationProtocolNegotiator}
*/
public interface JdkApplicationProtocolNegotiator extends ApplicationProtocolNegotiator {
/**
* Abstract factory pattern for wrapping an {@link SSLEngine} object. This is useful for NPN/APLN JDK support.
*/
public interface SslEngineWrapperFactory {
/**
* Abstract factory pattern for wrapping an {@link SSLEngine} object. This is useful for NPN/APLN support.
*
* @param engine The engine to wrap.
* @param applicationNegotiator The application level protocol negotiator
* @param isServer <ul>
* <li>{@code true} if the engine is for server side of connections</li>
* <li>{@code false} if the engine is for client side of connections</li>
* </ul>
* @return The resulting wrapped engine. This may just be {@code engine}.
*/
SSLEngine wrapSslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator,
boolean isServer);
}
/**
* Interface to define the role of an application protocol selector in the SSL handshake process. Either
* {@link ProtocolSelector#unsupported()} OR {@link ProtocolSelector#select(List)} will be called for each SSL
* handshake.
*/
public interface ProtocolSelector {
/**
* Callback invoked to let the application know that the peer does not support this
* {@link ApplicationProtocolNegotiator}.
*/
void unsupported();
/**
* Callback invoked to select the application level protocol from the {@code protocols} provided.
*
* @param protocols the protocols sent by the protocol advertiser
* @return the protocol selected by this {@link ProtocolSelector}. A {@code null} value will indicate the no
* protocols were selected but the handshake should not fail. The decision to fail the handshake is left to the
* other end negotiating the SSL handshake.
* @throws Exception If the {@code protocols} provide warrant failing the SSL handshake with a fatal alert.
*/
String select(List<String> protocols) throws Exception;
}
/**
* A listener to be notified by which protocol was select by its peer. Either the
* {@link ProtocolSelectionListener#unsupported()} OR the {@link ProtocolSelectionListener#selected(String)} method
* will be called for each SSL handshake.
*/
public interface ProtocolSelectionListener {
/**
* Callback invoked to let the application know that the peer does not support this
* {@link ApplicationProtocolNegotiator}.
*/
void unsupported();
/**
* Callback invoked to let this application know the protocol chosen by the peer.
*
* @param protocol the protocol selected by the peer. May be {@code null} or empty as supported by the
* application negotiation protocol.
* @throws Exception This may be thrown if the selected protocol is not acceptable and the desired behavior is
* to fail the handshake with a fatal alert.
*/
void selected(String protocol) throws Exception;
}
/**
* Factory interface for {@link ProtocolSelector} objects.
*/
public interface ProtocolSelectorFactory {
/**
* Generate a new instance of {@link ProtocolSelector}.
* @param engine The {@link SSLEngine} that the returned {@link ProtocolSelector} will be used to create an
* instance for.
* @param supportedProtocols The protocols that are supported.
* @return A new instance of {@link ProtocolSelector}.
*/
ProtocolSelector newSelector(SSLEngine engine, Set<String> supportedProtocols);
}
/**
* Factory interface for {@link ProtocolSelectionListener} objects.
*/
public interface ProtocolSelectionListenerFactory {
/**
* Generate a new instance of {@link ProtocolSelectionListener}.
* @param engine The {@link SSLEngine} that the returned {@link ProtocolSelectionListener} will be used to
* create an instance for.
* @param supportedProtocols The protocols that are supported in preference order.
* @return A new instance of {@link ProtocolSelectionListener}.
*/
ProtocolSelectionListener newListener(SSLEngine engine, List<String> supportedProtocols);
}
/**
* Get the {@link SslEngineWrapperFactory}.
*/
SslEngineWrapperFactory wrapperFactory();
/**
* Get the {@link ProtocolSelectorFactory}.
*/
ProtocolSelectorFactory protocolSelectorFactory();
/**
* Get the {@link ProtocolSelectionListenerFactory}.
*/
ProtocolSelectionListenerFactory protocolListenerFactory();
}

View File

@ -0,0 +1,209 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import static io.netty.handler.ssl.ApplicationProtocolUtil.toList;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
/**
* Common base class for {@link JdkApplicationProtocolNegotiator} classes to inherit from.
*/
class JdkBaseApplicationProtocolNegotiator implements JdkApplicationProtocolNegotiator {
private final List<String> protocols;
private final ProtocolSelectorFactory selectorFactory;
private final ProtocolSelectionListenerFactory listenerFactory;
private final SslEngineWrapperFactory wrapperFactory;
/**
* Create a new instance.
* @param wrapperFactory Determines which application protocol will be used by wrapping the SSLEngine in use.
* @param selectorFactory How the peer selecting the protocol should behave.
* @param listenerFactory How the peer being notified of the selected protocol should behave.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
protected JdkBaseApplicationProtocolNegotiator(SslEngineWrapperFactory wrapperFactory,
ProtocolSelectorFactory selectorFactory, ProtocolSelectionListenerFactory listenerFactory,
Iterable<String> protocols) {
this(wrapperFactory, selectorFactory, listenerFactory, toList(protocols));
}
/**
* Create a new instance.
* @param wrapperFactory Determines which application protocol will be used by wrapping the SSLEngine in use.
* @param selectorFactory How the peer selecting the protocol should behave.
* @param listenerFactory How the peer being notified of the selected protocol should behave.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
protected JdkBaseApplicationProtocolNegotiator(SslEngineWrapperFactory wrapperFactory,
ProtocolSelectorFactory selectorFactory, ProtocolSelectionListenerFactory listenerFactory,
String... protocols) {
this(wrapperFactory, selectorFactory, listenerFactory, toList(protocols));
}
/**
* Create a new instance.
* @param wrapperFactory Determines which application protocol will be used by wrapping the SSLEngine in use.
* @param selectorFactory How the peer selecting the protocol should behave.
* @param listenerFactory How the peer being notified of the selected protocol should behave.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
private JdkBaseApplicationProtocolNegotiator(SslEngineWrapperFactory wrapperFactory,
ProtocolSelectorFactory selectorFactory, ProtocolSelectionListenerFactory listenerFactory,
List<String> protocols) {
this.wrapperFactory = checkNotNull(wrapperFactory, "wrapperFactory");
this.selectorFactory = checkNotNull(selectorFactory, "selectorFactory");
this.listenerFactory = checkNotNull(listenerFactory, "listenerFactory");
this.protocols = Collections.unmodifiableList(checkNotNull(protocols, "protocols"));
}
@Override
public List<String> protocols() {
return protocols;
}
@Override
public ProtocolSelectorFactory protocolSelectorFactory() {
return selectorFactory;
}
@Override
public ProtocolSelectionListenerFactory protocolListenerFactory() {
return listenerFactory;
}
@Override
public SslEngineWrapperFactory wrapperFactory() {
return wrapperFactory;
}
static final ProtocolSelectorFactory FAIL_SELECTOR_FACTORY = new ProtocolSelectorFactory() {
@Override
public ProtocolSelector newSelector(SSLEngine engine, Set<String> supportedProtocols) {
return new FailProtocolSelector((JdkSslEngine) engine, supportedProtocols);
}
};
static final ProtocolSelectorFactory NO_FAIL_SELECTOR_FACTORY = new ProtocolSelectorFactory() {
@Override
public ProtocolSelector newSelector(SSLEngine engine, Set<String> supportedProtocols) {
return new NoFailProtocolSelector((JdkSslEngine) engine, supportedProtocols);
}
};
static final ProtocolSelectionListenerFactory FAIL_SELECTION_LISTENER_FACTORY =
new ProtocolSelectionListenerFactory() {
@Override
public ProtocolSelectionListener newListener(SSLEngine engine, List<String> supportedProtocols) {
return new FailProtocolSelectionListener((JdkSslEngine) engine, supportedProtocols);
}
};
static final ProtocolSelectionListenerFactory NO_FAIL_SELECTION_LISTENER_FACTORY =
new ProtocolSelectionListenerFactory() {
@Override
public ProtocolSelectionListener newListener(SSLEngine engine, List<String> supportedProtocols) {
return new NoFailProtocolSelectionListener((JdkSslEngine) engine, supportedProtocols);
}
};
protected static class NoFailProtocolSelector implements ProtocolSelector {
private final JdkSslEngine jettyWrapper;
private final Set<String> supportedProtocols;
public NoFailProtocolSelector(JdkSslEngine jettyWrapper, Set<String> supportedProtocols) {
this.jettyWrapper = jettyWrapper;
this.supportedProtocols = supportedProtocols;
}
@Override
public void unsupported() {
jettyWrapper.getSession().setApplicationProtocol(null);
}
@Override
public String select(List<String> protocols) throws Exception {
for (int i = 0; i < protocols.size(); ++i) {
String p = protocols.get(i);
if (supportedProtocols.contains(p)) {
jettyWrapper.getSession().setApplicationProtocol(p);
return p;
}
}
return noSelectMatchFound();
}
public String noSelectMatchFound() throws Exception {
jettyWrapper.getSession().setApplicationProtocol(null);
return null;
}
}
protected static final class FailProtocolSelector extends NoFailProtocolSelector {
public FailProtocolSelector(JdkSslEngine jettyWrapper, Set<String> supportedProtocols) {
super(jettyWrapper, supportedProtocols);
}
@Override
public String noSelectMatchFound() throws Exception {
throw new SSLHandshakeException("Selected protocol is not supported");
}
}
protected static class NoFailProtocolSelectionListener implements ProtocolSelectionListener {
private final JdkSslEngine jettyWrapper;
private final List<String> supportedProtocols;
public NoFailProtocolSelectionListener(JdkSslEngine jettyWrapper, List<String> supportedProtocols) {
this.jettyWrapper = jettyWrapper;
this.supportedProtocols = supportedProtocols;
}
@Override
public void unsupported() {
jettyWrapper.getSession().setApplicationProtocol(null);
}
@Override
public void selected(String protocol) throws Exception {
if (supportedProtocols.contains(protocol)) {
jettyWrapper.getSession().setApplicationProtocol(protocol);
} else {
noSelectedMatchFound(protocol);
}
}
public void noSelectedMatchFound(String protocol) throws Exception {
}
}
protected static final class FailProtocolSelectionListener extends NoFailProtocolSelectionListener {
public FailProtocolSelectionListener(JdkSslEngine jettyWrapper, List<String> supportedProtocols) {
super(jettyWrapper, supportedProtocols);
}
@Override
public void noSelectedMatchFound(String protocol) throws Exception {
throw new SSLHandshakeException("No compatible protocols found");
}
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
/**
* The {@link JdkApplicationProtocolNegotiator} to use if you do not care about NPN or ALPN and are using
* {@link SslProvider#JDK}.
*/
final class JdkDefaultApplicationProtocolNegotiator implements JdkApplicationProtocolNegotiator {
public static final JdkDefaultApplicationProtocolNegotiator INSTANCE =
new JdkDefaultApplicationProtocolNegotiator();
private static final SslEngineWrapperFactory DEFAULT_SSL_ENGINE_WRAPPER_FACTORY = new SslEngineWrapperFactory() {
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator,
boolean isServer) {
return engine;
}
};
private JdkDefaultApplicationProtocolNegotiator() {
}
@Override
public SslEngineWrapperFactory wrapperFactory() {
return DEFAULT_SSL_ENGINE_WRAPPER_FACTORY;
}
@Override
public ProtocolSelectorFactory protocolSelectorFactory() {
throw new UnsupportedOperationException("Application protocol negotiation unsupported");
}
@Override
public ProtocolSelectionListenerFactory protocolListenerFactory() {
throw new UnsupportedOperationException("Application protocol negotiation unsupported");
}
@Override
public List<String> protocols() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import javax.net.ssl.SSLEngine;
/**
* The {@link JdkApplicationProtocolNegotiator} to use if you need NPN and are using {@link SslProvider#JDK}.
*/
public final class JdkNpnApplicationProtocolNegotiator extends JdkBaseApplicationProtocolNegotiator {
private static final SslEngineWrapperFactory NPN_WRAPPER = new SslEngineWrapperFactory() {
{
if (!JdkNpnSslEngine.isAvailable()) {
throw new RuntimeException("NPN unsupported. Is your classpatch configured correctly?"
+ " See http://www.eclipse.org/jetty/documentation/current/npn-chapter.html#npn-starting");
}
}
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator,
boolean isServer) {
return new JdkNpnSslEngine(engine, applicationNegotiator, isServer);
}
};
/**
* Create a new instance.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(Iterable<String> protocols) {
this(false, protocols);
}
/**
* Create a new instance.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(String... protocols) {
this(false, protocols);
}
/**
* Create a new instance.
* @param failIfNoCommonProtocols Fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(boolean failIfNoCommonProtocols, Iterable<String> protocols) {
this(failIfNoCommonProtocols, failIfNoCommonProtocols, protocols);
}
/**
* Create a new instance.
* @param failIfNoCommonProtocols Fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(boolean failIfNoCommonProtocols, String... protocols) {
this(failIfNoCommonProtocols, failIfNoCommonProtocols, protocols);
}
/**
* Create a new instance.
* @param clientFailIfNoCommonProtocols Client side fail with a fatal alert if not common protocols are detected.
* @param serverFailIfNoCommonProtocols Server side fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(boolean clientFailIfNoCommonProtocols,
boolean serverFailIfNoCommonProtocols, Iterable<String> protocols) {
this(clientFailIfNoCommonProtocols ? FAIL_SELECTOR_FACTORY : NO_FAIL_SELECTOR_FACTORY,
serverFailIfNoCommonProtocols ? FAIL_SELECTION_LISTENER_FACTORY : NO_FAIL_SELECTION_LISTENER_FACTORY,
protocols);
}
/**
* Create a new instance.
* @param clientFailIfNoCommonProtocols Client side fail with a fatal alert if not common protocols are detected.
* @param serverFailIfNoCommonProtocols Server side fail with a fatal alert if not common protocols are detected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(boolean clientFailIfNoCommonProtocols,
boolean serverFailIfNoCommonProtocols, String... protocols) {
this(clientFailIfNoCommonProtocols ? FAIL_SELECTOR_FACTORY : NO_FAIL_SELECTOR_FACTORY,
serverFailIfNoCommonProtocols ? FAIL_SELECTION_LISTENER_FACTORY : NO_FAIL_SELECTION_LISTENER_FACTORY,
protocols);
}
/**
* Create a new instance.
* @param selectorFactory The factory which provides classes responsible for selecting the protocol.
* @param listenerFactory The factory which provides to be notified of which protocol was selected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(ProtocolSelectorFactory selectorFactory,
ProtocolSelectionListenerFactory listenerFactory, Iterable<String> protocols) {
super(NPN_WRAPPER, selectorFactory, listenerFactory, protocols);
}
/**
* Create a new instance.
* @param selectorFactory The factory which provides classes responsible for selecting the protocol.
* @param listenerFactory The factory which provides to be notified of which protocol was selected.
* @param protocols The order of iteration determines the preference of support for protocols.
*/
public JdkNpnApplicationProtocolNegotiator(ProtocolSelectorFactory selectorFactory,
ProtocolSelectionListenerFactory listenerFactory, String... protocols) {
super(NPN_WRAPPER, selectorFactory, listenerFactory, protocols);
}
}

View File

@ -16,6 +16,12 @@
package io.netty.handler.ssl;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
import io.netty.util.internal.PlatformDependent;
import java.util.HashSet;
import java.util.List;
import javax.net.ssl.SSLEngine;
@ -25,7 +31,7 @@ import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.npn.NextProtoNego.ClientProvider;
import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
final class JettyNpnSslEngine extends JettySslEngine {
final class JdkNpnSslEngine extends JdkSslEngine {
private static boolean available;
static boolean isAvailable() {
@ -52,30 +58,37 @@ final class JettyNpnSslEngine extends JettySslEngine {
}
}
JettyNpnSslEngine(SSLEngine engine, final List<String> nextProtocols, boolean server) {
super(engine, nextProtocols, server);
JdkNpnSslEngine(SSLEngine engine, final JdkApplicationProtocolNegotiator applicationNegotiator, boolean server) {
super(engine);
checkNotNull(applicationNegotiator, "applicationNegotiator");
if (server) {
final ProtocolSelectionListener protocolListener = checkNotNull(applicationNegotiator
.protocolListenerFactory().newListener(this, applicationNegotiator.protocols()),
"protocolListener");
NextProtoNego.put(engine, new ServerProvider() {
@Override
public void unsupported() {
getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1));
protocolListener.unsupported();
}
@Override
public List<String> protocols() {
return nextProtocols;
return applicationNegotiator.protocols();
}
@Override
public void protocolSelected(String protocol) {
getSession().setApplicationProtocol(protocol);
try {
protocolListener.selected(protocol);
} catch (Throwable t) {
PlatformDependent.throwException(t);
}
}
});
} else {
final String[] array = nextProtocols.toArray(new String[nextProtocols.size()]);
final String fallback = array[array.length - 1];
final ProtocolSelector protocolSelector = checkNotNull(applicationNegotiator.protocolSelectorFactory()
.newSelector(this, new HashSet<String>(applicationNegotiator.protocols())), "protocolSelector");
NextProtoNego.put(engine, new ClientProvider() {
@Override
public boolean supports() {
@ -84,20 +97,17 @@ final class JettyNpnSslEngine extends JettySslEngine {
@Override
public void unsupported() {
session.setApplicationProtocol(null);
protocolSelector.unsupported();
}
@Override
public String selectProtocol(List<String> protocols) {
for (int i = 0; i < array.length; ++i) {
String p = array[i];
if (protocols.contains(p)) {
session.setApplicationProtocol(p);
return p;
}
try {
return protocolSelector.select(protocols);
} catch (Throwable t) {
PlatformDependent.throwException(t);
return null;
}
session.setApplicationProtocol(fallback);
return fallback;
}
});
}
@ -105,13 +115,13 @@ final class JettyNpnSslEngine extends JettySslEngine {
@Override
public void closeInbound() throws SSLException {
NextProtoNego.remove(engine);
NextProtoNego.remove(getWrappedEngine());
super.closeInbound();
}
@Override
public void closeOutbound() {
NextProtoNego.remove(engine);
NextProtoNego.remove(getWrappedEngine());
super.closeOutbound();
}
}

View File

@ -23,10 +23,8 @@ import java.io.File;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
@ -39,7 +37,6 @@ import javax.security.auth.x500.X500Principal;
public final class JdkSslClientContext extends JdkSslContext {
private final SSLContext ctx;
private final List<String> nextProtocols;
/**
* Creates a new instance.
@ -80,7 +77,7 @@ public final class JdkSslClientContext extends JdkSslContext {
*/
public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
this(certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
JdkDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
}
/**
@ -94,10 +91,7 @@ public final class JdkSslClientContext extends JdkSslContext {
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -105,13 +99,35 @@ public final class JdkSslClientContext extends JdkSslContext {
*/
public JdkSslClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(certChainFile, trustManagerFactory, ciphers, cipherFilter,
toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
}
/**
* Creates a new instance.
*
* @param certChainFile an X.509 certificate chain file in PEM format.
* {@code null} to use the system default
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
* that verifies the certificates sent from servers.
* {@code null} to use the default.
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param apn Application Protocol Negotiator object.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
* {@code 0} to use the default value.
*/
public JdkSslClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);
super(ciphers, cipherFilter, apn);
try {
if (certChainFile == null) {
@ -168,11 +184,6 @@ public final class JdkSslClientContext extends JdkSslContext {
return true;
}
@Override
public List<String> nextProtocols() {
return nextProtocols;
}
@Override
public SSLContext context() {
return ctx;

View File

@ -16,13 +16,11 @@
package io.netty.handler.ssl;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -30,6 +28,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
/**
* An {@link SslContext} which uses JDK's SSL/TLS implementation.
*/
@ -120,18 +122,18 @@ public abstract class JdkSslContext extends SslContext {
private final String[] cipherSuites;
private final List<String> unmodifiableCipherSuites;
private final SslEngineWrapperFactory wrapperFactory;
private final JdkApplicationProtocolNegotiator apn;
JdkSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, SslEngineWrapperFactory wrapperFactory) {
if (wrapperFactory == null) {
throw new NullPointerException("wrapperFactory");
}
if (cipherFilter == null) {
throw new NullPointerException("cipherFilter");
}
cipherSuites = cipherFilter.filterCipherSuites(ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
JdkSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config,
boolean isServer) {
this(ciphers, cipherFilter, toNegotiator(config, isServer));
}
JdkSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn) {
this.apn = checkNotNull(apn, "apn");
cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
this.wrapperFactory = wrapperFactory;
}
/**
@ -184,6 +186,75 @@ public abstract class JdkSslContext extends SslContext {
}
private SSLEngine wrapEngine(SSLEngine engine) {
return wrapperFactory.wrapSslEngine(engine, nextProtocols(), isServer());
return apn.wrapperFactory().wrapSslEngine(engine, apn, isServer());
}
@Override
public JdkApplicationProtocolNegotiator applicationProtocolNegotiator() {
return apn;
}
/**
* Translate a {@link ApplicationProtocolConfiguration} object to a {@link JdkApplicationProtocolNegotiator} object.
* @param config The configuration which defines the translation
* @param isServer {@code true} if a server {@code false} otherwise.
* @return The results of the translation
*/
static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
if (config == null) {
return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
}
switch(config.protocol()) {
case NONE:
return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
case ALPN:
if (isServer) {
switch(config.selectorFailureBehavior()) {
case FATAL_ALERT:
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
case NO_ADVERTISE:
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
}
} else {
switch(config.selectedListenerFailureBehavior()) {
case ACCEPT:
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
case FATAL_ALERT:
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
}
}
case NPN:
if (isServer) {
switch(config.selectedListenerFailureBehavior()) {
case ACCEPT:
return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
case FATAL_ALERT:
return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
}
} else {
switch(config.selectorFailureBehavior()) {
case FATAL_ALERT:
return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
case NO_ADVERTISE:
return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
}
}
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.protocol()).append(" protocol").toString());
}
}
}

View File

@ -16,7 +16,6 @@
package io.netty.handler.ssl;
import java.nio.ByteBuffer;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
@ -25,27 +24,24 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
class JettySslEngine extends SSLEngine {
protected final SSLEngine engine;
protected final JettySslSession session;
JettySslEngine(SSLEngine engine, final List<String> nextProtocols, boolean server) {
if (nextProtocols == null) {
throw new NullPointerException("nextProtocols");
}
if (nextProtocols.isEmpty()) {
throw new IllegalArgumentException("nextProtocols can not be empty");
}
class JdkSslEngine extends SSLEngine {
private final SSLEngine engine;
private final JdkSslSession session;
JdkSslEngine(SSLEngine engine) {
this.engine = engine;
session = new JettySslSession(engine);
session = new JdkSslSession(engine);
}
@Override
public JettySslSession getSession() {
public JdkSslSession getSession() {
return session;
}
public SSLEngine getWrappedEngine() {
return engine;
}
@Override
public void closeInbound() throws SSLException {
engine.closeInbound();

View File

@ -43,7 +43,6 @@ import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
@ -53,7 +52,6 @@ import javax.net.ssl.SSLSessionContext;
public final class JdkSslServerContext extends JdkSslContext {
private final SSLContext ctx;
private final List<String> nextProtocols;
/**
* Creates a new instance.
@ -75,7 +73,7 @@ public final class JdkSslServerContext extends JdkSslContext {
*/
public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
this(certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
JdkDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
}
/**
@ -88,10 +86,7 @@ public final class JdkSslServerContext extends JdkSslContext {
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -99,13 +94,34 @@ public final class JdkSslServerContext extends JdkSslContext {
*/
public JdkSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(certChainFile, keyFile, keyPassword, ciphers, cipherFilter,
toNegotiator(apn, true), sessionCacheSize, sessionTimeout);
}
/**
* Creates a new instance.
*
* @param certChainFile an X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param apn Application Protocol Negotiator object.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
* {@code 0} to use the default value.
*/
public JdkSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, cipherFilter, wrapperFactory);
this.nextProtocols = translateProtocols(nextProtocols);
super(ciphers, cipherFilter, apn);
if (certChainFile == null) {
throw new NullPointerException("certChainFile");
@ -183,11 +199,6 @@ public final class JdkSslServerContext extends JdkSslContext {
return false;
}
@Override
public List<String> nextProtocols() {
return nextProtocols;
}
@Override
public SSLContext context() {
return ctx;

View File

@ -24,11 +24,11 @@ import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
final class JettySslSession implements SSLSession {
final class JdkSslSession implements SSLSession {
private final SSLEngine engine;
private volatile String applicationProtocol;
JettySslSession(SSLEngine engine) {
JdkSslSession(SSLEngine engine) {
this.engine = engine;
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
/**
* Factory for wrapping {@link SSLEngine} object in {@link JettyAlpnSslEngine} objects
*/
public final class JettyAlpnSslEngineWrapper implements SslEngineWrapperFactory {
private static JettyAlpnSslEngineWrapper instance;
private JettyAlpnSslEngineWrapper() throws SSLException {
if (!JettyAlpnSslEngine.isAvailable()) {
throw new SSLException("ALPN unsupported. Is your classpatch configured correctly?" +
" See http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-starting");
}
}
public static JettyAlpnSslEngineWrapper instance() throws SSLException {
if (instance == null) {
instance = new JettyAlpnSslEngineWrapper();
}
return instance;
}
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, List<String> protocols, boolean isServer) {
return new JettyAlpnSslEngine(engine, protocols, isServer);
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
/**
* Factory for wrapping {@link SSLEngine} object in {@link JettyNpnSslEngine} objects
*/
public final class JettyNpnSslEngineWrapper implements SslEngineWrapperFactory {
private static JettyNpnSslEngineWrapper instance;
private JettyNpnSslEngineWrapper() throws SSLException {
if (!JettyNpnSslEngine.isAvailable()) {
throw new SSLException("NPN unsupported. Is your classpatch configured correctly?" +
" See http://www.eclipse.org/jetty/documentation/current/npn-chapter.html#npn-starting");
}
}
public static JettyNpnSslEngineWrapper instance() throws SSLException {
if (instance == null) {
instance = new JettyNpnSslEngineWrapper();
}
return instance;
}
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, List<String> protocols, boolean isServer) {
return new JettyNpnSslEngine(engine, protocols, isServer);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
/**
* OpenSSL version of {@link ApplicationProtocolNegotiator}.
*/
public interface OpenSslApplicationProtocolNegotiator extends ApplicationProtocolNegotiator {
// The current need for this interface is primarily for consistency with the JDK provider
// How the OpenSsl provider will provide extensibility to control the application selection
// and notification algorithms is not yet known (JNI, pure java, tcnative hooks, etc...).
// OpenSSL itself is currently not in compliance with the specification for the 2 supported
// protocols (ALPN, NPN) with respect to allowing the handshakes to fail during the application
// protocol negotiation process. Issue https://github.com/openssl/openssl/issues/188 has been created for this.
}

View File

@ -15,21 +15,21 @@
*/
package io.netty.handler.ssl;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
/**
* Factory for not wrapping {@link SSLEngine} object and just returning it.
* Provides no {@link ApplicationProtocolNegotiator} functionality for OpenSSL.
*/
public final class DefaultSslWrapperFactory implements SslEngineWrapperFactory {
public static final DefaultSslWrapperFactory INSTANCE = new DefaultSslWrapperFactory();
final class OpenSslDefaultApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator {
static final OpenSslDefaultApplicationProtocolNegotiator INSTANCE =
new OpenSslDefaultApplicationProtocolNegotiator();
private DefaultSslWrapperFactory() {
private OpenSslDefaultApplicationProtocolNegotiator() {
}
@Override
public SSLEngine wrapSslEngine(SSLEngine engine, List<String> protocols, boolean isServer) {
return engine;
public List<String> protocols() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import static io.netty.handler.ssl.ApplicationProtocolUtil.toList;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import java.util.List;
/**
* OpenSSL {@link ApplicationProtocolNegotiator} for NPN.
*/
public final class OpenSslNpnApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator {
private final List<String> protocols;
public OpenSslNpnApplicationProtocolNegotiator(Iterable<String> protocols) {
this.protocols = checkNotNull(toList(protocols), "protocols");
}
public OpenSslNpnApplicationProtocolNegotiator(String... protocols) {
this.protocols = checkNotNull(toList(protocols), "protocols");
}
@Override
public List<String> protocols() {
return protocols;
}
}

View File

@ -15,20 +15,23 @@
*/
package io.netty.handler.ssl;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLContext;
/**
* A server-side {@link SslContext} which uses OpenSSL's SSL/TLS implementation.
*/
@ -65,7 +68,7 @@ public final class OpenSslServerContext extends SslContext {
private final List<String> unmodifiableCiphers = Collections.unmodifiableList(ciphers);
private final long sessionCacheSize;
private final long sessionTimeout;
private final List<String> protocols;
private final OpenSslApplicationProtocolNegotiator apn;
/** The OpenSSL SSL_CTX object */
private final long ctx;
@ -90,7 +93,7 @@ public final class OpenSslServerContext extends SslContext {
* {@code null} if it's not password-protected.
*/
public OpenSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
this(certChainFile, keyFile, keyPassword, null, null, 0, 0);
this(certChainFile, keyFile, keyPassword, null, OpenSslDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
}
/**
@ -102,8 +105,7 @@ public final class OpenSslServerContext extends SslContext {
* {@code null} if it's not password-protected.
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -111,20 +113,39 @@ public final class OpenSslServerContext extends SslContext {
*/
public OpenSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(certChainFile, keyFile, keyPassword, ciphers, toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
}
/**
* Creates a new instance.
*
* @param certChainFile an X.509 certificate chain file in PEM format
* @param keyFile a PKCS#8 private key file in PEM format
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param apn Application protocol negotiator.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
* {@code 0} to use the default value.
*/
public OpenSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, OpenSslApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
OpenSsl.ensureAvailability();
if (certChainFile == null) {
throw new NullPointerException("certChainFile");
}
checkNotNull(certChainFile, "certChainFile");
if (!certChainFile.isFile()) {
throw new IllegalArgumentException("certChainFile is not a file: " + certChainFile);
}
if (keyFile == null) {
throw new NullPointerException("keyPath");
}
checkNotNull(keyFile, "keyFile");
this.apn = checkNotNull(apn, "apn");
if (!keyFile.isFile()) {
throw new IllegalArgumentException("keyPath is not a file: " + keyFile);
}
@ -136,8 +157,6 @@ public final class OpenSslServerContext extends SslContext {
keyPassword = "";
}
protocols = translateProtocols(nextProtocols);
for (String c: ciphers) {
if (c == null) {
break;
@ -209,6 +228,7 @@ public final class OpenSslServerContext extends SslContext {
}
/* Set next protocols for next protocol negotiation extension, if specified */
List<String> protocols = apn.protocols();
if (!protocols.isEmpty()) {
// Convert the protocol list into a comma-separated string.
StringBuilder nextProtocolBuf = new StringBuilder();
@ -274,8 +294,8 @@ public final class OpenSslServerContext extends SslContext {
}
@Override
public List<String> nextProtocols() {
return protocols;
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
return apn;
}
/**
@ -297,6 +317,7 @@ public final class OpenSslServerContext extends SslContext {
*/
@Override
public SSLEngine newEngine(ByteBufAllocator alloc) {
List<String> protocols = apn.protocols();
if (protocols.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null);
} else {
@ -337,4 +358,38 @@ public final class OpenSslServerContext extends SslContext {
Pool.destroy(aprPool);
}
}
/**
* Translate a {@link ApplicationProtocolConfiguration} object to a
* {@link OpenSslApplicationProtocolNegotiator} object.
* @param config The configuration which defines the translation
* @param isServer {@code true} if a server {@code false} otherwise.
* @return The results of the translation
*/
private static OpenSslApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config,
boolean isServer) {
if (config == null) {
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
}
switch(config.protocol()) {
case NONE:
return OpenSslDefaultApplicationProtocolNegotiator.INSTANCE;
case NPN:
if (isServer) {
switch(config.selectedListenerFailureBehavior()) {
case CHOOSE_MY_LAST_PROTOCOL:
return new OpenSslNpnApplicationProtocolNegotiator(config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" behavior").toString());
}
} else {
throw new UnsupportedOperationException("OpenSSL provider does not support client mode");
}
default:
throw new UnsupportedOperationException(new StringBuilder("OpenSSL provider does not support ")
.append(config.protocol()).append(" protocol").toString());
}
}
}

View File

@ -20,18 +20,15 @@ import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import java.io.File;
import java.util.List;
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 java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* A secure socket protocol implementation which acts as a factory for {@link SSLEngine} and {@link SslHandler}.
* Internally, it is implemented via JDK's {@link SSLContext} or OpenSSL's {@code SSL_CTX}.
@ -77,28 +74,6 @@ public abstract class SslContext {
return SslProvider.JDK;
}
/**
* Translate an {@link Iterable} of protocols to an unmodifiable {@link List}
* @param protocols Protocols to translate
* @return An unmodifiable {@link List} of protocols
*/
public static List<String> translateProtocols(Iterable<String> protocols) {
Iterator<String> itr = protocols == null ? null : protocols.iterator();
if (itr != null) {
List<String> nextProtoList = new ArrayList<String>(4);
while (itr.hasNext()) {
String p = itr.next();
if (p == null) {
break;
}
nextProtoList.add(p);
}
return Collections.unmodifiableList(nextProtoList);
} else {
return Collections.emptyList();
}
}
/**
* Creates a new server-side {@link SslContext}.
*
@ -134,11 +109,7 @@ public abstract class SslContext {
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* and if OpenSSL is available
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -147,12 +118,11 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newServerContext(
null, certChainFile, keyFile, keyPassword,
ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
@ -183,7 +153,7 @@ public abstract class SslContext {
public static SslContext newServerContext(
SslProvider provider, File certChainFile, File keyFile, String keyPassword) throws SSLException {
return newServerContext(provider, certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
null, 0, 0);
}
/**
@ -199,11 +169,7 @@ public abstract class SslContext {
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* Only required if {@code provider} is {@link SslProvider#JDK}
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* and if the {@code provider} is {@link SslProvider#JDK}
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -213,8 +179,7 @@ public abstract class SslContext {
public static SslContext newServerContext(
SslProvider provider,
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider == null) {
@ -225,11 +190,11 @@ public abstract class SslContext {
case JDK:
return new JdkSslServerContext(
certChainFile, keyFile, keyPassword,
ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
case OPENSSL:
return new OpenSslServerContext(
certChainFile, keyFile, keyPassword,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
ciphers, apn, sessionCacheSize, sessionTimeout);
default:
throw new Error(provider.toString());
}
@ -295,11 +260,7 @@ public abstract class SslContext {
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* and if OpenSSL is available
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -309,12 +270,11 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newClientContext(
null, certChainFile, trustManagerFactory,
ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
@ -375,7 +335,7 @@ public abstract class SslContext {
public static SslContext newClientContext(
SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
return newClientContext(provider, certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
null, DefaultSslWrapperFactory.INSTANCE, 0, 0);
null, 0, 0);
}
/**
@ -391,11 +351,7 @@ public abstract class SslContext {
* @param ciphers the cipher suites to enable, in the order of preference.
* {@code null} to use the default cipher suites.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @param nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param wrapperFactory a factory used to wrap the underlying {@link SSLEngine}.
* This is required if {@code nextProtocols} is not {@code null} or empty
* and if the {@code provider} is {@link SslProvider#JDK}
* @param apn Provides a means to configure parameters related to application protocol negotiation.
* @param sessionCacheSize the size of the cache used for storing SSL session objects.
* {@code 0} to use the default value.
* @param sessionTimeout the timeout for the cached SSL session objects, in seconds.
@ -406,8 +362,7 @@ public abstract class SslContext {
public static SslContext newClientContext(
SslProvider provider,
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
Iterable<String> nextProtocols, SslEngineWrapperFactory wrapperFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider != null && provider != SslProvider.JDK) {
@ -416,7 +371,7 @@ public abstract class SslContext {
return new JdkSslClientContext(
certChainFile, trustManagerFactory,
ciphers, cipherFilter, nextProtocols, wrapperFactory, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
SslContext() { }
@ -449,12 +404,9 @@ public abstract class SslContext {
public abstract long sessionTimeout();
/**
* Returns the list of application layer protocols for the TLS NPN/ALPN extension, in the order of preference.
*
* @return the list of application layer protocols.
* {@code null} if NPN/ALPN extension has been disabled.
* Returns the object responsible for negotiating application layer protocols for the TLS NPN/ALPN extensions.
*/
public abstract List<String> nextProtocols();
public abstract ApplicationProtocolNegotiator applicationProtocolNegotiator();
/**
* Creates a new {@link SSLEngine}.

View File

@ -1,41 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import java.util.List;
import javax.net.ssl.SSLEngine;
/**
* Abstract factory pattern for wrapping an {@link SSLEngine} object.
* This is useful for NPN/APLN support.
*/
public interface SslEngineWrapperFactory {
/**
* Abstract factory pattern for wrapping an {@link SSLEngine} object.
* This is useful for NPN/APLN support.
*
* @param engine The engine to wrap
* @param protocols The application level protocols that are supported
* @param isServer
* <ul>
* <li>{@code true} if the engine is for server side of connections</li>
* <li>{@code false} if the engine is for client side of connections</li>
* </ul>
* @return The resulting wrapped engine. This may just be {@code engine}
*/
SSLEngine wrapSslEngine(SSLEngine engine, List<String> protocols, boolean isServer);
}

View File

@ -0,0 +1,438 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
import static org.mockito.Mockito.verify;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectorFactory;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.NetUtil;
import io.netty.util.concurrent.Future;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class JdkSslEngineTest {
private static final String APPLICATION_LEVEL_PROTOCOL = "my-protocol";
private static final String APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE = "my-protocol-FOO";
@Mock
private MessageReciever serverReceiver;
@Mock
private MessageReciever clientReceiver;
private Throwable serverException;
private Throwable clientException;
private SslContext serverSslCtx;
private SslContext clientSslCtx;
private ServerBootstrap sb;
private Bootstrap cb;
private Channel serverChannel;
private Channel serverConnectedChannel;
private Channel clientChannel;
private CountDownLatch serverLatch;
private CountDownLatch clientLatch;
private interface MessageReciever {
void messageReceived(ByteBuf msg);
}
private final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final MessageReciever receiver;
private final CountDownLatch latch;
public MessageDelegatorChannelHandler(MessageReciever receiver, CountDownLatch latch) {
super(false);
this.receiver = receiver;
this.latch = latch;
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
receiver.messageReceived(msg);
latch.countDown();
}
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
serverLatch = new CountDownLatch(1);
clientLatch = new CountDownLatch(1);
}
@After
public void tearDown() throws InterruptedException {
if (serverChannel != null) {
serverChannel.close().sync();
Future<?> serverGroup = sb.group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
Future<?> serverChildGroup = sb.childGroup().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
Future<?> clientGroup = cb.group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
serverGroup.sync();
serverChildGroup.sync();
clientGroup.sync();
}
clientChannel = null;
serverChannel = null;
serverConnectedChannel = null;
}
@Test
public void testNpn() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkNpnSslEngine.isAvailable()) {
throw new RuntimeException("NPN not on classpath");
}
JdkApplicationProtocolNegotiator apn = new JdkNpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL);
mySetup(apn);
runTest();
} catch (RuntimeException e) {
// NPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testNpnNoCompatibleProtocolsNoHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkNpnSslEngine.isAvailable()) {
throw new RuntimeException("NPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
runTest(null);
} catch (Exception e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testNpnNoCompatibleProtocolsClientHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkNpnSslEngine.isAvailable()) {
throw new RuntimeException("NPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
assertTrue(clientException instanceof SSLHandshakeException);
} catch (RuntimeException e) {
// NPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testNpnNoCompatibleProtocolsServerHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkNpnSslEngine.isAvailable()) {
throw new RuntimeException("NPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkNpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkNpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
assertTrue(serverException instanceof SSLHandshakeException);
} catch (RuntimeException e) {
// NPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testAlpn() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkAlpnSslEngine.isAvailable()) {
throw new RuntimeException("ALPN not on classpath");
}
JdkApplicationProtocolNegotiator apn = new JdkAlpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL);
mySetup(apn);
runTest();
} catch (Exception e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testAlpnNoCompatibleProtocolsNoHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkAlpnSslEngine.isAvailable()) {
throw new RuntimeException("ALPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
runTest(null);
} catch (Exception e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testAlpnNoCompatibleProtocolsServerHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkAlpnSslEngine.isAvailable()) {
throw new RuntimeException("ALPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(false, false,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
assertTrue(serverException instanceof SSLHandshakeException);
} catch (Exception e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testAlpnNoCompatibleProtocolsClientHandshakeFailure() throws Exception {
try {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!JdkAlpnSslEngine.isAvailable()) {
throw new RuntimeException("ALPN not on classpath");
}
JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(
new ProtocolSelectorFactory() {
@Override
public ProtocolSelector newSelector(SSLEngine engine, Set<String> supportedProtocols) {
return new ProtocolSelector() {
@Override
public void unsupported() {
}
@Override
public String select(List<String> protocols) {
return APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE;
}
};
}
}, JdkBaseApplicationProtocolNegotiator.FAIL_SELECTION_LISTENER_FACTORY,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
mySetup(serverApn, clientApn);
assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
assertTrue(clientException instanceof SSLHandshakeException);
} catch (Exception e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
private void mySetup(JdkApplicationProtocolNegotiator apn) throws InterruptedException, SSLException,
CertificateException {
mySetup(apn, apn);
}
private void mySetup(JdkApplicationProtocolNegotiator serverApn, JdkApplicationProtocolNegotiator clientApn)
throws InterruptedException, SSLException, CertificateException {
SelfSignedCertificate ssc = new SelfSignedCertificate();
serverSslCtx = new JdkSslServerContext(ssc.certificate(), ssc.privateKey(), null, null,
IdentityCipherSuiteFilter.INSTANCE, serverApn, 0, 0);
clientSslCtx = new JdkSslClientContext(null, InsecureTrustManagerFactory.INSTANCE, null,
IdentityCipherSuiteFilter.INSTANCE, clientApn, 0, 0);
serverConnectedChannel = null;
sb = new ServerBootstrap();
cb = new Bootstrap();
sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverSslCtx.newHandler(ch.alloc()));
p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
p.addLast(new ChannelHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause.getCause() instanceof SSLHandshakeException) {
serverException = cause.getCause();
serverLatch.countDown();
} else {
ctx.fireExceptionCaught(cause);
}
}
});
serverConnectedChannel = ch;
}
});
cb.group(new NioEventLoopGroup());
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(clientSslCtx.newHandler(ch.alloc()));
p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
p.addLast(new ChannelHandlerAdapter() {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause.getCause() instanceof SSLHandshakeException) {
clientException = cause.getCause();
clientLatch.countDown();
} else {
ctx.fireExceptionCaught(cause);
}
}
});
}
});
serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
}
private void runTest() throws Exception {
runTest(APPLICATION_LEVEL_PROTOCOL);
}
private void runTest(String expectedApplicationProtocol) throws Exception {
final ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes());
final ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes());
try {
writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver);
writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver);
verifyApplicationLevelProtocol(clientChannel, expectedApplicationProtocol);
verifyApplicationLevelProtocol(serverConnectedChannel, expectedApplicationProtocol);
} finally {
clientMessage.release();
serverMessage.release();
}
}
private void verifyApplicationLevelProtocol(Channel channel, String expectedApplicationProtocol) {
SslHandler handler = channel.pipeline().get(SslHandler.class);
assertNotNull(handler);
String[] protocol = handler.engine().getSession().getProtocol().split(":");
assertNotNull(protocol);
if (expectedApplicationProtocol != null && !expectedApplicationProtocol.isEmpty()) {
assertTrue("protocol.length must be greater than 1 but is " + protocol.length, protocol.length > 1);
assertEquals(expectedApplicationProtocol, protocol[1]);
} else {
assertEquals(1, protocol.length);
}
}
private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
MessageReciever receiver) throws Exception {
List<ByteBuf> dataCapture = null;
try {
sendChannel.writeAndFlush(message);
receiverLatch.await(5, TimeUnit.SECONDS);
message.resetReaderIndex();
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(receiver).messageReceived(captor.capture());
dataCapture = captor.getAllValues();
assertEquals(message, dataCapture.get(0));
} finally {
if (dataCapture != null) {
for (ByteBuf data : dataCapture) {
data.release();
}
}
}
}
}

View File

@ -1,231 +0,0 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.ssl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
import static org.mockito.Mockito.verify;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.NetUtil;
import io.netty.util.concurrent.Future;
import java.net.InetSocketAddress;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
public class JettySslEngineTest {
private static final String APPLICATION_LEVEL_PROTOCOL = "my-protocol";
@Mock
private MessageReciever serverReceiver;
@Mock
private MessageReciever clientReceiver;
private SslContext serverSslCtx;
private SslContext clientSslCtx;
private ServerBootstrap sb;
private Bootstrap cb;
private Channel serverChannel;
private Channel serverConnectedChannel;
private Channel clientChannel;
private CountDownLatch serverLatch;
private CountDownLatch clientLatch;
private interface MessageReciever {
void messageReceived(ByteBuf msg);
}
private final class MessageDelegatorChannelHandler extends SimpleChannelInboundHandler<ByteBuf> {
private final MessageReciever receiver;
private final CountDownLatch latch;
public MessageDelegatorChannelHandler(MessageReciever receiver, CountDownLatch latch) {
super(false);
this.receiver = receiver;
this.latch = latch;
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
receiver.messageReceived(msg);
latch.countDown();
}
}
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
serverLatch = new CountDownLatch(1);
clientLatch = new CountDownLatch(1);
}
@After
public void tearDown() throws InterruptedException {
if (serverChannel != null) {
serverChannel.close().sync();
Future<?> serverGroup = sb.group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
Future<?> serverChildGroup = sb.childGroup().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
Future<?> clientGroup = cb.group().shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
serverGroup.sync();
serverChildGroup.sync();
clientGroup.sync();
}
clientChannel = null;
serverChannel = null;
serverConnectedChannel = null;
}
@Test
public void testNpn() throws Exception {
SslEngineWrapperFactory wrapper = null;
try {
wrapper = JettyNpnSslEngineWrapper.instance();
} catch (SSLException e) {
// NPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
mySetup(wrapper);
runTest();
}
@Test
public void testAlpn() throws Exception {
SslEngineWrapperFactory wrapper = null;
try {
wrapper = JettyAlpnSslEngineWrapper.instance();
} catch (SSLException e) {
// ALPN availability is dependent on the java version. If NPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
mySetup(wrapper);
runTest();
}
private void mySetup(SslEngineWrapperFactory wrapper) throws InterruptedException, SSLException,
CertificateException {
SelfSignedCertificate ssc = new SelfSignedCertificate();
serverSslCtx = SslContext.newServerContext(SslProvider.JDK, ssc.certificate(), ssc.privateKey(), null, null,
IdentityCipherSuiteFilter.INSTANCE, Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
clientSslCtx = SslContext.newClientContext(SslProvider.JDK, null, InsecureTrustManagerFactory.INSTANCE, null,
IdentityCipherSuiteFilter.INSTANCE, Arrays.asList(APPLICATION_LEVEL_PROTOCOL), wrapper, 0, 0);
serverConnectedChannel = null;
sb = new ServerBootstrap();
cb = new Bootstrap();
sb.group(new NioEventLoopGroup(), new NioEventLoopGroup());
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverSslCtx.newHandler(ch.alloc()));
p.addLast(new MessageDelegatorChannelHandler(serverReceiver, serverLatch));
serverConnectedChannel = ch;
}
});
cb.group(new NioEventLoopGroup());
cb.channel(NioSocketChannel.class);
cb.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(clientSslCtx.newHandler(ch.alloc()));
p.addLast(new MessageDelegatorChannelHandler(clientReceiver, clientLatch));
}
});
serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel();
int port = ((InetSocketAddress) serverChannel.localAddress()).getPort();
ChannelFuture ccf = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port));
assertTrue(ccf.awaitUninterruptibly().isSuccess());
clientChannel = ccf.channel();
}
private void runTest() throws Exception {
final ByteBuf clientMessage = Unpooled.copiedBuffer("I am a client".getBytes());
final ByteBuf serverMessage = Unpooled.copiedBuffer("I am a server".getBytes());
try {
writeAndVerifyReceived(clientMessage.retain(), clientChannel, serverLatch, serverReceiver);
writeAndVerifyReceived(serverMessage.retain(), serverConnectedChannel, clientLatch, clientReceiver);
verifyApplicationLevelProtocol(clientChannel);
verifyApplicationLevelProtocol(serverConnectedChannel);
} finally {
clientMessage.release();
serverMessage.release();
}
}
private void verifyApplicationLevelProtocol(Channel channel) {
SslHandler handler = channel.pipeline().get(SslHandler.class);
assertNotNull(handler);
String[] protocol = handler.engine().getSession().getProtocol().split(":");
assertNotNull(protocol);
assertTrue("protocol.length must be greater than 1 but is " + protocol.length, protocol.length > 1);
assertEquals(APPLICATION_LEVEL_PROTOCOL, protocol[1]);
}
private static void writeAndVerifyReceived(ByteBuf message, Channel sendChannel, CountDownLatch receiverLatch,
MessageReciever receiver) throws Exception {
List<ByteBuf> dataCapture = null;
try {
sendChannel.writeAndFlush(message);
receiverLatch.await(5, TimeUnit.SECONDS);
message.resetReaderIndex();
ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
verify(receiver).messageReceived(captor.capture());
dataCapture = captor.getAllValues();
assertEquals(message, dataCapture.get(0));
} finally {
if (dataCapture != null) {
for (ByteBuf data : dataCapture) {
data.release();
}
}
}
}
}

74
pom.xml
View File

@ -109,10 +109,22 @@
<!-- Our Javadoc has poor enough quality to fail the build thanks to JDK8 javadoc which got more strict. -->
<maven.javadoc.failOnError>false</maven.javadoc.failOnError>
<!-- npn-boot does not work with JDK 8 -->
<jetty.alpn.version>8.0.0.v20140317</jetty.alpn.version>
<jetty.alpn.version>8.1.0.v20141016</jetty.alpn.version>
<argLine.bootcp>-Xbootclasspath/p:${jetty.alpn.path}</argLine.bootcp>
</properties>
</profile>
<profile>
<id>alpn-8u25</id>
<activation>
<property>
<name>java.version</name>
<value>1.8.0_25</value>
</property>
</activation>
<properties>
<jetty.alpn.version>8.1.1.v20141016</jetty.alpn.version>
</properties>
</profile>
<profile>
<id>linux</id>
<activation>
@ -283,7 +295,7 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -297,7 +309,7 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -311,7 +323,7 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -324,8 +336,8 @@
</property>
</activation>
<properties>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.npn.version>1.1.8.v20141013</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -338,8 +350,8 @@
</property>
</activation>
<properties>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.npn.version>1.1.8.v20141013</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -352,8 +364,8 @@
</property>
</activation>
<properties>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.npn.version>1.1.8.v20141013</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -366,8 +378,36 @@
</property>
</activation>
<properties>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.alpn.version>7.0.0.v20140317</jetty.alpn.version>
<jetty.npn.version>1.1.8.v20141013</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
<profile>
<id>npn-alpn-7u71</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_71</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.9.v20141016</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
<profile>
<id>npn-alpn-7u72</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_72</value>
</property>
</activation>
<properties>
<jetty.npn.version>1.1.9.v20141016</jetty.npn.version>
<jetty.alpn.version>7.1.0.v20141016</jetty.alpn.version>
<!-- Defer definition of Xbootclasspath to default or forcenpn profile -->
</properties>
</profile>
@ -394,9 +434,9 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jboss.marshalling.version>1.3.18.GA</jboss.marshalling.version>
<jetty.npn.version>1.1.7.v20140316</jetty.npn.version>
<jetty.npn.version>1.1.9.v20141016</jetty.npn.version>
<jetty.npn.path>${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${jetty.npn.version}/npn-boot-${jetty.npn.version}.jar</jetty.npn.path>
<jetty.alpn.version>8.0.0.v20140317</jetty.alpn.version>
<jetty.alpn.version>8.1.0.v20141016</jetty.alpn.version>
<jetty.alpn.path>${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${jetty.alpn.version}/alpn-boot-${jetty.alpn.version}.jar</jetty.alpn.path>
<argLine.common>
-server
@ -470,7 +510,7 @@
<dependency>
<groupId>org.eclipse.jetty.npn</groupId>
<artifactId>npn-api</artifactId>
<version>1.1.0.v20120525</version>
<version>1.1.1.v20141010</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.npn</groupId>
@ -480,7 +520,7 @@
<dependency>
<groupId>org.eclipse.jetty.alpn</groupId>
<artifactId>alpn-api</artifactId>
<version>1.0.0</version>
<version>1.1.0.v20141014</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.alpn</groupId>
@ -918,7 +958,7 @@
<instructions>
<Export-Package>${project.groupId}.*</Export-Package>
<!-- enforce JVM vendor package as optional -->
<Import-Package>sun.misc.*;resolution:=optional,sun.nio.ch;resolution:=optional,sun.security.*;resolution:=optional,org.eclipse.jetty.alpn;version="[1,)";resolution=optional,*</Import-Package>
<Import-Package>sun.misc.*;resolution:=optional,sun.nio.ch;resolution:=optional,sun.security.*;resolution:=optional,org.eclipse.jetty.npn;version="[1,)";resolution=optional,org.eclipse.jetty.alpn;version="[1,)";resolution=optional,*</Import-Package>
<!-- override "internal" private package convention -->
<Private-Package>!*</Private-Package>
</instructions>