JSSE: only resume sessions from Java client cache if same cipher suite and protocol are enabled

pull/209/head
Chris Conlon 2024-07-17 17:12:00 -06:00
parent 4e70b6827d
commit 7231009800
6 changed files with 236 additions and 47 deletions

View File

@ -1733,6 +1733,27 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionDup
return (jlong)(uintptr_t)wolfSSL_SESSION_dup(session);
}
JNIEXPORT jstring JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionCipherGetName
(JNIEnv* jenv, jclass jcl, jlong sessionPtr)
{
WOLFSSL_SESSION* session = (WOLFSSL_SESSION*)(uintptr_t)sessionPtr;
const char* cipherName;
jstring cipherStr = NULL;
(void)jcl;
if (jenv == NULL || session == NULL) {
return NULL;
}
cipherName = wolfSSL_SESSION_CIPHER_get_name(session);
if (cipherName != NULL) {
cipherStr = (*jenv)->NewStringUTF(jenv, cipherName);
}
return cipherStr;
}
JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLSession_freeNativeSession
(JNIEnv* jenv, jclass jcl, jlong sessionPtr)
{

View File

@ -183,6 +183,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionIsResumable
JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionDup
(JNIEnv *, jclass, jlong);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: wolfsslSessionCipherGetName
* Signature: (J)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionCipherGetName
(JNIEnv *, jclass, jlong);
/*
* Class: com_wolfssl_WolfSSLSession
* Method: freeNativeSession

View File

@ -260,6 +260,7 @@ public class WolfSSLSession {
private static native int wolfsslSessionIsSetup(long ssl);
private static native int wolfsslSessionIsResumable(long ssl);
private static native long wolfsslSessionDup(long session);
private static native String wolfsslSessionCipherGetName(long ssl);
private static native void freeNativeSession(long session);
private native byte[] getSessionID(long session);
private native int setServerID(long ssl, byte[] id, int len, int newSess);
@ -1389,6 +1390,29 @@ public class WolfSSLSession {
return wolfsslSessionDup(session);
}
/**
* Get cipher suite name from WOLFSSL_SESSION, calling native
* wolfSSL_SESSION_CIPHER_get_name().
*
* This method is static and does not check active state since this
* takes a native pointer and has no interaction with the rest of this
* object.
*
* @param session pointer to native WOLFSSL_SESSION structure. May have
* been obtained from getSession().
* @return String representation of the cipher suite used in native
* WOLFSSL_SESSION structure, or NULL if not able to find the
* session.
*/
public static String sessionGetCipherName(long session) {
if (session == 0) {
return null;
}
return wolfsslSessionCipherGetName(session);
}
/**
* Free the native WOLFSSL_SESSION structure pointed to be session.
*

View File

@ -296,13 +296,24 @@ public class WolfSSLAuthStore {
* @param port port number of peer being connected to
* @param host host of the peer being connected to
* @param clientMode if is client side then true, otherwise false
* @param enabledCipherSuites String array containing enabled cipher
* suites for the SSLSocket/SSLEngine requesting this session.
* Used to compare cipher suite of cached session against enabled
* cipher suites.
* @param enabledProtocols String array containing enabled protocols
* for the SSLSocket/SSLEngine requesting this session.
* Used to compare protocol of cached session against enabled
* protocols.
*
* @return an existing SSLSession from Java session cache, or a new
* object if not in cache, called on server side, or host
* is null
*/
protected synchronized WolfSSLImplementSSLSession getSession(
WolfSSLSession ssl, int port, String host, boolean clientMode) {
WolfSSLSession ssl, int port, String host, boolean clientMode,
String[] enabledCipherSuites, String[] enabledProtocols) {
boolean needNewSession = false;
WolfSSLImplementSSLSession ses = null;
String toHash = null;
@ -327,16 +338,51 @@ public class WolfSSLAuthStore {
* is shared between all threads */
synchronized (storeLock) {
/* generate cache key hash (host:port) */
/* Generate cache key hash (host:port) */
toHash = host.concat(Integer.toString(port));
/* try getting session out of Java store */
/* Try getting session out of Java store */
ses = store.get(toHash.hashCode());
if (ses == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"session not found in cache table, creating new");
/* not found in stored sessions create a new one */
/* Remove old entry from table. TLS 1.3 binder changes between
* resumptions and stored session should only be used to
* resume once. New session structure/object will be cached
* after the resumed session completes the handshake, for
* subsequent resumption attempts to use. */
store.remove(toHash.hashCode());
/* Check conditions where we need to create a new new session:
* 1. Session not found in cache
* 2. Session marked as not resumable
* 3. Original session cipher suite not available
* 4. Original session protocol version not available
*/
if (ses == null ||
!ses.isResumable() ||
!sessionCipherSuiteAvailable(ses, enabledCipherSuites) ||
!sessionProtocolAvailable(ses, enabledProtocols)) {
needNewSession = true;
}
if (needNewSession) {
if (ses == null) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"session not found in cache table, " +
"creating new session");
}
else if (!ses.isResumable()) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"native WOLFSSL_SESSION not resumable, " +
"creating new session");
}
else if (!sessionCipherSuiteAvailable(
ses, enabledCipherSuites)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"cipher suite used in original WOLFSSL_SESSION not " +
"available, creating new session");
}
/* Not found in stored sessions create a new one */
ses = new WolfSSLImplementSSLSession(ssl, port, host, this);
ses.setValid(true); /* new sessions marked as valid */
@ -345,35 +391,11 @@ public class WolfSSLAuthStore {
Integer.toString(ssl.hashCode()).getBytes());
}
else {
/* Remove old entry from table. TLS 1.3 binder changes between
* resumptions and stored session should only be used to
* resume once. New session structure/object will be cached
* after the resumed session completes the handshake, for
* subsequent resumption attempts to use. */
store.remove(toHash.hashCode());
/* Check if native WOLFSSL_SESSION is resumable before
* returning it for resumption. If not, create a new
* session instead. */
if (!ses.isResumable()) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"native WOLFSSL_SESSION not resumable, " +
"creating new session");
ses = new WolfSSLImplementSSLSession(ssl, port, host, this);
ses.setValid(true); /* new sessions marked as valid */
ses.isFromTable = false;
ses.setPseudoSessionId(
Integer.toString(ssl.hashCode()).getBytes());
return ses;
}
ses.isFromTable = true;
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"session found in cache, trying to resume");
ses.isFromTable = true;
if (ses.resume(ssl) != WolfSSL.SSL_SUCCESS) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"native wolfSSL_set_session() failed, " +
@ -384,13 +406,96 @@ public class WolfSSLAuthStore {
ses.isFromTable = false;
ses.setPseudoSessionId(
Integer.toString(ssl.hashCode()).getBytes());
}
}
return ses;
}
}
/**
* Check if cipher suite from original WOLFSSL_SESSION
* (WolfSSLImplementSSLSession) is available in new WolfSSLSession
* WolfSSLParameters.
*
* This is used in getSession(), since if we try resuming an old session
* but the cipher suite used in that session is not available in the
* ClientHello, the server will close the connection and send back an
* alert. If wolfSSL on the server side, this will be an illegal_parameter
* alert.
*
* @param ses WolfSSLImplementSSLSession to get existing cipher suite from
* to check.
* @param enabledCipherSuites cipher suites enabled, usually coming from
* WolfSSLEngineHelper.getCiphers().
*
* @return true if cipher suite from session is available in
* WolfSSLParameters enabled suites, otherwise false.
*/
private boolean sessionCipherSuiteAvailable(WolfSSLImplementSSLSession ses,
String[] enabledCipherSuites) {
String sessionCipher = null;
if (ses == null || enabledCipherSuites == null) {
return false;
}
sessionCipher = ses.getSessionCipherSuite();
if (Arrays.asList(enabledCipherSuites).contains(sessionCipher)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"WOLFSSL_SESSION cipher suite available in enabled ciphers");
return true;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"WOLFSSL_SESSION cipher suite (" + sessionCipher + ") differs " +
"from enabled suites list");
return false;
}
/**
* Check if protocol from original WOLFSSL_SESSION
* (WolfSSLImplementSSLSession) is available in new WolfSSLSession
* WolfSSLParameters.
*
* @param ses WolfSSLImplementSSLSession to get existing protocol from
* to check
* @param enabledProtocols protocols enabled on this SSLSocket/SSLEngine,
* usually coming from WolfSSLEngineHelper.getProtocols().
*
* @return true if protocol from session is available in WolfSSLParameters
* enabled protocols, otherwise false.
*/
private boolean sessionProtocolAvailable(WolfSSLImplementSSLSession ses,
String[] enabledProtocols) {
String sessionProtocol = null;
if (ses == null || enabledProtocols == null) {
return false;
}
sessionProtocol = ses.getProtocol();
if (sessionProtocol == null) {
return false;
}
if (Arrays.asList(enabledProtocols).contains(sessionProtocol)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"WOLFSSL_SESSION protocol available in enabled protocols");
return true;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"WOLFSSL_SESSION protocol (" + sessionProtocol + ") differs " +
"from enabled protocol list: " + Arrays.asList(enabledProtocols));
return false;
}
/**
* Print summary of current SessionStore (LinkedHashMap) status.
* Prints out size of current SessionStore. If size is greater than zero,

View File

@ -1199,7 +1199,7 @@ public class WolfSSLEngineHelper {
/* create non null session */
this.session = this.authStore.getSession(ssl, this.port,
sessCacheHostname, this.clientMode);
sessCacheHostname, this.clientMode, getCiphers(), getProtocols());
if (this.session != null) {
if (this.clientMode) {
@ -1345,6 +1345,11 @@ public class WolfSSLEngineHelper {
(err == WolfSSL.SSL_ERROR_WANT_READ ||
err == WolfSSL.SSL_ERROR_WANT_WRITE));
/* Update cached values in WolfSSLImplementSSLSession from
* WolfSSLSession, in case that goes out of scope and is garbage
* collected (ex: protocol version). */
this.session.updateStoredSessionValues();
return ret;
}
@ -1385,6 +1390,10 @@ public class WolfSSLEngineHelper {
*/
protected synchronized int saveSession() {
if (this.session != null && this.session.isValid()) {
/* Update values from WOLFSSL which are stored in
* WolfSSLImplementSSLSession (ex: protocol) */
this.session.updateStoredSessionValues();
if (this.clientMode) {
/* Only need to set resume on client side, server-side
* maintains session cache at native level. */

View File

@ -66,6 +66,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
private final HashMap<String, Object> binding;
private final int port;
private final String host;
String protocol = null;
Date creation = null;
Date accessed = null; /* when new connection was made using session */
byte[] pseudoSessionID = null; /* used with TLS 1.3*/
@ -130,6 +131,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
this.valid = false; /* flag if joining or resuming session is allowed */
this.peerCerts = null;
this.sesPtr = 0;
this.protocol = this.nullProtocol;
binding = new HashMap<String, Object>();
creation = new Date();
@ -154,6 +156,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
this.valid = false; /* flag if joining or resuming session is allowed */
this.peerCerts = null;
this.sesPtr = 0;
this.protocol = this.nullProtocol;
binding = new HashMap<String, Object>();
creation = new Date();
@ -175,6 +178,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
this.valid = false; /* flag if joining or resuming session is allowed */
this.peerCerts = null;
this.sesPtr = 0;
this.protocol = this.nullProtocol;
binding = new HashMap<String, Object>();
creation = new Date();
@ -224,6 +228,8 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
if (orig.peerCerts != null) {
this.peerCerts = orig.peerCerts.clone();
}
this.protocol = orig.protocol;
/* This session has been copied and is therefore not inside the
* WolfSSLAuthStore session cache table currently */
this.isInTable = false;
@ -690,20 +696,22 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
return null;
}
/**
* Return the cipher suite from the native WOLFSSL_SESSION structure.
*
* @return String representation of the cipher suite from the native
* WOLFSSL_SESSION structure, or NULL if not able to be
* retrieved.
*/
public synchronized String getSessionCipherSuite() {
synchronized (sesPtrLock) {
return WolfSSLSession.sessionGetCipherName(this.sesPtr);
}
}
@Override
public synchronized String getProtocol() {
if (ssl == null) {
return this.nullProtocol;
}
try {
return this.ssl.getVersion();
} catch (IllegalStateException | WolfSSLJNIException ex) {
Logger.getLogger(
WolfSSLImplementSSLSession.class.getName()).log(
Level.SEVERE, null, ex);
}
return null;
return this.protocol;
}
@Override
@ -816,6 +824,20 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession
this.sesPtrUpdatedAfterTable = true;
}
}
/* Update cached values in this SSLSession from WolfSSLSession,
* in case that goes out of scope and is garbage collected. */
updateStoredSessionValues();
}
}
protected synchronized void updateStoredSessionValues() {
try {
this.protocol = this.ssl.getVersion();
} catch (IllegalStateException | WolfSSLJNIException ex) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
"Not able to update stored WOLFSSL protocol");
}
}