io.undertow.protocols.ssl.UndertowXnioSsl Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.undertow.protocols.ssl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
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 java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import io.undertow.UndertowOptions;
import io.undertow.connector.ByteBufferPool;
import io.undertow.server.DefaultByteBufferPool;
import org.xnio.ChannelListener;
import org.xnio.ChannelListeners;
import org.xnio.FutureResult;
import org.xnio.IoFuture;
import org.xnio.IoUtils;
import org.xnio.Option;
import org.xnio.OptionMap;
import org.xnio.Options;
import org.xnio.Sequence;
import org.xnio.SslClientAuthMode;
import org.xnio.StreamConnection;
import org.xnio.Xnio;
import org.xnio.XnioExecutor;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;
import org.xnio.channels.AcceptingChannel;
import org.xnio.channels.AssembledConnectedSslStreamChannel;
import org.xnio.channels.BoundChannel;
import org.xnio.channels.ConnectedSslStreamChannel;
import org.xnio.channels.ConnectedStreamChannel;
import org.xnio.ssl.JsseSslUtils;
import org.xnio.ssl.JsseXnioSsl;
import org.xnio.ssl.SslConnection;
import org.xnio.ssl.XnioSsl;
import static org.xnio.IoUtils.safeClose;
import static io.undertow.UndertowMessages.MESSAGES;
/**
* @author Stuart Douglas
*/
public class UndertowXnioSsl extends XnioSsl {
private static final ByteBufferPool DEFAULT_BUFFER_POOL = new DefaultByteBufferPool(true, 17 * 1024, -1, 12);
private final ByteBufferPool bufferPool;
private final Executor delegatedTaskExecutor;
private volatile SSLContext sslContext;
/**
* Construct a new instance.
*
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @throws java.security.NoSuchProviderException if the given SSL provider is not found
* @throws java.security.NoSuchAlgorithmException if the given SSL algorithm is not supported
* @throws java.security.KeyManagementException if the SSL context could not be initialized
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap) throws NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException {
this(xnio, optionMap, DEFAULT_BUFFER_POOL, JsseSslUtils.createSSLContext(optionMap));
}
/**
* Construct a new instance.
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @param sslContext the SSL context to use for this instance
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, final SSLContext sslContext) {
this(xnio, optionMap, DEFAULT_BUFFER_POOL, sslContext);
}
/**
* Construct a new instance.
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @param sslContext the SSL context to use for this instance
* @param delegatedTaskExecutor Executor instance used to run {@link SSLEngine#getDelegatedTask() delegated tasks}.
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, final SSLContext sslContext, final Executor delegatedTaskExecutor) {
this(xnio, optionMap, DEFAULT_BUFFER_POOL, sslContext, delegatedTaskExecutor);
}
/**
* Construct a new instance.
*
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @param bufferPool
* @throws java.security.NoSuchProviderException if the given SSL provider is not found
* @throws java.security.NoSuchAlgorithmException if the given SSL algorithm is not supported
* @throws java.security.KeyManagementException if the SSL context could not be initialized
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool) throws NoSuchProviderException, NoSuchAlgorithmException, KeyManagementException {
this(xnio, optionMap, bufferPool, JsseSslUtils.createSSLContext(optionMap));
}
/**
* Construct a new instance.
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @param bufferPool
* @param sslContext the SSL context to use for this instance
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool, final SSLContext sslContext) {
this(xnio, optionMap, bufferPool, sslContext, null);
}
/**
* Construct a new instance.
* @param xnio the XNIO instance to associate with
* @param optionMap the options for this provider
* @param bufferPool
* @param sslContext the SSL context to use for this instance
* @param delegatedTaskExecutor Executor instance used to run {@link SSLEngine#getDelegatedTask() delegated tasks}.
*/
public UndertowXnioSsl(final Xnio xnio, final OptionMap optionMap, ByteBufferPool bufferPool, final SSLContext sslContext, final Executor delegatedTaskExecutor) {
super(xnio, sslContext, optionMap);
this.bufferPool = bufferPool;
this.sslContext = sslContext;
this.delegatedTaskExecutor = delegatedTaskExecutor;
}
/**
* Get the JSSE SSL context for this provider instance.
*
* @return the SSL context
*/
@SuppressWarnings("unused")
public SSLContext getSslContext() {
return sslContext;
}
/**
* Get the {@link Executor} used to run delegated tasks or {@code null} if no executor is configured.
*
* @return the delegated task executor or null
*/
Executor getDelegatedTaskExecutor() {
return delegatedTaskExecutor;
}
/**
* Get the SSL engine for a given connection.
*
* @return the SSL engine
*/
public static SSLEngine getSslEngine(SslConnection connection) {
if (connection instanceof UndertowSslConnection) {
return ((UndertowSslConnection) connection).getSSLEngine();
} else {
return JsseXnioSsl.getSslEngine(connection);
}
}
public static SslConduit getSslConduit(SslConnection connection) {
return ((UndertowSslConnection) connection).getSslConduit();
}
@SuppressWarnings("deprecation")
public IoFuture connectSsl(final XnioWorker worker, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener super ConnectedSslStreamChannel> openListener, final ChannelListener super BoundChannel> bindListener, final OptionMap optionMap) {
final FutureResult futureResult = new FutureResult<>(IoUtils.directExecutor());
final IoFuture futureSslConnection = openSslConnection(worker, bindAddress, destination, new ChannelListener() {
public void handleEvent(final SslConnection sslConnection) {
final ConnectedSslStreamChannel assembledChannel = new AssembledConnectedSslStreamChannel(sslConnection, sslConnection.getSourceChannel(), sslConnection.getSinkChannel());
if (!futureResult.setResult(assembledChannel)) {
safeClose(assembledChannel);
} else {
ChannelListeners.invokeChannelListener(assembledChannel, openListener);
}
}
}, bindListener, optionMap).addNotifier(new IoFuture.HandlingNotifier>() {
public void handleCancelled(final FutureResult result) {
result.setCancelled();
}
public void handleFailed(final IOException exception, final FutureResult result) {
result.setException(exception);
}
}, futureResult);
futureResult.getIoFuture().addNotifier(new IoFuture.HandlingNotifier>() {
public void handleCancelled(final IoFuture result) {
result.cancel();
}
}, futureSslConnection);
futureResult.addCancelHandler(futureSslConnection);
return futureResult.getIoFuture();
}
public IoFuture openSslConnection(final XnioWorker worker, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener super SslConnection> openListener, final ChannelListener super BoundChannel> bindListener, final OptionMap optionMap) {
final FutureResult futureResult = new FutureResult<>(worker);
final IoFuture connection = worker.openStreamConnection(bindAddress, destination, new StreamConnectionChannelListener(optionMap, destination, futureResult, openListener), bindListener, optionMap);
return setupSslConnection(futureResult, connection);
}
@Override
public IoFuture openSslConnection(final XnioIoThread ioThread, final InetSocketAddress bindAddress, final InetSocketAddress destination, final ChannelListener super SslConnection> openListener, final ChannelListener super BoundChannel> bindListener, final OptionMap optionMap) {
final FutureResult futureResult = new FutureResult<>(ioThread);
final IoFuture connection = ioThread.openStreamConnection(bindAddress, destination, new StreamConnectionChannelListener(optionMap, destination, futureResult, openListener), bindListener, optionMap);
return setupSslConnection(futureResult, connection);
}
public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap) {
return new UndertowSslConnection(connection, createSSLEngine(sslContext, optionMap, (InetSocketAddress) connection.getPeerAddress(), true), bufferPool, delegatedTaskExecutor);
}
public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap, boolean clientMode) {
return new UndertowSslConnection(connection, createSSLEngine(sslContext, optionMap, (InetSocketAddress) connection.getPeerAddress(), clientMode), bufferPool, delegatedTaskExecutor);
}
public SslConnection wrapExistingConnection(StreamConnection connection, OptionMap optionMap, URI destinationURI) {
SSLEngine sslEngine = createSSLEngine(sslContext, optionMap, getPeerAddress(destinationURI), true);
SSLParameters sslParameters = sslEngine.getSSLParameters();
if (sslParameters.getServerNames() == null || sslParameters.getServerNames().isEmpty()) {
setSNIHostName(connection.getPeerAddress(InetSocketAddress.class), optionMap, sslParameters);
sslEngine.setSSLParameters(sslParameters);
}
return new UndertowSslConnection(connection, sslEngine, bufferPool, delegatedTaskExecutor);
}
private InetSocketAddress getPeerAddress(URI destinationURI) {
String hostname = destinationURI.getHost();
int port = destinationURI.getPort();
if (port == -1) {
port = destinationURI.getScheme().equals("wss") ? 443 : 80;
}
return new InetSocketAddress(hostname, port);
}
/**
* Create a new SSL engine, configured from an option map.
*
* @param sslContext the SSL context
* @param optionMap the SSL options
* @param peerAddress the peer address of the connection
* @param client whether this SSL connection is run in client mode
* @return the configured SSL engine
*/
private static SSLEngine createSSLEngine(SSLContext sslContext, OptionMap optionMap, InetSocketAddress peerAddress, boolean client) {
final SSLEngine engine = sslContext.createSSLEngine(
optionMap.get(Options.SSL_PEER_HOST_NAME, peerAddress.getHostString()),
optionMap.get(Options.SSL_PEER_PORT, peerAddress.getPort())
);
engine.setUseClientMode(client);
engine.setEnableSessionCreation(optionMap.get(Options.SSL_ENABLE_SESSION_CREATION, true));
final Sequence cipherSuites = optionMap.get(Options.SSL_ENABLED_CIPHER_SUITES);
if (cipherSuites != null) {
final Set supported = new HashSet(Arrays.asList(engine.getSupportedCipherSuites()));
final List finalList = new ArrayList();
for (String name : cipherSuites) {
if (supported.contains(name)) {
finalList.add(name);
}
}
engine.setEnabledCipherSuites(finalList.toArray(new String[finalList.size()]));
}
final Sequence protocols = optionMap.get(Options.SSL_ENABLED_PROTOCOLS);
if (protocols != null) {
final Set supported = new HashSet(Arrays.asList(engine.getSupportedProtocols()));
final List finalList = new ArrayList();
for (String name : protocols) {
if (supported.contains(name)) {
finalList.add(name);
}
}
engine.setEnabledProtocols(finalList.toArray(new String[finalList.size()]));
}
if (!client) {
final SslClientAuthMode clientAuthMode = optionMap.get(Options.SSL_CLIENT_AUTH_MODE);
if (clientAuthMode != null) {
switch (clientAuthMode) {
case NOT_REQUESTED:
engine.setNeedClientAuth(false);
engine.setWantClientAuth(false);
break;
case REQUESTED:
engine.setWantClientAuth(true);
break;
case REQUIRED:
engine.setNeedClientAuth(true);
break;
default:
throw new IllegalStateException();
}
}
}
boolean useCipherSuitesOrder = optionMap.get(UndertowOptions.SSL_USER_CIPHER_SUITES_ORDER, false);
if (useCipherSuitesOrder) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setUseCipherSuitesOrder(true);
engine.setSSLParameters(sslParameters);
}
final String endpointIdentificationAlgorithm = optionMap.get(UndertowOptions.ENDPOINT_IDENTIFICATION_ALGORITHM);
if (endpointIdentificationAlgorithm != null) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm);
engine.setSSLParameters(sslParameters);
}
return engine;
}
private IoFuture setupSslConnection(FutureResult futureResult, IoFuture connection) {
connection.addNotifier(new IoFuture.HandlingNotifier>() {
public void handleCancelled(final FutureResult attachment) {
attachment.setCancelled();
}
public void handleFailed(final IOException exception, final FutureResult attachment) {
attachment.setException(exception);
}
}, futureResult);
futureResult.addCancelHandler(connection);
return futureResult.getIoFuture();
}
@SuppressWarnings("deprecation")
public AcceptingChannel createSslTcpServer(final XnioWorker worker, final InetSocketAddress bindAddress, final ChannelListener super AcceptingChannel> acceptListener, final OptionMap optionMap) throws IOException {
final AcceptingChannel server = createSslConnectionServer(worker, bindAddress, null, optionMap);
final AcceptingChannel acceptingChannel = new AcceptingChannel() {
public ConnectedSslStreamChannel accept() throws IOException {
final SslConnection connection = server.accept();
return connection == null ? null : new AssembledConnectedSslStreamChannel(connection, connection.getSourceChannel(), connection.getSinkChannel());
}
public ChannelListener.Setter extends AcceptingChannel> getAcceptSetter() {
return ChannelListeners.getDelegatingSetter(server.getAcceptSetter(), this);
}
public ChannelListener.Setter extends AcceptingChannel> getCloseSetter() {
return ChannelListeners.getDelegatingSetter(server.getCloseSetter(), this);
}
public SocketAddress getLocalAddress() {
return server.getLocalAddress();
}
public A getLocalAddress(final Class type) {
return server.getLocalAddress(type);
}
public void suspendAccepts() {
server.suspendAccepts();
}
public void resumeAccepts() {
server.resumeAccepts();
}
public boolean isAcceptResumed() {
return server.isAcceptResumed();
}
public void wakeupAccepts() {
server.wakeupAccepts();
}
public void awaitAcceptable() throws IOException {
server.awaitAcceptable();
}
public void awaitAcceptable(final long time, final TimeUnit timeUnit) throws IOException {
server.awaitAcceptable(time, timeUnit);
}
public XnioWorker getWorker() {
return server.getWorker();
}
@Deprecated
public XnioExecutor getAcceptThread() {
return server.getAcceptThread();
}
public XnioIoThread getIoThread() {
return server.getIoThread();
}
public void close() throws IOException {
server.close();
}
public boolean isOpen() {
return server.isOpen();
}
public boolean supportsOption(final Option> option) {
return server.supportsOption(option);
}
public T getOption(final Option option) throws IOException {
return server.getOption(option);
}
public T setOption(final Option option, final T value) throws IllegalArgumentException, IOException {
return server.setOption(option, value);
}
};
acceptingChannel.getAcceptSetter().set(acceptListener);
return acceptingChannel;
}
/**
* Updates the SSLContext that is in use. All new connections will use this new context, however established connections
* will not be affected.
*
* @param context The new context
*/
public void updateSSLContext(SSLContext context) {
this.sslContext = context;
}
public AcceptingChannel createSslConnectionServer(final XnioWorker worker, final InetSocketAddress bindAddress, final ChannelListener super AcceptingChannel> acceptListener, final OptionMap optionMap) throws IOException {
final UndertowAcceptingSslChannel server = new UndertowAcceptingSslChannel(this, worker.createStreamConnectionServer(bindAddress, null, optionMap), optionMap, bufferPool, false);
if (acceptListener != null) server.getAcceptSetter().set(acceptListener);
return server;
}
private void setSNIHostName(InetSocketAddress destination, OptionMap optionMap, SSLParameters params) {
String hostnameValue = optionMap.get(UndertowOptions.SSL_SNI_HOSTNAME);
if (hostnameValue == null && destination != null && !destination.getHostString().equals(destination.getAddress().getHostAddress())) {
// just add the SNIHostName if it's a hostname, SNI cannot be an IP address
hostnameValue = destination.getHostString();
}
if (hostnameValue != null) {
SNIHostName sniHostName = null;
try {
sniHostName = new SNIHostName(hostnameValue);
} catch (IllegalArgumentException e) {
throw MESSAGES.invalidSniHostname(hostnameValue, e);
}
params.setServerNames(Collections.singletonList(sniHostName));
}
}
private class StreamConnectionChannelListener implements ChannelListener {
private final OptionMap optionMap;
private final InetSocketAddress destination;
private final FutureResult futureResult;
private final ChannelListener super SslConnection> openListener;
StreamConnectionChannelListener(OptionMap optionMap, InetSocketAddress destination, FutureResult futureResult, ChannelListener super SslConnection> openListener) {
this.optionMap = optionMap;
this.destination = destination;
this.futureResult = futureResult;
this.openListener = openListener;
}
public void handleEvent(final StreamConnection connection) {
try {
SSLEngine sslEngine = JsseSslUtils.createSSLEngine(sslContext, optionMap, destination);
SSLParameters params = sslEngine.getSSLParameters();
setSNIHostName(destination, optionMap, params);
final String endpointIdentificationAlgorithm = optionMap.get(UndertowOptions.ENDPOINT_IDENTIFICATION_ALGORITHM);
if (endpointIdentificationAlgorithm != null) {
params.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm);
}
sslEngine.setSSLParameters(params);
final SslConnection wrappedConnection = new UndertowSslConnection(connection, sslEngine, bufferPool, delegatedTaskExecutor);
if (!futureResult.setResult(wrappedConnection)) {
IoUtils.safeClose(connection);
} else {
ChannelListeners.invokeChannelListener(wrappedConnection, openListener);
}
} catch (Throwable e) {
futureResult.setException(new IOException(e));
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy