wolfssljni/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java

1976 lines
74 KiB
Java

/* WolfSSLEngine.java
*
* Copyright (C) 2006-2024 wolfSSL Inc.
*
* This file is part of wolfSSL.
*
* wolfSSL is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* wolfSSL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
*/
package com.wolfssl.provider.jsse;
import com.wolfssl.WolfSSL;
import com.wolfssl.WolfSSLDebug;
import com.wolfssl.WolfSSLException;
import com.wolfssl.WolfSSLIORecvCallback;
import com.wolfssl.WolfSSLIOSendCallback;
import com.wolfssl.WolfSSLJNIException;
import com.wolfssl.WolfSSLSession;
import com.wolfssl.WolfSSLALPNSelectCallback;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.util.function.BiFunction;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.logging.Level;
import java.security.cert.CertificateEncodingException;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLParameters;
import java.net.SocketException;
import java.net.SocketTimeoutException;
/**
* wolfSSL implementation of SSLEngine.
*
* There is more verbose debugging available for this class apart
* from the normal 'wolfjsse.debug' logging. To enable more verbose
* logging use both the following system properties:
*
* System.setProperty("wolfjsse.debug", "true");
* System.setProperty("wolfsslengine.debug", "true");
*
* This will add extra debug logs around wrap() and unwrap() calls, as well
* as printing out the data sent/received in the I/O callbacks.
*
* @author wolfSSL
*/
public class WolfSSLEngine extends SSLEngine {
private WolfSSLEngineHelper engineHelper = null;
private WolfSSLSession ssl = null;
private com.wolfssl.WolfSSLContext ctx = null;
private WolfSSLAuthStore authStore = null;
private WolfSSLParameters params = null;
private byte[] toSend = null; /* encrypted packet to send */
private HandshakeStatus hs = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
/* Does TLS handshake need initialization */
private boolean needInit = true;
private final Object initLock = new Object();
/* Have cert/key been loaded? */
private boolean certKeyLoaded = false;
private boolean inBoundOpen = true;
private boolean outBoundOpen = true;
/* closed completely (post shutdown or before handshake) */
private boolean closed = true;
/* handshake started explicitly by user with beginHandshake() */
private boolean handshakeStartedExplicitly = false;
/* handshake completed */
private boolean handshakeFinished = false;
/* closeNotify status when shutting down */
private boolean closeNotifySent = false;
private boolean closeNotifyReceived = false;
/* session stored (WOLFSSL_SESSION), relevant on client side */
private boolean sessionStored = false;
/* TLS 1.3 session ticket received (on client side) */
private boolean sessionTicketReceived = false;
/* client/server mode has been set */
private boolean clientModeSet = false;
private SendCB sendCb = null;
private RecvCB recvCb = null;
private ByteBuffer netData = null;
private final Object netDataLock = new Object();
/* Locks for synchronization */
private final Object ioLock = new Object();
private final Object toSendLock = new Object();
/** ALPN selector callback, if set */
protected BiFunction<SSLEngine, List<String>, String> alpnSelector = null;
/** Turn on extra/verbose SSLEngine debug logging */
public boolean extraDebugEnabled = false;
/** Turn on Send/Recv callback debug to print out bytes sent/received.
* WARNING: enabling this will slow down sending and receiving data,
* enough so that app may run into timeouts. Enable with caution. */
public boolean ioDebugEnabled = false;
/**
* Turns on additional debugging based on system properties set.
*/
private void enableExtraDebug() {
/* turn on verbose extra debugging if 'wolfsslengine.debug'
* system property is set */
String engineDebug = System.getProperty("wolfsslengine.debug");
if ((engineDebug != null) && (engineDebug.equalsIgnoreCase("true"))) {
this.extraDebugEnabled = true;
}
}
/**
* Turns on additional debugging of I/O data based on system properties set.
*/
private void enableIODebug() {
/* turn on verbose extra debugging of bytes sent and received if
* 'wolfsslengine.io.debug' system property is set */
String engineIODebug = System.getProperty("wolfsslengine.io.debug");
if ((engineIODebug != null) &&
(engineIODebug.equalsIgnoreCase("true"))) {
this.ioDebugEnabled = true;
}
}
/**
* Create a new engine with no hints for session reuse
*
* @param ctx JNI level WolfSSLContext
* @param auth WolfSSLAuthStore to use
* @param params connection parameters to be used
* @throws WolfSSLException if there is an issue creating the engine
*/
protected WolfSSLEngine(com.wolfssl.WolfSSLContext ctx,
WolfSSLAuthStore auth, WolfSSLParameters params)
throws WolfSSLException {
super();
this.ctx = ctx;
this.authStore = auth;
this.params = params.copy();
try {
initSSL();
} catch (WolfSSLJNIException ex) {
throw new WolfSSLException("Error with WolfSSLEngine init");
}
this.engineHelper = new WolfSSLEngineHelper(this.ssl, this.authStore,
this.params);
}
/**
* Create a new engine with hints for session reuse
*
* @param ctx JNI level WolfSSLContext
* @param auth WolfSSLAuthStore to use
* @param params connection parameters to be used
* @param host to connect to
* @param port to connect to
* @throws WolfSSLException if there is an issue creating the engine
*/
protected WolfSSLEngine(com.wolfssl.WolfSSLContext ctx,
WolfSSLAuthStore auth, WolfSSLParameters params, String host,
int port) throws WolfSSLException {
super(host, port);
this.ctx = ctx;
this.authStore = auth;
this.params = params.copy();
try {
initSSL();
} catch (WolfSSLJNIException ex) {
throw new WolfSSLException("Error with WolfSSLEngine init");
}
this.engineHelper = new WolfSSLEngineHelper(this.ssl, this.authStore,
this.params, port, host);
}
/**
* Loads the key and certificate for this SSLEngine if not loaded yet.
*
* @throws SSLException on error
*/
private synchronized void LoadCertAndKey() throws SSLException {
/* Load cert and key */
if (certKeyLoaded) {
return;
}
try {
this.engineHelper.LoadKeyAndCertChain(null, this);
} catch (CertificateEncodingException | IOException |
WolfSSLException e) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"failed to load private key and/or cert chain");
throw new SSLException(e);
}
}
/**
* Initialize this WolfSSLEngine prior to handshaking.
*
* Internal method, should be called before any handshake.
*
* This logic is not included directly in WolfSSLEngine constructors
* to avoid possible 'this' escape before subclass is fully initialized
* when using 'this' in LoadKeyAndCertChain().
*
* @throws SSLException if initialization fails
*/
private void checkAndInitSSLEngine() throws SSLException {
synchronized (initLock) {
if (!needInit) {
return;
}
LoadCertAndKey();
this.engineHelper.initHandshake(this);
needInit = false;
closed = false; /* opened a connection */
}
}
/**
* Register I/O callbacks and contexts with WolfSSLSession and native
* wolfSSL. Uses singleton pattern on callbacks.
*
* Call unsetSSLCallbacks to unset/unregister these. Since the I/O
* context is set as the current SSLEngine object, this can prevent
* garbage collection of this SSLEngine object unless the context
* is unset from the WolfSSLSession.
*
* Protected by ioLock since all I/O operations are dependent on using
* these underlying I/O callbacks.
*
* @throws WolfSSLJNIException on native JNI error
*/
private void setSSLCallbacks() throws WolfSSLJNIException {
synchronized (ioLock) {
if (sendCb == null) {
sendCb = new SendCB();
}
if (recvCb == null) {
recvCb = new RecvCB();
}
ssl.setIORecv(recvCb);
ssl.setIOSend(sendCb);
ssl.setIOReadCtx(this);
ssl.setIOWriteCtx(this);
}
}
/**
* Unregister I/O callbacks and contexts with WolfSSLSession and
* native wolfSSL.
*
* Call setSSLCallbacks() to re-register these.
*
* Protected with ioLock since all I/O operations are dependent on
* using these underlying I/O callbacks.
*
* @throws WolfSSLJNIException on native JNI error
*/
private void unsetSSLCallbacks() throws WolfSSLJNIException {
synchronized (ioLock) {
ssl.setIORecv(null);
ssl.setIOSend(null);
ssl.setIOReadCtx(null);
ssl.setIOWriteCtx(null);
}
}
private void initSSL() throws WolfSSLException, WolfSSLJNIException {
if (sendCb == null) {
sendCb = new SendCB();
}
if (recvCb == null) {
recvCb = new RecvCB();
}
/* will throw WolfSSLException if issue creating WOLFSSL */
ssl = new WolfSSLSession(ctx);
enableExtraDebug();
enableIODebug();
}
/**
* Copy buffered data to be sent into provided output ByteBuffer.
*
* Data sent will be minimum of either buffered data size or
* destination buffer remaining space.
*
* Returns size of data copied.
*/
private int CopyOutPacket(ByteBuffer out) {
int sendSz = 0;
synchronized (toSendLock) {
if (this.toSend != null) {
sendSz = Math.min(this.toSend.length, out.remaining());
out.put(this.toSend, 0, sendSz);
if (sendSz != this.toSend.length) {
/* resize and adjust remaining toSend data */
byte[] tmp = new byte[this.toSend.length - sendSz];
System.arraycopy(this.toSend, sendSz, tmp, 0,
this.toSend.length - sendSz);
this.toSend = tmp;
}
else {
this.toSend = null;
}
}
}
return sendSz;
}
/**
* Helper function, updates internal close_notify alert status
* and inBound/outBoundOpen.
*/
private synchronized void UpdateCloseNotifyStatus() {
int ret;
synchronized (ioLock) {
ret = ssl.getShutdown();
}
if (ret == (WolfSSL.SSL_RECEIVED_SHUTDOWN |
WolfSSL.SSL_SENT_SHUTDOWN)) {
this.closeNotifySent = true;
this.closeNotifyReceived = true;
this.inBoundOpen = false;
if (this.toSend == null || this.toSend.length == 0) {
/* Don't close outbound if we have a close_notify alert
* send back to peer. Native wolfSSL may have already generated
* it and is reflected in SSL_SENT_SHUTDOWN flag, but we
* might have it cached in our Java SSLEngine object still to
* be sent. */
this.outBoundOpen = false;
}
closed = true;
} else if (ret == WolfSSL.SSL_RECEIVED_SHUTDOWN) {
this.closeNotifyReceived = true;
this.inBoundOpen = false;
} else if (ret == WolfSSL.SSL_SENT_SHUTDOWN) {
this.closeNotifySent = true;
this.outBoundOpen = false;
}
}
/**
* Handles logic during shutdown
*
* @return WolfSSL.SSL_SUCCESS on success, zero or negative on error
* @throws SocketException if ssl.shutdownSSL() encounters a socket error
* @throws SocketTimeoutException if ssl.shutdownSSL() times out
*/
private synchronized int ClosingConnection()
throws SocketException, SocketTimeoutException {
int ret;
/* Save session into WolfSSLAuthStore cache, saves session
* pointer for resumption if on client side. Protected with ioLock
* since underlying get1Session can use I/O with peek.
*
* Only store session if handshake is finished, SSL_get_error() does
* not have an active error state, and the session has not been
* stored previously. */
synchronized (ioLock) {
if (this.handshakeFinished && (ssl.getError(0) == 0) &&
!this.sessionStored) {
this.engineHelper.saveSession();
}
}
/* get current close_notify state */
UpdateCloseNotifyStatus();
if (this.closeNotifySent && this.closeNotifyReceived) {
return WolfSSL.SSL_SUCCESS;
}
/* send/recv close_notify as needed */
synchronized (ioLock) {
ret = ssl.shutdownSSL();
if (ssl.getError(ret) == WolfSSL.SSL_ERROR_ZERO_RETURN) {
/* got close_notify alert, reset ret to SSL_SUCCESS to continue
* and let corresponding close_notify to be sent */
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ClosingConnection(), ssl.getError() is ZERO_RETURN");
ret = WolfSSL.SSL_SUCCESS;
}
}
UpdateCloseNotifyStatus();
return ret;
}
/**
* Starts or continues SSL/TLS handshake.
*
* @return WolfSSL.SSL_SUCCESS or WolfSSL.SSL_FAILURE
* @throws SocketException if ssl.connect() or ssl.accept() encounters
* a socket exception.
* @throws SocketTimeoutException if ssl.connect() or ssl.accept()
* times out. This should not happen since infinite timeout is
* being used for these calls.
*/
private synchronized int DoHandshake() throws SSLException {
int ret = WolfSSL.SSL_SUCCESS;
try {
if (this.getUseClientMode()) {
synchronized (ioLock) {
ret = this.ssl.connect();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ssl.connect() ret:err = " + ret + " : " +
ssl.getError(ret));
}
}
else {
synchronized (ioLock) {
ret = this.ssl.accept();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ssl.accept() ret:err = " + ret + " : " +
ssl.getError(ret));
}
}
} catch (SocketTimeoutException | SocketException e) {
throw new SSLException(e);
}
return ret;
}
/**
* Write application data using ssl.write().
*
* Only sends up to maximum app data chunk size
* (SSLSession.getApplicationBufferSize()).
*
* @throws SocketException if ssl.write() encounters a socket error
* @throws SocketTimeoutException if ssl.write() times out. Shouldn't
* happen since this is using an infinite timeout.
* @return bytes sent on success, negative on error
*/
private synchronized int SendAppData(ByteBuffer[] in, int ofst, int len)
throws SocketException, SocketTimeoutException {
int i = 0;
int ret = 0;
int totalIn = 0;
int sendSz = 0;
int inputLeft = 0;
ByteBuffer dataBuf;
byte[] dataArr;
int[] pos = new int[len]; /* in[] positions */
int[] limit = new int[len]; /* in[] limits */
/* get total input data size, store input array positions */
for (i = ofst; i < ofst + len; i++) {
totalIn += in[i].remaining();
pos[i] = in[i].position();
limit[i] = in[i].limit();
}
/* only send up to maximum app data size chunk */
sendSz = Math.min(totalIn,
this.engineHelper.getSession().getApplicationBufferSize());
dataBuf = ByteBuffer.allocate(sendSz);
/* gather byte array of sendSz bytes from input buffers */
inputLeft = sendSz;
for (i = ofst; i < ofst + len; i++) {
int bufChunk = Math.min(in[i].remaining(), inputLeft);
in[i].limit(in[i].position() + bufChunk); /* set limit */
dataBuf.put(in[i]); /* get data */
inputLeft -= bufChunk;
in[i].limit(limit[i]); /* reset limit */
if (inputLeft == 0) {
break; /* reached data size needed, stop reading */
}
}
dataArr = new byte[sendSz];
dataBuf.rewind();
dataBuf.get(dataArr);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"calling ssl.write() with size: " + sendSz);
synchronized (ioLock) {
ret = this.ssl.write(dataArr, sendSz);
}
if (ret <= 0) {
/* error, reset in[] positions for next call */
for (i = ofst; i < ofst + len; i++) {
in[i].position(pos[i]);
}
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ssl.write() returning: " + ret);
return ret;
}
@Override
public synchronized SSLEngineResult wrap(ByteBuffer in, ByteBuffer out)
throws SSLException {
if (in == null) {
throw new SSLException("SSLEngine.wrap() bad arguments");
}
return wrap(new ByteBuffer[] { in }, 0, 1, out);
}
@Override
public synchronized SSLEngineResult wrap(ByteBuffer[] in, int ofst, int len,
ByteBuffer out) throws SSLException {
int ret = 0, i;
int produced = 0;
int consumed = 0;
/* Set initial status for SSLEngineResult return */
Status status = SSLEngineResult.Status.OK;
/* Sanity check buffer arguments. */
if (in == null || ofst + len > in.length || out == null) {
throw new SSLException("SSLEngine.wrap() bad arguments");
}
if (ofst < 0 || len < 0) {
throw new IndexOutOfBoundsException();
}
for (i = ofst; i < len; ++i) {
if (in[i] == null) {
throw new SSLException("SSLEngine.wrap() bad arguments");
}
}
if (out.isReadOnly()) {
throw new ReadOnlyBufferException();
}
if (!this.clientModeSet) {
throw new IllegalStateException(
"setUseClientMode() has not been called on this SSLEngine");
}
if (extraDebugEnabled) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"==== [ entering wrap() ] ===================================");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"setUseClientMode: " + this.engineHelper.getUseClientMode());
for (i = 0; i < len; i++) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].remaining(): " + in[i].remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].position(): " + in[i].position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].limit(): " + in[i].limit());
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ofst: " + ofst + ", len: " + len);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.remaining(): " + out.remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.position(): " + out.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.limit(): " + out.limit());
if (this.toSend != null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: " + this.toSend.length);
} else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: 0");
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifySent: " + this.closeNotifySent);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifyReceived: " + this.closeNotifyReceived);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"inBoundOpen: " + this.inBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"outBoundOpen: " + this.outBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"status: " + status);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeStatus: " + hs);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeFinished: " + this.handshakeFinished);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"===========================================================");
}
/* Set wolfSSL I/O callbacks and context for read/write operations */
try {
setSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
if (needInit) {
checkAndInitSSLEngine();
}
synchronized (netDataLock) {
this.netData = null;
}
/* Force out buffer to be large enough to hold max packet size */
if (out.remaining() <
this.engineHelper.getSession().getPacketBufferSize()) {
return new SSLEngineResult(Status.BUFFER_OVERFLOW, hs, 0, 0);
}
/* Copy buffered data to be sent into output buffer */
produced = CopyOutPacket(out);
/* Closing down connection if buffered data has been sent and:
* 1. Outbound has been closed (!outBoundOpen)
* 2. Inbound is closed and close_notify has been sent
*/
if (produced >= 0 &&
(!outBoundOpen || (!inBoundOpen && this.closeNotifySent))) {
/* Mark SSLEngine status as CLOSED */
status = SSLEngineResult.Status.CLOSED;
/* Handshake has finished and SSLEngine is closed, release
* global JNI verify callback pointer */
this.engineHelper.unsetVerifyCallback();
try {
ClosingConnection();
} catch (SocketException | SocketTimeoutException e) {
throw new SSLException(e);
}
produced += CopyOutPacket(out);
}
else if ((produced > 0) && !inBoundOpen &&
(!this.closeNotifySent && !this.closeNotifyReceived)) {
/* We had buffered data to send, but inbound was already closed.
* Most likely this is because we needed to send an alert to
* the peer. We should now mark outbound as closed since we
* won't be sending anything after the alert went out. */
this.outBoundOpen = false;
}
else if (produced == 0) {
/* continue handshake or application data */
if (!this.handshakeFinished) {
ret = DoHandshake();
}
else {
try {
ret = SendAppData(in, ofst, len);
if (ret > 0) {
consumed += ret;
}
} catch (SocketException | SocketTimeoutException e) {
throw new SSLException(e);
}
}
/* copy any produced data into output buffer */
produced += CopyOutPacket(out);
}
SetHandshakeStatus(ret);
if (extraDebugEnabled) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"==== [ exiting wrap() ] ===================================");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"setUseClientMode: " + this.engineHelper.getUseClientMode());
for (i = 0; i < len; i++) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].remaining(): " + in[i].remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].position(): " + in[i].position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ByteBuffer in["+i+"].limit(): " + in[i].limit());
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ofst: " + ofst + ", len: " + len);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.remaining(): " + out.remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.position(): " + out.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out.limit(): " + out.limit());
if (this.toSend != null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: " + this.toSend.length);
} else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: 0");
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifySent: " + this.closeNotifySent);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifyReceived: " + this.closeNotifyReceived);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeFinished: " + this.handshakeFinished);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"inBoundOpen: " + this.inBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"outBoundOpen: " + this.outBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeStatus: " + hs);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"status: " + status);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"consumed: " + consumed);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"produced: " + produced);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"===========================================================");
}
try {
/* Don't hold references to this SSLEngine object in WolfSSLSession,
* can prevent proper garbage collection */
unsetSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
return new SSLEngineResult(status, hs, consumed, produced);
}
/**
* Return total remaining space in array of ByteBuffers.
*
* @param out array of ByteBuffers to be polled for available space
* @param ofst offset into out array to begin
* @param length length of ByteBuffer array
*
* @return number of available/remaining bytes in array of ByteBuffers
* @throws IllegalArgumentException if readonly buffer found
*/
private static synchronized int getTotalOutputSize(ByteBuffer[] out,
int ofst, int length) {
int i = 0;
int maxOutSz = 0;
for (i = 0; i < length; i++) {
if (out[i + ofst] == null || out[i + ofst].isReadOnly()) {
throw new IllegalArgumentException(
"null or readonly out buffer found");
}
maxOutSz += out[i + ofst].remaining();
}
return maxOutSz;
}
/**
* Receive application data using ssl.read() from in buffer, placing
* processed/decrypted data into out[].
*
* @param out output ByteBuffer arrays, to hold processed/decoded plaintext
* @param ofst offset into out[] array to begin writing data
* @param length length of out[] array
*
* @throws SSLException if ssl.read() encounters socket error
* @return number of plaintext bytes received, or negative on error.
*/
private synchronized int RecvAppData(ByteBuffer[] out, int ofst, int length)
throws SSLException {
int i, sz, bufSpace;
int totalRead = 0;
int maxOutSz = 0;
int ret = 0;
int idx = 0; /* index into out[] array */
int err = 0;
byte[] tmp = null;
/* Calculate maximum output size across ByteBuffer arrays */
maxOutSz = getTotalOutputSize(out, ofst, length);
synchronized (ioLock) {
try {
/* If we only have one ByteBuffer, skip allocating
* separate intermediate byte[] and write directly to underlying
* ByteBuffer array */
if (out.length == 1) {
ret = this.ssl.read(out[0], maxOutSz, 0);
}
else {
tmp = new byte[maxOutSz];
ret = this.ssl.read(tmp, maxOutSz);
}
} catch (SocketTimeoutException | SocketException e) {
throw new SSLException(e);
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"RecvAppData(), ssl.read() ret = " + ret);
err = ssl.getError(ret);
}
if (ret <= 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"RecvAppData(), ssl.getError() = " + err);
switch (err) {
case WolfSSL.SSL_ERROR_WANT_READ:
case WolfSSL.SSL_ERROR_WANT_WRITE:
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"RecvAppData(), got WANT_READ/WANT_WRITE");
break;
/* In 0 and ZERO_RETURN cases we may have gotten a
* close_notify alert, check on shutdown status */
case WolfSSL.SSL_ERROR_ZERO_RETURN:
case 0:
if (err == WolfSSL.SSL_ERROR_ZERO_RETURN) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"RecvAppData(), got ZERO_RETURN");
}
/* check if is shutdown message */
synchronized (ioLock) {
if (ssl.getShutdown() ==
WolfSSL.SSL_RECEIVED_SHUTDOWN) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"RecvAppData(), received shutdown message");
try {
ret = ClosingConnection();
if (ret > 0) {
/* Returns number of bytes read,
* 0, or err */
ret = 0;
}
} catch (SocketException |
SocketTimeoutException e) {
throw new SSLException(e);
}
return ret;
}
}
break;
default:
throw new SSLException("wolfSSL_read() error: " +
ret + " , err = " + err);
}
}
else {
if (out.length == 1) {
totalRead = ret;
}
else {
/* write processed data into output buffers */
for (i = 0; i < ret;) {
if (idx + ofst >= length) {
/* no more output buffers left */
break;
}
bufSpace = out[idx + ofst].remaining();
if (bufSpace == 0) {
/* no more space in current out buffer, advance */
idx++;
continue;
}
sz = (bufSpace >= (ret - i)) ? (ret - i) : bufSpace;
out[idx + ofst].put(tmp, i, sz);
i += sz;
totalRead += sz;
if ((ret - i) > 0) {
idx++; /* go to next output buffer */
}
}
}
}
return totalRead;
}
@Override
public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer out)
throws SSLException {
if (out == null) {
throw new IllegalArgumentException(
"SSLEngine.unwrap() bad arguments");
}
return unwrap(in, new ByteBuffer[] { out }, 0, 1);
}
@Override
public synchronized SSLEngineResult unwrap(ByteBuffer in, ByteBuffer[] out,
int ofst, int length) throws SSLException {
int i, ret = 0, sz = 0, err = 0;
int inPosition = 0;
int inRemaining = 0;
int consumed = 0;
int produced = 0;
byte[] tmp;
/* Set initial status for SSLEngineResult return */
Status status = SSLEngineResult.Status.OK;
/* Sanity check buffer arguments. */
if (in == null || out == null || ofst + length > out.length) {
throw new IllegalArgumentException(
"SSLEngine.unwrap() bad arguments");
}
if (ofst < 0 || length < 0) {
throw new IndexOutOfBoundsException();
}
for (i = ofst; i < length; ++i) {
if (out[i] == null) {
throw new IllegalArgumentException(
"SSLEngine.unwrap() bad arguments");
}
if (out[i].isReadOnly()) {
throw new ReadOnlyBufferException();
}
}
if (!this.clientModeSet) {
throw new IllegalStateException(
"setUseClientMode() has not been called on this SSLEngine");
}
synchronized (netDataLock) {
this.netData = in;
inPosition = in.position();
inRemaining = in.remaining();
}
if (extraDebugEnabled) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"==== [ entering unwrap() ] =================================");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"setUseClientMode: " + this.engineHelper.getUseClientMode());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.remaining(): " + in.remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.position(): " + in.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.limit(): " + in.limit());
for (i = 0; i < length; i++) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].remaining(): " + out[i].remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].position(): " + out[i].position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].limit(): " + out[i].limit());
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ofst: " + ofst + ", length: " + length);
if (this.toSend != null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: " + this.toSend.length);
} else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: 0");
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifySent: " + this.closeNotifySent);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifyReceived: " + this.closeNotifyReceived);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"inBoundOpen: " + this.inBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"outBoundOpen: " + this.outBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeFinished: " + this.handshakeFinished);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeStatus: " + hs);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"status: " + status);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"===========================================================");
}
/* Set wolfSSL I/O callbacks and context for read/write operations */
try {
setSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
if (getUseClientMode() && needInit) {
/* If unwrap() is called before handshake has started, return
* WANT_WRAP since we'll need to send a ClientHello first */
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP &&
(this.toSend != null) && (this.toSend.length > 0)) {
/* Already have data buffered to send and in NEED_WRAP state,
* just return so wrap() can be called */
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
else {
if (needInit) {
checkAndInitSSLEngine();
}
if (outBoundOpen == false) {
try {
if (ClosingConnection() == WolfSSL.SSL_SUCCESS) {
/* Mark SSLEngine status as CLOSED */
status = SSLEngineResult.Status.CLOSED;
/* Handshake has finished and SSLEngine is closed,
* release, global JNI verify callback pointer */
this.engineHelper.unsetVerifyCallback();
}
} catch (SocketException | SocketTimeoutException e) {
throw new SSLException(e);
}
}
else {
if (this.handshakeFinished == false) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"starting or continuing handshake");
ret = DoHandshake();
}
else {
/* If we have input data, make sure output buffer length is
* greater than zero, otherwise ask app to expand out buffer.
* There may be edge cases where this could be tightened up,
* but this will err on the side of giving us more output
* space than we need. */
if (inRemaining > 0 &&
getTotalOutputSize(out, ofst, length) == 0) {
status = SSLEngineResult.Status.BUFFER_OVERFLOW;
}
else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"receiving application data");
ret = RecvAppData(out, ofst, length);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"received application data: " + ret +
" bytes (from RecvAppData)");
if (ret > 0) {
produced += ret;
}
else {
synchronized (netDataLock) {
if (ret == 0 && in.remaining() > 0 &&
getTotalOutputSize(out, ofst,
length) == 0) {
/* We have more data to read, but no more
* out space left in ByteBuffer[], ask for
* more */
status =
SSLEngineResult.Status.BUFFER_OVERFLOW;
}
}
}
}
/* If we haven't stored session yet, try again. For TLS 1.3
* we may need to wait for session ticket. We do try
* right after wolfSSL_connect/accept() finishes, but
* we might not have had session ticket at that time. */
synchronized (ioLock) {
if (this.handshakeFinished && (ssl.getError(0) == 0) &&
!this.sessionStored) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"calling engineHelper.saveSession()");
int ret2 = this.engineHelper.saveSession();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"return from saveSession(), ret = " + ret2);
if (ret2 == WolfSSL.SSL_SUCCESS) {
this.sessionStored = true;
}
}
}
}
if (outBoundOpen == false || this.closeNotifySent) {
/* Mark SSLEngine status as CLOSED */
status = SSLEngineResult.Status.CLOSED;
/* Handshake has finished and SSLEngine is closed,
* release, global JNI verify callback pointer */
this.engineHelper.unsetVerifyCallback();
}
synchronized (ioLock) {
err = ssl.getError(ret);
}
if (ret < 0 &&
(err != WolfSSL.SSL_ERROR_WANT_READ) &&
(err != WolfSSL.SSL_ERROR_WANT_WRITE)) {
if (err == WolfSSL.UNKNOWN_ALPN_PROTOCOL_NAME_E) {
/* Native wolfSSL could not negotiate a common ALPN
* protocol */
this.inBoundOpen = false;
throw new SSLHandshakeException(
"Unrecognized protocol name error, ret:err = " +
ret + " : " + err);
}
else {
/* Native wolfSSL threw an exception when unwrapping
* data, close inbound since we can't receive more
* data */
this.inBoundOpen = false;
if (err == WolfSSL.FATAL_ERROR) {
/* If client side and we received fatal alert,
* close outbound since we won't be receiving
* any more data */
this.outBoundOpen = false;
}
throw new SSLException(
"wolfSSL error, ret:err = " + ret + " : " + err);
}
}
synchronized (toSendLock) {
synchronized (netDataLock) {
if (ret < 0 && err == WolfSSL.SSL_ERROR_WANT_READ &&
in.remaining() == 0 && (this.toSend == null ||
(this.toSend != null && this.toSend.length == 0))) {
/* Need more data */
status = SSLEngineResult.Status.BUFFER_UNDERFLOW;
}
}
}
}
synchronized (netDataLock) {
consumed += in.position() - inPosition;
}
SetHandshakeStatus(ret);
}
/* If client side, handshake is done, and we have just received a
* TLS 1.3 session ticket, we should return FINISHED HandshakeStatus
* from unwrap() directly but not from getHandshakeStatus(). Keep track
* of if we have received ticket, so we only set/return this once */
synchronized (ioLock) {
if (this.getUseClientMode() && this.handshakeFinished &&
this.ssl.hasSessionTicket() &&
this.sessionTicketReceived == false) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"received session ticket, returning " +
"HandshakeStatus FINISHED");
hs = SSLEngineResult.HandshakeStatus.FINISHED;
this.sessionTicketReceived = true;
}
}
if (extraDebugEnabled == true) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"==== [ exiting unwrap() ] ==================================");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"setUseClientMode: " + this.engineHelper.getUseClientMode());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.remaining(): " + in.remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.position(): " + in.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"in.limit(): " + in.limit());
for (i = 0; i < length; i++) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].remaining(): " + out[i].remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].position(): " + out[i].position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"out["+i+"].limit(): " + out[i].limit());
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ofst: " + ofst + ", length: " + length);
if (this.toSend != null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: " + this.toSend.length);
} else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"toSend.length: 0");
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeFinished: " + this.handshakeFinished);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifySent: " + this.closeNotifySent);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"closeNotifyReceived: " + this.closeNotifyReceived);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"inBoundOpen: " + this.inBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"outBoundOpen: " + this.outBoundOpen);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"handshakeStatus: " + hs);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"status: " + status);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"consumed: " + consumed);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"produced: " + produced);
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"===========================================================");
}
try {
/* Don't hold references to this SSLEngine object in WolfSSLSession,
* can prevent proper garbage collection */
unsetSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
return new SSLEngineResult(status, hs, consumed, produced);
}
/**
* Sets handshake status after I/O operation of unwrap(), helper function.
*/
private synchronized void SetHandshakeStatus(int ret) {
int err = 0;
/* Get current wolfSSL error, synchronize on ioLock in case I/O is
* happening and error state may change */
synchronized (ioLock) {
err = ssl.getError(ret);
}
/* Lock access to this.toSend and this.toRead */
synchronized (toSendLock) {
if (this.handshakeFinished == true) {
/* close_notify sent by wolfSSL but not across transport yet */
if (this.closeNotifySent == true &&
this.toSend != null && this.toSend.length > 0) {
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
/* close_notify received, need to send one back */
else if (this.closeNotifyReceived == true &&
this.closeNotifySent == false) {
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
/* close_notify sent, need to read peer's */
else if (this.closeNotifySent == true &&
this.closeNotifyReceived == false) {
/* Receiving close_notify is optional after we have sent
* one. Denote that with NOT_HANDSHAKING here */
hs = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
else if (!this.outBoundOpen && !this.closeNotifySent) {
/* We just closed outBound, NEED_WRAP to generate and
* send close_notify */
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
else {
hs = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
}
else {
synchronized (netDataLock) {
synchronized (ioLock) {
if (ssl.handshakeDone() && this.toSend == null) {
this.handshakeFinished = true;
hs = SSLEngineResult.HandshakeStatus.FINISHED;
this.engineHelper.getSession().updateStoredSessionValues();
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"SSL/TLS handshake finished");
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"SSL/TLS protocol: " +
this.engineHelper.getSession().getProtocol());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"SSL/TLS cipher suite: " +
this.engineHelper.getSession().getCipherSuite());
}
/* give priority of WRAP/UNWRAP to state of our internal
* I/O data buffers first, then wolfSSL err status */
else if (this.toSend != null && this.toSend.length > 0) {
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
else if (this.netData != null &&
this.netData.remaining() > 0 &&
this.inBoundOpen == true) {
hs = SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
}
else if (err == WolfSSL.SSL_ERROR_WANT_READ) {
hs = SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
}
else if (err == WolfSSL.SSL_ERROR_WANT_WRITE) {
hs = SSLEngineResult.HandshakeStatus.NEED_WRAP;
}
else {
hs = SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
}
} /* synchronized ioLock */
} /* synchronized netDataLock */
}
} /* synchronized toSendLock */
return;
}
@Override
public Runnable getDelegatedTask() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getDelegatedTask()");
/* no tasks left to run */
return null;
}
@Override
public synchronized void closeInbound() throws SSLException {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered closeInbound");
if (!inBoundOpen)
return;
if (inBoundOpen && !closed) {
/* this checks that peer sent back shutdown message */
throw new SSLException("Closing in bound before shutdown is done");
}
else
{
inBoundOpen = false;
}
}
@Override
public synchronized boolean isInboundDone() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered isInboundDone()");
return !inBoundOpen;
}
@Override
public synchronized void closeOutbound() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered closeOutbound, outBoundOpen = false");
outBoundOpen = false;
/* If handshake has not started yet, close inBound as well */
if (needInit) {
inBoundOpen = true;
}
/* Update status based on internal state. Some calling applications
* loop around getHandshakeStatus(), it needs to be up to date. */
SetHandshakeStatus(0);
}
@Override
public synchronized boolean isOutboundDone() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered isOutboundDone()");
return !outBoundOpen;
}
@Override
public String[] getSupportedCipherSuites() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getSupportedCipherSuites()");
return WolfSSLEngineHelper.getAllCiphers();
}
@Override
public String[] getEnabledCipherSuites() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getEnabledCipherSuites()");
return this.engineHelper.getCiphers();
}
@Override
public synchronized void setEnabledCipherSuites(String[] suites) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setEnabledCipherSuites()");
this.engineHelper.setCiphers(suites);
}
@Override
public String[] getSupportedProtocols() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getSupportedProtocols()");
return WolfSSLEngineHelper.getAllProtocols();
}
@Override
public synchronized String[] getEnabledProtocols() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getEnabledProtocols()");
return this.engineHelper.getProtocols();
}
@Override
public synchronized void setEnabledProtocols(String[] protocols) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setEnabledProtocols()");
this.engineHelper.setProtocols(protocols);
}
@Override
public synchronized SSLSession getSession() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getSession()");
try {
/* Need cert loaded for getSession().getLocalCertificates() */
LoadCertAndKey();
} catch (SSLException e) {
return null;
}
return this.engineHelper.getSession();
}
/**
* Has the underlying WOLFSSL / WolfSSLSession been resumed / reused.
* This calls down to native wolfSSL_session_reused()
*
* NON-STANDARD API, not part of JSSE. Must cast SSLEngine back
* to WolfSSLEngine to use.
*
* @return true if session has been resumed, otherwise false
* @throws SSLException if native JNI call fails or underlying
* WolfSSLSession has been freed
*/
public synchronized boolean sessionResumed() throws SSLException {
if (this.ssl != null) {
try {
int resume = this.ssl.sessionReused();
if (resume == 1) {
return true;
}
} catch (IllegalStateException | WolfSSLJNIException e) {
throw new SSLException(e);
}
}
return false;
}
public synchronized SSLSession getHandshakeSession() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeSession()");
return this.engineHelper.getSession();
}
/**
* Explicitly start the SSL/TLS handshake.
*
* This method does not block until handshake is completed, unlike
* the SSLSocket.startHandshake() method.
*
* The handshake may be started implicitly by a call to wrap() or unwrap(),
* if those methods are called and the handshake is not done yet. In that
* case, the user has not explicitly started the handshake themselves
* and may inadvertently call beginHandshake() unknowing that the handshake
* has already started. For that case, we should just return without
* error/exception, since the user is just trying to start the first
* initial handshake.
*
* beginHandshake() may also be called again to initiate renegotiation.
* wolfJSSE does not support renegotiation inside SSLEngine yet. In that
* case, we throw an SSLException to notify callers renegotiation is not
* supported.
*
* @throws SSLException if a problem was encountered while initiating
* a SSL/TLS handshake on this SSLEngine.
* @throws IllegalStateException if the client/server mode has not yet
* been set.
*/
@Override
public synchronized void beginHandshake() throws SSLException {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered beginHandshake()");
if (!this.clientModeSet) {
throw new IllegalStateException(
"setUseClientMode() has not been called on this SSLEngine");
}
if (this.handshakeStartedExplicitly) {
/* Renegotiation (thus calling beginHandshake() multiple times)
* is not supported in wolfJSSE SSLEngine implementation yet. If
* already called once by user, throw SSLException. */
throw new SSLException("Renegotiation not supported");
}
else if (!this.needInit && !this.handshakeFinished) {
/* Handshake has started implicitly by wrap() or unwrap(). Simply
* return since this is the first time that the user has called
* beginHandshake() themselves. */
this.handshakeStartedExplicitly = true;
return;
}
/* No network data source yet */
synchronized (netDataLock) {
this.netData = null;
}
if (outBoundOpen == false) {
throw new SSLException(
"beginHandshake() called but outbound closed");
}
try {
setSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
if (needInit == true) {
/* will throw SSLHandshakeException if session creation is
not allowed */
checkAndInitSSLEngine();
}
try {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"calling engineHelper.doHandshake()");
int ret = this.engineHelper.doHandshake(1, 0);
SetHandshakeStatus(ret);
/* Mark that the user has explicitly started the handshake
* on this SSLEngine by calling beginHandshake() */
this.handshakeStartedExplicitly = true;
} catch (SocketTimeoutException e) {
e.printStackTrace();
throw new SSLException(e);
} finally {
try {
/* Don't hold references to this SSLEngine object in
* WolfSSLSession, can prevent proper garbage collection */
unsetSSLCallbacks();
} catch (WolfSSLJNIException e) {
throw new SSLException(e);
}
}
}
@Override
public synchronized SSLEngineResult.HandshakeStatus getHandshakeStatus() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeStatus(): " + hs);
/* Update status based on internal state. Some calling applications
* loop around getHandshakeStatus(), it needs to be up to date. */
SetHandshakeStatus(0);
return hs;
}
@Override
public synchronized void setUseClientMode(boolean mode) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setUseClientMode(" + mode + ")");
this.engineHelper.setUseClientMode(mode);
this.clientModeSet = true;
}
@Override
public synchronized boolean getUseClientMode() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getUseClientMode()");
return this.engineHelper.getUseClientMode();
}
@Override
public synchronized void setNeedClientAuth(boolean need) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setNeedClientAuth(" + need + ")");
this.engineHelper.setNeedClientAuth(need);
}
@Override
public synchronized boolean getNeedClientAuth() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getNeedClientAuth()");
return this.engineHelper.getNeedClientAuth();
}
@Override
public synchronized void setWantClientAuth(boolean want) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setWantClientAuth(" + want + ")");
this.engineHelper.setWantClientAuth(want);
}
@Override
public synchronized boolean getWantClientAuth() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getWantClientAuth()");
return this.engineHelper.getWantClientAuth();
}
@Override
public synchronized void setEnableSessionCreation(boolean flag) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setEnableSessionCreation(" + flag + ")");
this.engineHelper.setEnableSessionCreation(flag);
}
@Override
public synchronized boolean getEnableSessionCreation() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getEnableSessionCreation()");
return this.engineHelper.getEnableSessionCreation();
}
public synchronized String getApplicationProtocol() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getApplicationProtocol()");
return this.engineHelper.getAlpnSelectedProtocolString();
}
/**
* Returns the application protocol value negotiated on a handshake
* currently in progress.
*
* After the handshake has finished, this will return null. To get the
* ALPN protocol negotiated during the handshake, after it has completed,
* call getApplicationProtocol().
*
* Not marked at @Override since this API was added as of
* Java SE 8 Maintenance Release 3, and Java 7 SSLSocket will not
* have this.
*
* @return String representating the application protocol negotiated
*/
public synchronized String getHandshakeApplicationProtocol() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocol()");
if (!this.needInit && !this.handshakeFinished) {
return this.engineHelper.getAlpnSelectedProtocolString();
}
return null;
}
/**
* Returns the callback that selects an application protocol during the
* SSL/TLS handshake.
*
* @return the callback function, or null if no callback has been set
*/
public synchronized BiFunction<SSLEngine,List<String>,String>
getHandshakeApplicationProtocolSelector() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getHandshakeApplicationProtocolSelector()");
return this.alpnSelector;
}
/**
* Registers a callback function that selects an application protocol
* value for the SSL/TLS handshake.
*
* Usage of this callback will override any values set by
* SSLParameters.setApplicationProtocols().
*
* Callback argument descriptions:
*
* SSLEngine - the current SSLEngine, allows for inspection by the
* callback if needed
* List&lt;String&gt; - List of Strings representing application protocol
* names sent by the peer
* String - Result of the callback is an application protocol
* name String, or null if none of the peer's protocols
* are acceptable. If return value is an empty String,
* ALPN will not be used.
*
* @param selector callback used to select ALPN protocol for handshake
*/
public synchronized void setHandshakeApplicationProtocolSelector(
BiFunction<SSLEngine,List<String>,String> selector) {
int ret = 0;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setHandshakeApplicationProtocolSelector()");
if (selector != null) {
ALPNSelectCallback alpnCb = new ALPNSelectCallback();
try {
/* Pass in SSLSocket Object for use inside callback */
ret = this.ssl.setAlpnSelectCb(alpnCb, this);
if (ret != WolfSSL.SSL_SUCCESS) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Native setAlpnSelectCb() failed, ret = " + ret +
", not setting selector");
return;
}
/* called from within ALPNSelectCallback during the handshake */
this.alpnSelector = selector;
} catch (WolfSSLJNIException e) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Exception while calling ssl.setAlpnSelectCb, not setting");
}
}
}
/**
* Inner class that implement the ALPN select callback which is registered
* with our com.wolfssl.WolfSSLSession when
* setHandshakeApplicationProtocolSelector() has been called.
*/
class ALPNSelectCallback implements WolfSSLALPNSelectCallback
{
public int alpnSelectCallback(WolfSSLSession ssl, String[] out,
String[] in, Object arg) {
SSLEngine engine = (SSLEngine)arg;
List<String> peerProtos = new ArrayList<String>();
String selected = null;
if (alpnSelector == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelector null inside ALPNSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (!(arg instanceof SSLEngine)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"alpnSelectCallback arg not type of SSLEngine");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
if (in.length == 0) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"peer protocol list is 0 inside alpnSelectCallback");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protos sent by peer: " + Arrays.toString(in));
for (String s: in) {
peerProtos.add(s);
}
selected = alpnSelector.apply(engine, peerProtos);
if (selected == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol string is null, no peer match");
return WolfSSL.SSL_TLSEXT_ERR_ALERT_FATAL;
}
else {
if (selected.isEmpty()) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN not being used, selected proto empty");
return WolfSSL.SSL_TLSEXT_ERR_NOACK;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"ALPN protocol selected by callback: " + selected);
out[0] = selected;
return WolfSSL.SSL_TLSEXT_ERR_OK;
}
}
}
/**
* Set the SSLParameters for this SSLEngine.
*
* @param params SSLParameters to set for this SSLEngine object
*/
public synchronized void setSSLParameters(SSLParameters params) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered setSSLParameters()");
if (params != null) {
WolfSSLParametersHelper.importParams(params, this.params);
}
}
/**
* Gets the SSLParameters for this SSLEngine.
*
* @return SSLParameters for this SSLEngine object.
*/
@Override
public synchronized SSLParameters getSSLParameters() {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"entered getSSLParameters()");
return WolfSSLParametersHelper.decoupleParams(this.params);
}
/**
* Copies buffer to end of to send queue. Encrypted packet is ready to be
* sent out.
*
* @param in byte array with encrypted data to be sent
* @param sz size of data in input array to be sent
*
* @return number of bytes placed into send queue
*/
protected synchronized int internalSendCb(byte[] in, int sz) {
int totalSz = sz, idx = 0;
byte[] prevToSend = null;
synchronized (toSendLock) {
/* Make copy of existing toSend array before expanding */
if (this.toSend != null) {
prevToSend = this.toSend.clone();
totalSz += this.toSend.length;
}
/* Allocate new array to hold data to be sent */
this.toSend = new byte[totalSz];
/* Copy existing toSend data over */
if (prevToSend != null) {
System.arraycopy(prevToSend, 0, this.toSend, idx,
prevToSend.length);
idx += prevToSend.length;
}
System.arraycopy(in, 0, this.toSend, idx, sz);
}
if (ioDebugEnabled == true) {
WolfSSLDebug.logHex(getClass(), WolfSSLDebug.INFO,
"CB Write", in, sz);
}
return sz;
}
/**
* Internal receive callback. Reads from netData and gives bytes back
* to native wolfSSL for processing.
*
* @param toRead byte array into which to place data read from transport
* @param sz number of bytes that should be read/copied from transport
*
* @return number of bytes read into toRead array or negative
* value on error
*/
protected synchronized int internalRecvCb(byte[] toRead, int sz) {
int max = 0;
synchronized (netDataLock) {
if (ioDebugEnabled == true) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"CB Read: requesting " + sz + " bytes");
if (this.netData != null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"CB Read: netData.remaining() = " +
this.netData.remaining());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"CB Read: netData.position() = " +
this.netData.position());
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"CB Read: netData.limit() = " +
this.netData.limit());
}
}
if (this.netData == null || this.netData.remaining() == 0) {
if (ioDebugEnabled == true) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"CB Read: returning WOLFSSL_CBIO_ERR_WANT_READ");
}
return WolfSSL.WOLFSSL_CBIO_ERR_WANT_READ;
}
max = (sz < this.netData.remaining()) ? sz : this.netData.remaining();
this.netData.get(toRead, 0, max);
if (ioDebugEnabled == true) {
WolfSSLDebug.logHex(getClass(), WolfSSLDebug.INFO,
"CB Read", toRead, max);
}
return max;
}
}
private class SendCB implements WolfSSLIOSendCallback {
protected SendCB() {
}
public int sendCallback(WolfSSLSession ssl, byte[] toSend, int sz,
Object engine) {
return ((WolfSSLEngine)engine).internalSendCb(toSend, sz);
}
}
private class RecvCB implements WolfSSLIORecvCallback {
protected RecvCB() {
}
public int receiveCallback(WolfSSLSession ssl, byte[] out, int sz,
Object engine) {
return ((WolfSSLEngine)engine).internalRecvCb(out, sz);
}
}
@SuppressWarnings("deprecation")
@Override
protected synchronized void finalize() throws Throwable {
if (this.ssl != null) {
this.ssl.freeSSL();
this.ssl = null;
}
super.finalize();
}
}