/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
Java | 2364 lines | 1538 code | 216 blank | 610 comment | 303 complexity | a88f759a49bc67481fa6e49390a37f4e MD5 | raw file
Possible License(s): Apache-2.0
Large files files are truncated, but you can click here to view the full file
- /*
- * Copyright 2012 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:
- *
- * https://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 io.netty.buffer.ByteBuf;
- import io.netty.buffer.ByteBufAllocator;
- import io.netty.buffer.ByteBufUtil;
- import io.netty.buffer.CompositeByteBuf;
- import io.netty.buffer.Unpooled;
- import io.netty.channel.AbstractCoalescingBufferQueue;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelConfig;
- import io.netty.channel.ChannelException;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.ChannelInboundHandler;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.ChannelOutboundBuffer;
- import io.netty.channel.ChannelOutboundHandler;
- import io.netty.channel.ChannelPipeline;
- import io.netty.channel.ChannelPromise;
- import io.netty.handler.codec.ByteToMessageDecoder;
- import io.netty.handler.codec.DecoderException;
- import io.netty.handler.codec.UnsupportedMessageTypeException;
- import io.netty.util.ReferenceCountUtil;
- import io.netty.util.concurrent.DefaultPromise;
- import io.netty.util.concurrent.EventExecutor;
- import io.netty.util.concurrent.Future;
- import io.netty.util.concurrent.FutureListener;
- import io.netty.util.concurrent.ImmediateExecutor;
- import io.netty.util.concurrent.Promise;
- import io.netty.util.concurrent.PromiseNotifier;
- import io.netty.util.internal.ObjectUtil;
- import io.netty.util.internal.PlatformDependent;
- import io.netty.util.internal.UnstableApi;
- import io.netty.util.internal.logging.InternalLogger;
- import io.netty.util.internal.logging.InternalLoggerFactory;
- import java.io.IOException;
- import java.net.SocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.ClosedChannelException;
- import java.nio.channels.DatagramChannel;
- import java.nio.channels.SocketChannel;
- import java.util.List;
- import java.util.concurrent.Executor;
- import java.util.concurrent.RejectedExecutionException;
- import java.util.concurrent.ScheduledFuture;
- import java.util.concurrent.TimeUnit;
- import java.util.regex.Pattern;
- import javax.net.ssl.SSLEngine;
- import javax.net.ssl.SSLEngineResult;
- import javax.net.ssl.SSLEngineResult.HandshakeStatus;
- import javax.net.ssl.SSLEngineResult.Status;
- import javax.net.ssl.SSLException;
- import javax.net.ssl.SSLHandshakeException;
- import javax.net.ssl.SSLSession;
- import static io.netty.buffer.ByteBufUtil.ensureWritableSuccess;
- import static io.netty.handler.ssl.SslUtils.getEncryptedPacketLength;
- import static io.netty.util.internal.ObjectUtil.checkNotNull;
- import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
- /**
- * Adds <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">SSL
- * · TLS</a> and StartTLS support to a {@link Channel}. Please refer
- * to the <strong>"SecureChat"</strong> example in the distribution or the web
- * site for the detailed usage.
- *
- * <h3>Beginning the handshake</h3>
- * <p>
- * Beside using the handshake {@link ChannelFuture} to get notified about the completion of the handshake it's
- * also possible to detect it by implement the
- * {@link ChannelInboundHandler#userEventTriggered(ChannelHandlerContext, Object)}
- * method and check for a {@link SslHandshakeCompletionEvent}.
- *
- * <h3>Handshake</h3>
- * <p>
- * The handshake will be automatically issued for you once the {@link Channel} is active and
- * {@link SSLEngine#getUseClientMode()} returns {@code true}.
- * So no need to bother with it by your self.
- *
- * <h3>Closing the session</h3>
- * <p>
- * To close the SSL session, the {@link #closeOutbound()} method should be
- * called to send the {@code close_notify} message to the remote peer. One
- * exception is when you close the {@link Channel} - {@link SslHandler}
- * intercepts the close request and send the {@code close_notify} message
- * before the channel closure automatically. Once the SSL session is closed,
- * it is not reusable, and consequently you should create a new
- * {@link SslHandler} with a new {@link SSLEngine} as explained in the
- * following section.
- *
- * <h3>Restarting the session</h3>
- * <p>
- * To restart the SSL session, you must remove the existing closed
- * {@link SslHandler} from the {@link ChannelPipeline}, insert a new
- * {@link SslHandler} with a new {@link SSLEngine} into the pipeline,
- * and start the handshake process as described in the first section.
- *
- * <h3>Implementing StartTLS</h3>
- * <p>
- * <a href="https://en.wikipedia.org/wiki/STARTTLS">StartTLS</a> is the
- * communication pattern that secures the wire in the middle of the plaintext
- * connection. Please note that it is different from SSL · TLS, that
- * secures the wire from the beginning of the connection. Typically, StartTLS
- * is composed of three steps:
- * <ol>
- * <li>Client sends a StartTLS request to server.</li>
- * <li>Server sends a StartTLS response to client.</li>
- * <li>Client begins SSL handshake.</li>
- * </ol>
- * If you implement a server, you need to:
- * <ol>
- * <li>create a new {@link SslHandler} instance with {@code startTls} flag set
- * to {@code true},</li>
- * <li>insert the {@link SslHandler} to the {@link ChannelPipeline}, and</li>
- * <li>write a StartTLS response.</li>
- * </ol>
- * Please note that you must insert {@link SslHandler} <em>before</em> sending
- * the StartTLS response. Otherwise the client can send begin SSL handshake
- * before {@link SslHandler} is inserted to the {@link ChannelPipeline}, causing
- * data corruption.
- * <p>
- * The client-side implementation is much simpler.
- * <ol>
- * <li>Write a StartTLS request,</li>
- * <li>wait for the StartTLS response,</li>
- * <li>create a new {@link SslHandler} instance with {@code startTls} flag set
- * to {@code false},</li>
- * <li>insert the {@link SslHandler} to the {@link ChannelPipeline}, and</li>
- * <li>Initiate SSL handshake.</li>
- * </ol>
- *
- * <h3>Known issues</h3>
- * <p>
- * Because of a known issue with the current implementation of the SslEngine that comes
- * with Java it may be possible that you see blocked IO-Threads while a full GC is done.
- * <p>
- * So if you are affected you can workaround this problem by adjust the cache settings
- * like shown below:
- *
- * <pre>
- * SslContext context = ...;
- * context.getServerSessionContext().setSessionCacheSize(someSaneSize);
- * context.getServerSessionContext().setSessionTime(someSameTimeout);
- * </pre>
- * <p>
- * What values to use here depends on the nature of your application and should be set
- * based on monitoring and debugging of it.
- * For more details see
- * <a href="https://github.com/netty/netty/issues/832">#832</a> in our issue tracker.
- */
- public class SslHandler extends ByteToMessageDecoder implements ChannelOutboundHandler {
- private static final InternalLogger logger =
- InternalLoggerFactory.getInstance(SslHandler.class);
- private static final Pattern IGNORABLE_CLASS_IN_STACK = Pattern.compile(
- "^.*(?:Socket|Datagram|Sctp|Udt)Channel.*$");
- private static final Pattern IGNORABLE_ERROR_MESSAGE = Pattern.compile(
- "^.*(?:connection.*(?:reset|closed|abort|broken)|broken.*pipe).*$", Pattern.CASE_INSENSITIVE);
- private static final int STATE_SENT_FIRST_MESSAGE = 1;
- private static final int STATE_FLUSHED_BEFORE_HANDSHAKE = 1 << 1;
- private static final int STATE_READ_DURING_HANDSHAKE = 1 << 2;
- private static final int STATE_HANDSHAKE_STARTED = 1 << 3;
- /**
- * Set by wrap*() methods when something is produced.
- * {@link #channelReadComplete(ChannelHandlerContext)} will check this flag, clear it, and call ctx.flush().
- */
- private static final int STATE_NEEDS_FLUSH = 1 << 4;
- private static final int STATE_OUTBOUND_CLOSED = 1 << 5;
- private static final int STATE_CLOSE_NOTIFY = 1 << 6;
- private static final int STATE_PROCESS_TASK = 1 << 7;
- /**
- * This flag is used to determine if we need to call {@link ChannelHandlerContext#read()} to consume more data
- * when {@link ChannelConfig#isAutoRead()} is {@code false}.
- */
- private static final int STATE_FIRE_CHANNEL_READ = 1 << 8;
- private static final int STATE_UNWRAP_REENTRY = 1 << 9;
- /**
- * <a href="https://tools.ietf.org/html/rfc5246#section-6.2">2^14</a> which is the maximum sized plaintext chunk
- * allowed by the TLS RFC.
- */
- private static final int MAX_PLAINTEXT_LENGTH = 16 * 1024;
- private enum SslEngineType {
- TCNATIVE(true, COMPOSITE_CUMULATOR) {
- @Override
- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException {
- int nioBufferCount = in.nioBufferCount();
- int writerIndex = out.writerIndex();
- final SSLEngineResult result;
- if (nioBufferCount > 1) {
- /*
- * If {@link OpenSslEngine} is in use,
- * we can use a special {@link OpenSslEngine#unwrap(ByteBuffer[], ByteBuffer[])} method
- * that accepts multiple {@link ByteBuffer}s without additional memory copies.
- */
- ReferenceCountedOpenSslEngine opensslEngine = (ReferenceCountedOpenSslEngine) handler.engine;
- try {
- handler.singleBuffer[0] = toByteBuffer(out, writerIndex, out.writableBytes());
- result = opensslEngine.unwrap(in.nioBuffers(in.readerIndex(), len), handler.singleBuffer);
- } finally {
- handler.singleBuffer[0] = null;
- }
- } else {
- result = handler.engine.unwrap(toByteBuffer(in, in.readerIndex(), len),
- toByteBuffer(out, writerIndex, out.writableBytes()));
- }
- out.writerIndex(writerIndex + result.bytesProduced());
- return result;
- }
- @Override
- ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator,
- int pendingBytes, int numComponents) {
- return allocator.directBuffer(((ReferenceCountedOpenSslEngine) handler.engine)
- .calculateMaxLengthForWrap(pendingBytes, numComponents));
- }
- @Override
- int calculatePendingData(SslHandler handler, int guess) {
- int sslPending = ((ReferenceCountedOpenSslEngine) handler.engine).sslPending();
- return sslPending > 0 ? sslPending : guess;
- }
- @Override
- boolean jdkCompatibilityMode(SSLEngine engine) {
- return ((ReferenceCountedOpenSslEngine) engine).jdkCompatibilityMode;
- }
- },
- CONSCRYPT(true, COMPOSITE_CUMULATOR) {
- @Override
- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException {
- int nioBufferCount = in.nioBufferCount();
- int writerIndex = out.writerIndex();
- final SSLEngineResult result;
- if (nioBufferCount > 1) {
- /*
- * Use a special unwrap method without additional memory copies.
- */
- try {
- handler.singleBuffer[0] = toByteBuffer(out, writerIndex, out.writableBytes());
- result = ((ConscryptAlpnSslEngine) handler.engine).unwrap(
- in.nioBuffers(in.readerIndex(), len),
- handler.singleBuffer);
- } finally {
- handler.singleBuffer[0] = null;
- }
- } else {
- result = handler.engine.unwrap(toByteBuffer(in, in.readerIndex(), len),
- toByteBuffer(out, writerIndex, out.writableBytes()));
- }
- out.writerIndex(writerIndex + result.bytesProduced());
- return result;
- }
- @Override
- ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator,
- int pendingBytes, int numComponents) {
- return allocator.directBuffer(
- ((ConscryptAlpnSslEngine) handler.engine).calculateOutNetBufSize(pendingBytes, numComponents));
- }
- @Override
- int calculatePendingData(SslHandler handler, int guess) {
- return guess;
- }
- @Override
- boolean jdkCompatibilityMode(SSLEngine engine) {
- return true;
- }
- },
- JDK(false, MERGE_CUMULATOR) {
- @Override
- SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException {
- int writerIndex = out.writerIndex();
- ByteBuffer inNioBuffer = toByteBuffer(in, in.readerIndex(), len);
- int position = inNioBuffer.position();
- final SSLEngineResult result = handler.engine.unwrap(inNioBuffer,
- toByteBuffer(out, writerIndex, out.writableBytes()));
- out.writerIndex(writerIndex + result.bytesProduced());
- // This is a workaround for a bug in Android 5.0. Android 5.0 does not correctly update the
- // SSLEngineResult.bytesConsumed() in some cases and just return 0.
- //
- // See:
- // - https://android-review.googlesource.com/c/platform/external/conscrypt/+/122080
- // - https://github.com/netty/netty/issues/7758
- if (result.bytesConsumed() == 0) {
- int consumed = inNioBuffer.position() - position;
- if (consumed != result.bytesConsumed()) {
- // Create a new SSLEngineResult with the correct bytesConsumed().
- return new SSLEngineResult(
- result.getStatus(), result.getHandshakeStatus(), consumed, result.bytesProduced());
- }
- }
- return result;
- }
- @Override
- ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator,
- int pendingBytes, int numComponents) {
- // As for the JDK SSLEngine we always need to allocate buffers of the size required by the SSLEngine
- // (normally ~16KB). This is required even if the amount of data to encrypt is very small. Use heap
- // buffers to reduce the native memory usage.
- //
- // Beside this the JDK SSLEngine also (as of today) will do an extra heap to direct buffer copy
- // if a direct buffer is used as its internals operate on byte[].
- return allocator.heapBuffer(handler.engine.getSession().getPacketBufferSize());
- }
- @Override
- int calculatePendingData(SslHandler handler, int guess) {
- return guess;
- }
- @Override
- boolean jdkCompatibilityMode(SSLEngine engine) {
- return true;
- }
- };
- static SslEngineType forEngine(SSLEngine engine) {
- return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE :
- engine instanceof ConscryptAlpnSslEngine ? CONSCRYPT : JDK;
- }
- SslEngineType(boolean wantsDirectBuffer, Cumulator cumulator) {
- this.wantsDirectBuffer = wantsDirectBuffer;
- this.cumulator = cumulator;
- }
- abstract SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int len, ByteBuf out) throws SSLException;
- abstract int calculatePendingData(SslHandler handler, int guess);
- abstract boolean jdkCompatibilityMode(SSLEngine engine);
- abstract ByteBuf allocateWrapBuffer(SslHandler handler, ByteBufAllocator allocator,
- int pendingBytes, int numComponents);
- // BEGIN Platform-dependent flags
- /**
- * {@code true} if and only if {@link SSLEngine} expects a direct buffer and so if a heap buffer
- * is given will make an extra memory copy.
- */
- final boolean wantsDirectBuffer;
- // END Platform-dependent flags
- /**
- * When using JDK {@link SSLEngine}, we use {@link #MERGE_CUMULATOR} because it works only with
- * one {@link ByteBuffer}.
- *
- * When using {@link OpenSslEngine}, we can use {@link #COMPOSITE_CUMULATOR} because it has
- * {@link OpenSslEngine#unwrap(ByteBuffer[], ByteBuffer[])} which works with multiple {@link ByteBuffer}s
- * and which does not need to do extra memory copies.
- */
- final Cumulator cumulator;
- }
- private volatile ChannelHandlerContext ctx;
- private final SSLEngine engine;
- private final SslEngineType engineType;
- private final Executor delegatedTaskExecutor;
- private final boolean jdkCompatibilityMode;
- /**
- * Used if {@link SSLEngine#wrap(ByteBuffer[], ByteBuffer)} and {@link SSLEngine#unwrap(ByteBuffer, ByteBuffer[])}
- * should be called with a {@link ByteBuf} that is only backed by one {@link ByteBuffer} to reduce the object
- * creation.
- */
- private final ByteBuffer[] singleBuffer = new ByteBuffer[1];
- private final boolean startTls;
- private final SslTasksRunner sslTaskRunnerForUnwrap = new SslTasksRunner(true);
- private final SslTasksRunner sslTaskRunner = new SslTasksRunner(false);
- private SslHandlerCoalescingBufferQueue pendingUnencryptedWrites;
- private Promise<Channel> handshakePromise = new LazyChannelPromise();
- private final LazyChannelPromise sslClosePromise = new LazyChannelPromise();
- private int packetLength;
- private short state;
- private volatile long handshakeTimeoutMillis = 10000;
- private volatile long closeNotifyFlushTimeoutMillis = 3000;
- private volatile long closeNotifyReadTimeoutMillis;
- volatile int wrapDataSize = MAX_PLAINTEXT_LENGTH;
- /**
- * Creates a new instance which runs all delegated tasks directly on the {@link EventExecutor}.
- *
- * @param engine the {@link SSLEngine} this handler will use
- */
- public SslHandler(SSLEngine engine) {
- this(engine, false);
- }
- /**
- * Creates a new instance which runs all delegated tasks directly on the {@link EventExecutor}.
- *
- * @param engine the {@link SSLEngine} this handler will use
- * @param startTls {@code true} if the first write request shouldn't be
- * encrypted by the {@link SSLEngine}
- */
- public SslHandler(SSLEngine engine, boolean startTls) {
- this(engine, startTls, ImmediateExecutor.INSTANCE);
- }
- /**
- * Creates a new instance.
- *
- * @param engine the {@link SSLEngine} this handler will use
- * @param delegatedTaskExecutor the {@link Executor} that will be used to execute tasks that are returned by
- * {@link SSLEngine#getDelegatedTask()}.
- */
- public SslHandler(SSLEngine engine, Executor delegatedTaskExecutor) {
- this(engine, false, delegatedTaskExecutor);
- }
- /**
- * Creates a new instance.
- *
- * @param engine the {@link SSLEngine} this handler will use
- * @param startTls {@code true} if the first write request shouldn't be
- * encrypted by the {@link SSLEngine}
- * @param delegatedTaskExecutor the {@link Executor} that will be used to execute tasks that are returned by
- * {@link SSLEngine#getDelegatedTask()}.
- */
- public SslHandler(SSLEngine engine, boolean startTls, Executor delegatedTaskExecutor) {
- this.engine = ObjectUtil.checkNotNull(engine, "engine");
- this.delegatedTaskExecutor = ObjectUtil.checkNotNull(delegatedTaskExecutor, "delegatedTaskExecutor");
- engineType = SslEngineType.forEngine(engine);
- this.startTls = startTls;
- this.jdkCompatibilityMode = engineType.jdkCompatibilityMode(engine);
- setCumulator(engineType.cumulator);
- }
- public long getHandshakeTimeoutMillis() {
- return handshakeTimeoutMillis;
- }
- public void setHandshakeTimeout(long handshakeTimeout, TimeUnit unit) {
- checkNotNull(unit, "unit");
- setHandshakeTimeoutMillis(unit.toMillis(handshakeTimeout));
- }
- public void setHandshakeTimeoutMillis(long handshakeTimeoutMillis) {
- this.handshakeTimeoutMillis = checkPositiveOrZero(handshakeTimeoutMillis, "handshakeTimeoutMillis");
- }
- /**
- * Sets the number of bytes to pass to each {@link SSLEngine#wrap(ByteBuffer[], int, int, ByteBuffer)} call.
- * <p>
- * This value will partition data which is passed to write
- * {@link #write(ChannelHandlerContext, Object, ChannelPromise)}. The partitioning will work as follows:
- * <ul>
- * <li>If {@code wrapDataSize <= 0} then we will write each data chunk as is.</li>
- * <li>If {@code wrapDataSize > data size} then we will attempt to aggregate multiple data chunks together.</li>
- * <li>If {@code wrapDataSize > data size} Else if {@code wrapDataSize <= data size} then we will divide the data
- * into chunks of {@code wrapDataSize} when writing.</li>
- * </ul>
- * <p>
- * If the {@link SSLEngine} doesn't support a gather wrap operation (e.g. {@link SslProvider#OPENSSL}) then
- * aggregating data before wrapping can help reduce the ratio between TLS overhead vs data payload which will lead
- * to better goodput. Writing fixed chunks of data can also help target the underlying transport's (e.g. TCP)
- * frame size. Under lossy/congested network conditions this may help the peer get full TLS packets earlier and
- * be able to do work sooner, as opposed to waiting for the all the pieces of the TLS packet to arrive.
- * @param wrapDataSize the number of bytes which will be passed to each
- * {@link SSLEngine#wrap(ByteBuffer[], int, int, ByteBuffer)} call.
- */
- @UnstableApi
- public final void setWrapDataSize(int wrapDataSize) {
- this.wrapDataSize = wrapDataSize;
- }
- /**
- * @deprecated use {@link #getCloseNotifyFlushTimeoutMillis()}
- */
- @Deprecated
- public long getCloseNotifyTimeoutMillis() {
- return getCloseNotifyFlushTimeoutMillis();
- }
- /**
- * @deprecated use {@link #setCloseNotifyFlushTimeout(long, TimeUnit)}
- */
- @Deprecated
- public void setCloseNotifyTimeout(long closeNotifyTimeout, TimeUnit unit) {
- setCloseNotifyFlushTimeout(closeNotifyTimeout, unit);
- }
- /**
- * @deprecated use {@link #setCloseNotifyFlushTimeoutMillis(long)}
- */
- @Deprecated
- public void setCloseNotifyTimeoutMillis(long closeNotifyFlushTimeoutMillis) {
- setCloseNotifyFlushTimeoutMillis(closeNotifyFlushTimeoutMillis);
- }
- /**
- * Gets the timeout for flushing the close_notify that was triggered by closing the
- * {@link Channel}. If the close_notify was not flushed in the given timeout the {@link Channel} will be closed
- * forcibly.
- */
- public final long getCloseNotifyFlushTimeoutMillis() {
- return closeNotifyFlushTimeoutMillis;
- }
- /**
- * Sets the timeout for flushing the close_notify that was triggered by closing the
- * {@link Channel}. If the close_notify was not flushed in the given timeout the {@link Channel} will be closed
- * forcibly.
- */
- public final void setCloseNotifyFlushTimeout(long closeNotifyFlushTimeout, TimeUnit unit) {
- setCloseNotifyFlushTimeoutMillis(unit.toMillis(closeNotifyFlushTimeout));
- }
- /**
- * See {@link #setCloseNotifyFlushTimeout(long, TimeUnit)}.
- */
- public final void setCloseNotifyFlushTimeoutMillis(long closeNotifyFlushTimeoutMillis) {
- this.closeNotifyFlushTimeoutMillis = checkPositiveOrZero(closeNotifyFlushTimeoutMillis,
- "closeNotifyFlushTimeoutMillis");
- }
- /**
- * Gets the timeout (in ms) for receiving the response for the close_notify that was triggered by closing the
- * {@link Channel}. This timeout starts after the close_notify message was successfully written to the
- * remote peer. Use {@code 0} to directly close the {@link Channel} and not wait for the response.
- */
- public final long getCloseNotifyReadTimeoutMillis() {
- return closeNotifyReadTimeoutMillis;
- }
- /**
- * Sets the timeout for receiving the response for the close_notify that was triggered by closing the
- * {@link Channel}. This timeout starts after the close_notify message was successfully written to the
- * remote peer. Use {@code 0} to directly close the {@link Channel} and not wait for the response.
- */
- public final void setCloseNotifyReadTimeout(long closeNotifyReadTimeout, TimeUnit unit) {
- setCloseNotifyReadTimeoutMillis(unit.toMillis(closeNotifyReadTimeout));
- }
- /**
- * See {@link #setCloseNotifyReadTimeout(long, TimeUnit)}.
- */
- public final void setCloseNotifyReadTimeoutMillis(long closeNotifyReadTimeoutMillis) {
- this.closeNotifyReadTimeoutMillis = checkPositiveOrZero(closeNotifyReadTimeoutMillis,
- "closeNotifyReadTimeoutMillis");
- }
- /**
- * Returns the {@link SSLEngine} which is used by this handler.
- */
- public SSLEngine engine() {
- return engine;
- }
- /**
- * Returns the name of the current application-level protocol.
- *
- * @return the protocol name or {@code null} if application-level protocol has not been negotiated
- */
- public String applicationProtocol() {
- SSLEngine engine = engine();
- if (!(engine instanceof ApplicationProtocolAccessor)) {
- return null;
- }
- return ((ApplicationProtocolAccessor) engine).getNegotiatedApplicationProtocol();
- }
- /**
- * Returns a {@link Future} that will get notified once the current TLS handshake completes.
- *
- * @return the {@link Future} for the initial TLS handshake if {@link #renegotiate()} was not invoked.
- * The {@link Future} for the most recent {@linkplain #renegotiate() TLS renegotiation} otherwise.
- */
- public Future<Channel> handshakeFuture() {
- return handshakePromise;
- }
- /**
- * Use {@link #closeOutbound()}
- */
- @Deprecated
- public ChannelFuture close() {
- return closeOutbound();
- }
- /**
- * Use {@link #closeOutbound(ChannelPromise)}
- */
- @Deprecated
- public ChannelFuture close(ChannelPromise promise) {
- return closeOutbound(promise);
- }
- /**
- * Sends an SSL {@code close_notify} message to the specified channel and
- * destroys the underlying {@link SSLEngine}. This will <strong>not</strong> close the underlying
- * {@link Channel}. If you want to also close the {@link Channel} use {@link Channel#close()} or
- * {@link ChannelHandlerContext#close()}
- */
- public ChannelFuture closeOutbound() {
- return closeOutbound(ctx.newPromise());
- }
- /**
- * Sends an SSL {@code close_notify} message to the specified channel and
- * destroys the underlying {@link SSLEngine}. This will <strong>not</strong> close the underlying
- * {@link Channel}. If you want to also close the {@link Channel} use {@link Channel#close()} or
- * {@link ChannelHandlerContext#close()}
- */
- public ChannelFuture closeOutbound(final ChannelPromise promise) {
- final ChannelHandlerContext ctx = this.ctx;
- if (ctx.executor().inEventLoop()) {
- closeOutbound0(promise);
- } else {
- ctx.executor().execute(new Runnable() {
- @Override
- public void run() {
- closeOutbound0(promise);
- }
- });
- }
- return promise;
- }
- private void closeOutbound0(ChannelPromise promise) {
- setState(STATE_OUTBOUND_CLOSED);
- engine.closeOutbound();
- try {
- flush(ctx, promise);
- } catch (Exception e) {
- if (!promise.tryFailure(e)) {
- logger.warn("{} flush() raised a masked exception.", ctx.channel(), e);
- }
- }
- }
- /**
- * Return the {@link Future} that will get notified if the inbound of the {@link SSLEngine} is closed.
- *
- * This method will return the same {@link Future} all the time.
- *
- * @see SSLEngine
- */
- public Future<Channel> sslCloseFuture() {
- return sslClosePromise;
- }
- @Override
- public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
- try {
- if (!pendingUnencryptedWrites.isEmpty()) {
- // Check if queue is not empty first because create a new ChannelException is expensive
- pendingUnencryptedWrites.releaseAndFailAll(ctx,
- new ChannelException("Pending write on removal of SslHandler"));
- }
- pendingUnencryptedWrites = null;
- SSLHandshakeException cause = null;
- // If the handshake is not done yet we should fail the handshake promise and notify the rest of the
- // pipeline.
- if (!handshakePromise.isDone()) {
- cause = new SSLHandshakeException("SslHandler removed before handshake completed");
- if (handshakePromise.tryFailure(cause)) {
- ctx.fireUserEventTriggered(new SslHandshakeCompletionEvent(cause));
- }
- }
- if (!sslClosePromise.isDone()) {
- if (cause == null) {
- cause = new SSLHandshakeException("SslHandler removed before handshake completed");
- }
- notifyClosePromise(cause);
- }
- } finally {
- ReferenceCountUtil.release(engine);
- }
- }
- @Override
- public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
- ctx.bind(localAddress, promise);
- }
- @Override
- public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
- ChannelPromise promise) throws Exception {
- ctx.connect(remoteAddress, localAddress, promise);
- }
- @Override
- public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
- ctx.deregister(promise);
- }
- @Override
- public void disconnect(final ChannelHandlerContext ctx,
- final ChannelPromise promise) throws Exception {
- closeOutboundAndChannel(ctx, promise, true);
- }
- @Override
- public void close(final ChannelHandlerContext ctx,
- final ChannelPromise promise) throws Exception {
- closeOutboundAndChannel(ctx, promise, false);
- }
- @Override
- public void read(ChannelHandlerContext ctx) throws Exception {
- if (!handshakePromise.isDone()) {
- setState(STATE_READ_DURING_HANDSHAKE);
- }
- ctx.read();
- }
- private static IllegalStateException newPendingWritesNullException() {
- return new IllegalStateException("pendingUnencryptedWrites is null, handlerRemoved0 called?");
- }
- @Override
- public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
- if (!(msg instanceof ByteBuf)) {
- UnsupportedMessageTypeException exception = new UnsupportedMessageTypeException(msg, ByteBuf.class);
- ReferenceCountUtil.safeRelease(msg);
- promise.setFailure(exception);
- } else if (pendingUnencryptedWrites == null) {
- ReferenceCountUtil.safeRelease(msg);
- promise.setFailure(newPendingWritesNullException());
- } else {
- pendingUnencryptedWrites.add((ByteBuf) msg, promise);
- }
- }
- @Override
- public void flush(ChannelHandlerContext ctx) throws Exception {
- // Do not encrypt the first write request if this handler is
- // created with startTLS flag turned on.
- if (startTls && !isStateSet(STATE_SENT_FIRST_MESSAGE)) {
- setState(STATE_SENT_FIRST_MESSAGE);
- pendingUnencryptedWrites.writeAndRemoveAll(ctx);
- forceFlush(ctx);
- // Explicit start handshake processing once we send the first message. This will also ensure
- // we will schedule the timeout if needed.
- startHandshakeProcessing(true);
- return;
- }
- if (isStateSet(STATE_PROCESS_TASK)) {
- return;
- }
- try {
- wrapAndFlush(ctx);
- } catch (Throwable cause) {
- setHandshakeFailure(ctx, cause);
- PlatformDependent.throwException(cause);
- }
- }
- private void wrapAndFlush(ChannelHandlerContext ctx) throws SSLException {
- if (pendingUnencryptedWrites.isEmpty()) {
- // It's important to NOT use a voidPromise here as the user
- // may want to add a ChannelFutureListener to the ChannelPromise later.
- //
- // See https://github.com/netty/netty/issues/3364
- pendingUnencryptedWrites.add(Unpooled.EMPTY_BUFFER, ctx.newPromise());
- }
- if (!handshakePromise.isDone()) {
- setState(STATE_FLUSHED_BEFORE_HANDSHAKE);
- }
- try {
- wrap(ctx, false);
- } finally {
- // We may have written some parts of data before an exception was thrown so ensure we always flush.
- // See https://github.com/netty/netty/issues/3900#issuecomment-172481830
- forceFlush(ctx);
- }
- }
- // This method will not call setHandshakeFailure(...) !
- private void wrap(ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
- ByteBuf out = null;
- ByteBufAllocator alloc = ctx.alloc();
- try {
- final int wrapDataSize = this.wrapDataSize;
- // Only continue to loop if the handler was not removed in the meantime.
- // See https://github.com/netty/netty/issues/5860
- outer: while (!ctx.isRemoved()) {
- ChannelPromise promise = ctx.newPromise();
- ByteBuf buf = wrapDataSize > 0 ?
- pendingUnencryptedWrites.remove(alloc, wrapDataSize, promise) :
- pendingUnencryptedWrites.removeFirst(promise);
- if (buf == null) {
- break;
- }
- if (out == null) {
- out = allocateOutNetBuf(ctx, buf.readableBytes(), buf.nioBufferCount());
- }
- SSLEngineResult result = wrap(alloc, engine, buf, out);
- if (buf.isReadable()) {
- pendingUnencryptedWrites.addFirst(buf, promise);
- // When we add the buffer/promise pair back we need to be sure we don't complete the promise
- // later. We only complete the promise if the buffer is completely consumed.
- promise = null;
- } else {
- buf.release();
- }
- // We need to write any data before we invoke any methods which may trigger re-entry, otherwise
- // writes may occur out of order and TLS sequencing may be off (e.g. SSLV3_ALERT_BAD_RECORD_MAC).
- if (out.isReadable()) {
- final ByteBuf b = out;
- out = null;
- if (promise != null) {
- ctx.write(b, promise);
- } else {
- ctx.write(b);
- }
- } else if (promise != null) {
- ctx.write(Unpooled.EMPTY_BUFFER, promise);
- }
- // else out is not readable we can re-use it and so save an extra allocation
- if (result.getStatus() == Status.CLOSED) {
- // Make a best effort to preserve any exception that way previously encountered from the handshake
- // or the transport, else fallback to a general error.
- Throwable exception = handshakePromise.cause();
- if (exception == null) {
- exception = sslClosePromise.cause();
- if (exception == null) {
- exception = new SslClosedEngineException("SSLEngine closed already");
- }
- }
- pendingUnencryptedWrites.releaseAndFailAll(ctx, exception);
- return;
- } else {
- switch (result.getHandshakeStatus()) {
- case NEED_TASK:
- if (!runDelegatedTasks(inUnwrap)) {
- // We scheduled a task on the delegatingTaskExecutor, so stop processing as we will
- // resume once the task completes.
- break outer;
- }
- break;
- case FINISHED:
- case NOT_HANDSHAKING: // work around for android bug that skips the FINISHED state.
- setHandshakeSuccess();
- break;
- case NEED_WRAP:
- // If we are expected to wrap again and we produced some data we need to ensure there
- // is something in the queue to process as otherwise we will not try again before there
- // was more added. Failing to do so may fail to produce an alert that can be
- // consumed by the remote peer.
- if (result.bytesProduced() > 0 && pendingUnencryptedWrites.isEmpty()) {
- pendingUnencryptedWrites.add(Unpooled.EMPTY_BUFFER);
- }
- break;
- case NEED_UNWRAP:
- // The underlying engine is starving so we need to feed it with more data.
- // See https://github.com/netty/netty/pull/5039
- readIfNeeded(ctx);
- return;
- default:
- throw new IllegalStateException(
- "Unknown handshake status: " + result.getHandshakeStatus());
- }
- }
- }
- } finally {
- if (out != null) {
- out.release();
- }
- if (inUnwrap) {
- setState(STATE_NEEDS_FLUSH);
- }
- }
- }
- /**
- * This method will not call
- * {@link #setHandshakeFailure(ChannelHandlerContext, Throwable, boolean, boolean, boolean)} or
- * {@link #setHandshakeFailure(ChannelHandlerContext, Throwable)}.
- * @return {@code true} if this method ends on {@link SSLEngineResult.HandshakeStatus#NOT_HANDSHAKING}.
- */
- private boolean wrapNonAppData(final ChannelHandlerContext ctx, boolean inUnwrap) throws SSLException {
- ByteBuf out = null;
- ByteBufAllocator alloc = ctx.alloc();
- try {
- // Only continue to loop if the handler was not removed in the meantime.
- // See https://github.com/netty/netty/issues/5860
- outer: while (!ctx.isRemoved()) {
- if (out == null) {
- // As this is called for the handshake we have no real idea how big the buffer needs to be.
- // That said 2048 should give us enough room to include everything like ALPN / NPN data.
- // If this is not enough we will increase the buffer in wrap(...).
- out = allocateOutNetBuf(ctx, 2048, 1);
- }
- SSLEngineResult result = wrap(alloc, engine, Unpooled.EMPTY_BUFFER, out);
- if (result.bytesProduced() > 0) {
- ctx.write(out).addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) {
- Throwable cause = future.cause();
- if (cause != null) {
- setHandshakeFailureTransportFailure(ctx, cause);
- }
- }
- });
- if (inUnwrap) {
- setState(STATE_NEEDS_FLUSH);
- }
- out = null;
- }
- HandshakeStatus status = result.getHandshakeStatus();
- switch (status) {
- case FINISHED:
- // We may be here because we read data and discovered the remote peer initiated a renegotiation
- // and this write is to complete the new handshake. The user may have previously done a
- // writeAndFlush which wasn't able to wrap data due to needing the pending handshake, so we
- // attempt to wrap application data here if any is pending.
- if (setHandshakeSuccess() && inUnwrap && !pendingUnencryptedWrites.isEmpty()) {
- wrap(ctx, true);
- }
- return false;
- case NEED_TASK:
- if (!runDelegatedTasks(inUnwrap)) {
- // We scheduled a task on the delegatingTaskExecutor, so stop processing as we will
- // resume once the task completes.
- break outer;
- }
- break;
- case NEED_UNWRAP:
- if (inUnwrap || unwrapNonAppData(ctx) <= 0) {
- // If we asked for a wrap, the engine requested an unwrap, and we are in unwrap there is
- // no use in trying to call wrap again because we have already attempted (or will after we
- // return) to feed more data to the engine.
- return false;
- }
- break;
- case NEED_WRAP:
- break;
- case NOT_HANDSHAKING:
- if (setHandshakeSuccess() && inUnwrap && !pendingUnencryptedWrites.isEmpty()) {
- wrap(ctx, true);
- }
- // Workaround for TLS False Start problem reported at:
- // https://github.com/netty/netty/issues/1108#issuecomment-14266970
- if (!inUnwrap) {
- unwrapNonAppData(ctx);
- }
- return true;
- default:
- throw new IllegalStateException("Unknown handshake status: " + result.getHandshakeStatus());
- }
- // Check if did not produce any bytes and if so break out of the loop, but only if we did not process
- // a task as last action. It's fine to not produce any data as part of executing a task.
- if (result.bytesProduced() == 0 && status != HandshakeStatus.NEED_TASK) {
- break;
- }
- // It should not consume empty buffers when it is not handshaking
- // Fix for Android, where it was encrypting empty buffers even when not handshaking
- if (result.bytesConsumed() == 0 && result.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
- break;
- }
- }
- } finally {
- if (out != null) {
- out.release();
- }
- }
- return false;
- }
- private SSLEngineResult wrap(ByteBufAllocator alloc, SSLEngine engine, ByteBuf in, ByteBuf out)
- throws SSLException {
- ByteBuf newDirectIn = null;
- try {
- int readerIndex = in.readerIndex();
- int readableBytes = in.readableBytes();
- // We will call SslEngine.wrap(ByteBuffer[], ByteBuffer) to allow efficient handling of
- // CompositeByteBuf without force an extra memory copy when CompositeByteBuffer.nioBuffer() is called.
- final ByteBuffer[] in0;
- if (in.isDirect() || !engineType.wantsDirectBuffer) {
- // As CompositeByteBuf.nioBufferCount() can be expensive (as it needs to check all composed ByteBuf
- // to calculate the count) we will just assume a CompositeByteBuf contains more then 1 ByteBuf.
- // The worst that can happen is that we allocate an extra ByteBuffer[] in CompositeByteBuf.nioBuffers()
- // which is better then walking the composed ByteBuf in most cases.
- if (!(in instanceof CompositeByteBuf) && in.nioBufferCount() == 1) {
- in0 = singleBuffer;
- // We know its only backed by 1 ByteBuffer so use internalNioBuffer to keep object allocation
- // to a minimum.
- in0[0] = in.internalNioBuffer(readerIndex, readableBytes);
- } else {
- in0 = in.nioBuffers();
- }
- } else {
- // We could even go further here and check if its a CompositeByteBuf and if so try to decompose it and
- // only replace the ByteBuffer that are not direct. At the moment we just will replace the whole
- // CompositeByteBuf to keep the complexity to a minimum
- newDirectIn = alloc.directBuffer(readableBytes);
- newDirectIn.writeBytes(in, readerIndex, readableBytes);
- in0 = singleBuffer;
- in0[0] = newDirectIn.internalNioBuffer(newDirectIn.readerIndex(), readableBytes);
- }
- for (;;) {
- ByteBuffer out0 = out.nioBuffer(out.writerIndex(), out.writableBytes());
- SSLEngineResult result = engine.wrap(in0, out0);
- in.skipBytes(result.bytesConsumed());
- out.writerIndex(out.writerIndex() + result.bytesProduced());
- if (result.getStatus() == Status.BUFFER_OVERFLOW) {
- out.ensureWritable(engine.getSession().getPacketBufferSize());
- } else {
- return result;
- }
- }
- } finally {
- // Null out to allow GC of ByteBuffer
- singleBuffer[0] = null;
- if (newDirectIn != null) {
- newDirectIn.release();
- }
- }
- }
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- boolean handshakeFailed = handshakePromise.cause() != null;
- ClosedChannelException exception = new ClosedChannelException();
- // Make sure to release SSLEngine,
- // and notify the handshake future if the connection has been closed during handshake.
- setHandshakeFailure(ctx, exception, !isStateSet(STATE_OUTBOUND_CLOSED), isStateSet(STATE_HANDSHAKE_STARTED),
- false);
- // Ensure we always notify the sslClosePromise as well
- notifyClosePromise(exception);
- try {
- super.channelInactive(ctx);
- } catch (DecoderException e) {
- if (!handshakeFailed || !(e.getCause() instanceof SSLException)) {
- // We only rethrow the exception if the handshake did not fail before channelInactive(...) was called
- // as otherwise this may produce duplicated failures as super.channelInactive(...) will also call
- // channelRead(...).
- //
- // See https://github.com/netty/netty/issues/10119
- throw e;
- }
- }
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- if (ignoreException(cause)) {
- // It is safe to ignore the 'connection reset by peer' or
- // 'broken pipe' error after sending close_notify.
- if (logger.isDebugEnabled()) {
- logger.debug(
- "{} Swallowing a harmless 'connection reset by peer / broken pipe' error that occurred " +
- "while writing close_notify in response to the peer's close_notify", ctx.channel(), cause);
- }
- // Close the connection explicitly just in case the transport
- // did not close the connection automatically.
- if (ctx.channel().isActive()) {
- ctx.close();
- }
- } else {
- ctx.fireExceptionCaught(cause);
- }
- }
- /**
- * Checks if the given {@link Throwable} can be ignore and just "swallowed"
- *
- * When an ssl connection is closed a close_notify message is sent.
- * After that the peer also sends close_notify however, it's not mandatory to receive
- * the close_notify. The party who sent the initial close_notify …
Large files files are truncated, but you can click here to view the full file