Backport ALPN and Mutual Auth SSL

Motivation:

Improvements were made on the main line to support ALPN and mutual
authentication for TLS. These should be backported.

Modifications:

- Backport commits from the master branch
  - f8af84d599
  - e74c8edba3

Result:

Support for ALPN and mutual authentication.
This commit is contained in:
Scott Mitchell 2014-10-03 18:16:04 -04:00 committed by Trustin Lee
parent 746c8cab32
commit 04f77b76f8
34 changed files with 3151 additions and 422 deletions

View File

@ -0,0 +1,35 @@
/*
* 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.util.internal;
/**
* A grab-bag of useful utility methods.
*/
public final class ObjectUtil {
private ObjectUtil() {
}
/**
* Checks that the given argument is not null. If it is, throws {@link NullPointerException}.
* Otherwise, returns the argument.
*/
public static <T> T checkNotNull(T arg, String text) {
if (arg == null) {
throw new NullPointerException(text);
}
return arg;
}
}

View File

@ -26,6 +26,12 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheClientCodec;
import io.netty.handler.codec.memcache.binary.BinaryMemcacheObjectAggregator;
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.SslContext;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
@ -45,7 +51,15 @@ public final class MemcacheClient {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
sslCtx = SslContext.newClientContext(InsecureTrustManagerFactory.INSTANCE);
sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE, null,
IdentityCipherSuiteFilter.INSTANCE,
new ApplicationProtocolConfig(
Protocol.ALPN,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
SelectedProtocol.HTTP_1_1.protocolName()),
0, 0);
} else {
sslCtx = null;
}

View File

