All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.ssl.ReferenceCountedOpenSslEngine Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * Copyright 2016 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.handler.ssl.util.LazyJavaxX509Certificate;
import io.netty.handler.ssl.util.LazyX509Certificate;
import io.netty.internal.tcnative.AsyncTask;
import io.netty.internal.tcnative.Buffer;
import io.netty.internal.tcnative.SSL;
import io.netty.util.AbstractReferenceCounted;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import io.netty.util.internal.SuppressJava6Requirement;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.internal.UnstableApi;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;

import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import javax.security.cert.X509Certificate;

import static io.netty.handler.ssl.OpenSsl.memoryAddress;
import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH;
import static io.netty.util.internal.EmptyArrays.EMPTY_STRINGS;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkNotNullArrayParam;
import static io.netty.util.internal.ObjectUtil.checkNotNullWithIAE;
import static java.lang.Integer.MAX_VALUE;
import static java.lang.Math.min;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.FINISHED;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_TASK;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NEED_WRAP;
import static javax.net.ssl.SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
import static javax.net.ssl.SSLEngineResult.Status.BUFFER_OVERFLOW;
import static javax.net.ssl.SSLEngineResult.Status.BUFFER_UNDERFLOW;
import static javax.net.ssl.SSLEngineResult.Status.CLOSED;
import static javax.net.ssl.SSLEngineResult.Status.OK;

