Java tutorial
/* * 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: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.ssl; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; 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.ObjectUtil; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import io.netty.util.internal.SuppressJava6Requirement; 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.net.ssl.SSLSessionContext; import javax.security.cert.X509Certificate; import static io.netty.handler.ssl.OpenSsl.memoryAddress; import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2; import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2_HELLO; import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V3; import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1; import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_1; import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2; import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_3; import static io.netty.handler.ssl.SslUtils.SSL_RECORD_HEADER_LENGTH; import static io.netty.util.internal.EmptyArrays.EMPTY_CERTIFICATES; import static io.netty.util.internal.EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES; import static io.netty.util.internal.ObjectUtil.checkNotNull; 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 * <a href="https://www.openssl.org/docs/crypto/BIO_s_bio.html#EXAMPLE">OpenSSL BIO abstractions</a>. * <p>Instances of this class must be {@link #release() released} or else native memory will leak! * * <p>Instances of this class <strong>must</strong> 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<ReferenceCountedOpenSslEngine> 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! */ private 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; // Reference Counting private final ResourceLeakTracker<ReferenceCountedOpenSslEngine> 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 volatile ClientAuth clientAuth = ClientAuth.NONE; private volatile Certificate[] localCertificateChain; // Updated once a new handshake is started and so the SSLSession reused. private volatile long lastAccessed = -1; private String endPointIdentificationAlgorithm; // Store as object as AlgorithmConstraints only exists since java 7. private Object algorithmConstraints; private List<String> 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 handshakeException; /** * 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(); 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 = EmptyArrays.EMPTY_STRINGS; } else { String[] algs = SSL.getSigAlgs(ssl); if (algs == null) { peerSupportedSignatureAlgorithms = EmptyArrays.EMPTY_STRINGS; } else { Set<String> algorithmList = new LinkedHashSet<String>(algs.length); for (String alg : algs) { String converted = SignatureAlgorithmConverter.toJavaName(alg); if (converted != null) { algorithmList.add(converted); } } peerSupportedSignatureAlgorithms = algorithmList.toArray(new String[0]); } } } return peerSupportedSignatureAlgorithms.clone(); } } @Override public List<byte[]> getStatusResponses() { byte[] ocspResponse = null; if (enableOcsp && clientMode) { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { ocspResponse = SSL.getOcspResponse(ssl); } } } return ocspResponse == null ? Collections.<byte[]>emptyList() : Collections.singletonList(ocspResponse); } }; } else { session = new DefaultOpenSslSession(context.sessionContext()); } engineMap = context.engineMap; enableOcsp = context.enableOcsp; // context.keyCertChain will only be non-null if we do not use the KeyManagerFactory. In this case // localCertificateChain will be set in setKeyMaterial(...). localCertificateChain = context.keyCertChain; this.jdkCompatibilityMode = jdkCompatibilityMode; 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); if (context.protocols != null) { 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)) { 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); } // 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 EmptyArrays.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()); } localCertificateChain = keyMaterial.certificateChain(); return true; } final synchronized SecretKeySpec masterKey() { if (isDestroyed()) { return null; } return new SecretKeySpec(SSL.getMasterKey(ssl), "AES"); } /** * 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); } @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; 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; } /** * Write encrypted data to the OpenSSL network BIO. */ private ByteBuf writeEncryptedData(final ByteBuffer src, int len) { 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) { 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. */ final int calculateMaxLengthForWrap(int plaintextLength, int numComponents) { return (int) min(maxWrapBufferSize, 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 if (srcs == null) { throw new IllegalArgumentException("srcs is null"); } if (dst == null) { throw new IllegalArgumentException("dst is null"); } 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; // 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 (handshakeException != 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) { 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; // Flush any data that may have been written implicitly by OpenSSL in case a shutdown/alert occurs. bytesProduced = SSL.bioFlushByteBuffer(networkBIO); 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)); } if (bytesWritten > 0) { bytesConsumed += bytesWritten; // Determine how much encrypted data was generated: final int pendingNow = SSL.bioLengthByteBuffer(networkBIO); bytesProduced += bioLengthBefore - pendingNow; bioLengthBefore = pendingNow; 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 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 != FINISHED ? getHandshakeStatus() : FINISHED), bytesConsumed, bytesProduced); } private SSLEngineResult newResultMayFinishHandshake(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus hs, int bytesConsumed, int bytesProduced) throws SSLException { return newResult(status, mayFinishHandshake(hs != FINISHED ? getHandshakeStatus() : FINISHED), 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) { String errorString = SSL.getErrorString(error); if (logger.isDebugEnabled()) { logger.debug("{} failed with {}: OpenSSL error: {} {}", operation, sslError, error, errorString); } // There was an internal error -- shutdown shutdown(); if (handshakeState == HandshakeState.FINISHED) { return new SSLException(errorString); } SSLHandshakeException exception = new SSLHandshakeException(errorString); // If we have a handshakeException stored already we should include it as well to help the user debug things. if (handshakeException != null) { exception.initCause(handshakeException); handshakeException = null; } return exception; } 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 ObjectUtil.checkNotNull(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 + "))"); } if (dsts == null) { throw new IllegalArgumentException("dsts is null"); } 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 = dsts[i]; if (dst == null) { throw new IllegalArgumentException("dsts[" + i + "] is null"); } 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 = srcs[i]; if (src == null) { throw new IllegalArgumentException("srcs[" + i + "] is null"); } len += src.remaining(); } synchronized (this) { if (isInboundDone()) { return isOutboundDone() || isDestroyed() ? CLOSED_NOT_HANDSHAKING : NEED_WRAP_CLOSED; } SSLEngineResult.HandshakeStatus status = NOT_HANDSHAKING; // 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 (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); bioWriteCopyBuf = writeEncryptedData(src, pendingEncryptedBytes); } 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 = readPlaintextData(dst); // 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 SSLEngineResult sslReadErrorResult(int error, int stackError, int bytesConsumed, int bytesProduced) throws SSLException { // 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) { if (handshakeException == null && handshakeState != HandshakeState.FINISHED) { // we seems 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. handshakeException = new SSLHandshakeException(SSL.getErrorString(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 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() && SSL.getHandshakeCount(ssl) > 1 && // 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. !SslUtils.PROTOCOL_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(); } } @Override public final synchronized Runnable getDelegatedTask() { if (isDestroyed()) { return null; } final Runnable task = SSL.getTask(ssl); if (task == null) { return null; } return new Runnable() { @Override public void run() { if (isDestroyed()) { // The engine was destroyed in the meantime, just return. return; } try { task.run(); } finally { // The task was run, reset needTask to false so getHandshakeStatus() returns the correct value. needTask = false; } } }; } @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 http://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(new String[0]); } @Override public final String[] getEnabledCipherSuites() { final String[] enabled; synchronized (this) { if (!isDestroyed()) { enabled = SSL.getCiphers(ssl); } else { return EmptyArrays.EMPTY_STRINGS; } } if (enabled == null) { return EmptyArrays.EMPTY_STRINGS; } else { List<String> enabledList = new ArrayList<String>(); synchronized (this) { for (int i = 0; i < enabled.length; i++) { String mapped = toJavaCipherSuite(enabled[i]); final String cipher = mapped == null ? enabled[i] : mapped; if (!OpenSsl.isTlsv13Supported() && SslUtils.isTLSv13Cipher(cipher)) { continue; } enabledList.add(cipher); } } return enabledList.toArray(new String[0]); } } @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) { if (!isDestroyed()) { // TODO: Should we also adjust the protocols based on if there are any ciphers left that can be used // for TLSv1.3 or for previor SSL/TLS versions ? try { // Set non TLSv1.3 ciphers. SSL.setCipherSuites(ssl, cipherSuiteSpec, false); if (OpenSsl.isTlsv13Supported()) { // Set TLSv1.3 ciphers. SSL.setCipherSuites(ssl, cipherSuiteSpecTLSv13, true); } } 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(new String[0]); } @Override public final String[] getEnabledProtocols() { List<String> enabled = new ArrayList<String>(6); // Seems like there is no way to explicit disable SSLv2Hello in openssl so it is always enabled enabled.add(PROTOCOL_SSL_V2_HELLO); int opts; synchronized (this) { if (!isDestroyed()) { opts = SSL.getOptions(ssl); } else { return enabled.toArray(new String[0]); } } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1, PROTOCOL_TLS_V1)) { enabled.add(PROTOCOL_TLS_V1); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_1, PROTOCOL_TLS_V1_1)) { enabled.add(PROTOCOL_TLS_V1_1); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_2, PROTOCOL_TLS_V1_2)) { enabled.add(PROTOCOL_TLS_V1_2); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_TLSv1_3, PROTOCOL_TLS_V1_3)) { enabled.add(PROTOCOL_TLS_V1_3); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv2, PROTOCOL_SSL_V2)) { enabled.add(PROTOCOL_SSL_V2); } if (isProtocolEnabled(opts, SSL.SSL_OP_NO_SSLv3, PROTOCOL_SSL_V3)) { enabled.add(PROTOCOL_SSL_V3); } return enabled.toArray(new String[0]); } 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) { if (protocols == null) { // This is correct from the API docs throw new IllegalArgumentException(); } 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(PROTOCOL_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(PROTOCOL_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(PROTOCOL_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(PROTOCOL_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(PROTOCOL_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 (p.equals(PROTOCOL_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; } } } synchronized (this) { 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 = handshakeException; assert exception != null; handshakeException = 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) { assert handshakeException == null; handshakeException = cause; } private SSLEngineResult.HandshakeStatus handshake() throws SSLException { if (needTask) { return NEED_TASK; } if (handshakeState == HandshakeState.FINISHED) { return FINISHED; } checkEngineClosed(); if (handshakeException != 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 (lastAccessed == -1) { lastAccessed = System.currentTimeMillis(); } 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; } // Check if we have a pending exception that was created during the handshake and if so throw it after // shutdown the connection. if (handshakeException != 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(); return FINISHED; } private SSLEngineResult.HandshakeStatus mayFinishHandshake(SSLEngineResult.HandshakeStatus status) throws SSLException { if (status == NOT_HANDSHAKING && 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(); } 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 <a href="https://www.openssl.org/docs/man1.0.2/ssl/SSL_set_verify.html">SSL_set_verify</a> and * {@link SSL#setVerify(long, int, int)}. */ @UnstableApi public final synchronized void setVerify(int verifyMode, int depth) { 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; } 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."); } if (version >= 8) { if (!isDestroyed()) { if (clientMode) { final List<String> 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(); final boolean endPointVerificationEnabled = isEndPointVerificationEnabled( endPointIdentificationAlgorithm); // 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 && endPointVerificationEnabled) { 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); } 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 String protocol; private String cipher; private byte[] id; private long creationTime; private volatile int applicationBufferSize = MAX_PLAINTEXT_LENGTH; // lazy init for memory reasons private Map<String, Object> values; DefaultOpenSslSession(OpenSslSessionContext sessionContext) { this.sessionContext = sessionContext; } private SSLSessionBindingEvent newSSLSessionBindingEvent(String name) { return new SSLSessionBindingEvent(session, name); } @Override public byte[] getId() { synchronized (ReferenceCountedOpenSslEngine.this) { if (id == null) { return EmptyArrays.EMPTY_BYTES; } return id.clone(); } } @Override public SSLSessionContext getSessionContext() { return sessionContext; } @Override public long getCreationTime() { synchronized (ReferenceCountedOpenSslEngine.this) { if (creationTime == 0 && !isDestroyed()) { creationTime = SSL.getTime(ssl) * 1000L; } } return creationTime; } @Override public long getLastAccessedTime() { long lastAccessed = ReferenceCountedOpenSslEngine.this.lastAccessed; // if lastAccessed is -1 we will just return the creation time as the handshake was not started yet. return lastAccessed == -1 ? getCreationTime() : lastAccessed; } @Override public void invalidate() { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { SSL.setTimeout(ssl, 0); } } } @Override public boolean isValid() { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { return System.currentTimeMillis() - (SSL.getTimeout(ssl) * 1000L) < (SSL.getTime(ssl) * 1000L); } } return false; } @Override public void putValue(String name, Object value) { ObjectUtil.checkNotNull(name, "name"); ObjectUtil.checkNotNull(value, "value"); final Object old; synchronized (this) { Map<String, Object> values = this.values; if (values == null) { // Use size of 2 to keep the memory overhead small values = this.values = new HashMap<String, Object>(2); } old = values.put(name, value); } if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we alway use the wrapper if needed. ((SSLSessionBindingListener) value).valueBound(newSSLSessionBindingEvent(name)); } notifyUnbound(old, name); } @Override public Object getValue(String name) { ObjectUtil.checkNotNull(name, "name"); synchronized (this) { if (values == null) { return null; } return values.get(name); } } @Override public void removeValue(String name) { ObjectUtil.checkNotNull(name, "name"); final Object old; synchronized (this) { Map<String, Object> values = this.values; if (values == null) { return; } old = values.remove(name); } notifyUnbound(old, name); } @Override public String[] getValueNames() { synchronized (this) { Map<String, Object> values = this.values; if (values == null || values.isEmpty()) { return EmptyArrays.EMPTY_STRINGS; } return values.keySet().toArray(new String[0]); } } private void notifyUnbound(Object value, String name) { if (value instanceof SSLSessionBindingListener) { // Use newSSLSessionBindingEvent so we alway 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() throws SSLException { synchronized (ReferenceCountedOpenSslEngine.this) { if (!isDestroyed()) { id = SSL.getSessionId(ssl); cipher = toJavaCipherSuite(SSL.getCipherForSSL(ssl)); protocol = SSL.getVersion(ssl); initPeerCerts(); selectApplicationProtocol(); calculateMaxWrapOverhead(); handshakeState = HandshakeState.FINISHED; } else { throw new SSLException("Already closed"); } } } /** * Init peer certificates that can be obtained via {@link #getPeerCertificateChain()} * and {@link #getPeerCertificates()}. */ private void initPeerCerts() { // Return the full chain from the JNI layer. byte[][] chain = SSL.getPeerCertChain(ssl); if (clientMode) { if (isEmpty(chain)) { peerCerts = EMPTY_CERTIFICATES; x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES; } else { peerCerts = new Certificate[chain.length]; x509PeerCerts = new X509Certificate[chain.length]; initCerts(chain, 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 byte[] clientCert = SSL.getPeerCertificate(ssl); if (isEmpty(clientCert)) { peerCerts = EMPTY_CERTIFICATES; x509PeerCerts = EMPTY_JAVAX_X509_CERTIFICATES; } else { if (isEmpty(chain)) { peerCerts = new Certificate[] { new OpenSslX509Certificate(clientCert) }; x509PeerCerts = new X509Certificate[] { new OpenSslJavaxX509Certificate(clientCert) }; } else { peerCerts = new Certificate[chain.length + 1]; x509PeerCerts = new X509Certificate[chain.length + 1]; peerCerts[0] = new OpenSslX509Certificate(clientCert); x509PeerCerts[0] = new OpenSslJavaxX509Certificate(clientCert); initCerts(chain, 1); } } } } private void initCerts(byte[][] chain, int startPos) { for (int i = 0; i < chain.length; i++) { int certPos = startPos + i; peerCerts[certPos] = new OpenSslX509Certificate(chain[i]); x509PeerCerts[certPos] = new OpenSslJavaxX509Certificate(chain[i]); } } /** * Select the application protocol used. */ private void selectApplicationProtocol() throws SSLException { ApplicationProtocolConfig.SelectedListenerFailureBehavior behavior = apn .selectedListenerFailureBehavior(); List<String> 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<String> 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); } } } } @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 = ReferenceCountedOpenSslEngine.this.localCertificateChain; if (localCerts == null) { return null; } return localCerts.clone(); } @Override public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException { synchronized (ReferenceCountedOpenSslEngine.this) { 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 = ReferenceCountedOpenSslEngine.this.localCertificateChain; if (local == null || local.length == 0) { return null; } return ((java.security.cert.X509Certificate) local[0]).getIssuerX500Principal(); } @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 maxEncryptedPacketLength(); } @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; } } } }