@ -27,11 +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.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>
@ -54,8 +57,13 @@ public final class SpdyClient {
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx = SslContext.newClientContext(
null, InsecureTrustManagerFactory.INSTANCE, null,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
null, InsecureTrustManagerFactory.INSTANCE, null, IdentityCipherSuiteFilter.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,11 +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.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>
@ -55,8 +58,13 @@ public final class SpdyServer {
// Configure SSL.
SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContext.newServerContext(
ssc.certificate(), ssc.privateKey(), null, null,
Arrays.asList(SelectedProtocol.SPDY_3_1.protocolName(), SelectedProtocol.HTTP_1_1.protocolName()),
ssc.certificate(), ssc.privateKey(), null, null, IdentityCipherSuiteFilter.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

@ -66,6 +66,22 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.alpn</groupId>
<artifactId>alpn-api</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.alpn</groupId>
<artifactId>alpn-boot</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

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,33 @@
/*
* 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;
/**
* Provides a means to filter the supplied cipher suite based upon the supported and default cipher suites.
*/
public interface CipherSuiteFilter {
/**
* Filter the requested {@code ciphers} based upon other cipher characteristics.
* @param ciphers The requested ciphers
* @param defaultCiphers The default recommended ciphers for the current {@link SSLEngine} as determined by Netty
* @param supportedCiphers The supported ciphers for the current {@link SSLEngine}
* @return The filter list of ciphers. Must not return {@code null}.
*/
String[] filterCipherSuites(Iterable<String> ciphers, List<String> defaultCiphers, Set<String> supportedCiphers);
}

View File

@ -0,0 +1,47 @@
/*
* 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;
import java.util.Set;
/**
* This class will not do any filtering of ciphers suites.
*/
public final class IdentityCipherSuiteFilter implements CipherSuiteFilter {
public static final IdentityCipherSuiteFilter INSTANCE = new IdentityCipherSuiteFilter();
private IdentityCipherSuiteFilter() { }
@Override
public String[] filterCipherSuites(Iterable<String> ciphers, List<String> defaultCiphers,
Set<String> supportedCiphers) {
if (ciphers == null) {
return defaultCiphers.toArray(new String[defaultCiphers.size()]);
} else {
List<String> newCiphers = new ArrayList<String>(supportedCiphers.size());
for (String c : ciphers) {
if (c == null) {
break;
}
newCiphers.add(c);
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}
}

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

@ -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.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;
import javax.net.ssl.SSLException;
import org.eclipse.jetty.alpn.ALPN;
import org.eclipse.jetty.alpn.ALPN.ClientProvider;
import org.eclipse.jetty.alpn.ALPN.ServerProvider;
final class JdkAlpnSslEngine extends JdkSslEngine {
private static boolean available;
static boolean isAvailable() {
updateAvailability();
return available;
}
private static void updateAvailability() {
if (available) {
return;
}
try {
// Try to get the bootstrap class loader.
ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent();
if (bootloader == null) {
// If failed, use the system class loader,
// although it's not perfect to tell if APLN extension has been loaded.
bootloader = ClassLoader.getSystemClassLoader();
}
Class.forName("sun.security.ssl.ALPNExtension", true, bootloader);
available = true;
} catch (Exception ignore) {
// alpn-boot was not loaded.
}
}
JdkAlpnSslEngine(SSLEngine engine, final JdkApplicationProtocolNegotiator applicationNegotiator, boolean server) {
super(engine);
checkNotNull(applicationNegotiator, "applicationNegotiator");
if (server) {
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) {
try {
return protocolSelector.select(protocols);
} catch (Throwable t) {
PlatformDependent.throwException(t);
return null;
}
}
@Override
public void unsupported() {
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 applicationNegotiator.protocols();
}
@Override
public void selected(String protocol) {
try {
protocolListener.selected(protocol);
} catch (Throwable t) {
PlatformDependent.throwException(t);
}
}
@Override
public void unsupported() {
protocolListener.unsupported();
}
});
}
}
@Override
public void closeInbound() throws SSLException {
ALPN.remove(getWrappedEngine());
super.closeInbound();
}
@Override
public void closeOutbound() {
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

@ -0,0 +1,127 @@
/*
* 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.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;
import javax.net.ssl.SSLException;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.npn.NextProtoNego.ClientProvider;
import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
final class JdkNpnSslEngine extends JdkSslEngine {
private static boolean available;
static boolean isAvailable() {
updateAvailability();
return available;
}
private static void updateAvailability() {
if (available) {
return;
}
try {
// Try to get the bootstrap class loader.
ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent();
if (bootloader == null) {
// If failed, use the system class loader,
// although it's not perfect to tell if NPN extension has been loaded.
bootloader = ClassLoader.getSystemClassLoader();
}
Class.forName("sun.security.ssl.NextProtoNegoExtension", true, bootloader);
available = true;
} catch (Exception ignore) {
// npn-boot was not loaded.
}
}
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() {
protocolListener.unsupported();
}
@Override
public List<String> protocols() {
return applicationNegotiator.protocols();
}
@Override
public void protocolSelected(String protocol) {
try {
protocolListener.selected(protocol);
} catch (Throwable t) {
PlatformDependent.throwException(t);
}
}
});
} else {
final ProtocolSelector protocolSelector = checkNotNull(applicationNegotiator.protocolSelectorFactory()
.newSelector(this, new HashSet<String>(applicationNegotiator.protocols())), "protocolSelector");
NextProtoNego.put(engine, new ClientProvider() {
@Override
public boolean supports() {
return true;
}
@Override
public void unsupported() {
protocolSelector.unsupported();
}
@Override
public String selectProtocol(List<String> protocols) {
try {
return protocolSelector.select(protocols);
} catch (Throwable t) {
PlatformDependent.throwException(t);
return null;
}
}
});
}
}
@Override
public void closeInbound() throws SSLException {
NextProtoNego.remove(getWrappedEngine());
super.closeInbound();
}
@Override
public void closeOutbound() {
NextProtoNego.remove(getWrappedEngine());
super.closeOutbound();
}
}

View File

@ -16,22 +16,16 @@
package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import java.io.File;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
import java.io.File;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A client-side {@link SslContext} which uses JDK's SSL/TLS implementation.
@ -39,13 +33,12 @@ import java.util.List;
public final class JdkSslClientContext extends JdkSslContext {
private final SSLContext ctx;
private final List<String> nextProtocols;
/**
* Creates a new instance.
*/
public JdkSslClientContext() throws SSLException {
this(null, null, null, null, 0, 0);
this(null, null);
}
/**
@ -79,7 +72,8 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@code null} to use the default.
*/
public JdkSslClientContext(File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
this(certChainFile, trustManagerFactory, null, null, 0, 0);
this(certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
JdkDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
}
/**
@ -92,8 +86,8 @@ public final class JdkSslClientContext extends JdkSslContext {
* {@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 nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @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.
@ -101,65 +95,120 @@ public final class JdkSslClientContext extends JdkSslContext {
*/
public JdkSslClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(certChainFile, trustManagerFactory, ciphers, cipherFilter,
toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
}
super(ciphers);
/**
* 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 {
this(certChainFile, trustManagerFactory, null, null, null, null,
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
if (nextProtocols != null && nextProtocols.iterator().hasNext()) {
if (!JettyNpnSslEngine.isAvailable()) {
throw new SSLException("NPN/ALPN unsupported: " + nextProtocols);
}
/**
* Creates a new instance.
* @param trustCertChainFile 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 or the results of parsing {@code trustCertChainFile}
* @param keyCertChainFile an X.509 certificate chain file in PEM format.
* This provides the public key for mutual authentication.
* {@code null} to use the system default
* @param keyFile a PKCS#8 private key file in PEM format.
* This provides the private key for mutual authentication.
* {@code null} for no mutual authentication.
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* Ignored if {@code keyFile} is {@code null}.
* @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to servers.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* @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 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.
* {@code 0} to use the default value.
*/
public JdkSslClientContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory,
ciphers, cipherFilter, toNegotiator(apn, false), sessionCacheSize, sessionTimeout);
}
List<String> nextProtoList = new ArrayList<String>();
for (String p: nextProtocols) {
if (p == null) {
break;
}
nextProtoList.add(p);
}
this.nextProtocols = Collections.unmodifiableList(nextProtoList);
} else {
this.nextProtocols = Collections.emptyList();
}
/**
* Creates a new instance.
* @param trustCertChainFile 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 or the results of parsing {@code trustCertChainFile}
* @param keyCertChainFile an X.509 certificate chain file in PEM format.
* This provides the public key for mutual authentication.
* {@code null} to use the system default
* @param keyFile a PKCS#8 private key file in PEM format.
* This provides the private key for mutual authentication.
* {@code null} for no mutual authentication.
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* Ignored if {@code keyFile} is {@code null}.
* @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to servers.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* @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 trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, cipherFilter, apn);
try {
if (certChainFile == null) {
ctx = SSLContext.getInstance(PROTOCOL);
if (trustManagerFactory == null) {
ctx.init(null, null, null);
} else {
trustManagerFactory.init((KeyStore) null);
ctx.init(null, trustManagerFactory.getTrustManagers(), null);
}
} else {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
try {
for (ByteBuf buf: certs) {
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
X500Principal principal = cert.getSubjectX500Principal();
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
}
} finally {
for (ByteBuf buf: certs) {
buf.release();
}
}
// Set up trust manager factory to use our key store.
if (trustManagerFactory == null) {
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
}
trustManagerFactory.init(ks);
// Initialize the SSLContext to work with the trust managers.
ctx = SSLContext.getInstance(PROTOCOL);
ctx.init(null, trustManagerFactory.getTrustManagers(), null);
if (trustCertChainFile != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertChainFile, trustManagerFactory);
}
if (keyFile != null) {
keyManagerFactory = buildKeyManagerFactory(keyCertChainFile, keyFile, keyPassword, keyManagerFactory);
}
ctx = SSLContext.getInstance(PROTOCOL);
ctx.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(),
trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(),
null);
SSLSessionContext sessCtx = ctx.getClientSessionContext();
if (sessionCacheSize > 0) {
@ -169,7 +218,7 @@ public final class JdkSslClientContext extends JdkSslContext {
sessCtx.setSessionTimeout((int) Math.min(sessionTimeout, Integer.MAX_VALUE));
}
} catch (Exception e) {
throw new SSLException("failed to initialize the server-side SSL context", e);
throw new SSLException("failed to initialize the client-side SSL context", e);
}
}
@ -178,11 +227,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,17 +16,50 @@
package io.netty.handler.ssl;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufInputStream;
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.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
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.SSLSessionContext;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
/**
* An {@link SslContext} which uses JDK's SSL/TLS implementation.
@ -38,9 +71,11 @@ public abstract class JdkSslContext extends SslContext {
static final String PROTOCOL = "TLS";
static final String[] PROTOCOLS;
static final List<String> DEFAULT_CIPHERS;
static final Set<String> SUPPORTED_CIPHERS;
static {
SSLContext context;
int i;
try {
context = SSLContext.getInstance(PROTOCOL);
context.init(null, null, null);
@ -51,10 +86,14 @@ public abstract class JdkSslContext extends SslContext {
SSLEngine engine = context.createSSLEngine();
// Choose the sensible default list of protocols.
String[] supportedProtocols = engine.getSupportedProtocols();
final String[] supportedProtocols = engine.getSupportedProtocols();
Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
for (i = 0; i < supportedProtocols.length; ++i) {
supportedProtocolsSet.add(supportedProtocols[i]);
}
List<String> protocols = new ArrayList<String>();
addIfSupported(
supportedProtocols, protocols,
supportedProtocolsSet, protocols,
"TLSv1.2", "TLSv1.1", "TLSv1");
if (!protocols.isEmpty()) {
@ -64,10 +103,14 @@ public abstract class JdkSslContext extends SslContext {
}
// Choose the sensible default list of cipher suites.
String[] supportedCiphers = engine.getSupportedCipherSuites();
final String[] supportedCiphers = engine.getSupportedCipherSuites();
SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
for (i = 0; i < supportedCiphers.length; ++i) {
SUPPORTED_CIPHERS.add(supportedCiphers[i]);
}
List<String> ciphers = new ArrayList<String>();
addIfSupported(
supportedCiphers, ciphers,
SUPPORTED_CIPHERS, ciphers,
// XXX: Make sure to sync this list with OpenSslEngineFactory.
// GCM (Galois/Counter Mode) requires JDK 8.
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
@ -97,22 +140,28 @@ public abstract class JdkSslContext extends SslContext {
}
}
private static void addIfSupported(String[] supported, List<String> enabled, String... names) {
for (String n: names) {
for (String s: supported) {
if (n.equals(s)) {
enabled.add(s);
break;
}
private static void addIfSupported(Set<String> supported, List<String> enabled, String... names) {
for (int i = 0; i < names.length; ++i) {
String n = names[i];
if (supported.contains(n)) {
enabled.add(n);
}
}
}
private final String[] cipherSuites;
private final List<String> unmodifiableCipherSuites;
private final JdkApplicationProtocolNegotiator apn;
JdkSslContext(Iterable<String> ciphers) {
cipherSuites = toCipherSuiteArray(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));
}
@ -166,25 +215,227 @@ public abstract class JdkSslContext extends SslContext {
}
private SSLEngine wrapEngine(SSLEngine engine) {
if (nextProtocols().isEmpty()) {
return engine;
} else {
return new JettyNpnSslEngine(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());
}
}
private static String[] toCipherSuiteArray(Iterable<String> ciphers) {
if (ciphers == null) {
return DEFAULT_CIPHERS.toArray(new String[DEFAULT_CIPHERS.size()]);
} else {
List<String> newCiphers = new ArrayList<String>();
for (String c: ciphers) {
if (c == null) {
break;
}
newCiphers.add(c);
}
return newCiphers.toArray(new String[newCiphers.size()]);
/**
* Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
* @param certChainFile a 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 kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
* @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
*/
protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword,
KeyManagerFactory kmf)
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException,
CertificateException, KeyException, IOException {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
}
/**
* Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
* and a certificate chain.
* @param certChainFile a X.509 certificate chain file in PEM format
* @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension
* Reference Guide for information about standard algorithm names.
* @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 kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
* @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
* and a certificate chain.
*/
protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile,
String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, InvalidAlgorithmParameterException, IOException,
CertificateException, KeyException, UnrecoverableKeyException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
KeyFactory rsaKF = KeyFactory.getInstance("RSA");
KeyFactory dsaKF = KeyFactory.getInstance("DSA");
ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release();
char[] keyPasswordChars = keyPassword == null ? new char[0] : keyPassword.toCharArray();
PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
PrivateKey key;
try {
key = rsaKF.generatePrivate(encodedKeySpec);
} catch (InvalidKeySpecException ignore) {
key = dsaKF.generatePrivate(encodedKeySpec);
}
List<Certificate> certChain = new ArrayList<Certificate>();
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
try {
for (ByteBuf buf: certs) {
certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
}
} finally {
for (ByteBuf buf: certs) {
buf.release();
}
}
ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
// Set up key manager factory to use our key store
if (kmf == null) {
kmf = KeyManagerFactory.getInstance(keyAlgorithm);
}
kmf.init(ks, keyPasswordChars);
return kmf;
}
/**
* Build a {@link TrustManagerFactory} from a certificate chain file.
* @param certChainFile The certificate file to build from.
* @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not {@code null}.
* @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile}
*/
protected static TrustManagerFactory buildTrustManagerFactory(File certChainFile,
TrustManagerFactory trustManagerFactory)
throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
try {
for (ByteBuf buf: certs) {
X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
X500Principal principal = cert.getSubjectX500Principal();
ks.setCertificateEntry(principal.getName("RFC2253"), cert);
}
} finally {
for (ByteBuf buf: certs) {
buf.release();
}
}
// Set up trust manager factory to use our key store.
if (trustManagerFactory == null) {
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
}
trustManagerFactory.init(ks);
return trustManagerFactory;
}
/**
* Generates a key specification for an (encrypted) private key.
*
* @param password characters, if {@code null} or empty an unencrypted key is assumed
* @param key bytes of the DER encoded private key
*
* @return a key specification
*
* @throws IOException if parsing {@code key} fails
* @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
* @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown
* @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated
* @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt
* {@code key}
* @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
*/
private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidKeyException, InvalidAlgorithmParameterException {
if (password == null || password.length == 0) {
return new PKCS8EncodedKeySpec(key);
}
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
return encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}

