Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.wildfly.openssl;
import static org.wildfly.openssl.Messages.MESSAGES;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
public final class OpenSSLEngine extends SSLEngine {
private static final Logger LOG = Logger.getLogger(OpenSSLEngine.class.getName());
private static final SSLException ENGINE_CLOSED = new SSLException(MESSAGES.engineIsClosed());
private static final SSLException RENEGOTIATION_UNSUPPORTED = new SSLException(MESSAGES.renegotiationNotSupported());
private static final SSLException ENCRYPTED_PACKET_OVERSIZED = new SSLException(MESSAGES.oversidedPacket());
static {
ENGINE_CLOSED.setStackTrace(new StackTraceElement[0]);
RENEGOTIATION_UNSUPPORTED.setStackTrace(new StackTraceElement[0]);
ENCRYPTED_PACKET_OVERSIZED.setStackTrace(new StackTraceElement[0]);
DESTROYED_UPDATER = AtomicIntegerFieldUpdater.newUpdater(OpenSSLEngine.class, "destroyed");
}
static final int MAX_PLAINTEXT_LENGTH = 16 * 1024; // 2^14
private static final int MAX_COMPRESSED_LENGTH = MAX_PLAINTEXT_LENGTH + 1024;
private static final int MAX_CIPHERTEXT_LENGTH = MAX_COMPRESSED_LENGTH + 1024;
// Protocols
protected static final int VERIFY_DEPTH = 10;
private static final String[] SUPPORTED_PROTOCOLS = {
SSL.SSL_PROTO_SSLv2Hello,
SSL.SSL_PROTO_SSLv2,
SSL.SSL_PROTO_SSLv3,
SSL.SSL_PROTO_TLSv1,
SSL.SSL_PROTO_TLSv1_1,
SSL.SSL_PROTO_TLSv1_2
};
private static final Set SUPPORTED_PROTOCOLS_SET = new HashSet<>(Arrays.asList(SUPPORTED_PROTOCOLS));
// Header (5) + Data (2^14) + Compression (1024) + Encryption (1024) + MAC (20) + Padding (256)
static final int MAX_ENCRYPTED_PACKET_LENGTH = MAX_CIPHERTEXT_LENGTH + 5 + 20 + 256;
public static final int DEFAULT_CERTIFICATE_VALIDATION_DEPTH = 100;
public OpenSSLSessionContext getSessionContext() {
if(clientMode) {
return openSSLContextSPI.engineGetClientSessionContext();
} else {
return openSSLContextSPI.engineGetServerSessionContext();
}
}
public boolean isClientMode() {
return clientMode;
}
enum ClientAuthMode {
NONE,
OPTIONAL,
REQUIRE,
}
private static final AtomicIntegerFieldUpdater DESTROYED_UPDATER;
static final String INVALID_CIPHER = "SSL_NULL_WITH_NULL_NULL";
private static final long EMPTY_ADDR = SSL.getInstance().bufferAddress(ByteBuffer.allocate(0));
// OpenSSL state
private final long sslCtx;
private long ssl = 0;
private long networkBIO = 0;
private int serverSelectedCipher = -1;
private final OpenSSLContextSPI openSSLContextSPI;
/**
* 0 - not accepted, 1 - accepted implicitly via wrap()/unwrap(), 2 -
* accepted explicitly via beginHandshake() call
*/
private int accepted;
private boolean alpnRegistered = false;
private boolean handshakeFinished;
private boolean receivedShutdown;
private volatile int destroyed;
private boolean wantClientAuth = false;
private boolean needClientAuth = false;
private volatile ClientAuthMode clientAuth = ClientAuthMode.NONE;
// SSL Engine status variables
private boolean isInboundDone;
private boolean isOutboundDone;
private boolean engineClosed;
private boolean clientMode;
private String[] applicationProtocols;
private String selectedApplicationProtocol;
private SSLSession handshakeSession;
private String host;
private int port;
private List tasks = new ArrayList<>();
/**
* Creates a new instance
*
* @param sslCtx an OpenSSL {@code SSL_CTX} object
* engine
* @param clientMode {@code true} if this is used for clients, {@code false}
* otherwise
*/
OpenSSLEngine(long sslCtx,
boolean clientMode, OpenSSLContextSPI openSSLContextSPI) {
this(sslCtx, clientMode, openSSLContextSPI, null, -1);
}
OpenSSLEngine(long sslCtx,
boolean clientMode, OpenSSLContextSPI openSSLContextSPI, String host, int port) {
if (sslCtx == 0) {
throw new IllegalStateException(MESSAGES.noSslContext());
}
this.sslCtx = sslCtx;
this.clientMode = clientMode;
this.openSSLContextSPI = openSSLContextSPI;
this.host = host;
this.port = port;
}
private void initSsl() {
if(ssl == 0 && DESTROYED_UPDATER.get(this) == 0) {
ssl = SSL.getInstance().newSSL(sslCtx, !clientMode);
networkBIO = SSL.getInstance().makeNetworkBIO(ssl);
if(clientMode) {
openSSLContextSPI.engineGetClientSessionContext().tryAttachClientSideSession(ssl, host, port);
}
for(Runnable task : tasks) {
task.run();
}
tasks = null;
}
}
/**
* Destroys this engine.
*/
public synchronized void shutdown() {
if (DESTROYED_UPDATER.compareAndSet(this, 0, 1)) {
if(ssl != 0) {
SSL.getInstance().freeSSL(ssl);
SSL.getInstance().freeBIO(networkBIO);
}
ssl = networkBIO = 0;
// internal errors can cause shutdown without marking the engine closed
isInboundDone = isOutboundDone = engineClosed = true;
}
}
/**
* Write plaintext data to the OpenSSL internal BIO
*
* Calling this function with src.remaining == 0 is undefined.
*/
private int writePlaintextData(final ByteBuffer src) {
final int pos = src.position();
final int limit = src.limit();
final int len = Math.min(limit - pos, MAX_PLAINTEXT_LENGTH);
final int sslWrote;
initSsl();
if (src.isDirect()) {
final long addr = SSL.getInstance().bufferAddress(src) + pos;
sslWrote = SSL.getInstance().writeToSSL(ssl, addr, len);
if (sslWrote > 0) {
src.position(pos + sslWrote);
return sslWrote;
}
} else {
ByteBuffer buf = ByteBuffer.allocateDirect(len);
try {
final long addr = memoryAddress(buf);
src.limit(pos + len);
buf.put(src);
src.limit(limit);
sslWrote = SSL.getInstance().writeToSSL(ssl, addr, len);
if (sslWrote > 0) {
src.position(pos + sslWrote);
return sslWrote;
} else {
src.position(pos);
}
} finally {
buf.clear();
ByteBufferUtils.cleanDirectBuffer(buf);
}
}
throw new IllegalStateException(MESSAGES.sslWriteFailed(sslWrote));
}
/**
* Write encrypted data to the OpenSSL network BIO.
*/
private int writeEncryptedData(final ByteBuffer src) {
final int pos = src.position();
final int len = src.remaining();
if (src.isDirect()) {
final long addr = SSL.getInstance().bufferAddress(src) + pos;
final int netWrote = SSL.getInstance().writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(pos + netWrote);
return netWrote;
}
} else {
ByteBuffer buf = ByteBuffer.allocateDirect(len);
try {
final long addr = memoryAddress(buf);
buf.put(src);
final int netWrote = SSL.getInstance().writeToBIO(networkBIO, addr, len);
if (netWrote >= 0) {
src.position(pos + netWrote);
return netWrote;
} else {
src.position(pos);
}
} finally {
buf.clear();
ByteBufferUtils.cleanDirectBuffer(buf);
}
}
return -1;
}
/**
* Read plaintext data from the OpenSSL internal BIO
*/
private int readPlaintextData(final ByteBuffer dst) {
initSsl();
if (dst.isDirect()) {
final int pos = dst.position();
final long addr = SSL.getInstance().bufferAddress(dst) + pos;
final int len = dst.limit() - pos;
final int sslRead = SSL.getInstance().readFromSSL(ssl, addr, len);
if (sslRead > 0) {
dst.position(pos + sslRead);
return sslRead;
}
} else {
final int pos = dst.position();
final int limit = dst.limit();
final int len = Math.min(MAX_ENCRYPTED_PACKET_LENGTH, limit - pos);
final ByteBuffer buf = ByteBuffer.allocateDirect(len);
try {
final long addr = memoryAddress(buf);
final int sslRead = SSL.getInstance().readFromSSL(ssl, addr, len);
if (sslRead > 0) {
buf.limit(sslRead);
dst.limit(pos + sslRead);
dst.put(buf);
dst.limit(limit);
return sslRead;
}
} finally {
buf.clear();
ByteBufferUtils.cleanDirectBuffer(buf);
}
}
return 0;
}
/**
* Read encrypted data from the OpenSSL network BIO
*/
private int readEncryptedData(final ByteBuffer dst, final int pending) {
if (dst.isDirect() && dst.remaining() >= pending) {
final int pos = dst.position();
final long addr = SSL.getInstance().bufferAddress(dst) + pos;
final int bioRead = SSL.getInstance().readFromBIO(networkBIO, addr, pending);
if (bioRead > 0) {
dst.position(pos + bioRead);
return bioRead;
}
} else {
final ByteBuffer buf = ByteBuffer.allocateDirect(pending);
try {
final long addr = memoryAddress(buf);
final int bioRead = SSL.getInstance().readFromBIO(networkBIO, addr, pending);
if (bioRead > 0) {
buf.limit(bioRead);
int oldLimit = dst.limit();
dst.limit(dst.position() + bioRead);
dst.put(buf);
dst.limit(oldLimit);
return bioRead;
}
} finally {
buf.clear();
ByteBufferUtils.cleanDirectBuffer(buf);
}
}
return 0;
}
@Override
public synchronized SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) throws SSLException {
// Check to make sure the engine has not been closed
if (destroyed != 0) {
return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
}
// Throw required runtime exceptions
if (srcs == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
if (dst == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
if (offset + length > srcs.length) {
throw new IndexOutOfBoundsException(MESSAGES.invalidOffset(offset, length, srcs.length));
}
if (dst.isReadOnly()) {
throw new ReadOnlyBufferException();
}
// Prepare OpenSSL to work in server mode and receive handshake
if (accepted == 0) {
beginHandshakeImplicitly();
}
// In handshake or close_notify stages, check if call to wrap was made
// without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
return new SSLEngineResult(getEngineStatus(), SSLEngineResult.HandshakeStatus.NEED_UNWRAP, 0, 0);
}
int bytesProduced = 0;
int pendingNet;
// Check for pending data in the network BIO
pendingNet = SSL.getInstance().pendingWrittenBytesInBIO(networkBIO);
if (pendingNet > 0) {
// Do we have enough room in dst to write encrypted data?
int capacity = dst.remaining();
if (capacity < pendingNet) {
return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, handshakeStatus, 0, bytesProduced);
}
// Write the pending data from the network BIO into the dst buffer
try {
bytesProduced += readEncryptedData(dst, pendingNet);
} catch (Exception e) {
throw new SSLException(e);
}
if(serverSelectedCipher == -1 && !clientMode) {
ByteBuffer duplicate = dst.duplicate();
duplicate.flip();
serverSelectedCipher = OpenSSLServerHelloExplorer.getCipherSuite(duplicate);
SSL.getInstance().saveServerCipher(ssl, serverSelectedCipher);
}
// If isOuboundDone is set, then the data from the network BIO
// was the close_notify message -- we are not required to wait
// for the receipt the peer's close_notify message -- shutdown.
if (isOutboundDone) {
shutdown();
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), 0, bytesProduced);
}
// There was no pending data in the network BIO -- encrypt any application data
int bytesConsumed = 0;
int endOffset = offset + length;
for (int i = offset; i < endOffset; ++i) {
final ByteBuffer src = srcs[i];
if (src == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
while (src.hasRemaining()) {
// Write plaintext application data to the SSL engine
try {
bytesConsumed += writePlaintextData(src);
} catch (Exception e) {
throw new SSLException(e);
}
// Check to see if the engine wrote data into the network BIO
pendingNet = SSL.getInstance().pendingWrittenBytesInBIO(networkBIO);
if (pendingNet > 0) {
// Do we have enough room in dst to write encrypted data?
int capacity = dst.remaining();
if (capacity < pendingNet) {
return new SSLEngineResult(
SSLEngineResult.Status.BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
}
// Write the pending data from the network BIO into the dst buffer
try {
bytesProduced += readEncryptedData(dst, pendingNet);
} catch (Exception e) {
throw new SSLException(e);
}
if(serverSelectedCipher == -1 && !clientMode) {
ByteBuffer duplicate = dst.duplicate();
duplicate.flip();
serverSelectedCipher = OpenSSLServerHelloExplorer.getCipherSuite(duplicate);
SSL.getInstance().saveServerCipher(ssl, serverSelectedCipher);
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
}
}
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
@Override
public synchronized SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) throws SSLException {
// Check to make sure the engine has not been closed
if (destroyed != 0) {
return new SSLEngineResult(SSLEngineResult.Status.CLOSED, SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
}
initSsl();
// Throw required runtime exceptions
if (src == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
if (dsts == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
if (offset >= dsts.length || offset + length > dsts.length) {
throw new IndexOutOfBoundsException(MESSAGES.invalidOffset(offset, length, dsts.length));
}
int capacity = 0;
final int endOffset = offset + length;
for (int i = offset; i < endOffset; i++) {
ByteBuffer dst = dsts[i];
if (dst == null) {
throw new IllegalArgumentException(MESSAGES.bufferIsNull());
}
if (dst.isReadOnly()) {
throw new ReadOnlyBufferException();
}
capacity += dst.remaining();
}
// Prepare OpenSSL to work in server mode and receive handshake
if (accepted == 0) {
beginHandshakeImplicitly();
}
// In handshake or close_notify stages, check if call to unwrap was made
// without regard to the handshake status.
SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
if ((!handshakeFinished || engineClosed) && handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
return new SSLEngineResult(getEngineStatus(), SSLEngineResult.HandshakeStatus.NEED_WRAP, 0, 0);
}
int len = src.remaining();
// protect against protocol overflow attack vector
if (len > MAX_ENCRYPTED_PACKET_LENGTH) {
isInboundDone = true;
isOutboundDone = true;
engineClosed = true;
shutdown();
throw ENCRYPTED_PACKET_OVERSIZED;
}
// Write encrypted data to network BIO
int bytesConsumed = -1;
try {
int written = writeEncryptedData(src);
if (written >= 0) {
if (bytesConsumed == -1) {
bytesConsumed = written;
} else {
bytesConsumed += written;
}
}
} catch (Exception e) {
throw new SSLException(e);
}
int lastPrimingReadResult = SSL.getInstance().readFromSSL(ssl, EMPTY_ADDR, 0); // priming read
// check if SSL_read returned <= 0. In this case we need to check the error and see if it was something
// fatal.
if (lastPrimingReadResult <= 0) {
// Check for OpenSSL errors caused by the priming read
long error = SSL.getInstance().getLastErrorNumber();
if (error != SSL.SSL_ERROR_NONE) {
String err = SSL.getInstance().getErrorString(error);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine(MESSAGES.readFromSSLFailed(error, lastPrimingReadResult, err));
}
// There was an internal error -- shutdown
shutdown();
throw new SSLException(err);
}
}
if (bytesConsumed < 0) {
bytesConsumed = 0;
}
// There won't be any application data until we're done handshaking
//
// We first check handshakeFinished to eliminate the overhead of extra JNI call if possible.
int pendingApp = (handshakeFinished || SSL.getInstance().isInInit(ssl) == 0) ? SSL.getInstance().pendingReadableBytesInSSL(ssl) : 0;
int bytesProduced = 0;
while (pendingApp > 0) {
// Do we have enough room in dsts to write decrypted data?
if (capacity < pendingApp) {
return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW, getHandshakeStatus(), bytesConsumed, 0);
}
// Write decrypted data to dsts buffers
int idx = offset;
while (idx < endOffset) {
ByteBuffer dst = dsts[idx];
if (!dst.hasRemaining()) {
idx++;
continue;
}
if (pendingApp <= 0) {
break;
}
int bytesRead;
try {
bytesRead = readPlaintextData(dst);
} catch (Exception e) {
throw new SSLException(e);
}
if (bytesRead == 0) {
break;
}
bytesProduced += bytesRead;
pendingApp -= bytesRead;
if (!dst.hasRemaining()) {
idx++;
}
}
pendingApp = SSL.getInstance().pendingReadableBytesInSSL(ssl);
}
// Check to see if we received a close_notify message from the peer
if (!receivedShutdown && (SSL.getInstance().getShutdown(ssl) & SSL.SSL_RECEIVED_SHUTDOWN) == SSL.SSL_RECEIVED_SHUTDOWN) {
receivedShutdown = true;
closeOutbound();
closeInbound();
}
if (bytesProduced == 0 && bytesConsumed == 0) {
return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW, getHandshakeStatus(), bytesConsumed, bytesProduced);
} else {
return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(), bytesConsumed, bytesProduced);
}
}
@Override
public Runnable getDelegatedTask() {
// Currently, we do not delegate SSL computation tasks
// TODO: in the future, possibly create tasks to do encrypt / decrypt async
return null;
}
@Override
public synchronized void closeInbound() throws SSLException {
if (isInboundDone) {
return;
}
isInboundDone = true;
engineClosed = true;
shutdown();
if (accepted != 0 && !receivedShutdown) {
throw new SSLException(MESSAGES.inboundIsClosed());
}
}
@Override
public synchronized boolean isInboundDone() {
return isInboundDone || engineClosed;
}
@Override
public synchronized void closeOutbound() {
if (isOutboundDone) {
return;
}
isOutboundDone = true;
engineClosed = true;
if (accepted != 0 && destroyed == 0) {
int mode = SSL.getInstance().getShutdown(ssl);
if ((mode & SSL.SSL_SENT_SHUTDOWN) != SSL.SSL_SENT_SHUTDOWN) {
SSL.getInstance().shutdownSSL(ssl);
}
} else {
// engine closing before initial handshake
shutdown();
}
}
@Override
public synchronized boolean isOutboundDone() {
return isOutboundDone;
}
@Override
public String[] getSupportedCipherSuites() {
return OpenSSLContextSPI.getAvailableCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
initSsl();
String[] enabled = SSL.getInstance().getCiphers(ssl);
if (enabled == null) {
return new String[0];
} else {
for (int i = 0; i < enabled.length; i++) {
String mapped = toJavaCipherSuite(enabled[i], ssl);
if (mapped != null) {
enabled[i] = mapped;
}
}
return enabled;
}
}
@Override
public void setEnabledCipherSuites(String[] cipherSuites) {
if (cipherSuites == null) {
throw new IllegalArgumentException(MESSAGES.nullCipherSuites());
}
Runnable task = () -> {
final StringBuilder buf = new StringBuilder();
for (String cipherSuite : cipherSuites) {
if (cipherSuite == null) {
break;
}
String converted = CipherSuiteConverter.toOpenSsl(cipherSuite);
if (converted != null) {
cipherSuite = converted;
}
Set missing = new HashSet<>();
Set availbile = new HashSet<>(Arrays.asList(OpenSSLContextSPI.getAvailableCipherSuites()));
if (!availbile.contains(cipherSuite)) {
if (LOG.isLoggable(Level.FINEST)) {
missing.add(cipherSuite);
}
}
if (!missing.isEmpty() && LOG.isLoggable(Level.FINEST)) {
LOG.fine("Unsupported cypher suites " + missing + " available " + availbile);
}
buf.append(cipherSuite);
buf.append(':');
}
if (buf.length() == 0) {
throw new IllegalArgumentException(MESSAGES.emptyCipherSuiteList());
}
buf.setLength(buf.length() - 1);
final String cipherSuiteSpec = buf.toString();
try {
SSL.getInstance().setCipherSuites(ssl, cipherSuiteSpec);
} catch (Exception e) {
throw new IllegalStateException(MESSAGES.failedCipherSuite(cipherSuiteSpec), e);
}
};
if(ssl == 0) {
tasks.add(task);
} else {
task.run();
}
}
@Override
public String[] getSupportedProtocols() {
return SUPPORTED_PROTOCOLS.clone();
}
@Override
public String[] getEnabledProtocols() {
initSsl();
List enabled = new ArrayList<>();
// Seems like there is no way to explict disable SSLv2Hello in openssl so it is always enabled
enabled.add(SSL.SSL_PROTO_SSLv2Hello);
int opts = SSL.getInstance().getOptions(ssl);
if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) {
enabled.add(SSL.SSL_PROTO_TLSv1);
}
if ((opts & SSL.SSL_OP_NO_TLSv1_1) == 0) {
enabled.add(SSL.SSL_PROTO_TLSv1_1);
}
if ((opts & SSL.SSL_OP_NO_TLSv1_2) == 0) {
enabled.add(SSL.SSL_PROTO_TLSv1_2);
}
if ((opts & SSL.SSL_OP_NO_SSLv2) == 0) {
enabled.add(SSL.SSL_PROTO_SSLv2);
}
if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) {
enabled.add(SSL.SSL_PROTO_SSLv3);
}
int size = enabled.size();
if (size == 0) {
return new String[0];
} else {
return enabled.toArray(new String[size]);
}
}
@Override
public void setEnabledProtocols(String[] protocols) {
if (protocols == null) {
// This is correct from the API docs
throw new IllegalArgumentException();
}
Runnable task = () -> {
boolean sslv2 = false;
boolean sslv3 = false;
boolean tlsv1 = false;
boolean tlsv1_1 = false;
boolean tlsv1_2 = false;
for (String p : protocols) {
if (!SUPPORTED_PROTOCOLS_SET.contains(p)) {
throw new IllegalArgumentException(MESSAGES.unsupportedProtocol(p));
}
if (p.equals(SSL.SSL_PROTO_SSLv2)) {
sslv2 = true;
} else if (p.equals(SSL.SSL_PROTO_SSLv3)) {
sslv3 = true;
} else if (p.equals(SSL.SSL_PROTO_TLSv1)) {
tlsv1 = true;
} else if (p.equals(SSL.SSL_PROTO_TLSv1_1)) {
tlsv1_1 = true;
} else if (p.equals(SSL.SSL_PROTO_TLSv1_2)) {
tlsv1_2 = true;
}
}
// Enable all and then disable what we not want
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_ALL);
if (!sslv2) {
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_NO_SSLv2);
}
if (!sslv3) {
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_NO_SSLv3);
}
if (!tlsv1) {
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_NO_TLSv1);
}
if (!tlsv1_1) {
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_NO_TLSv1_1);
}
if (!tlsv1_2) {
SSL.getInstance().setOptions(ssl, SSL.SSL_OP_NO_TLSv1_2);
}
};
if(ssl == 0) {
tasks.add(task);
} else {
task.run();
}
}
@Override
public SSLSession getSession() {
initSsl();
if (!handshakeFinished) {
return getHandshakeSession();
}
SSLSession session = getSessionContext().getSession(SSL.getInstance().getSessionId(getSsl()));
if(session == null) {
if(handshakeSession == null) {
handshakeSession = new OpenSSlSession(!clientMode, getSessionContext());
}
return handshakeSession;
}
return session;
}
@Override
public synchronized void beginHandshake() throws SSLException {
if (engineClosed || destroyed != 0) {
throw ENGINE_CLOSED;
}
if (clientMode) {
switch (accepted) {
case 0:
handshake();
accepted = 2;
break;
case 1:
// 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.
accepted = 2; // Next time this method is invoked by the user, we should raise an exception.
break;
case 2:
throw RENEGOTIATION_UNSUPPORTED;
default:
throw new Error();
}
} else {
if (accepted > 0) {
renegotiate();
}
accepted = 2;
}
}
private void beginHandshakeImplicitly() throws SSLException {
if (engineClosed || destroyed != 0) {
throw ENGINE_CLOSED;
}
if (accepted == 0) {
handshake();
accepted = 1;
}
}
private void handshake() throws SSLException {
initSsl();
if (!alpnRegistered) {
alpnRegistered = true;
if (!isClientMode()) {
SSL.getInstance().setServerALPNCallback(ssl, new ServerALPNCallback() {
@Override
public String select(String[] data) {
String version = SSL.getInstance().getVersion(ssl);
if(applicationProtocols == null || version == null || !version.equals("TLSv1.2")) {
//only offer ALPN on TLS 1.2, try and force http/1.1 if it is offered, otherwise fail the connection
//it seems wrong to hard code protocols in the SSL impl, but openssl does not really allow alpn to be enabled
//on a per engine basis
for(String i : data) {
if(i.equals("http/1.1")) {
return i;
}
}
return null;
}
for (String proto : applicationProtocols) {
for (String clientProto : data) {
if (clientProto.equals(proto)) {
selectedApplicationProtocol = proto;
return proto;
}
}
}
return null;
}
});
} else if(applicationProtocols != null){
SSL.getInstance().setAlpnProtos(ssl, applicationProtocols);
}
}
int code = SSL.getInstance().doHandshake(ssl);
if (code <= 0) {
// Check for OpenSSL errors caused by the handshake
long error = SSL.getInstance().getLastErrorNumber();
if (error != SSL.SSL_ERROR_NONE) {
String err = SSL.getInstance().getErrorString(error);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Engine handshake failure " + err);
}
// There was an internal error -- shutdown
shutdown();
throw new SSLException(err);
}
} else {
// if SSL_do_handshake returns > 0 it means the handshake was finished. This means we can update
// handshakeFinished directly and so eliminate uncessary calls to SSL.getInstance().isInInit(...)
handshakeFinished();
}
}
private void handshakeFinished() {
handshakeFinished = true;
if(isClientMode() && applicationProtocols != null) {
selectedApplicationProtocol = SSL.getInstance().getAlpnSelected(ssl);
}
if(handshakeSession != null || clientMode) {
byte[] sessionId = SSL.getInstance().getSessionId(ssl);
if (handshakeSession != null) {
getSessionContext().mergeHandshakeSession(handshakeSession, sessionId);
}
if (clientMode) {
openSSLContextSPI.engineGetClientSessionContext().storeClientSideSession(ssl, host, port, sessionId);
}
}
}
private void renegotiate() throws SSLException {
initSsl();
handshakeFinished = false;
int code = SSL.getInstance().renegotiate(ssl);
if (code <= 0) {
// Check for OpenSSL errors caused by the handshake
long error = SSL.getInstance().getLastErrorNumber();
if (error != SSL.SSL_ERROR_NONE) {
String err = SSL.getInstance().getErrorString(error);
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("Renegotiation failure " + err);
}
// There was an internal error -- shutdown
shutdown();
throw new SSLException(err);
}
}
}
private static long memoryAddress(ByteBuffer buf) {
return SSL.getInstance().bufferAddress(buf);
}
private SSLEngineResult.Status getEngineStatus() {
return engineClosed ? SSLEngineResult.Status.CLOSED : SSLEngineResult.Status.OK;
}
@Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
if (accepted == 0 || destroyed != 0) {
return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
initSsl();
// Check if we are in the initial handshake phase
if (!handshakeFinished) {
// There is pending data in the network BIO -- call wrap
if (SSL.getInstance().pendingWrittenBytesInBIO(networkBIO) != 0) {
return SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
// No pending data to be sent to the peer
// Check to see if we have finished handshaking
if (SSL.getInstance().isInInit(ssl) == 0) {
handshakeFinished();
return SSLEngineResult.HandshakeStatus.FINISHED;
}
// No pending data and still handshaking
// Must be waiting on the peer to send more data
return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
}
// Check if we are in the shutdown phase
if (engineClosed) {
// Waiting to send the close_notify message
if (SSL.getInstance().pendingWrittenBytesInBIO(networkBIO) != 0) {
return SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
// Must be waiting to receive the close_notify message
return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
}
return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
/**
* Converts the specified OpenSSL cipher suite to the Java cipher suite.
*/
static String toJavaCipherSuite(String openSslCipherSuite, long ssl) {
if (openSslCipherSuite == null) {
return null;
}
String prefix = toJavaCipherSuitePrefix(SSL.getInstance().getVersion(ssl));
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.length() == 0) {
c = 0;
} else {
c = protocolVersion.charAt(0);
}
switch (c) {
case 'T':
return "TLS";
case 'S':
return "SSL";
default:
return "UNKNOWN";
}
}
@Override
public void setUseClientMode(boolean clientMode) {
if(ssl == 0) {
this.clientMode = clientMode;
} else if (clientMode != this.clientMode) {
throw new UnsupportedOperationException();
}
}
@Override
public boolean getUseClientMode() {
return clientMode;
}
@Override
public void setNeedClientAuth(boolean b) {
needClientAuth = b;
setClientAuth(needClientAuth ? ClientAuthMode.REQUIRE : wantClientAuth ? ClientAuthMode.OPTIONAL : ClientAuthMode.NONE);
}
@Override
public boolean getNeedClientAuth() {
return clientAuth == ClientAuthMode.REQUIRE;
}
@Override
public void setWantClientAuth(boolean b) {
wantClientAuth = b;
setClientAuth(needClientAuth ? ClientAuthMode.REQUIRE : wantClientAuth ? ClientAuthMode.OPTIONAL : ClientAuthMode.NONE);
}
@Override
public boolean getWantClientAuth() {
return clientAuth == ClientAuthMode.OPTIONAL;
}
private void setClientAuth(ClientAuthMode mode) {
if (clientMode) {
return;
}
if (clientAuth == mode) {
// No need to issue any JNI calls if the mode is the same
return;
}
clientAuth = mode;
Runnable task = () -> {
switch (mode) {
case NONE:
SSL.getInstance().setSSLVerify(ssl, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH);
break;
case REQUIRE:
SSL.getInstance().setSSLVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, VERIFY_DEPTH);
break;
case OPTIONAL:
SSL.getInstance().setSSLVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, VERIFY_DEPTH);
break;
}
};
if (ssl == 0) {
tasks.add(task);
} else {
task.run();
}
}
@Override
public void setEnableSessionCreation(boolean b) {
//TODO
if (b) {
//throw new UnsupportedOperationException();
}
}
@Override
public boolean getEnableSessionCreation() {
return false;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
// Call shutdown as the user may have created the OpenSslEngine and not used it at all.
shutdown();
}
@Override
public SSLSession getHandshakeSession() {
initSsl();
if(handshakeFinished) {
return null;
}
if(handshakeSession == null) {
handshakeSession = new OpenSSlSession(!clientMode, getSessionContext());
}
return handshakeSession;
}
public String getSelectedApplicationProtocol() {
return selectedApplicationProtocol;
}
public String[] getApplicationProtocols() {
return applicationProtocols;
}
public void setApplicationProtocols(String ... applicationProtocols) {
this.applicationProtocols = applicationProtocols;
}
public static boolean isAlpnSupported() {
return SSL.getInstance().isAlpnSupported();
}
long getSsl() {
initSsl();
return ssl;
}
boolean isHandshakeFinished() {
return handshakeFinished;
}
@Override
public SSLParameters getSSLParameters() {
return super.getSSLParameters();
}
@Override
public void setSSLParameters(SSLParameters sslParameters) {
super.setSSLParameters(sslParameters);
Runnable config = () -> {
// Use server's preference order for ciphers (rather than
// client's)
boolean orderCiphersSupported = false;
try {
orderCiphersSupported = SSL.getInstance().hasOp(SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
if (orderCiphersSupported) {
if (sslParameters.getUseCipherSuitesOrder()) {
SSL.getInstance().setSSLOptions(ssl, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
}
}
} catch (UnsatisfiedLinkError e) {
// Ignore
}
if (!orderCiphersSupported) {
// OpenSSL does not support ciphers ordering.
LOG.fine("The version of SSL in use does not support cipher ordering");
}
int value = 0;
if (sslParameters.getNeedClientAuth()) {
value = SSL.SSL_CVERIFY_REQUIRE;
} else if (sslParameters.getWantClientAuth()) {
value = SSL.SSL_CVERIFY_OPTIONAL;
} else {
value = SSL.SSL_CVERIFY_NONE;
}
SSL.getInstance().setSSLVerify(ssl, value, DEFAULT_CERTIFICATE_VALIDATION_DEPTH);
};
if(ssl == 0) {
tasks.add(config);
} else {
config.run();
}
}
void setHost(final String host) {
this.host = host;
}
void setPort(final int port) {
this.port = port;
}
}