diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index 60e9431e..66096f5c 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -1723,6 +1723,27 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionIsResumable #endif } +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) { diff --git a/native/com_wolfssl_WolfSSLSession.h b/native/com_wolfssl_WolfSSLSession.h index e0c06b74..4821bf1e 100644 --- a/native/com_wolfssl_WolfSSLSession.h +++ b/native/com_wolfssl_WolfSSLSession.h @@ -175,6 +175,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionIsSetup JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_wolfsslSessionIsResumable (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 diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index 9d22e4f9..576e55bc 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -259,6 +259,7 @@ private native int read(long ssl, byte[] data, int offset, int sz, private native long get1Session(long ssl); private static native int wolfsslSessionIsSetup(long ssl); private static native int wolfsslSessionIsResumable(long ssl); + 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); @@ -1364,6 +1365,29 @@ public static int sessionIsResumable(long session) { return wolfsslSessionIsResumable(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. * diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLAuthStore.java b/src/java/com/wolfssl/provider/jsse/WolfSSLAuthStore.java index 68fcf45e..d6a760e7 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLAuthStore.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLAuthStore.java @@ -296,13 +296,24 @@ protected void resizeCache(int sz, int side) { * @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 @@ protected synchronized WolfSSLImplementSSLSession getSession( * 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 @@ protected synchronized WolfSSLImplementSSLSession getSession( 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 @@ protected synchronized WolfSSLImplementSSLSession getSession( 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, diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java index 50fc54c5..329718d4 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java @@ -1199,7 +1199,7 @@ private void initHandshakeInternal(SSLSocket socket, SSLEngine engine) /* 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 @@ else if (peerAddr != null) { (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 @@ protected synchronized void unsetVerifyCallback() { */ 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. */ diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java index 3fcffd44..0161b44f 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLImplementSSLSession.java @@ -66,6 +66,7 @@ public class WolfSSLImplementSSLSession extends ExtendedSSLSession private final HashMap 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 WolfSSLImplementSSLSession (WolfSSLSession in, int port, String host, this.valid = false; /* flag if joining or resuming session is allowed */ this.peerCerts = null; this.sesPtr = 0; + this.protocol = this.nullProtocol; binding = new HashMap(); creation = new Date(); @@ -154,6 +156,7 @@ public WolfSSLImplementSSLSession (WolfSSLSession in, this.valid = false; /* flag if joining or resuming session is allowed */ this.peerCerts = null; this.sesPtr = 0; + this.protocol = this.nullProtocol; binding = new HashMap(); creation = new Date(); @@ -175,6 +178,7 @@ public WolfSSLImplementSSLSession (WolfSSLAuthStore params) { this.valid = false; /* flag if joining or resuming session is allowed */ this.peerCerts = null; this.sesPtr = 0; + this.protocol = this.nullProtocol; binding = new HashMap(); creation = new Date(); @@ -224,6 +228,8 @@ public WolfSSLImplementSSLSession (WolfSSLImplementSSLSession orig) { 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 synchronized String getCipherSuite() { return null; } - @Override - public synchronized String getProtocol() { - if (ssl == null) { - return this.nullProtocol; + /** + * 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); } + } - try { - return this.ssl.getVersion(); - } catch (IllegalStateException | WolfSSLJNIException ex) { - Logger.getLogger( - WolfSSLImplementSSLSession.class.getName()).log( - Level.SEVERE, null, ex); - } - return null; + @Override + public synchronized String getProtocol() { + return this.protocol; } @Override @@ -816,6 +824,20 @@ protected synchronized void setResume() { 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"); } }