View File

@ -13,12 +13,9 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.ssl;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.npn.NextProtoNego.ClientProvider;
import org.eclipse.jetty.npn.NextProtoNego.ServerProvider;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
@ -26,105 +23,32 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import java.nio.ByteBuffer;
import java.util.List;
final class JettyNpnSslEngine extends SSLEngine {
private static boolean available;
static boolean isAvailable() {
updateAvailability();
return available;
}
private static void updateAvailability() {
if (available) {
return;
}
try {
// Try to get the bootstrap class loader.
ClassLoader bootloader = ClassLoader.getSystemClassLoader().getParent();
if (bootloader == null) {
// If failed, use the system class loader,
// although it's not perfect to tell if NPN extension has been loaded.
bootloader = ClassLoader.getSystemClassLoader();
}
Class.forName("sun.security.ssl.NextProtoNegoExtension", true, bootloader);
available = true;
} catch (Exception ignore) {
// npn-boot was not loaded.
}
}
class JdkSslEngine extends SSLEngine {
private final SSLEngine engine;
private final JettyNpnSslSession session;
JettyNpnSslEngine(SSLEngine engine, final List<String> nextProtocols, boolean server) {
assert !nextProtocols.isEmpty();
private final JdkSslSession session;
JdkSslEngine(SSLEngine engine) {
this.engine = engine;
session = new JettyNpnSslSession(engine);
if (server) {
NextProtoNego.put(engine, new ServerProvider() {
@Override
public void unsupported() {
getSession().setApplicationProtocol(nextProtocols.get(nextProtocols.size() - 1));
}
@Override
public List<String> protocols() {
return nextProtocols;
}
@Override
public void protocolSelected(String protocol) {
getSession().setApplicationProtocol(protocol);
}
});
} else {
final String[] list = nextProtocols.toArray(new String[nextProtocols.size()]);
final String fallback = list[list.length - 1];
NextProtoNego.put(engine, new ClientProvider() {
@Override
public boolean supports() {
return true;
}
@Override
public void unsupported() {
session.setApplicationProtocol(null);
}
@Override
public String selectProtocol(List<String> protocols) {
for (String p: list) {
if (protocols.contains(p)) {
return p;
}
}
return fallback;
}
});
}
session = new JdkSslSession(engine);
}
@Override
public JettyNpnSslSession getSession() {
public JdkSslSession getSession() {
return session;
}
public SSLEngine getWrappedEngine() {
return engine;
}
@Override
public void closeInbound() throws SSLException {
NextProtoNego.remove(engine);
engine.closeInbound();
}
@Override
public void closeOutbound() {
NextProtoNego.remove(engine);
engine.closeOutbound();
}

View File

@ -16,35 +16,15 @@
package io.netty.handler.ssl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import java.io.File;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSessionContext;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
/**
* A server-side {@link SslContext} which uses JDK's SSL/TLS implementation.
@ -52,7 +32,6 @@ import java.util.List;
public final class JdkSslServerContext extends JdkSslContext {
private final SSLContext ctx;
private final List<String> nextProtocols;
/**
* Creates a new instance.
@ -73,7 +52,8 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@code null} if it's not password-protected.
*/
public JdkSslServerContext(File certChainFile, File keyFile, String keyPassword) throws SSLException {
this(certChainFile, keyFile, keyPassword, null, null, 0, 0);
this(certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
JdkDefaultApplicationProtocolNegotiator.INSTANCE, 0, 0);
}
/**
@ -85,8 +65,8 @@ public final class JdkSslServerContext extends JdkSslContext {
* {@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 cipherFilter a filter to apply over the supplied list of ciphers
* @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.
@ -94,87 +74,118 @@ public final class JdkSslServerContext extends JdkSslContext {
*/
public JdkSslServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(certChainFile, keyFile, keyPassword, ciphers, cipherFilter,
toNegotiator(apn, true), sessionCacheSize, sessionTimeout);
}
super(ciphers);
/**
* 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 {
this(null, null, certChainFile, keyFile, keyPassword, null,
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
if (certChainFile == null) {
throw new NullPointerException("certChainFile");
}
if (keyFile == null) {
throw new NullPointerException("keyFile");
}
/**
* Creates a new instance.
* @param trustCertChainFile an X.509 certificate chain file in PEM format.
* This provides the certificate chains used for mutual authentication.
* {@code null} to use the system default
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
* that verifies the certificates sent from clients.
* {@code null} to use the default or the results of parsing {@code trustCertChainFile}.
* @param keyCertChainFile 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 keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to clients.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* @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
* Only required if {@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.
* {@code 0} to use the default value.
*/
public JdkSslServerContext(File trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
this(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword, keyManagerFactory,
ciphers, cipherFilter, toNegotiator(apn, true), sessionCacheSize, sessionTimeout);
}
if (keyPassword == null) {
keyPassword = "";
}
if (nextProtocols != null && nextProtocols.iterator().hasNext()) {
if (!JettyNpnSslEngine.isAvailable()) {
throw new SSLException("NPN/ALPN unsupported: " + nextProtocols);
}
List<String> list = new ArrayList<String>();
for (String p: nextProtocols) {
if (p == null) {
break;
}
list.add(p);
}
this.nextProtocols = Collections.unmodifiableList(list);
} else {
this.nextProtocols = Collections.emptyList();
}
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
/**
* Creates a new instance.
* @param trustCertChainFile an X.509 certificate chain file in PEM format.
* This provides the certificate chains used for mutual authentication.
* {@code null} to use the system default
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
* that verifies the certificates sent from clients.
* {@code null} to use the default or the results of parsing {@code trustCertChainFile}
* @param keyCertChainFile 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 keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to clients.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* @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
* Only required if {@code provider} is {@link SslProvider#JDK}
* @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 trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
super(ciphers, cipherFilter, apn);
if (keyFile == null && keyManagerFactory == null) {
throw new NullPointerException("keyFile, keyManagerFactory");
}
try {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
KeyFactory rsaKF = KeyFactory.getInstance("RSA");
KeyFactory dsaKF = KeyFactory.getInstance("DSA");
ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
encodedKeyBuf.readBytes(encodedKey).release();
char[] keyPasswordChars = keyPassword.toCharArray();
PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
PrivateKey key;
try {
key = rsaKF.generatePrivate(encodedKeySpec);
} catch (InvalidKeySpecException ignore) {
key = dsaKF.generatePrivate(encodedKeySpec);
if (trustCertChainFile != null) {
trustManagerFactory = buildTrustManagerFactory(trustCertChainFile, trustManagerFactory);
}
List<Certificate> certChain = new ArrayList<Certificate>();
ByteBuf[] certs = PemReader.readCertificates(certChainFile);
try {
for (ByteBuf buf: certs) {
certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
}
} finally {
for (ByteBuf buf: certs) {
buf.release();
}
if (keyFile != null) {
keyManagerFactory = buildKeyManagerFactory(keyCertChainFile, keyFile, keyPassword, keyManagerFactory);
}
ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
kmf.init(ks, keyPasswordChars);
// Initialize the SSLContext to work with our key managers.
ctx = SSLContext.getInstance(PROTOCOL);
ctx.init(kmf.getKeyManagers(), null, null);
ctx.init(keyManagerFactory.getKeyManagers(),
trustManagerFactory == null ? null : trustManagerFactory.getTrustManagers(),
null);
SSLSessionContext sessCtx = ctx.getServerSessionContext();
if (sessionCacheSize > 0) {
@ -193,48 +204,8 @@ public final class JdkSslServerContext extends JdkSslContext {
return false;
}
@Override
public List<String> nextProtocols() {
return nextProtocols;
}
@Override
public SSLContext context() {
return ctx;
}
/**
* Generates a key specification for an (encrypted) private key.
*
* @param password characters, if {@code null} or empty an unencrypted key is assumed
* @param key bytes of the DER encoded private key
*
* @return a key specification
*
* @throws IOException if parsing {@code key} fails
* @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown
* @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is unkown
* @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be generated
* @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to decrypt
* {@code key}
* @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow faulty
*/
private static PKCS8EncodedKeySpec generateKeySpec(char[] password, byte[] key)
throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
InvalidKeyException, InvalidAlgorithmParameterException {
if (password == null || password.length == 0) {
return new PKCS8EncodedKeySpec(key);
}
EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
return encryptedPrivateKeyInfo.getKeySpec(cipher);
}
}

View File

@ -0,0 +1,169 @@
/*
* 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;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.security.cert.X509Certificate;
import java.security.Principal;
import java.security.cert.Certificate;
final class JdkSslSession implements SSLSession {
private final SSLEngine engine;
private volatile String applicationProtocol;
JdkSslSession(SSLEngine engine) {
this.engine = engine;
}
void setApplicationProtocol(String applicationProtocol) {
if (applicationProtocol != null) {
applicationProtocol = applicationProtocol.replace(':', '_');
}
this.applicationProtocol = applicationProtocol;
}
@Override
public String getProtocol() {
final String protocol = unwrap().getProtocol();
final String applicationProtocol = this.applicationProtocol;
if (applicationProtocol == null) {
if (protocol != null) {
return protocol.replace(':', '_');
} else {
return null;
}
}
final StringBuilder buf = new StringBuilder(32);
if (protocol != null) {
buf.append(protocol.replace(':', '_'));
buf.append(':');
} else {
buf.append("null:");
}
buf.append(applicationProtocol);
return buf.toString();
}
private SSLSession unwrap() {
return engine.getSession();
}
@Override
public byte[] getId() {
return unwrap().getId();
}
@Override
public SSLSessionContext getSessionContext() {
return unwrap().getSessionContext();
}
@Override
public long getCreationTime() {
return unwrap().getCreationTime();
}
@Override
public long getLastAccessedTime() {
return unwrap().getLastAccessedTime();
}
@Override
public void invalidate() {
unwrap().invalidate();
}
@Override
public boolean isValid() {
return unwrap().isValid();
}
@Override
public void putValue(String s, Object o) {
unwrap().putValue(s, o);
}
@Override
public Object getValue(String s) {
return unwrap().getValue(s);
}
@Override
public void removeValue(String s) {
unwrap().removeValue(s);
}
@Override
public String[] getValueNames() {
return unwrap().getValueNames();
}
@Override
public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
return unwrap().getPeerCertificates();
}
@Override
public Certificate[] getLocalCertificates() {
return unwrap().getLocalCertificates();
}
@Override
public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
return unwrap().getPeerCertificateChain();
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
return unwrap().getPeerPrincipal();
}
@Override
public Principal getLocalPrincipal() {
return unwrap().getLocalPrincipal();
}
@Override
public String getCipherSuite() {
return unwrap().getCipherSuite();
}
@Override
public String getPeerHost() {
return unwrap().getPeerHost();
}
@Override
public int getPeerPort() {
return unwrap().getPeerPort();
}
@Override
public int getPacketBufferSize() {
return unwrap().getPacketBufferSize();
}
@Override
public int getApplicationBufferSize() {
return unwrap().getApplicationBufferSize();
}
}

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

@ -0,0 +1,35 @@
/*
* 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;
/**
* Provides no {@link ApplicationProtocolNegotiator} functionality for OpenSSL.
*/
final class OpenSslDefaultApplicationProtocolNegotiator implements OpenSslApplicationProtocolNegotiator {
static final OpenSslDefaultApplicationProtocolNegotiator INSTANCE =
new OpenSslDefaultApplicationProtocolNegotiator();
private OpenSslDefaultApplicationProtocolNegotiator() {
}
@Override
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> nextProtocols;
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);
}
@ -135,9 +156,6 @@ public final class OpenSslServerContext extends SslContext {
if (keyPassword == null) {
keyPassword = "";
}
if (nextProtocols == null) {
nextProtocols = Collections.emptyList();
}
for (String c: ciphers) {
if (c == null) {
@ -146,15 +164,6 @@ public final class OpenSslServerContext extends SslContext {
this.ciphers.add(c);
}
List<String> nextProtoList = new ArrayList<String>();
for (String p: nextProtocols) {
if (p == null) {
break;
}
nextProtoList.add(p);
}
this.nextProtocols = Collections.unmodifiableList(nextProtoList);
// Allocate a new APR pool.
aprPool = Pool.create(0);
@ -219,11 +228,12 @@ public final class OpenSslServerContext extends SslContext {
}
/* Set next protocols for next protocol negotiation extension, if specified */
if (!nextProtoList.isEmpty()) {
List<String> protocols = apn.protocols();
if (!protocols.isEmpty()) {
// Convert the protocol list into a comma-separated string.
StringBuilder nextProtocolBuf = new StringBuilder();
for (String p: nextProtoList) {
nextProtocolBuf.append(p);
for (int i = 0; i < protocols.size(); ++i) {
nextProtocolBuf.append(protocols.get(i));
nextProtocolBuf.append(',');
}
nextProtocolBuf.setLength(nextProtocolBuf.length() - 1);
@ -283,9 +293,8 @@ public final class OpenSslServerContext extends SslContext {
return sessionTimeout;
}
@Override
public List<String> nextProtocols() {
return nextProtocols;
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
return apn;
}
/**
@ -307,10 +316,11 @@ public final class OpenSslServerContext extends SslContext {
*/
@Override
public SSLEngine newEngine(ByteBufAllocator alloc) {
if (nextProtocols.isEmpty()) {
List<String> protocols = apn.protocols();
if (protocols.isEmpty()) {
return new OpenSslEngine(ctx, alloc, null);
} else {
return new OpenSslEngine(ctx, alloc, nextProtocols.get(nextProtocols.size() - 1));
return new OpenSslEngine(ctx, alloc, protocols.get(protocols.size() - 1));
}
}
@ -347,4 +357,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,13 +20,16 @@ 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.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.util.List;
/**
* A secure socket protocol implementation which acts as a factory for {@link SSLEngine} and {@link SslHandler}.
@ -51,7 +54,6 @@ import java.util.List;
* </pre>
*/
public abstract class SslContext {
/**
* Returns the default server-side implementation provider currently in use.
*
@ -82,7 +84,7 @@ public abstract class SslContext {
* @return a new server-side {@link SslContext}
*/
public static SslContext newServerContext(File certChainFile, File keyFile) throws SSLException {
return newServerContext(null, certChainFile, keyFile, null, null, null, 0, 0);
return newServerContext(certChainFile, keyFile, null);
}
/**
@ -96,7 +98,7 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword) throws SSLException {
return newServerContext(null, certChainFile, keyFile, keyPassword, null, null, 0, 0);
return newServerContext(null, certChainFile, keyFile, keyPassword);
}
/**
@ -108,8 +110,8 @@ public abstract class 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 cipherFilter a filter to apply over the supplied list of ciphers
* @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.
@ -118,11 +120,11 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newServerContext(
null, certChainFile, keyFile, keyPassword,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
@ -136,7 +138,7 @@ public abstract class SslContext {
*/
public static SslContext newServerContext(
SslProvider provider, File certChainFile, File keyFile) throws SSLException {
return newServerContext(provider, certChainFile, keyFile, null, null, null, 0, 0);
return newServerContext(provider, certChainFile, keyFile, null);
}
/**
@ -152,7 +154,8 @@ 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, null, 0, 0);
return newServerContext(provider, certChainFile, keyFile, keyPassword, null, IdentityCipherSuiteFilter.INSTANCE,
null, 0, 0);
}
/**
@ -166,18 +169,58 @@ public abstract class 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 cipherFilter a filter to apply over the supplied list of ciphers
* Only required if {@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.
* {@code 0} to use the default value.
* @return a new server-side {@link SslContext}
*/
public static SslContext newServerContext(
SslProvider provider,
public static SslContext newServerContext(SslProvider provider,
File certChainFile, File keyFile, String keyPassword,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newServerContext(provider, null, null, certChainFile, keyFile, keyPassword, null,
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
* Creates a new server-side {@link SslContext}.
* @param provider the {@link SslContext} implementation to use.
* {@code null} to use the current default one.
* @param trustCertChainFile an X.509 certificate chain file in PEM format.
* This provides the certificate chains used for mutual authentication.
* {@code null} to use the system default
* @param trustManagerFactory the {@link TrustManagerFactory} that provides the {@link TrustManager}s
* that verifies the certificates sent from clients.
* {@code null} to use the default or the results of parsing {@code trustCertChainFile}.
* This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
* @param keyCertChainFile 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 keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to clients.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
* @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
* Only required if {@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.
* {@code 0} to use the default value.
* @return a new server-side {@link SslContext}
*/
public static SslContext newServerContext(SslProvider provider,
File trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider == null) {
@ -187,12 +230,15 @@ public abstract class SslContext {
switch (provider) {
case JDK:
return new JdkSslServerContext(
certChainFile, keyFile, keyPassword,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
case OPENSSL:
if (trustCertChainFile != null) {
throw new UnsupportedOperationException("OpenSSL provider does not support mutual authentication");
}
return new OpenSslServerContext(
certChainFile, keyFile, keyPassword,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
keyCertChainFile, keyFile, keyPassword,
ciphers, apn, sessionCacheSize, sessionTimeout);
default:
throw new Error(provider.toString());
}
@ -204,7 +250,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext() throws SSLException {
return newClientContext(null, null, null, null, null, 0, 0);
return newClientContext(null, null, null);
}
/**
@ -215,7 +261,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(File certChainFile) throws SSLException {
return newClientContext(null, certChainFile, null, null, null, 0, 0);
return newClientContext(null, certChainFile);
}
/**
@ -228,7 +274,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(TrustManagerFactory trustManagerFactory) throws SSLException {
return newClientContext(null, null, trustManagerFactory, null, null, 0, 0);
return newClientContext(null, null, trustManagerFactory);
}
/**
@ -244,7 +290,7 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
return newClientContext(null, certChainFile, trustManagerFactory, null, null, 0, 0);
return newClientContext(null, certChainFile, trustManagerFactory);
}
/**
@ -257,8 +303,8 @@ public abstract class SslContext {
* {@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 nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @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.
@ -268,11 +314,11 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newClientContext(
null, certChainFile, trustManagerFactory,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
@ -284,7 +330,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(SslProvider provider) throws SSLException {
return newClientContext(provider, null, null, null, null, 0, 0);
return newClientContext(provider, null, null);
}
/**
@ -298,7 +344,7 @@ public abstract class SslContext {
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(SslProvider provider, File certChainFile) throws SSLException {
return newClientContext(provider, certChainFile, null, null, null, 0, 0);
return newClientContext(provider, certChainFile, null);
}
/**
@ -314,7 +360,7 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
SslProvider provider, TrustManagerFactory trustManagerFactory) throws SSLException {
return newClientContext(provider, null, trustManagerFactory, null, null, 0, 0);
return newClientContext(provider, trustManagerFactory);
}
/**
@ -332,7 +378,8 @@ public abstract class SslContext {
*/
public static SslContext newClientContext(
SslProvider provider, File certChainFile, TrustManagerFactory trustManagerFactory) throws SSLException {
return newClientContext(provider, certChainFile, trustManagerFactory, null, null, 0, 0);
return newClientContext(provider, certChainFile, trustManagerFactory, null, IdentityCipherSuiteFilter.INSTANCE,
null, 0, 0);
}
/**
@ -347,8 +394,8 @@ public abstract class SslContext {
* {@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 nextProtocols the application layer protocols to accept, in the order of preference.
* {@code null} to disable TLS NPN/ALPN extension.
* @param cipherFilter a filter to apply over the supplied list of ciphers
* @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.
@ -356,19 +403,61 @@ public abstract class SslContext {
*
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(
SslProvider provider,
public static SslContext newClientContext(SslProvider provider,
File certChainFile, TrustManagerFactory trustManagerFactory,
Iterable<String> ciphers, Iterable<String> nextProtocols,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
return newClientContext(provider, certChainFile, trustManagerFactory, null, null, null, null,
ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
/**
* Creates a new client-side {@link SslContext}.
* @param provider the {@link SslContext} implementation to use.
* {@code null} to use the current default one.
* @param trustCertChainFile 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 or the results of parsing {@code trustCertChainFile}.
* This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
* @param keyCertChainFile an X.509 certificate chain file in PEM format.
* This provides the public key for mutual authentication.
* {@code null} to use the system default
* @param keyFile a PKCS#8 private key file in PEM format.
* This provides the private key for mutual authentication.
* {@code null} for no mutual authentication.
* @param keyPassword the password of the {@code keyFile}.
* {@code null} if it's not password-protected.
* Ignored if {@code keyFile} is {@code null}.
* @param keyManagerFactory the {@link KeyManagerFactory} that provides the {@link KeyManager}s
* that is used to encrypt data being sent to servers.
* {@code null} to use the default or the results of parsing
* {@code keyCertChainFile} and {@code keyFile}.
* This parameter is ignored if {@code provider} is not {@link SslProvider#JDK}.
* @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 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.
* {@code 0} to use the default value.
*
* @return a new client-side {@link SslContext}
*/
public static SslContext newClientContext(SslProvider provider,
File trustCertChainFile, TrustManagerFactory trustManagerFactory,
File keyCertChainFile, File keyFile, String keyPassword, KeyManagerFactory keyManagerFactory,
Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
long sessionCacheSize, long sessionTimeout) throws SSLException {
if (provider != null && provider != SslProvider.JDK) {
throw new SSLException("client context unsupported for: " + provider);
}
return new JdkSslClientContext(
certChainFile, trustManagerFactory,
ciphers, nextProtocols, sessionCacheSize, sessionTimeout);
return new JdkSslClientContext(trustCertChainFile, trustManagerFactory, keyCertChainFile, keyFile, keyPassword,
keyManagerFactory, ciphers, cipherFilter, apn, sessionCacheSize, sessionTimeout);
}
SslContext() { }
@ -401,12 +490,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

@ -0,0 +1,58 @@
/*
* 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;
import java.util.Set;
/**
* This class will filter all requested ciphers out that are not supported by the current {@link SSLEngine}.
*/
public final class SupportedCipherSuiteFilter implements CipherSuiteFilter {
public static final SupportedCipherSuiteFilter INSTANCE = new SupportedCipherSuiteFilter();
private SupportedCipherSuiteFilter() { }
@Override
public String[] filterCipherSuites(Iterable<String> ciphers, List<String> defaultCiphers,
Set<String> supportedCiphers) {
if (defaultCiphers == null) {
throw new NullPointerException("defaultCiphers");
}
if (supportedCiphers == null) {
throw new NullPointerException("supportedCiphers");
}
final List<String> newCiphers;
if (ciphers == null) {
newCiphers = new ArrayList<String>(defaultCiphers.size());
ciphers = defaultCiphers;
} else {
newCiphers = new ArrayList<String>(supportedCiphers.size());
}
for (String c : ciphers) {
if (c == null) {
break;
}
if (supportedCiphers.contains(c)) {
newCiphers.add(c);
}
}
return newCiphers.toArray(new String[newCiphers.size()]);
}
}

View File

@ -0,0 +1,568 @@
/*
* 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.io.File;
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 channelRead0(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);
}
}
@Test
public void testMutualAuthSameCerts() throws Exception {
mySetupMutualAuth(new File(getClass().getResource("test_unencrypted.pem").getFile()),
new File(getClass().getResource("test.crt").getFile()),
null);
runTest(null);
}
@Test
public void testMutualAuthDiffCerts() throws Exception {
File serverKeyFile = new File(getClass().getResource("test_encrypted.pem").getFile());
File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
String serverKeyPassword = "12345";
File clientKeyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
String clientKeyPassword = "12345";
mySetupMutualAuth(clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
runTest(null);
}
@Test
public void testMutualAuthDiffCertsServerFailure() throws Exception {
File serverKeyFile = new File(getClass().getResource("test_encrypted.pem").getFile());
File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
String serverKeyPassword = "12345";
File clientKeyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
String clientKeyPassword = "12345";
// Client trusts server but server only trusts itself
mySetupMutualAuth(serverCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
serverCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
assertTrue(serverException instanceof SSLHandshakeException);
}
@Test
public void testMutualAuthDiffCertsClientFailure() throws Exception {
File serverKeyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());
File serverCrtFile = new File(getClass().getResource("test.crt").getFile());
String serverKeyPassword = null;
File clientKeyFile = new File(getClass().getResource("test2_unencrypted.pem").getFile());
File clientCrtFile = new File(getClass().getResource("test2.crt").getFile());
String clientKeyPassword = null;
// Server trusts client but client only trusts itself
mySetupMutualAuth(clientCrtFile, serverKeyFile, serverCrtFile, serverKeyPassword,
clientCrtFile, clientKeyFile, clientCrtFile, clientKeyPassword);
assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
assertTrue(clientException instanceof SSLHandshakeException);
}
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 mySetupMutualAuth(File keyFile, File crtFile, String keyPassword)
throws SSLException, CertificateException, InterruptedException {
mySetupMutualAuth(crtFile, keyFile, crtFile, keyPassword, crtFile, keyFile, crtFile, keyPassword);
}
private void mySetupMutualAuth(
File servertTrustCrtFile, File serverKeyFile, File serverCrtFile, String serverKeyPassword,
File clientTrustCrtFile, File clientKeyFile, File clientCrtFile, String clientKeyPassword)
throws InterruptedException, SSLException, CertificateException {
serverSslCtx = new JdkSslServerContext(servertTrustCrtFile, null,
serverCrtFile, serverKeyFile, serverKeyPassword, null,
null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 0, 0);
clientSslCtx = new JdkSslClientContext(clientTrustCrtFile, null,
clientCrtFile, clientKeyFile, clientKeyPassword, null,
null, IdentityCipherSuiteFilter.INSTANCE, (ApplicationProtocolConfig) null, 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();
SSLEngine engine = serverSslCtx.newEngine(ch.alloc());
engine.setUseClientMode(false);
engine.setNeedClientAuth(true);
p.addLast(new SslHandler(engine));
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);
if (expectedApplicationProtocol != null) {
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

@ -31,6 +31,14 @@ public class JdkSslServerContextTest {
new JdkSslServerContext(crtFile, keyFile, "12345");
}
@Test
public void testJdkSslServerWithEncryptedPrivateKey2() throws SSLException {
File keyFile = new File(getClass().getResource("test2_encrypted.pem").getFile());
File crtFile = new File(getClass().getResource("test2.crt").getFile());
new JdkSslServerContext(crtFile, keyFile, "12345");
}
@Test
public void testJdkSslServerWithUnencryptedPrivateKey() throws SSLException {
File keyFile = new File(getClass().getResource("test_unencrypted.pem").getFile());

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdACCQCp0Mn/2UCl2TANBgkqhkiG9w0BAQsFADA2MTQwMgYDVQQDDCtj
ZmYyNGEwY2I4NGFmNjExZDdhODFjMGI4MDY4OTA2OC5uZXR0eS50ZXN0MB4XDTE0
MTAxNzE4NDczM1oXDTE0MTExNjE4NDczM1owNjE0MDIGA1UEAwwrY2ZmMjRhMGNi
ODRhZjYxMWQ3YTgxYzBiODA2ODkwNjgubmV0dHkudGVzdDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALgddI5XJcUK45ONr4QTfZZxbJJeOYKPEWVIWK/P
Wz6EJXt3hDdpmnaRUKAv4mMIFlxWVkxTqa/dB3hjcm5hPvNgPAUaEWzMtGd32p95
sJzbxiWvxhf5rqF0n1Zk5KX+EcasiCupNg3TL7gfTSSZfaGSWf460oaCS6WCU4X9
XTUhys7N5BFM+uQLE048CnkBCO1An980Fau/0+BLXgW+iJC6XWTJbpZ+r7rDpBKl
+HmQQ5tgGlCZcnhmS9bzYT3hoag6JkDoIwbFsVOkwemxZGb8GsGE74/rrzUJ9MdR
/ETCA2km1na6ESst0/wm0qD3clJahP8xEoaJ+W1TFGizRWkCAwEAATANBgkqhkiG
9w0BAQsFAAOCAQEAmeGPRWXzu7+f20ZJA/u6WjcmsUhSTtt0YcBNtii4Pm0snIE9
UyRBGlvS2uFHTilD7MOYOHX6ATlHZAsfegpiPE5jCvE4CzFPpQaVAT/sKNtsWH43
ZQHn4NK1DAFIVDysO3AGGhL0mub8iWEYHs81+6tSSFlbDFqwYtw7ueerhVLUIaIa
S0SvtXUVitX2LzMlYCEto2s50fcqOnj8uve/dG8BmiwR1DqqVKkAWAXf8uGhwwD+
659E3g9vNz6QUchd8K/TIv52i8EDuWu3FElohmfFUXu43A+Z+lbuDrEW3suqTC3y
0JIa2DfHWA7WTyF4UD32aAC+U6BLIOA6WoPi1Q==
-----END CERTIFICATE-----

View File

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECCqT2dycwPtCAgIIAASCBMg/Z60Q85kVL5kv
q8WIIY9tbXo/2Q+6rspxdit9SRd86MV9QRdfZ5Vjwt0JTa+Rd1gMaNK4PySW23bq
F2+dD0sjVBcE24Qg0h4BcmL+YBdTftBfk7NDH/rHhsew7DZru9fdDvkO9bV3jXIz
fARW9U7JIfgAi6CfJ8Q1PS7sg6dVtrcjMRIie32x0TSbZrn+h9AaXpLHsC8oXiyY
BhWe4i9B7PobyJ0r/CTBFhbfUCGwRyHac0+bZXvlcwX9wy3W7jagc6RDlznOpowU
FP35CQGeKsJ9WD+yy5MU8X8M8v+eeaJk4oX+PSWJX669CxbYocVP/+LUtOXpe+4h
7yMmVNLUtsgBlY6tNsU0XBQkrqqb+voSxVBEVZ1WTKgLWsE/EiQ2P2GU8Gnr+J6c
/yHxw0D4q9J3jV40SiuXQlgFwlf8u9FuVjOcGxTidfKXyvNqPKqgkf9QD+7E09q3
JQoNbI/A8BXrpdx9h87Gt0TblPwVJP2nf5whig9W62R4y9SWybUUNr2MFNkvEfKe
1QK8isf+HlvIO+VBYi4jof9HkWLwnAszlkpC+k1cOiSjNRn8QyLzsqX7A/VuS6W8
6kKeND4yRNA4b7rfQqhyGg7gBwiwN+22UF6SKiikX4TB1ZyLdzlbPe0L+X/Gq0Jz
Kf+8/slgzB5K9WpDtKsARH/lRPAx1rcascvFxMuCJL5O9MO9l4xWDJor71WgPC2N
KwXxvEW3Kyvs3pSgWc8MC0BKcD9WIAahAlAVmSQBxDNWvJlGTgUVhzPqan7h03Fd
nWAxSn315ObfK9rjbqUBO9x/nkSZFS9nApmeiWkOIwVzgNfAfb9md07TYyC/rpK3
nGIsThekqqQULMQaAPmEFqUj6A/0KlpBj1gZwddYvVvEL/MuQO0QBdz4n/OncxYP
TVoQEqXsndmNQnkuk2Kr4FACV2M9rbr84HJUIZVGGVSM5h80GrRqK03qpTzM8Nkc
e04R4KDpLDKHm+G4xYZbbraIGXNTkhxTqdNA2FyjJWFurmpQyFay55vC6WBFBVNA
BGVIqD1/9K3dJJGlpiHyymRCK9YGvflZlSr7dm7PW7PPEthwTijbAHkABOKsFSiu
xaUj027WIVuDb5FFIAaF3Wmn4GFXvsSH+8L95CQuXGB8J/5Buo+/Hg6S7PeDwrf+
qNRAfg9vxo+AZOWpWfGEYGHQeX6BxVjdffar9RwL99cele4h2FgBLtIuAXvgLPyx
b+MIjDliCe1Nqx0PCCuaB1xRnaKiwbl7itDidzI8BUAaFcKxbBH2lpr44+vYPVHb
70Xrw55RLvrVYKAcaZgryTNOvbRatifJIMg3kf8V++2rwUMoZ+DQfXin/C4S/2/b
c6I1OvYaGxmI1YiI6qSpOryDSzTNlDEWcdh5feuixiP5RbyaQFswq2fH0hsWWHS4
OsCeqT0nm5vd1CdUFQJ4Nuh/TTdgCAVKk5yJZJvH2BX77I2d4T0ZRGHLDKUm8P0E
n6ntrMqLFR+QooONAZg0DTaxvbsCvaupRJCn9NgiwtXyYJKbvf5F8NEOe57NoGwd
LqQ332mVTuJ1DiqnChLoe7Mz7OY21RsTa/AK5Q/onClvBATrLD0ynK4WiLn4+hGs
HK5t3audgdnrLxs4UoA=
-----END ENCRYPTED PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4HXSOVyXFCuOT
ja+EE32WcWySXjmCjxFlSFivz1s+hCV7d4Q3aZp2kVCgL+JjCBZcVlZMU6mv3Qd4
Y3JuYT7zYDwFGhFszLRnd9qfebCc28Ylr8YX+a6hdJ9WZOSl/hHGrIgrqTYN0y+4
H00kmX2hkln+OtKGgkulglOF/V01IcrOzeQRTPrkCxNOPAp5AQjtQJ/fNBWrv9Pg
S14FvoiQul1kyW6Wfq+6w6QSpfh5kEObYBpQmXJ4ZkvW82E94aGoOiZA6CMGxbFT
pMHpsWRm/BrBhO+P6681CfTHUfxEwgNpJtZ2uhErLdP8JtKg93JSWoT/MRKGiflt
UxRos0VpAgMBAAECggEAYiTZd/L+oD3AuGwjrp0RKjwGKzPtJiqLlFjvZbB8LCQX
Muyv3zX8781glDNSU4YBHXGsiP1kC+ofzE3+ttZBz0xyUinmNgAc/rbGJJKi0crZ
okdDqo4fR9O6CDy6Ib4Azc40vEl0FgSIgHa3EZZ8gL9aF4pVpPwZxP1m9prrr6EP
SOlJP7rJNA/sTpuy0gz+UAu2Xf53pdkREUW7E2uzIGwrHxQVserN7Xxtft/zT79/
oIHF09pHfiqE8a2TuVvVavjwV6787PSewFs7j8iKId9bpo1O7iqvj0UKOE+/63Lf
1pWRn7lRGS9ACw8EoyTY/M0njUbDEfaObJUzt08pjQKBgQDevZLRQjbGDtKOfQe6
PKb/6PeFEE466NPFKH1bEz26VmC5vzF8U7lk71S11Dma51+vbOENzS5VlqOWqO+N
CyXTzb8a0rHXXUEP4+V6CazesTOEoBKViDswt2ffJfQYoCOFfKrcKq0j1Ps8Svhq
yzcMjAfX8eKIDWxK3qk+09SBtwKBgQDTm2Te4ENYwV5be+Z5L5See8gHNU5w3RtU
koO54TYBeJOTsTTtGDqEg60MoWIcx69OAJlHwTp5nPV5fhrjB8I9WUmI+2sPK7sU
OmhV/QzPjr6HW7fpbvbZ6fT+/Ay3aREa+qsJMypXsoqML1/fAeBno3hvHQt5Neog
leu3m0/x3wKBgQCCc8b8FeqcfuvkleejtIgeU2Q8I3ud1uTIkNkyMQezDYni385s
wWBQdDdJsvz181LAHGWGvsfHSs2OnGyIT6Ic9WBaplGQD8beNpwcqHP9jQzePR4F
Q99evdvw/nqCva9wK76p6bizxrZJ7qKlcVVRXOXvHHSPOEVXaCb5a/kG6wKBgGN6
2G8XC1I8hfmIRA+Q2NOw6ZbJ7riMmf6mapsGT3ddkjOKyZD1JP2LUd1wOUnCbp3D
FkxvgOgPbC/Toxw8V4qz4Sgu2mPlcSvPUaGrN0yUlOnZqpppek9z96OwJuJK2KnQ
Unweu7dCznOdCfszTKYsacAC7ZPsTsdG8+v7bhgNAoGBAL8wlTp3tfQ2iuGDnQaf
268BBUtqp2qPlGPXCdkc5XXbnHXLFY/UYGw27Vh+UNW8UORTFYEb8XPvUxB4q2Mx
8ZZdcjFB1J4dM2+KGr51CEuzzpFuhFU8Nn4D/hcfYNKg733gTeSoI0Gs2Y9R+bDo
+cA9UxmyFSgS+Dq/7BOmPCDI
-----END PRIVATE KEY-----

148
pom.xml
View File

@ -109,7 +109,20 @@
<!-- 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 -->
<argLine.bootcp>-D_</argLine.bootcp>
<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>
@ -281,6 +294,8 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</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>
@ -293,6 +308,8 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</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>
@ -305,6 +322,109 @@
</activation>
<properties>
<jetty.npn.version>1.1.6.v20130911</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-7u55</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_55</value>
</property>
</activation>
<properties>
<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-7u60</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_60</value>
</property>
</activation>
<properties>
<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-7u65</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_65</value>
</property>
</activation>
<properties>
<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-7u67</id>
<activation>
<property>
<name>java.version</name>
<value>1.7.0_67</value>
</property>
</activation>
<properties>
<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>
<profile>
<!--
This profile exists because either ALPN or NPN can exits on the class path at once, but not both.
The JDK version is typically used to distinguish which should be used but there is some overlap
where both could be used. ALPN is the default and this profile is enabled with a -Dforcenpn=true arugument
-->
<id>forcenpn</id>
<activation>
<property>
<name>forcenpn</name>
<value>true</value>
</property>
</activation>
<properties>
<argLine.bootcp>-Xbootclasspath/p:${jetty.npn.path}</argLine.bootcp>
</properties>
</profile>
</profiles>
@ -313,8 +433,10 @@
<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.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
-dsa -da -ea:io.netty...
@ -380,13 +502,23 @@
<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>
<artifactId>npn-boot</artifactId>
<version>${jetty.npn.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.alpn</groupId>
<artifactId>alpn-api</artifactId>
<version>1.1.0.v20141014</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.alpn</groupId>
<artifactId>alpn-boot</artifactId>
<version>${jetty.alpn.version}</version>
</dependency>
<!-- Google Protocol Buffers - completely optional -->
<dependency>
@ -672,6 +804,14 @@
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.9</version>
<dependencies>
<!-- Upgrade ASM and support Java 8 bytecode -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId>
<version>5.0.3</version>
</dependency>
</dependencies>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
@ -798,7 +938,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,*</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>