/**
 * Implements a {@link SSLEngine} using
 * OpenSSL BIO abstractions.
 * 

Instances of this class must be {@link #release() released} or else native memory will leak! * *

Instances of this class must be released before the {@link ReferenceCountedOpenSslContext} * the instance depends upon are released. Otherwise if any method of this class is called which uses the * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash. */ public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted, ApplicationProtocolAccessor { private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class); private static final ResourceLeakDetector leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ReferenceCountedOpenSslEngine.class); private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2 = 0; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3 = 1; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1 = 2; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1 = 3; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2 = 4; private static final int OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3 = 5; private static final int[] OPENSSL_OP_NO_PROTOCOLS = { SSL.SSL_OP_NO_SSLv2, SSL.SSL_OP_NO_SSLv3, SSL.SSL_OP_NO_TLSv1, SSL.SSL_OP_NO_TLSv1_1, SSL.SSL_OP_NO_TLSv1_2, SSL.SSL_OP_NO_TLSv1_3 }; /** * Depends upon tcnative ... only use if tcnative is available! */ static final int MAX_PLAINTEXT_LENGTH = SSL.SSL_MAX_PLAINTEXT_LENGTH; /** * Depends upon tcnative ... only use if tcnative is available! */ static final int MAX_RECORD_SIZE = SSL.SSL_MAX_RECORD_LENGTH; private static final SSLEngineResult NEED_UNWRAP_OK = new SSLEngineResult(OK, NEED_UNWRAP, 0, 0); private static final SSLEngineResult NEED_UNWRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_UNWRAP, 0, 0); private static final SSLEngineResult NEED_WRAP_OK = new SSLEngineResult(OK, NEED_WRAP, 0, 0); private static final SSLEngineResult NEED_WRAP_CLOSED = new SSLEngineResult(CLOSED, NEED_WRAP, 0, 0); private static final SSLEngineResult CLOSED_NOT_HANDSHAKING = new SSLEngineResult(CLOSED, NOT_HANDSHAKING, 0, 0); // OpenSSL state private long ssl; private long networkBIO; private enum HandshakeState { /** * Not started yet. */ NOT_STARTED, /** * Started via unwrap/wrap. */ STARTED_IMPLICITLY, /** * Started via {@link #beginHandshake()}. */ STARTED_EXPLICITLY, /** * Handshake is finished. */ FINISHED } private HandshakeState handshakeState = HandshakeState.NOT_STARTED; private boolean receivedShutdown; private volatile boolean destroyed; private volatile String applicationProtocol; private volatile boolean needTask; private boolean hasTLSv13Cipher; private boolean sessionSet; // Reference Counting private final ResourceLeakTracker leak; private final AbstractReferenceCounted refCnt = new AbstractReferenceCounted() { @Override public ReferenceCounted touch(Object hint) { if (leak != null) { leak.record(hint); } return ReferenceCountedOpenSslEngine.this; } @Override protected void deallocate() { shutdown(); if (leak != null) { boolean closed = leak.close(ReferenceCountedOpenSslEngine.this); assert closed; } parentContext.release(); } }; private final Set enabledProtocols = new LinkedHashSet(); private volatile ClientAuth clientAuth = ClientAuth.NONE; private String endPointIdentificationAlgorithm; // Store as object as AlgorithmConstraints only exists since java 7. private Object algorithmConstraints; private List sniHostNames; // Mark as volatile as accessed by checkSniHostnameMatch(...) and also not specify the SNIMatcher type to allow us // using it with java7. private volatile Collection matchers; // SSL Engine status variables private boolean isInboundDone; private boolean outboundClosed; final boolean jdkCompatibilityMode; private final boolean clientMode; final ByteBufAllocator alloc; private final OpenSslEngineMap engineMap; private final OpenSslApplicationProtocolNegotiator apn; private final ReferenceCountedOpenSslContext parentContext; private final OpenSslSession session; private final ByteBuffer[] singleSrcBuffer = new ByteBuffer[1]; private final ByteBuffer[] singleDstBuffer = new ByteBuffer[1]; private final boolean enableOcsp; private int maxWrapOverhead; private int maxWrapBufferSize; private Throwable pendingException; /** * Create a new instance. * @param context Reference count release responsibility is not transferred! The callee still owns this object. * @param alloc The allocator to use. * @param peerHost The peer host name. * @param peerPort The peer port. * @param jdkCompatibilityMode {@code true} to behave like described in * https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html. * {@code false} allows for partial and/or multiple packets to be process in a single * wrap or unwrap call. * @param leakDetection {@code true} to enable leak detection of this object. */ ReferenceCountedOpenSslEngine(ReferenceCountedOpenSslContext context, final ByteBufAllocator alloc, String peerHost, int peerPort, boolean jdkCompatibilityMode, boolean leakDetection) { super(peerHost, peerPort); OpenSsl.ensureAvailability(); engineMap = context.engineMap; enableOcsp = context.enableOcsp; this.jdkCompatibilityMode = jdkCompatibilityMode; this.alloc = checkNotNull(alloc, "alloc"); apn = (OpenSslApplicationProtocolNegotiator) context.applicationProtocolNegotiator(); clientMode = context.isClient(); if (PlatformDependent.javaVersion() >= 7) { session = new ExtendedOpenSslSession(new DefaultOpenSslSession(context.sessionContext())) { private String[] peerSupportedSignatureAlgorithms; private List requestedServerNames; @Override public List getRequestedServerNames() { if (clientMode) { return Java8SslUtils.getSniHostNames(sniHostNames); } else { synchronized (ReferenceCountedOpenSslEngine.this) { if (requestedServerNames == null) { if (isDestroyed()) { requestedServerNames = Collections.emptyList(); } else { String name = SSL.getSniHostname(ssl); if (name == null) { requestedServerNames = Collections.emptyList(); } else { // Convert to bytes as we do not want to do any strict validation of the // SNIHostName while creating it. requestedServerNames = Java8SslUtils.getSniHostName( SSL.getSniHostname(ssl).getBytes(CharsetUtil.UTF_8)); } } } return requestedServerNames; } } } @Override public String[] getPeerSupportedSignatureAlgorithms() { synchronized (ReferenceCountedOpenSslEngine.this) { if (peerSupportedSignatureAlgorithms == null) { if (isDestroyed()) { peerSupportedSignatureAlgorithms = EMPTY_STRINGS; } else { String[] algs = SSL.getSigAlgs(ssl); if (algs == null) { peerSupportedSignatureAlgorithms = EMPTY_STRINGS; } else { Set algorithmList = new LinkedHashSet(algs.length); for (String alg: algs) { String converted = SignatureAlgorithmConverter.toJavaName(alg); if (converted != null) { algorithmList.add(converted); } } peerSupportedSignatureAlgorithms = algorithmList.toArray(EMPTY_STRINGS); } } } return peerSupportedSignatureAlgorithms.clone(); } } @Override public List getStatusResponses() { byte[] ocspResponse = null; if (enableOcsp && clientMode) { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { ocspResponse = SSL.getOcspResponse(ssl); } } } return ocspResponse == null ? Collections.emptyList() : Collections.singletonList(ocspResponse); } }; } else { session = new DefaultOpenSslSession(context.sessionContext()); } if (!context.sessionContext().useKeyManager()) { session.setLocalCertificate(context.keyCertChain); } Lock readerLock = context.ctxLock.readLock(); readerLock.lock(); final long finalSsl; try { finalSsl = SSL.newSSL(context.ctx, !context.isClient()); } finally { readerLock.unlock(); } synchronized (this) { ssl = finalSsl; try { networkBIO = SSL.bioNewByteBuffer(ssl, context.getBioNonApplicationBufferSize()); // Set the client auth mode, this needs to be done via setClientAuth(...) method so we actually call the // needed JNI methods. setClientAuth(clientMode ? ClientAuth.NONE : context.clientAuth); assert context.protocols != null; this.hasTLSv13Cipher = context.hasTLSv13Cipher; setEnabledProtocols(context.protocols); // Use SNI if peerHost was specified and a valid hostname // See https://github.com/netty/netty/issues/4746 if (clientMode && SslUtils.isValidHostNameForSNI(peerHost)) { // If on java8 and later we should do some extra validation to ensure we can construct the // SNIHostName later again. if (PlatformDependent.javaVersion() >= 8) { if (Java8SslUtils.isValidHostNameForSNI(peerHost)) { SSL.setTlsExtHostName(ssl, peerHost); sniHostNames = Collections.singletonList(peerHost); } } else { SSL.setTlsExtHostName(ssl, peerHost); sniHostNames = Collections.singletonList(peerHost); } } if (enableOcsp) { SSL.enableOcsp(ssl); } if (!jdkCompatibilityMode) { SSL.setMode(ssl, SSL.getMode(ssl) | SSL.SSL_MODE_ENABLE_PARTIAL_WRITE); } if (isProtocolEnabled(SSL.getOptions(ssl), SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { final boolean enableTickets = clientMode ? ReferenceCountedOpenSslContext.CLIENT_ENABLE_SESSION_TICKET_TLSV13 : ReferenceCountedOpenSslContext.SERVER_ENABLE_SESSION_TICKET_TLSV13; if (enableTickets) { // We should enable session tickets for stateless resumption when TLSv1.3 is enabled. This // is also done by OpenJDK and without this session resumption does not work at all with // BoringSSL when TLSv1.3 is used as BoringSSL only supports stateless resumption with TLSv1.3: // // See: // - https://bugs.openjdk.java.net/browse/JDK-8223922 // - https://boringssl.googlesource.com/boringssl/+/refs/heads/master/ssl/tls13_server.cc#104 SSL.clearOptions(ssl, SSL.SSL_OP_NO_TICKET); } } if (OpenSsl.isBoringSSL() && clientMode) { // If in client-mode and BoringSSL let's allow to renegotiate once as the server may use this // for client auth. // // See https://github.com/netty/netty/issues/11529 SSL.setRenegotiateMode(ssl, SSL.SSL_RENEGOTIATE_ONCE); } // setMode may impact the overhead. calculateMaxWrapOverhead(); } catch (Throwable cause) { // Call shutdown so we are sure we correctly release all native memory and also guard against the // case when shutdown() will be called by the finalizer again. shutdown(); PlatformDependent.throwException(cause); } } // Now that everything looks good and we're going to successfully return the // object so we need to retain a reference to the parent context. parentContext = context; parentContext.retain(); // Only create the leak after everything else was executed and so ensure we don't produce a false-positive for // the ResourceLeakDetector. leak = leakDetection ? leakDetector.track(this) : null; } final synchronized String[] authMethods() { if (isDestroyed()) { return EMPTY_STRINGS; } return SSL.authenticationMethods(ssl); } final boolean setKeyMaterial(OpenSslKeyMaterial keyMaterial) throws Exception { synchronized (this) { if (isDestroyed()) { return false; } SSL.setKeyMaterial(ssl, keyMaterial.certificateChainAddress(), keyMaterial.privateKeyAddress()); } session.setLocalCertificate(keyMaterial.certificateChain()); return true; } final synchronized SecretKeySpec masterKey() { if (isDestroyed()) { return null; } return new SecretKeySpec(SSL.getMasterKey(ssl), "AES"); } synchronized boolean isSessionReused() { if (isDestroyed()) { return false; } return SSL.isSessionReused(ssl); } /** * Sets the OCSP response. */ @UnstableApi public void setOcspResponse(byte[] response) { if (!enableOcsp) { throw new IllegalStateException("OCSP stapling is not enabled"); } if (clientMode) { throw new IllegalStateException("Not a server SSLEngine"); } synchronized (this) { if (!isDestroyed()) { SSL.setOcspResponse(ssl, response); } } } /** * Returns the OCSP response or {@code null} if the server didn't provide a stapled OCSP response. */ @UnstableApi public byte[] getOcspResponse() { if (!enableOcsp) { throw new IllegalStateException("OCSP stapling is not enabled"); } if (!clientMode) { throw new IllegalStateException("Not a client SSLEngine"); } synchronized (this) { if (isDestroyed()) { return EmptyArrays.EMPTY_BYTES; } return SSL.getOcspResponse(ssl); } } @Override public final int refCnt() { return refCnt.refCnt(); } @Override public final ReferenceCounted retain() { refCnt.retain(); return this; } @Override public final ReferenceCounted retain(int increment) { refCnt.retain(increment); return this; } @Override public final ReferenceCounted touch() { refCnt.touch(); return this; } @Override public final ReferenceCounted touch(Object hint) { refCnt.touch(hint); return this; } @Override public final boolean release() { return refCnt.release(); } @Override public final boolean release(int decrement) { return refCnt.release(decrement); } // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier // java8 version we don't use @Override annotations here. public String getApplicationProtocol() { return applicationProtocol; } // These method will override the method defined by Java 8u251 and later. As we may compile with an earlier // java8 version we don't use @Override annotations here. public String getHandshakeApplicationProtocol() { return applicationProtocol; } @Override public final synchronized SSLSession getHandshakeSession() { // Javadocs state return value should be: // null if this instance is not currently handshaking, or if the current handshake has not // progressed far enough to create a basic SSLSession. Otherwise, this method returns the // SSLSession currently being negotiated. switch(handshakeState) { case NOT_STARTED: case FINISHED: return null; default: return session; } } /** * Returns the pointer to the {@code SSL} object for this {@link ReferenceCountedOpenSslEngine}. * Be aware that it is freed as soon as the {@link #release()} or {@link #shutdown()} methods are called. * At this point {@code 0} will be returned. */ public final synchronized long sslPointer() { return ssl; } /** * Destroys this engine. */ public final synchronized void shutdown() { if (!destroyed) { destroyed = true; // Let's check if engineMap is null as it could be in theory if we throw an OOME during the construction of // ReferenceCountedOpenSslEngine (before we assign the field). This is needed as shutdown() is called from // the finalizer as well. if (engineMap != null) { engineMap.remove(ssl); } SSL.freeSSL(ssl); ssl = networkBIO = 0; isInboundDone = outboundClosed = true; } // On shutdown clear all errors SSL.clearError(); } /** * Write plaintext data to the OpenSSL internal BIO * * Calling this function with src.remaining == 0 is undefined. */ private int writePlaintextData(final ByteBuffer src, int len) { final int pos = src.position(); final int limit = src.limit(); final int sslWrote; if (src.isDirect()) { sslWrote = SSL.writeToSSL(ssl, bufferAddress(src) + pos, len); if (sslWrote > 0) { src.position(pos + sslWrote); } } else { ByteBuf buf = alloc.directBuffer(len); try { src.limit(pos + len); buf.setBytes(0, src); src.limit(limit); sslWrote = SSL.writeToSSL(ssl, memoryAddress(buf), len); if (sslWrote > 0) { src.position(pos + sslWrote); } else { src.position(pos); } } finally { buf.release(); } } return sslWrote; } synchronized void bioSetFd(int fd) { if (!isDestroyed()) { SSL.bioSetFd(this.ssl, fd); } } /** * Write encrypted data to the OpenSSL network BIO. */ private ByteBuf writeEncryptedData(final ByteBuffer src, int len) throws SSLException { final int pos = src.position(); if (src.isDirect()) { SSL.bioSetByteBuffer(networkBIO, bufferAddress(src) + pos, len, false); } else { final ByteBuf buf = alloc.directBuffer(len); try { final int limit = src.limit(); src.limit(pos + len); buf.writeBytes(src); // Restore the original position and limit because we don't want to consume from `src`. src.position(pos); src.limit(limit); SSL.bioSetByteBuffer(networkBIO, memoryAddress(buf), len, false); return buf; } catch (Throwable cause) { buf.release(); PlatformDependent.throwException(cause); } } return null; } /** * Read plaintext data from the OpenSSL internal BIO */ private int readPlaintextData(final ByteBuffer dst) throws SSLException { final int sslRead; final int pos = dst.position(); if (dst.isDirect()) { sslRead = SSL.readFromSSL(ssl, bufferAddress(dst) + pos, dst.limit() - pos); if (sslRead > 0) { dst.position(pos + sslRead); } } else { final int limit = dst.limit(); final int len = min(maxEncryptedPacketLength0(), limit - pos); final ByteBuf buf = alloc.directBuffer(len); try { sslRead = SSL.readFromSSL(ssl, memoryAddress(buf), len); if (sslRead > 0) { dst.limit(pos + sslRead); buf.getBytes(buf.readerIndex(), dst); dst.limit(limit); } } finally { buf.release(); } } return sslRead; } /** * Visible only for testing! */ final synchronized int maxWrapOverhead() { return maxWrapOverhead; } /** * Visible only for testing! */ final synchronized int maxEncryptedPacketLength() { return maxEncryptedPacketLength0(); } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. */ final int maxEncryptedPacketLength0() { return maxWrapOverhead + MAX_PLAINTEXT_LENGTH; } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapBufferSize} and {@link #maxWrapOverhead} is achieved * via other synchronized blocks. *
* Calculates the max size of a single wrap operation for the given plaintextLength and * numComponents. */ final int calculateMaxLengthForWrap(int plaintextLength, int numComponents) { return (int) min(maxWrapBufferSize, plaintextLength + (long) maxWrapOverhead * numComponents); } /** * This method is intentionally not synchronized, only use if you know you are in the EventLoop * thread and visibility on {@link #maxWrapOverhead} is achieved via other synchronized blocks. *
* Calculates the size of the out net buf to create for the given plaintextLength and numComponents. * This is not related to the max size per wrap, as we can wrap chunks at a time into one out net buf. */ final int calculateOutNetBufSize(int plaintextLength, int numComponents) { return (int) min(MAX_VALUE, plaintextLength + (long) maxWrapOverhead * numComponents); } final synchronized int sslPending() { return sslPending0(); } /** * It is assumed this method is called in a synchronized block (or the constructor)! */ private void calculateMaxWrapOverhead() { maxWrapOverhead = SSL.getMaxWrapOverhead(ssl); // maxWrapBufferSize must be set after maxWrapOverhead because there is a dependency on this value. // If jdkCompatibility mode is off we allow enough space to encrypt 16 buffers at a time. This could be // configurable in the future if necessary. maxWrapBufferSize = jdkCompatibilityMode ? maxEncryptedPacketLength0() : maxEncryptedPacketLength0() << 4; } private int sslPending0() { // OpenSSL has a limitation where if you call SSL_pending before the handshake is complete OpenSSL will throw a // "called a function you should not call" error. Using the TLS_method instead of SSLv23_method may solve this // issue but this API is only available in 1.1.0+ [1]. // [1] https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_new.html return handshakeState != HandshakeState.FINISHED ? 0 : SSL.sslPending(ssl); } private boolean isBytesAvailableEnoughForWrap(int bytesAvailable, int plaintextLength, int numComponents) { return bytesAvailable - (long) maxWrapOverhead * numComponents >= plaintextLength; } @Override public final SSLEngineResult wrap( final ByteBuffer[] srcs, int offset, final int length, final ByteBuffer dst) throws SSLException { // Throw required runtime exceptions checkNotNullWithIAE(srcs, "srcs"); checkNotNullWithIAE(dst, "dst"); if (offset >= srcs.length || offset + length > srcs.length) { throw new IndexOutOfBoundsException( "offset: " + offset + ", length: " + length + " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); } if (dst.isReadOnly()) { throw new ReadOnlyBufferException(); } synchronized (this) { if (isOutboundDone()) { // All drained in the outbound buffer return isInboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_UNWRAP_CLOSED; } int bytesProduced = 0; ByteBuf bioReadCopyBuf = null; try { // Setup the BIO buffer so that we directly write the encryption results into dst. if (dst.isDirect()) { SSL.bioSetByteBuffer(networkBIO, bufferAddress(dst) + dst.position(), dst.remaining(), true); } else { bioReadCopyBuf = alloc.directBuffer(dst.remaining()); SSL.bioSetByteBuffer(networkBIO, memoryAddress(bioReadCopyBuf), bioReadCopyBuf.writableBytes(), true); } int bioLengthBefore = SSL.bioLengthByteBuffer(networkBIO); // Explicitly use outboundClosed as we want to drain any bytes that are still present. if (outboundClosed) { // If the outbound was closed we want to ensure we can produce the alert to the destination buffer. // This is true even if we not using jdkCompatibilityMode. // // We use a plaintextLength of 2 as we at least want to have an alert fit into it. // https://tools.ietf.org/html/rfc5246#section-7.2 if (!isBytesAvailableEnoughForWrap(dst.remaining(), 2, 1)) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } // There is something left to drain. // See https://github.com/netty/netty/issues/6260 bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (bytesProduced <= 0) { return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, 0); } // It is possible when the outbound was closed there was not enough room in the non-application // buffers to hold the close_notify. We should keep trying to close until we consume all the data // OpenSSL can give us. if (!doSSLShutdown()) { return newResultMayFinishHandshake(NOT_HANDSHAKING, 0, bytesProduced); } bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); return newResultMayFinishHandshake(NEED_WRAP, 0, bytesProduced); } // Flush any data that may be implicitly generated by OpenSSL (handshake, close, etc..). SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; HandshakeState oldHandshakeState = handshakeState; // Prepare OpenSSL to work in server mode and receive handshake if (handshakeState != HandshakeState.FINISHED) { if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { // Update accepted so we know we triggered the handshake via wrap handshakeState = HandshakeState.STARTED_IMPLICITLY; } // Flush any data that may have been written implicitly during the handshake by OpenSSL. bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (pendingException != null) { // TODO(scott): It is possible that when the handshake failed there was not enough room in the // non-application buffers to hold the alert. We should get all the data before progressing on. // However I'm not aware of a way to do this with the OpenSSL APIs. // See https://github.com/netty/netty/issues/6385. // We produced / consumed some data during the handshake, signal back to the caller. // If there is a handshake exception and we have produced data, we should send the data before // we allow handshake() to throw the handshake exception. // // When the user calls wrap() again we will propagate the handshake error back to the user as // soon as there is no more data to was produced (as part of an alert etc). if (bytesProduced > 0) { return newResult(NEED_WRAP, 0, bytesProduced); } // Nothing was produced see if there is a handshakeException that needs to be propagated // to the caller by calling handshakeException() which will return the right HandshakeStatus // if it can "recover" from the exception for now. return newResult(handshakeException(), 0, 0); } status = handshake(); // Handshake may have generated more data, for example if the internal SSL buffer is small // we may have freed up space by flushing above. bytesProduced = bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); if (status == NEED_TASK) { return newResult(status, 0, bytesProduced); } if (bytesProduced > 0) { // If we have filled up the dst buffer and we have not finished the handshake we should try to // wrap again. Otherwise we should only try to wrap again if there is still data pending in // SSL buffers. return newResult(mayFinishHandshake(status != FINISHED ? bytesProduced == bioLengthBefore ? NEED_WRAP : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED), 0, bytesProduced); } if (status == NEED_UNWRAP) { // Signal if the outbound is done or not. return isOutboundDone() ? NEED_UNWRAP_CLOSED : NEED_UNWRAP_OK; } // Explicit use outboundClosed and not outboundClosed() as we want to drain any bytes that are // still present. if (outboundClosed) { bytesProduced = SSL.bioFlushByteBuffer(networkBIO); return newResultMayFinishHandshake(status, 0, bytesProduced); } } final int endOffset = offset + length; if (jdkCompatibilityMode || // If the handshake was not finished before we entered the method, we also ensure we only // wrap one record. We do this to ensure we not produce any extra data before the caller // of the method is able to observe handshake completion and react on it. oldHandshakeState != HandshakeState.FINISHED) { int srcsLen = 0; for (int i = offset; i < endOffset; ++i) { final ByteBuffer src = srcs[i]; if (src == null) { throw new IllegalArgumentException("srcs[" + i + "] is null"); } if (srcsLen == MAX_PLAINTEXT_LENGTH) { continue; } srcsLen += src.remaining(); if (srcsLen > MAX_PLAINTEXT_LENGTH || srcsLen < 0) { // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to MAX_PLAINTEXT_LENGTH. // This also help us to guard against overflow. // We not break out here as we still need to check for null entries in srcs[]. srcsLen = MAX_PLAINTEXT_LENGTH; } } // jdkCompatibilityMode will only produce a single TLS packet, and we don't aggregate src buffers, // so we always fix the number of buffers to 1 when checking if the dst buffer is large enough. if (!isBytesAvailableEnoughForWrap(dst.remaining(), srcsLen, 1)) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0); } } // There was no pending data in the network BIO -- encrypt any application data int bytesConsumed = 0; assert bytesProduced == 0; // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. bytesProduced = SSL.bioFlushByteBuffer(networkBIO); if (bytesProduced > 0) { return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } // There was a pending exception that we just delayed because there was something to produce left. // Throw it now and shutdown the engine. if (pendingException != null) { Throwable error = pendingException; pendingException = null; shutdown(); // Throw a new exception wrapping the pending exception, so the stacktrace is meaningful and // contains all the details. throw new SSLException(error); } for (; offset < endOffset; ++offset) { final ByteBuffer src = srcs[offset]; final int remaining = src.remaining(); if (remaining == 0) { continue; } final int bytesWritten; if (jdkCompatibilityMode) { // Write plaintext application data to the SSL engine. We don't have to worry about checking // if there is enough space if jdkCompatibilityMode because we only wrap at most // MAX_PLAINTEXT_LENGTH and we loop over the input before hand and check if there is space. bytesWritten = writePlaintextData(src, min(remaining, MAX_PLAINTEXT_LENGTH - bytesConsumed)); } else { // OpenSSL's SSL_write keeps state between calls. We should make sure the amount we attempt to // write is guaranteed to succeed so we don't have to worry about keeping state consistent // between calls. final int availableCapacityForWrap = dst.remaining() - bytesProduced - maxWrapOverhead; if (availableCapacityForWrap <= 0) { return new SSLEngineResult(BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced); } bytesWritten = writePlaintextData(src, min(remaining, availableCapacityForWrap)); } // Determine how much encrypted data was generated. // // Even if SSL_write doesn't consume any application data it is possible that OpenSSL will // produce non-application data into the BIO. For example session tickets.... // See https://github.com/netty/netty/issues/10041 final int pendingNow = SSL.bioLengthByteBuffer(networkBIO); bytesProduced += bioLengthBefore - pendingNow; bioLengthBefore = pendingNow; if (bytesWritten > 0) { bytesConsumed += bytesWritten; if (jdkCompatibilityMode || bytesProduced == dst.remaining()) { return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } } else { int sslError = SSL.getError(ssl, bytesWritten); if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { // This means the connection was shutdown correctly, close inbound and outbound if (!receivedShutdown) { closeAll(); bytesProduced += bioLengthBefore - SSL.bioLengthByteBuffer(networkBIO); // If we have filled up the dst buffer and we have not finished the handshake we should // try to wrap again. Otherwise we should only try to wrap again if there is still data // pending in SSL buffers. SSLEngineResult.HandshakeStatus hs = mayFinishHandshake( status != FINISHED ? bytesProduced == dst.remaining() ? NEED_WRAP : getHandshakeStatus(SSL.bioLengthNonApplication(networkBIO)) : FINISHED); return newResult(hs, bytesConsumed, bytesProduced); } return newResult(NOT_HANDSHAKING, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_READ) { // If there is no pending data to read from BIO we should go back to event loop and try // to read more data [1]. It is also possible that event loop will detect the socket has // been closed. [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html return newResult(NEED_UNWRAP, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_WRITE) { // SSL_ERROR_WANT_WRITE typically means that the underlying transport is not writable // and we should set the "want write" flag on the selector and try again when the // underlying transport is writable [1]. However we are not directly writing to the // underlying transport and instead writing to a BIO buffer. The OpenSsl documentation // says we should do the following [1]: // // "When using a buffering BIO, like a BIO pair, data must be written into or retrieved // out of the BIO before being able to continue." // // In practice this means the destination buffer doesn't have enough space for OpenSSL // to write encrypted data to. This is an OVERFLOW condition. // [1] https://www.openssl.org/docs/manmaster/ssl/SSL_write.html if (bytesProduced > 0) { // If we produced something we should report this back and let the user call // wrap again. return newResult(NEED_WRAP, bytesConsumed, bytesProduced); } return newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return newResult(NEED_TASK, bytesConsumed, bytesProduced); } else { // Everything else is considered as error throw shutdownWithError("SSL_write", sslError); } } } return newResultMayFinishHandshake(status, bytesConsumed, bytesProduced); } finally { SSL.bioClearByteBuffer(networkBIO); if (bioReadCopyBuf == null) { dst.position(dst.position() + bytesProduced); } else { assert bioReadCopyBuf.readableBytes() <= dst.remaining() : "The destination buffer " + dst + " didn't have enough remaining space to hold the encrypted content in " + bioReadCopyBuf; dst.put(bioReadCopyBuf.internalNioBuffer(bioReadCopyBuf.readerIndex(), bytesProduced)); bioReadCopyBuf.release(); } } } } private SSLEngineResult newResult(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { return newResult(OK, hs, bytesConsumed, bytesProduced); } private SSLEngineResult newResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) { // If isOutboundDone, then the data from the network BIO // was the close_notify message and all was consumed we are not required to wait // for the receipt the peer's close_notify message -- shutdown. if (isOutboundDone()) { if (isInboundDone()) { // If the inbound was done as well, we need to ensure we return NOT_HANDSHAKING to signal we are done. hs = NOT_HANDSHAKING; // As the inbound and the outbound is done we can shutdown the engine now. shutdown(); } return new SSLEngineResult(CLOSED, hs, bytesConsumed, bytesProduced); } if (hs == NEED_TASK) { // Set needTask to true so getHandshakeStatus() will return the correct value. needTask = true; } return new SSLEngineResult(status, hs, bytesConsumed, bytesProduced); } private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return newResult(mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); } private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return newResult(status, mayFinishHandshake(hs, bytesConsumed, bytesProduced), bytesConsumed, bytesProduced); } /** * Log the error, shutdown the engine and throw an exception. */ private SSLException shutdownWithError(String operations, int sslError) { return shutdownWithError(operations, sslError, SSL.getLastErrorNumber()); } private SSLException shutdownWithError(String operation, int sslError, int error) { if (logger.isDebugEnabled()) { String errorString = SSL.getErrorString(error); logger.debug("{} failed with {}: OpenSSL error: {} {}", operation, sslError, error, errorString); } // There was an internal error -- shutdown shutdown(); SSLException exception = newSSLExceptionForError(error); // If we have a pendingException stored already we should include it as well to help the user debug things. if (pendingException != null) { exception.initCause(pendingException); pendingException = null; } return exception; } private SSLEngineResult handleUnwrapException(int bytesConsumed, int bytesProduced, SSLException e) throws SSLException { int lastError = SSL.getLastErrorNumber(); if (lastError != 0) { return sslReadErrorResult(SSL.SSL_ERROR_SSL, lastError, bytesConsumed, bytesProduced); } throw e; } public final SSLEngineResult unwrap( final ByteBuffer[] srcs, int srcsOffset, final int srcsLength, final ByteBuffer[] dsts, int dstsOffset, final int dstsLength) throws SSLException { // Throw required runtime exceptions checkNotNullWithIAE(srcs, "srcs"); if (srcsOffset >= srcs.length || srcsOffset + srcsLength > srcs.length) { throw new IndexOutOfBoundsException( "offset: " + srcsOffset + ", length: " + srcsLength + " (expected: offset <= offset + length <= srcs.length (" + srcs.length + "))"); } checkNotNullWithIAE(dsts, "dsts"); if (dstsOffset >= dsts.length || dstsOffset + dstsLength > dsts.length) { throw new IndexOutOfBoundsException( "offset: " + dstsOffset + ", length: " + dstsLength + " (expected: offset <= offset + length <= dsts.length (" + dsts.length + "))"); } long capacity = 0; final int dstsEndOffset = dstsOffset + dstsLength; for (int i = dstsOffset; i < dstsEndOffset; i ++) { ByteBuffer dst = checkNotNullArrayParam(dsts[i], i, "dsts"); if (dst.isReadOnly()) { throw new ReadOnlyBufferException(); } capacity += dst.remaining(); } final int srcsEndOffset = srcsOffset + srcsLength; long len = 0; for (int i = srcsOffset; i < srcsEndOffset; i++) { ByteBuffer src = checkNotNullArrayParam(srcs[i], i, "srcs"); len += src.remaining(); } synchronized (this) { if (isInboundDone()) { return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED; } SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; HandshakeState oldHandshakeState = handshakeState; // Prepare OpenSSL to work in server mode and receive handshake if (handshakeState != HandshakeState.FINISHED) { if (handshakeState != HandshakeState.STARTED_EXPLICITLY) { // Update accepted so we know we triggered the handshake via wrap handshakeState = HandshakeState.STARTED_IMPLICITLY; } status = handshake(); if (status == NEED_TASK) { return newResult(status, 0, 0); } if (status == NEED_WRAP) { return NEED_WRAP_OK; } // Check if the inbound is considered to be closed if so let us try to wrap again. if (isInboundDone) { return NEED_WRAP_CLOSED; } } int sslPending = sslPending0(); int packetLength; // The JDK implies that only a single SSL packet should be processed per unwrap call [1]. If we are in // JDK compatibility mode then we should honor this, but if not we just wrap as much as possible. If there // are multiple records or partial records this may reduce thrashing events through the pipeline. // [1] https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLEngine.html if (jdkCompatibilityMode || // If the handshake was not finished before we entered the method, we also ensure we only // unwrap one record. We do this to ensure we not produce any extra data before the caller // of the method is able to observe handshake completion and react on it. oldHandshakeState != HandshakeState.FINISHED) { if (len < SSL_RECORD_HEADER_LENGTH) { return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } packetLength = SslUtils.getEncryptedPacketLength(srcs, srcsOffset); if (packetLength == SslUtils.NOT_ENCRYPTED) { throw new NotSslRecordException("not an SSL/TLS record"); } final int packetLengthDataOnly = packetLength - SSL_RECORD_HEADER_LENGTH; if (packetLengthDataOnly > capacity) { // Not enough space in the destination buffer so signal the caller that the buffer needs to be // increased. if (packetLengthDataOnly > MAX_RECORD_SIZE) { // The packet length MUST NOT exceed 2^14 [1]. However we do accommodate more data to support // legacy use cases which may violate this condition (e.g. OpenJDK's SslEngineImpl). If the max // length is exceeded we fail fast here to avoid an infinite loop due to the fact that we // won't allocate a buffer large enough. // [1] https://tools.ietf.org/html/rfc5246#section-6.2.1 throw new SSLException("Illegal packet length: " + packetLengthDataOnly + " > " + session.getApplicationBufferSize()); } else { session.tryExpandApplicationBufferSize(packetLengthDataOnly); } return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); } if (len < packetLength) { // We either don't have enough data to read the packet length or not enough for reading the whole // packet. return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } } else if (len == 0 && sslPending <= 0) { return newResultMayFinishHandshake(BUFFER_UNDERFLOW, status, 0, 0); } else if (capacity == 0) { return newResultMayFinishHandshake(BUFFER_OVERFLOW, status, 0, 0); } else { packetLength = (int) min(MAX_VALUE, len); } // This must always be the case when we reached here as if not we returned BUFFER_UNDERFLOW. assert srcsOffset < srcsEndOffset; // This must always be the case if we reached here. assert capacity > 0; // Number of produced bytes int bytesProduced = 0; int bytesConsumed = 0; try { srcLoop: for (;;) { ByteBuffer src = srcs[srcsOffset]; int remaining = src.remaining(); final ByteBuf bioWriteCopyBuf; int pendingEncryptedBytes; if (remaining == 0) { if (sslPending <= 0) { // We must skip empty buffers as BIO_write will return 0 if asked to write something // with length 0. if (++srcsOffset >= srcsEndOffset) { break; } continue; } else { bioWriteCopyBuf = null; pendingEncryptedBytes = SSL.bioLengthByteBuffer(networkBIO); } } else { // Write more encrypted data into the BIO. Ensure we only read one packet at a time as // stated in the SSLEngine javadocs. pendingEncryptedBytes = min(packetLength, remaining); try { bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); } catch (SSLException e) { // Ensure we correctly handle the error stack. return handleUnwrapException(bytesConsumed, bytesProduced, e); } } try { for (;;) { ByteBuffer dst = dsts[dstsOffset]; if (!dst.hasRemaining()) { // No space left in the destination buffer, skip it. if (++dstsOffset >= dstsEndOffset) { break srcLoop; } continue; } int bytesRead; try { bytesRead = readPlaintextData(dst); } catch (SSLException e) { // Ensure we correctly handle the error stack. return handleUnwrapException(bytesConsumed, bytesProduced, e); } // We are directly using the ByteBuffer memory for the write, and so we only know what has // been consumed after we let SSL decrypt the data. At this point we should update the // number of bytes consumed, update the ByteBuffer position, and release temp ByteBuf. int localBytesConsumed = pendingEncryptedBytes - SSL.bioLengthByteBuffer(networkBIO); bytesConsumed += localBytesConsumed; packetLength -= localBytesConsumed; pendingEncryptedBytes -= localBytesConsumed; src.position(src.position() + localBytesConsumed); if (bytesRead > 0) { bytesProduced += bytesRead; if (!dst.hasRemaining()) { sslPending = sslPending0(); // Move to the next dst buffer as this one is full. if (++dstsOffset >= dstsEndOffset) { return sslPending > 0 ? newResult(BUFFER_OVERFLOW, status, bytesConsumed, bytesProduced) : newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } } else if (packetLength == 0 || jdkCompatibilityMode) { // We either consumed all data or we are in jdkCompatibilityMode and have consumed // a single TLS packet and should stop consuming until this method is called again. break srcLoop; } } else { int sslError = SSL.getError(ssl, bytesRead); if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { // break to the outer loop as we want to read more data which means we need to // write more to the BIO. break; } else if (sslError == SSL.SSL_ERROR_ZERO_RETURN) { // This means the connection was shutdown correctly, close inbound and outbound if (!receivedShutdown) { closeAll(); } return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } else if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return newResult(isInboundDone() ? CLOSED : OK, NEED_TASK, bytesConsumed, bytesProduced); } else { return sslReadErrorResult(sslError, SSL.getLastErrorNumber(), bytesConsumed, bytesProduced); } } } if (++srcsOffset >= srcsEndOffset) { break; } } finally { if (bioWriteCopyBuf != null) { bioWriteCopyBuf.release(); } } } } finally { SSL.bioClearByteBuffer(networkBIO); rejectRemoteInitiatedRenegotiation(); } // Check to see if we received a close_notify message from the peer. if (!receivedShutdown && (SSL.getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) { closeAll(); } return newResultMayFinishHandshake(isInboundDone() ? CLOSED : OK, status, bytesConsumed, bytesProduced); } } private boolean needWrapAgain(int stackError) { // Check if we have a pending handshakeException and if so see if we need to consume all pending data from the // BIO first or can just shutdown and throw it now. // This is needed so we ensure close_notify etc is correctly send to the remote peer. // See https://github.com/netty/netty/issues/3900 if (SSL.bioLengthNonApplication(networkBIO) > 0) { // we seem to have data left that needs to be transferred and so the user needs // call wrap(...). Store the error so we can pick it up later. if (pendingException == null) { pendingException = newSSLExceptionForError(stackError); } else if (shouldAddSuppressed(pendingException, stackError)) { ThrowableUtil.addSuppressed(pendingException, newSSLExceptionForError(stackError)); } // We need to clear all errors so we not pick up anything that was left on the stack on the next // operation. Note that shutdownWithError(...) will cleanup the stack as well so its only needed here. SSL.clearError(); return true; } return false; } private SSLException newSSLExceptionForError(int stackError) { String message = SSL.getErrorString(stackError); return handshakeState == HandshakeState.FINISHED ? new OpenSslException(message, stackError) : new OpenSslHandshakeException(message, stackError); } private static boolean shouldAddSuppressed(Throwable target, int errorCode) { for (Throwable suppressed: ThrowableUtil.getSuppressed(target)) { if (suppressed instanceof NativeSslException && ((NativeSslException) suppressed).errorCode() == errorCode) { /// An exception with this errorCode was already added before. return false; } } return true; } private SSLEngineResult sslReadErrorResult(int error, int stackError, int bytesConsumed, int bytesProduced) throws SSLException { if (needWrapAgain(stackError)) { // There is something that needs to be send to the remote peer before we can teardown. // This is most likely some alert. return new SSLEngineResult(OK, NEED_WRAP, bytesConsumed, bytesProduced); } throw shutdownWithError("SSL_read", error, stackError); } private void closeAll() throws SSLException { receivedShutdown = true; closeOutbound(); closeInbound(); } private void rejectRemoteInitiatedRenegotiation() throws SSLHandshakeException { // As rejectRemoteInitiatedRenegotiation() is called in a finally block we also need to check if we shutdown // the engine before as otherwise SSL.getHandshakeCount(ssl) will throw an NPE if the passed in ssl is 0. // See https://github.com/netty/netty/issues/7353 if (!isDestroyed() && (!clientMode && SSL.getHandshakeCount(ssl) > 1 || // Let's allow to renegotiate once for client auth. clientMode && SSL.getHandshakeCount(ssl) > 2) && // As we may count multiple handshakes when TLSv1.3 is used we should just ignore this here as // renegotiation is not supported in TLSv1.3 as per spec. !SslProtocols.TLS_v1_3.equals(session.getProtocol()) && handshakeState == HandshakeState.FINISHED) { // TODO: In future versions me may also want to send a fatal_alert to the client and so notify it // that the renegotiation failed. shutdown(); throw new SSLHandshakeException("remote-initiated renegotiation not allowed"); } } public final SSLEngineResult unwrap(final ByteBuffer[] srcs, final ByteBuffer[] dsts) throws SSLException { return unwrap(srcs, 0, srcs.length, dsts, 0, dsts.length); } private ByteBuffer[] singleSrcBuffer(ByteBuffer src) { singleSrcBuffer[0] = src; return singleSrcBuffer; } private void resetSingleSrcBuffer() { singleSrcBuffer[0] = null; } private ByteBuffer[] singleDstBuffer(ByteBuffer src) { singleDstBuffer[0] = src; return singleDstBuffer; } private void resetSingleDstBuffer() { singleDstBuffer[0] = null; } @Override public final synchronized SSLEngineResult unwrap( final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException { try { return unwrap(singleSrcBuffer(src), 0, 1, dsts, offset, length); } finally { resetSingleSrcBuffer(); } } @Override public final synchronized SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException { try { return wrap(singleSrcBuffer(src), dst); } finally { resetSingleSrcBuffer(); } } @Override public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException { try { return unwrap(singleSrcBuffer(src), singleDstBuffer(dst)); } finally { resetSingleSrcBuffer(); resetSingleDstBuffer(); } } @Override public final synchronized SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException { try { return unwrap(singleSrcBuffer(src), dsts); } finally { resetSingleSrcBuffer(); } } private class TaskDecorator implements Runnable { protected final R task; TaskDecorator(R task) { this.task = task; } @Override public void run() { runAndResetNeedTask(task); } } private final class AsyncTaskDecorator extends TaskDecorator implements AsyncRunnable { AsyncTaskDecorator(AsyncTask task) { super(task); } @Override public void run(final Runnable runnable) { if (isDestroyed()) { // The engine was destroyed in the meantime, just return. return; } task.runAsync(new TaskDecorator(runnable)); } } private void runAndResetNeedTask(Runnable task) { // We need to synchronize on the ReferenceCountedOpenSslEngine, we are sure the SSL object // will not be freed by the user calling for example shutdown() concurrently. synchronized (ReferenceCountedOpenSslEngine.this) { try { if (isDestroyed()) { // The engine was destroyed in the meantime, just return. return; } task.run(); if (handshakeState != HandshakeState.FINISHED && !isDestroyed()) { // Call SSL.doHandshake(...) If the handshake was not finished yet. This might be needed // to fill the application buffer and so have getHandshakeStatus() return the right value // in this case. if (SSL.doHandshake(ssl) <= 0) { SSL.clearError(); } } } finally { // The task was run, reset needTask to false so getHandshakeStatus() returns the correct value. needTask = false; } } } @Override public final synchronized Runnable getDelegatedTask() { if (isDestroyed()) { return null; } final Runnable task = SSL.getTask(ssl); if (task == null) { return null; } if (task instanceof AsyncTask) { return new AsyncTaskDecorator((AsyncTask) task); } return new TaskDecorator(task); } @Override public final synchronized void closeInbound() throws SSLException { if (isInboundDone) { return; } isInboundDone = true; if (isOutboundDone()) { // Only call shutdown if there is no outbound data pending. // See https://github.com/netty/netty/issues/6167 shutdown(); } if (handshakeState != HandshakeState.NOT_STARTED && !receivedShutdown) { throw new SSLException( "Inbound closed before receiving peer's close_notify: possible truncation attack?"); } } @Override public final synchronized boolean isInboundDone() { return isInboundDone; } @Override public final synchronized void closeOutbound() { if (outboundClosed) { return; } outboundClosed = true; if (handshakeState != HandshakeState.NOT_STARTED && !isDestroyed()) { int mode = SSL.getShutdown(ssl); if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) { doSSLShutdown(); } } else { // engine closing before initial handshake shutdown(); } } /** * Attempt to call {@link SSL#shutdownSSL(long)}. * @return {@code false} if the call to {@link SSL#shutdownSSL(long)} was not attempted or returned an error. */ private boolean doSSLShutdown() { if (SSL.isInInit(ssl) != 0) { // Only try to call SSL_shutdown if we are not in the init state anymore. // Otherwise we will see 'error:140E0197:SSL routines:SSL_shutdown:shutdown while in init' in our logs. // // See also https://hg.nginx.org/nginx/rev/062c189fee20 return false; } int err = SSL.shutdownSSL(ssl); if (err < 0) { int sslErr = SSL.getError(ssl, err); if (sslErr == SSL.SSL_ERROR_SYSCALL || sslErr == SSL.SSL_ERROR_SSL) { if (logger.isDebugEnabled()) { int error = SSL.getLastErrorNumber(); logger.debug("SSL_shutdown failed: OpenSSL error: {} {}", error, SSL.getErrorString(error)); } // There was an internal error -- shutdown shutdown(); return false; } SSL.clearError(); } return true; } @Override public final synchronized boolean isOutboundDone() { // Check if there is anything left in the outbound buffer. // We need to ensure we only call SSL.pendingWrittenBytesInBIO(...) if the engine was not destroyed yet. return outboundClosed && (networkBIO == 0 || SSL.bioLengthNonApplication(networkBIO) == 0); } @Override public final String[] getSupportedCipherSuites() { return OpenSsl.AVAILABLE_CIPHER_SUITES.toArray(EMPTY_STRINGS); } @Override public final String[] getEnabledCipherSuites() { final String[] extraCiphers; final String[] enabled; final boolean tls13Enabled; synchronized (this) { if (!isDestroyed()) { enabled = SSL.getCiphers(ssl); int opts = SSL.getOptions(ssl); if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, SslProtocols.TLS_v1_3)) { extraCiphers = OpenSsl.EXTRA_SUPPORTED_TLS_1_3_CIPHERS; tls13Enabled = true; } else { extraCiphers = EMPTY_STRINGS; tls13Enabled = false; } } else { return EMPTY_STRINGS; } } if (enabled == null) { return EMPTY_STRINGS; } else { Set enabledSet = new LinkedHashSet(enabled.length + extraCiphers.length); synchronized (this) { for (int i = 0; i < enabled.length; i++) { String mapped = toJavaCipherSuite(enabled[i]); final String cipher = mapped == null ? enabled[i] : mapped; if ((!tls13Enabled || !OpenSsl.isTlsv13Supported()) && SslUtils.isTLSv13Cipher(cipher)) { continue; } enabledSet.add(cipher); } Collections.addAll(enabledSet, extraCiphers); } return enabledSet.toArray(EMPTY_STRINGS); } } @Override public final void setEnabledCipherSuites(String[] cipherSuites) { checkNotNull(cipherSuites, "cipherSuites"); final StringBuilder buf = new StringBuilder(); final StringBuilder bufTLSv13 = new StringBuilder(); CipherSuiteConverter.convertToCipherStrings(Arrays.asList(cipherSuites), buf, bufTLSv13, OpenSsl.isBoringSSL()); final String cipherSuiteSpec = buf.toString(); final String cipherSuiteSpecTLSv13 = bufTLSv13.toString(); if (!OpenSsl.isTlsv13Supported() && !cipherSuiteSpecTLSv13.isEmpty()) { throw new IllegalArgumentException("TLSv1.3 is not supported by this java version."); } synchronized (this) { hasTLSv13Cipher = !cipherSuiteSpecTLSv13.isEmpty(); if (!isDestroyed()) { try { // Set non TLSv1.3 ciphers. SSL.setCipherSuites(ssl, cipherSuiteSpec, false); if (OpenSsl.isTlsv13Supported()) { // Set TLSv1.3 ciphers. SSL.setCipherSuites(ssl, OpenSsl.checkTls13Ciphers(logger, cipherSuiteSpecTLSv13), true); } // We also need to update the enabled protocols to ensure we disable the protocol if there are // no compatible ciphers left. Set protocols = new HashSet(enabledProtocols); // We have no ciphers that are compatible with none-TLSv1.3, let us explicit disable all other // protocols. if (cipherSuiteSpec.isEmpty()) { protocols.remove(SslProtocols.TLS_v1); protocols.remove(SslProtocols.TLS_v1_1); protocols.remove(SslProtocols.TLS_v1_2); protocols.remove(SslProtocols.SSL_v3); protocols.remove(SslProtocols.SSL_v2); protocols.remove(SslProtocols.SSL_v2_HELLO); } // We have no ciphers that are compatible with TLSv1.3, let us explicit disable it. if (cipherSuiteSpecTLSv13.isEmpty()) { protocols.remove(SslProtocols.TLS_v1_3); } // Update the protocols but not cache the value. We only cache when we call it from the user // code or when we construct the engine. setEnabledProtocols0(protocols.toArray(EMPTY_STRINGS), !hasTLSv13Cipher); } catch (Exception e) { throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec, e); } } else { throw new IllegalStateException("failed to enable cipher suites: " + cipherSuiteSpec); } } } @Override public final String[] getSupportedProtocols() { return OpenSsl.SUPPORTED_PROTOCOLS_SET.toArray(EMPTY_STRINGS); } @Override public final String[] getEnabledProtocols() { return enabledProtocols.toArray(EMPTY_STRINGS); } private static boolean isProtocolEnabled(int opts, int disableMask, String protocolString) { // We also need to check if the actual protocolString is supported as depending on the openssl API // implementations it may use a disableMask of 0 (BoringSSL is doing this for example). return (opts & disableMask) == 0 && OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(protocolString); } /** * {@inheritDoc} * TLS doesn't support a way to advertise non-contiguous versions from the client's perspective, and the client * just advertises the max supported version. The TLS protocol also doesn't support all different combinations of * discrete protocols, and instead assumes contiguous ranges. OpenSSL has some unexpected behavior * (e.g. handshake failures) if non-contiguous protocols are used even where there is a compatible set of protocols * and ciphers. For these reasons this method will determine the minimum protocol and the maximum protocol and * enabled a contiguous range from [min protocol, max protocol] in OpenSSL. */ @Override public final void setEnabledProtocols(String[] protocols) { checkNotNullWithIAE(protocols, "protocols"); synchronized (this) { enabledProtocols.clear(); // Seems like there is no way to explicit disable SSLv2Hello in openssl, so it is always enabled enabledProtocols.add(SslProtocols.SSL_v2_HELLO); Collections.addAll(enabledProtocols, protocols); setEnabledProtocols0(protocols, !hasTLSv13Cipher); } } private void setEnabledProtocols0(String[] protocols, boolean explicitDisableTLSv13) { assert Thread.holdsLock(this); // This is correct from the API docs int minProtocolIndex = OPENSSL_OP_NO_PROTOCOLS.length; int maxProtocolIndex = 0; for (String p: protocols) { if (!OpenSsl.SUPPORTED_PROTOCOLS_SET.contains(p)) { throw new IllegalArgumentException("Protocol " + p + " is not supported."); } if (p.equals(SslProtocols.SSL_v2)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV2; } } else if (p.equals(SslProtocols.SSL_v3)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_SSLV3; } } else if (p.equals(SslProtocols.TLS_v1)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1; } } else if (p.equals(SslProtocols.TLS_v1_1)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_1; } } else if (p.equals(SslProtocols.TLS_v1_2)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_2; } } else if (!explicitDisableTLSv13 && p.equals(SslProtocols.TLS_v1_3)) { if (minProtocolIndex > OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { minProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; } if (maxProtocolIndex < OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3) { maxProtocolIndex = OPENSSL_OP_NO_PROTOCOL_INDEX_TLSv1_3; } } } if (!isDestroyed()) { // Clear out options which disable protocols SSL.clearOptions(ssl, SSL.SSL_OP_NO_SSLv2 | SSL.SSL_OP_NO_SSLv3 | SSL.SSL_OP_NO_TLSv1 | SSL.SSL_OP_NO_TLSv1_1 | SSL.SSL_OP_NO_TLSv1_2 | SSL.SSL_OP_NO_TLSv1_3); int opts = 0; for (int i = 0; i < minProtocolIndex; ++i) { opts |= OPENSSL_OP_NO_PROTOCOLS[i]; } assert maxProtocolIndex != MAX_VALUE; for (int i = maxProtocolIndex + 1; i < OPENSSL_OP_NO_PROTOCOLS.length; ++i) { opts |= OPENSSL_OP_NO_PROTOCOLS[i]; } // Disable protocols we do not want SSL.setOptions(ssl, opts); } else { throw new IllegalStateException("failed to enable protocols: " + Arrays.asList(protocols)); } } @Override public final SSLSession getSession() { return session; } @Override public final synchronized void beginHandshake() throws SSLException { switch (handshakeState) { case STARTED_IMPLICITLY: checkEngineClosed(); // A user did not start handshake by calling this method by him/herself, // but handshake has been started already by wrap() or unwrap() implicitly. // Because it's the user's first time to call this method, it is unfair to // raise an exception. From the user's standpoint, he or she never asked // for renegotiation. handshakeState = HandshakeState.STARTED_EXPLICITLY; // Next time this method is invoked by the user, calculateMaxWrapOverhead(); // we should raise an exception. break; case STARTED_EXPLICITLY: // Nothing to do as the handshake is not done yet. break; case FINISHED: throw new SSLException("renegotiation unsupported"); case NOT_STARTED: handshakeState = HandshakeState.STARTED_EXPLICITLY; if (handshake() == NEED_TASK) { // Set needTask to true so getHandshakeStatus() will return the correct value. needTask = true; } calculateMaxWrapOverhead(); break; default: throw new Error(); } } private void checkEngineClosed() throws SSLException { if (isDestroyed()) { throw new SSLException("engine closed"); } } private static SSLEngineResult.HandshakeStatus pendingStatus(int pendingStatus) { // Depending on if there is something left in the BIO we need to WRAP or UNWRAP return pendingStatus > 0 ? NEED_WRAP : NEED_UNWRAP; } private static boolean isEmpty(Object[] arr) { return arr == null || arr.length == 0; } private static boolean isEmpty(byte[] cert) { return cert == null || cert.length == 0; } private SSLEngineResult.HandshakeStatus handshakeException() throws SSLException { if (SSL.bioLengthNonApplication(networkBIO) > 0) { // There is something pending, we need to consume it first via a WRAP so we don't loose anything. return NEED_WRAP; } Throwable exception = pendingException; assert exception != null; pendingException = null; shutdown(); if (exception instanceof SSLHandshakeException) { throw (SSLHandshakeException) exception; } SSLHandshakeException e = new SSLHandshakeException("General OpenSslEngine problem"); e.initCause(exception); throw e; } /** * Should be called if the handshake will be failed due a callback that throws an exception. * This cause will then be used to give more details as part of the {@link SSLHandshakeException}. */ final void initHandshakeException(Throwable cause) { if (pendingException == null) { pendingException = cause; } else { ThrowableUtil.addSuppressed(pendingException, cause); } } private SSLEngineResult.HandshakeStatus handshake() throws SSLException { if (needTask) { return NEED_TASK; } if (handshakeState == HandshakeState.FINISHED) { return FINISHED; } checkEngineClosed(); if (pendingException != null) { // Let's call SSL.doHandshake(...) again in case there is some async operation pending that would fill the // outbound buffer. if (SSL.doHandshake(ssl) <= 0) { // Clear any error that was put on the stack by the handshake SSL.clearError(); } return handshakeException(); } // Adding the OpenSslEngine to the OpenSslEngineMap so it can be used in the AbstractCertificateVerifier. engineMap.add(this); if (!sessionSet) { if (!parentContext.sessionContext().setSessionFromCache(ssl, session, getPeerHost(), getPeerPort())) { // The session was not reused via the cache. Call prepareHandshake() to ensure we remove all previous // stored key-value pairs. session.prepareHandshake(); } sessionSet = true; } int code = SSL.doHandshake(ssl); if (code <= 0) { int sslError = SSL.getError(ssl, code); if (sslError == SSL.SSL_ERROR_WANT_READ || sslError == SSL.SSL_ERROR_WANT_WRITE) { return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); } if (sslError == SSL.SSL_ERROR_WANT_X509_LOOKUP || sslError == SSL.SSL_ERROR_WANT_CERTIFICATE_VERIFY || sslError == SSL.SSL_ERROR_WANT_PRIVATE_KEY_OPERATION) { return NEED_TASK; } if (needWrapAgain(SSL.getLastErrorNumber())) { // There is something that needs to be send to the remote peer before we can teardown. // This is most likely some alert. return NEED_WRAP; } // Check if we have a pending exception that was created during the handshake and if so throw it after // shutdown the connection. if (pendingException != null) { return handshakeException(); } // Everything else is considered as error throw shutdownWithError("SSL_do_handshake", sslError); } // We have produced more data as part of the handshake if this is the case the user should call wrap(...) if (SSL.bioLengthNonApplication(networkBIO) > 0) { return NEED_WRAP; } // if SSL_do_handshake returns > 0 or sslError == SSL.SSL_ERROR_NAME it means the handshake was finished. session.handshakeFinished(SSL.getSessionId(ssl), SSL.getCipherForSSL(ssl), SSL.getVersion(ssl), SSL.getPeerCertificate(ssl), SSL.getPeerCertChain(ssl), SSL.getTime(ssl) * 1000L, parentContext.sessionTimeout() * 1000L); selectApplicationProtocol(); return FINISHED; } private SSLEngineResult.HandshakeStatus mayFinishHandshake( SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return hs == NEED_UNWRAP && bytesProduced > 0 || hs == NEED_WRAP && bytesConsumed > 0 ? handshake() : mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED); } private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) throws SSLException { if (status == NOT_HANDSHAKING) { if (handshakeState != HandshakeState.FINISHED) { // If the status was NOT_HANDSHAKING and we not finished the handshake we need to call // SSL_do_handshake() again return handshake(); } if (!isDestroyed() && SSL.bioLengthNonApplication(networkBIO) > 0) { // We have something left that needs to be wrapped. return NEED_WRAP; } } return status; } @Override public final synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() { // Check if we are in the initial handshake phase or shutdown phase if (needPendingStatus()) { if (needTask) { // There is a task outstanding return NEED_TASK; } return pendingStatus(SSL.bioLengthNonApplication(networkBIO)); } return NOT_HANDSHAKING; } private SSLEngineResult.HandshakeStatus getHandshakeStatus(int pending) { // Check if we are in the initial handshake phase or shutdown phase if (needPendingStatus()) { if (needTask) { // There is a task outstanding return NEED_TASK; } return pendingStatus(pending); } return NOT_HANDSHAKING; } private boolean needPendingStatus() { return handshakeState != HandshakeState.NOT_STARTED && !isDestroyed() && (handshakeState != HandshakeState.FINISHED || isInboundDone() || isOutboundDone()); } /** * Converts the specified OpenSSL cipher suite to the Java cipher suite. */ private String toJavaCipherSuite(String openSslCipherSuite) { if (openSslCipherSuite == null) { return null; } String version = SSL.getVersion(ssl); String prefix = toJavaCipherSuitePrefix(version); return CipherSuiteConverter.toJava(openSslCipherSuite, prefix); } /** * Converts the protocol version string returned by {@link SSL#getVersion(long)} to protocol family string. */ private static String toJavaCipherSuitePrefix(String protocolVersion) { final char c; if (protocolVersion == null || protocolVersion.isEmpty()) { c = 0; } else { c = protocolVersion.charAt(0); } switch (c) { case 'T': return "TLS"; case 'S': return "SSL"; default: return "UNKNOWN"; } } @Override public final void setUseClientMode(boolean clientMode) { if (clientMode != this.clientMode) { throw new UnsupportedOperationException(); } } @Override public final boolean getUseClientMode() { return clientMode; } @Override public final void setNeedClientAuth(boolean b) { setClientAuth(b ? ClientAuth.REQUIRE : ClientAuth.NONE); } @Override public final boolean getNeedClientAuth() { return clientAuth == ClientAuth.REQUIRE; } @Override public final void setWantClientAuth(boolean b) { setClientAuth(b ? ClientAuth.OPTIONAL : ClientAuth.NONE); } @Override public final boolean getWantClientAuth() { return clientAuth == ClientAuth.OPTIONAL; } /** * See SSL_set_verify and * {@link SSL#setVerify(long, int, int)}. */ @UnstableApi public final synchronized void setVerify(int verifyMode, int depth) { if (!isDestroyed()) { SSL.setVerify(ssl, verifyMode, depth); } } private void setClientAuth(ClientAuth mode) { if (clientMode) { return; } synchronized (this) { if (clientAuth == mode) { // No need to issue any JNI calls if the mode is the same return; } if (!isDestroyed()) { switch (mode) { case NONE: SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; case REQUIRE: SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; case OPTIONAL: SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, ReferenceCountedOpenSslContext.VERIFY_DEPTH); break; default: throw new Error(mode.toString()); } } clientAuth = mode; } } @Override public final void setEnableSessionCreation(boolean b) { if (b) { throw new UnsupportedOperationException(); } } @Override public final boolean getEnableSessionCreation() { return false; } @SuppressJava6Requirement(reason = "Usage guarded by java version check") @Override public final synchronized SSLParameters getSSLParameters() { SSLParameters sslParameters = super.getSSLParameters(); int version = PlatformDependent.javaVersion(); if (version >= 7) { sslParameters.setEndpointIdentificationAlgorithm(endPointIdentificationAlgorithm); Java7SslParametersUtils.setAlgorithmConstraints(sslParameters, algorithmConstraints); if (version >= 8) { if (sniHostNames != null) { Java8SslUtils.setSniHostNames(sslParameters, sniHostNames); } if (!isDestroyed()) { Java8SslUtils.setUseCipherSuitesOrder( sslParameters, (SSL.getOptions(ssl) & SSL.SSL_OP_CIPHER_SERVER_PREFERENCE) != 0); } Java8SslUtils.setSNIMatchers(sslParameters, matchers); } } return sslParameters; } @SuppressJava6Requirement(reason = "Usage guarded by java version check") @Override public final synchronized void setSSLParameters(SSLParameters sslParameters) { int version = PlatformDependent.javaVersion(); if (version >= 7) { if (sslParameters.getAlgorithmConstraints() != null) { throw new IllegalArgumentException("AlgorithmConstraints are not supported."); } boolean isDestroyed = isDestroyed(); if (version >= 8) { if (!isDestroyed) { if (clientMode) { final List sniHostNames = Java8SslUtils.getSniHostNames(sslParameters); for (String name: sniHostNames) { SSL.setTlsExtHostName(ssl, name); } this.sniHostNames = sniHostNames; } if (Java8SslUtils.getUseCipherSuitesOrder(sslParameters)) { SSL.setOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); } else { SSL.clearOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE); } } matchers = sslParameters.getSNIMatchers(); } final String endPointIdentificationAlgorithm = sslParameters.getEndpointIdentificationAlgorithm(); if (!isDestroyed) { // If the user asks for hostname verification we must ensure we verify the peer. // If the user disables hostname verification we leave it up to the user to change the mode manually. if (clientMode && isEndPointVerificationEnabled(endPointIdentificationAlgorithm)) { SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRED, -1); } } this.endPointIdentificationAlgorithm = endPointIdentificationAlgorithm; algorithmConstraints = sslParameters.getAlgorithmConstraints(); } super.setSSLParameters(sslParameters); } private static boolean isEndPointVerificationEnabled(String endPointIdentificationAlgorithm) { return endPointIdentificationAlgorithm != null && !endPointIdentificationAlgorithm.isEmpty(); } private boolean isDestroyed() { return destroyed; } final boolean checkSniHostnameMatch(byte[] hostname) { return Java8SslUtils.checkSniHostnameMatch(matchers, hostname); } @Override public String getNegotiatedApplicationProtocol() { return applicationProtocol; } private static long bufferAddress(ByteBuffer b) { assert b.isDirect(); if (PlatformDependent.hasUnsafe()) { return PlatformDependent.directBufferAddress(b); } return Buffer.address(b); } /** * Select the application protocol used. */ private void selectApplicationProtocol() throws SSLException { ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn.selectedListenerFailureBehavior(); List protocols = apn.protocols(); String applicationProtocol; switch (apn.protocol()) { case NONE: break; // We always need to check for applicationProtocol == null as the remote peer may not support // the TLS extension or may have returned an empty selection. case ALPN: applicationProtocol = SSL.getAlpnSelected(ssl); if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; case NPN: applicationProtocol = SSL.getNextProtoNegotiated(ssl); if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; case NPN_AND_ALPN: applicationProtocol = SSL.getAlpnSelected(ssl); if (applicationProtocol == null) { applicationProtocol = SSL.getNextProtoNegotiated(ssl); } if (applicationProtocol != null) { ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol( protocols, behavior, applicationProtocol); } break; default: throw new Error(); } } private String selectApplicationProtocol(List protocols, ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior, String applicationProtocol) throws SSLException { if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT) { return applicationProtocol; } else { int size = protocols.size(); assert size > 0; if (protocols.contains(applicationProtocol)) { return applicationProtocol; } else { if (behavior == ApplicationProtocolConfig.SelectedListenerFailureBehavior.CHOOSE_MY_LAST_PROTOCOL) { return protocols.get(size - 1); } else { throw new SSLException("unknown protocol " + applicationProtocol); } } } } private static final X509Certificate[] JAVAX_CERTS_NOT_SUPPORTED = new X509Certificate[0]; private final class DefaultOpenSslSession implements OpenSslSession { private final OpenSslSessionContext sessionContext; // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any // thread. private X509Certificate[] x509PeerCerts; private Certificate[] peerCerts; private boolean valid = true; private String protocol; private String cipher; private OpenSslSessionId id = OpenSslSessionId.NULL_ID; private long creationTime; // Updated once a new handshake is started and so the SSLSession reused. private long lastAccessed = -1; private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH; private volatile Certificate[] localCertificateChain; private volatile Map keyValueStorage = new ConcurrentHashMap(); DefaultOpenSslSession(OpenSslSessionContext sessionContext) { this.sessionContext = sessionContext; } private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) { return new SSLSessionBindingEvent(session, name); } @Override public void prepareHandshake() { keyValueStorage.clear(); } @Override public void setSessionDetails( long creationTime, long lastAccessedTime, OpenSslSessionId sessionId, Map keyValueStorage) { synchronized (ReferenceCountedOpenSslEngine.this) { if (this.id == OpenSslSessionId.NULL_ID) { this.id = sessionId; this.creationTime = creationTime; this.lastAccessed = lastAccessedTime; // Update the key value storage. It's fine to just drop the previous stored values on the floor // as the JDK does the same in the sense that it will use a new SSLSessionImpl instance once the // handshake was done this.keyValueStorage = keyValueStorage; } } } @Override public Map keyValueStorage() { return keyValueStorage; } @Override public OpenSslSessionId sessionId() { synchronized (ReferenceCountedOpenSslEngine.this) { if (this.id == OpenSslSessionId.NULL_ID && !isDestroyed()) { byte[] sessionId = SSL.getSessionId(ssl); if (sessionId != null) { id = new OpenSslSessionId(sessionId); } } return id; } } @Override public void setLocalCertificate(Certificate[] localCertificate) { this.localCertificateChain = localCertificate; } @Override public byte[] getId() { return sessionId().cloneBytes(); } @Override public OpenSslSessionContext getSessionContext() { return sessionContext; } @Override public long getCreationTime() { synchronized (ReferenceCountedOpenSslEngine.this) { return creationTime; } } @Override public void setLastAccessedTime(long time) { synchronized (ReferenceCountedOpenSslEngine.this) { this.lastAccessed = time; } } @Override public long getLastAccessedTime() { // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet. synchronized (ReferenceCountedOpenSslEngine.this) { return lastAccessed == -1 ? creationTime : lastAccessed; } } @Override public void invalidate() { synchronized (ReferenceCountedOpenSslEngine.this) { valid = false; sessionContext.removeFromCache(id); } } @Override public boolean isValid() { synchronized (ReferenceCountedOpenSslEngine.this) { return valid || sessionContext.isInCache(id); } } @Override public void putValue(String name, Object value) { checkNotNull(name, "name"); checkNotNull(value, "value"); final Object old = keyValueStorage.put(name, value); if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we always use the wrapper if needed. ((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name)); } notifyUnbound(old, name); } @Override public Object getValue(String name) { checkNotNull(name, "name"); return keyValueStorage.get(name); } @Override public void removeValue(String name) { checkNotNull(name, "name"); final Object old = keyValueStorage.remove(name); notifyUnbound(old, name); } @Override public String[] getValueNames() { return keyValueStorage.keySet().toArray(EMPTY_STRINGS); } private void notifyUnbound(Object value, String name) { if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we always use the wrapper if needed. ((SSLSessionBindingListener) value).valueUnbound(newSSLSessionBindingEvent(name)); } } /** * Finish the handshake and so init everything in the {@link OpenSslSession} that should be accessible by * the user. */ @Override public void handshakeFinished(byte[] id, String cipher, String protocol, byte[] peerCertificate, byte[][] peerCertificateChain, long creationTime, long timeout) throws SSLException { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { if (this.id == OpenSslSessionId.NULL_ID) { // if the handshake finished and it was not a resumption let ensure we try to set the id this.id = id == null ? OpenSslSessionId.NULL_ID : new OpenSslSessionId(id); // Once the handshake was done the lastAccessed and creationTime should be the same if we // did not set it earlier via setSessionDetails(...) this.creationTime = lastAccessed = creationTime; } this.cipher = toJavaCipherSuite(cipher); this.protocol = protocol; if (clientMode) { if (isEmpty(peerCertificateChain)) { peerCerts = EmptyArrays.EMPTY_CERTIFICATES; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } } else { peerCerts = new Certificate[peerCertificateChain.length]; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[peerCertificateChain.length]; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } initCerts(peerCertificateChain, 0); } } else { // if used on the server side SSL_get_peer_cert_chain(...) will not include the remote peer // certificate. We use SSL_get_peer_certificate to get it in this case and add it to our // array later. // // See https://www.openssl.org/docs/ssl/SSL_get_peer_cert_chain.html if (isEmpty(peerCertificate)) { peerCerts = EmptyArrays.EMPTY_CERTIFICATES; x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; } else { if (isEmpty(peerCertificateChain)) { peerCerts = new Certificate[] {new LazyX509Certificate(peerCertificate)}; if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[] { new LazyJavaxX509Certificate(peerCertificate) }; } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } } else { peerCerts = new Certificate[peerCertificateChain.length + 1]; peerCerts[0] = new LazyX509Certificate(peerCertificate); if (OpenSsl.JAVAX_CERTIFICATE_CREATION_SUPPORTED) { x509PeerCerts = new X509Certificate[peerCertificateChain.length + 1]; x509PeerCerts[0] = new LazyJavaxX509Certificate(peerCertificate); } else { x509PeerCerts = JAVAX_CERTS_NOT_SUPPORTED; } initCerts(peerCertificateChain, 1); } } } calculateMaxWrapOverhead(); handshakeState = HandshakeState.FINISHED; } else { throw new SSLException("Already closed"); } } } private void initCerts(byte[][] chain, int startPos) { for (int i = 0; i < chain.length; i++) { int certPos = startPos + i; peerCerts[certPos] = new LazyX509Certificate(chain[i]); if (x509PeerCerts != JAVAX_CERTS_NOT_SUPPORTED) { x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]); } } } @Override public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { synchronized (ReferenceCountedOpenSslEngine.this) { if (isEmpty(peerCerts)) { throw new SSLPeerUnverifiedException("peer not verified"); } return peerCerts.clone(); } } @Override public Certificate[] getLocalCertificates() { Certificate[] localCerts = this.localCertificateChain; if (localCerts == null) { return null; } return localCerts.clone(); } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { synchronized (ReferenceCountedOpenSslEngine.this) { if (x509PeerCerts == JAVAX_CERTS_NOT_SUPPORTED) { // Not supported by the underlying JDK, so just throw. This is fine in terms of the API // contract. See SSLSession.html#getPeerCertificateChain(). throw new UnsupportedOperationException(); } if (isEmpty(x509PeerCerts)) { throw new SSLPeerUnverifiedException("peer not verified"); } return x509PeerCerts.clone(); } } @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { Certificate[] peer = getPeerCertificates(); // No need for null or length > 0 is needed as this is done in getPeerCertificates() // already. return ((java.security.cert.X509Certificate) peer[0]).getSubjectX500Principal(); } @Override public Principal getLocalPrincipal() { Certificate[] local = this.localCertificateChain; if (local == null || local.length == 0) { return null; } return ((java.security.cert.X509Certificate) local[0]).getSubjectX500Principal(); } @Override public String getCipherSuite() { synchronized (ReferenceCountedOpenSslEngine.this) { if (cipher == null) { return SslUtils.INVALID_CIPHER; } return cipher; } } @Override public String getProtocol() { String protocol = this.protocol; if (protocol == null) { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { protocol = SSL.getVersion(ssl); } else { protocol = StringUtil.EMPTY_STRING; } } } return protocol; } @Override public String getPeerHost() { return ReferenceCountedOpenSslEngine.this.getPeerHost(); } @Override public int getPeerPort() { return ReferenceCountedOpenSslEngine.this.getPeerPort(); } @Override public int getPacketBufferSize() { return SSL.SSL_MAX_ENCRYPTED_LENGTH; } @Override public int getApplicationBufferSize() { return applicationBufferSize; } @Override public void tryExpandApplicationBufferSize(int packetLengthDataOnly) { if (packetLengthDataOnly > MAX_PLAINTEXT_LENGTH && applicationBufferSize != MAX_RECORD_SIZE) { applicationBufferSize = MAX_RECORD_SIZE; } } @Override public String toString() { return "DefaultOpenSslSession{" + "sessionContext=" + sessionContext + ", id=" + id + '}'; } @Override public int hashCode() { return sessionId().hashCode(); } @Override public boolean equals(Object o) { if (o == this) { return true; } // We trust all sub-types as we use different types but the interface is package-private if (!(o instanceof OpenSslSession)) { return false; } return sessionId().equals(((OpenSslSession) o).sessionId()); } } private interface NativeSslException { int errorCode(); } private static final class OpenSslException extends SSLException implements NativeSslException { private final int errorCode; OpenSslException(String reason, int errorCode) { super(reason); this.errorCode = errorCode; } @Override public int errorCode() { return errorCode; } } private static final class OpenSslHandshakeException extends SSLHandshakeException implements NativeSslException { private final int errorCode; OpenSslHandshakeException(String reason, int errorCode) { super(reason); this.errorCode = errorCode; } @Override public int errorCode() { return errorCode; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy