diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index badc284..897e530 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -63,6 +63,12 @@ int NativeALPNSelectCb(WOLFSSL *ssl, const unsigned char **out, int NativeTls13SecretCb(WOLFSSL *ssl, int id, const unsigned char* secret, int secretSz, void* ctx); +#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SESSION_TICKET) +/* Session ticket callback prototype */ +int NativeSessionTicketCb(WOLFSSL *ssl, const unsigned char* ticket, + int ticketLen, void* ctx); +#endif + #ifdef HAVE_CRL /* global object refs for CRL callback */ static jobject g_crlCbIfaceObj; @@ -5622,6 +5628,30 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setTls13SecretCb #endif } +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setSessionTicketCb + (JNIEnv* jenv, jobject jcl, jlong sslPtr) +{ +#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SESSION_TICKET) + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + int ret = SSL_SUCCESS; + (void)jcl; + + if (jenv == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + /* Java layer handles setting and giving back user CTX */ + ret = wolfSSL_set_SessionTicket_cb(ssl, NativeSessionTicketCb, NULL); + + return ret; +#else + (void)jenv; + (void)jcl; + (void)sslPtr; + return NOT_COMPILED_IN; +#endif +} + #if defined(WOLFSSL_TLS13) && !defined(WOLFCRYPT_ONLY) && \ defined(HAVE_SECRET_CALLBACK) @@ -5764,6 +5794,146 @@ int NativeTls13SecretCb(WOLFSSL *ssl, int id, const unsigned char* secret, #endif /* WOLFSSL_TLS13 && !WOLFCRYPT_ONLY && HAVE_SECRET_CALLBACK */ +#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_SESSION_TICKET) + +int NativeSessionTicketCb(WOLFSSL* ssl, const unsigned char* ticket, + int ticketLen, void* ctx) +{ + JNIEnv* jenv; /* JNI environment */ + jclass excClass; /* WolfSSLJNIException class */ + int needsDetach = 0; /* Should we explicitly detach? */ + jint retval = 0; + jint vmret = 0; + + jobject* g_cachedSSLObj; /* WolfSSLSession cached object */ + jclass sslClass; /* WolfSSLSession class */ + jmethodID sessTicketCbMethodId; /* internalTls13SecretCallback ID */ + jbyteArray ticketArr = NULL; + + if (g_vm == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + /* get JavaEnv from JavaVM */ + vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6)); + if (vmret == JNI_EDETACHED) { +#ifdef __ANDROID__ + vmret = (*g_vm)->AttachCurrentThread(g_vm, &jenv, NULL); +#else + vmret = (*g_vm)->AttachCurrentThread(g_vm, (void**) &jenv, NULL); +#endif + if (vmret) { + return -1; + } + needsDetach = 1; + } + else if (vmret != JNI_OK) { + return -1; + } + + /* Find exception class in case we need it */ + excClass = (*jenv)->FindClass(jenv, "com/wolfssl/WolfSSLJNIException"); + if ((*jenv)->ExceptionOccurred(jenv)) { + (*jenv)->ExceptionDescribe(jenv); + (*jenv)->ExceptionClear(jenv); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + /* Get stored WolfSSLSession object */ + g_cachedSSLObj = (jobject*) wolfSSL_get_jobject(ssl); + if (!g_cachedSSLObj) { + (*jenv)->ThrowNew(jenv, excClass, + "Can't get native WolfSSLSession object reference in " + "NativeSessionTicketCb"); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + /* Lookup WolfSSLSession class from object */ + sslClass = (*jenv)->GetObjectClass(jenv, (jobject)(*g_cachedSSLObj)); + if (sslClass == NULL) { + (*jenv)->ThrowNew(jenv, excClass, + "Can't get native WolfSSLSession class reference in " + "NativeSessionTicketCb"); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + /* Call internal TLS 1.3 secret callback */ + sessTicketCbMethodId = (*jenv)->GetMethodID(jenv, sslClass, + "internalSessionTicketCallback", "(Lcom/wolfssl/WolfSSLSession;[B)I"); + if (sessTicketCbMethodId == NULL) { + if ((*jenv)->ExceptionOccurred(jenv)) { + (*jenv)->ExceptionDescribe(jenv); + (*jenv)->ExceptionClear(jenv); + } + (*jenv)->ThrowNew(jenv, excClass, + "Error getting internalSessionTicketCallback method from JNI"); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + if (ticketLen > 0) { + /* Create jbyteArray to hold session ticket */ + ticketArr = (*jenv)->NewByteArray(jenv, ticketLen); + if (ticketArr == NULL) { + (*jenv)->ThrowNew(jenv, excClass, + "Error creating new jbyteArray in NativeSessionTicketCb"); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + (*jenv)->SetByteArrayRegion(jenv, ticketArr, 0, ticketLen, + (jbyte*)ticket); + if ((*jenv)->ExceptionOccurred(jenv)) { + (*jenv)->ExceptionDescribe(jenv); + (*jenv)->ExceptionClear(jenv); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + /* Call Java session ticket callback, ignore native CTX since Java + * handles it */ + retval = (*jenv)->CallIntMethod(jenv, (jobject)(*g_cachedSSLObj), + sessTicketCbMethodId, (jobject)(*g_cachedSSLObj), ticketArr); + if ((*jenv)->ExceptionOccurred(jenv)) { + (*jenv)->ExceptionDescribe(jenv); + (*jenv)->ExceptionClear(jenv); + (*jenv)->ThrowNew(jenv, excClass, + "Exception while calling internalSessionTicketCallback()"); + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return -1; + } + + /* Delete local refs */ + (*jenv)->DeleteLocalRef(jenv, ticketArr); + } + + /* Detach JNIEnv from thread */ + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + + return (int)retval; +} + +#endif /* WOLFSSL_TLS13 && !WOLFCRYPT_ONLY && HAVE_SECRET_CALLBACK */ + JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useSecureRenegotiation (JNIEnv* jenv, jobject jcl, jlong ssl) { diff --git a/native/com_wolfssl_WolfSSLSession.h b/native/com_wolfssl_WolfSSLSession.h index 77f8d4e..017e457 100644 --- a/native/com_wolfssl_WolfSSLSession.h +++ b/native/com_wolfssl_WolfSSLSession.h @@ -863,6 +863,14 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setALPNSelectCb JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setTls13SecretCb (JNIEnv *, jobject, jlong); +/* + * Class: com_wolfssl_WolfSSLSession + * Method: setSessionTicketCb + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setSessionTicketCb + (JNIEnv *, jobject, jlong); + /* * Class: com_wolfssl_WolfSSLSession * Method: keepArrays diff --git a/scripts/infer.sh b/scripts/infer.sh index 97bc30d..531b0d4 100755 --- a/scripts/infer.sh +++ b/scripts/infer.sh @@ -61,6 +61,7 @@ infer --fail-on-issue run -- javac \ src/java/com/wolfssl/WolfSSLRsaSignCallback.java \ src/java/com/wolfssl/WolfSSLRsaVerifyCallback.java \ src/java/com/wolfssl/WolfSSLSession.java \ + src/java/com/wolfssl/WolfSSLSessionTicketCallback.java \ src/java/com/wolfssl/WolfSSLTls13SecretCallback.java \ src/java/com/wolfssl/WolfSSLVerifyCallback.java \ src/java/com/wolfssl/WolfSSLVerifyDecryptCallback.java \ diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index 3e75708..dd39fb6 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -62,6 +62,7 @@ public class WolfSSLSession { private Object rsaDecCtx; private Object alpnSelectArg; private Object tls13SecretCtx; + private Object sessionTicketCtx; /* reference to the associated WolfSSLContext */ private WolfSSLContext ctx = null; @@ -80,10 +81,14 @@ public class WolfSSLSession { * ALPN select callback */ private WolfSSLALPNSelectCallback internAlpnSelectCb; - /* user-registered TLS 1.3 secret callbcak, called by internal + /* user-registered TLS 1.3 secret callback, called by internal * WolfSSLSession TLS 1.3 secret callback */ private WolfSSLTls13SecretCallback internTls13SecretCb; + /* user-registered session ticket callback, called by internal + * WolfSSLSession session ticket callback */ + private WolfSSLSessionTicketCallback internSessionTicketCb; + /* have session tickets been enabled for this session? Default to false. */ private boolean sessionTicketsEnabled = false; @@ -282,6 +287,13 @@ public class WolfSSLSession { this.tls13SecretCtx); } + private int internalSessionTicketCallback(WolfSSLSession ssl, byte[] ticket) + { + /* call user-registered session ticket callback */ + return internSessionTicketCb.sessionTicketCallback(ssl, ticket, + this.sessionTicketCtx); + } + /** * Verifies that the current WolfSSLSession object is active. * @@ -415,6 +427,7 @@ public class WolfSSLSession { private native int useALPN(long ssl, String protocols, int options); private native int setALPNSelectCb(long ssl); private native int setTls13SecretCb(long ssl); + private native int setSessionTicketCb(long ssl); private native void keepArrays(long ssl); private native byte[] getClientRandom(long ssl); private native int useSecureRenegotiation(long ssl); @@ -4819,6 +4832,47 @@ public class WolfSSLSession { return ret; } + /** + * Register session ticket callback. + * + * The callback registered by this method is called by native wolfSSL + * when a session ticket is received from the peer. + * + * @param cb callback to be registered with this SSL session + * @param ctx Object that will be passed back to user inside callback + * + * @return SSL_SUCCESS on success. + * NOT_COMPILED_IN if wolfSSL was not compiled with + * session ticket support, and other negative value on error. + * + * @throws IllegalStateException WolfSSLSession has been freed + * @throws WolfSSLJNIException Internal JNI error + */ + public int setSessionTicketCb(WolfSSLSessionTicketCallback cb, Object ctx) + throws IllegalStateException, WolfSSLJNIException { + + int ret = 0; + + confirmObjectIsActive(); + + synchronized (sslLock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, "entered setSessionTicketCb(" + + cb + ", ctx: " + ctx + ")"); + + ret = setSessionTicketCb(this.sslPtr); + if (ret == WolfSSL.SSL_SUCCESS) { + /* Set session ticket callback */ + internSessionTicketCb = cb; + + /* Set session ticket ctx Object, returned to user in cb */ + this.sessionTicketCtx = ctx; + } + } + + return ret; + } + /** * Do not free temporary arrays at end of handshake. * diff --git a/src/java/com/wolfssl/WolfSSLSessionTicketCallback.java b/src/java/com/wolfssl/WolfSSLSessionTicketCallback.java new file mode 100644 index 0000000..08bffc6 --- /dev/null +++ b/src/java/com/wolfssl/WolfSSLSessionTicketCallback.java @@ -0,0 +1,59 @@ +/* WolfSSLSessionTicketCallback.java + * + * Copyright (C) 2006-2025 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; + +/** + * wolfSSL session ticket callback interface. + * This interface specifies how applications should implement the session + * ticket callback class, to be used by wolfSSL when receiving a session + * ticket message. + *

+ * To use this interface, native wolfSSL must be compiled with + * HAVE_SESSION_TICKET defined. + *

+ * After implementing this interface, it should be passed as a parameter + * to the + * {@link WolfSSLSession#setSessionTicketCb(WolfSSLSessionTicketCallback, Object) + * WolfSSLSession.setSessionTicketCb()} method to be registered with the native + * wolfSSL library. + */ +public interface WolfSSLSessionTicketCallback { + + /** + * Callback method which is called when native wolfSSL receives a + * session ticket message. + * + * @param ssl the current SSL session object from which the + * callback was initiated + * @param ticket Session ticket received as a byte array + * @param ctx Optional user context if set when callback was + * registered + * + * @return 0 on success. wolfSSL does not currently do anything with + * the return value of this method, but is in place for + * future expansion if needed. + */ + public int sessionTicketCallback(WolfSSLSession ssl, byte[] ticket, + Object ctx); +} + + diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java index 2d89f69..b2a6c1e 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java @@ -29,6 +29,7 @@ import com.wolfssl.WolfSSLIOSendCallback; import com.wolfssl.WolfSSLJNIException; import com.wolfssl.WolfSSLSession; import com.wolfssl.WolfSSLALPNSelectCallback; +import com.wolfssl.WolfSSLSessionTicketCallback; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; @@ -111,11 +112,16 @@ public class WolfSSLEngine extends SSLEngine { /* TLS 1.3 session ticket received (on client side) */ private boolean sessionTicketReceived = false; + /* Number of session tickets received, incremented in + * SessionTicketCB callback */ + private int sessionTicketCount = 0; + /* client/server mode has been set */ private boolean clientModeSet = false; private SendCB sendCb = null; private RecvCB recvCb = null; + private SessionTicketCB sessTicketCb = null; private ByteBuffer netData = null; private final Object netDataLock = new Object(); @@ -306,6 +312,12 @@ public class WolfSSLEngine extends SSLEngine { ssl.setIOSend(sendCb); ssl.setIOReadCtx(this); ssl.setIOWriteCtx(this); + + /* Session ticket callback */ + if (sessTicketCb == null) { + sessTicketCb = new SessionTicketCB(); + } + ssl.setSessionTicketCb(sessTicketCb, this); } } @@ -338,6 +350,9 @@ public class WolfSSLEngine extends SSLEngine { if (recvCb == null) { recvCb = new RecvCB(); } + if (sessTicketCb == null) { + sessTicketCb = new SessionTicketCB(); + } /* will throw WolfSSLException if issue creating WOLFSSL */ ssl = new WolfSSLSession(ctx, false); @@ -1022,6 +1037,7 @@ public class WolfSSLEngine extends SSLEngine { int produced = 0; long dtlsPrevDropCount = 0; long dtlsCurrDropCount = 0; + int prevSessionTicketCount = 0; byte[] tmp; /* Set initial status for SSLEngineResult return */ @@ -1149,11 +1165,13 @@ public class WolfSSLEngine extends SSLEngine { } } else { - /* Get previous DTLS drop count, before we process any - * incomming data. Allows us to set BUFFER_UNDERFLOW status - * (or not if packet decrypt failed and was dropped) */ + /* Get previous DTLS drop count and session ticket count, + * before we process any incomming data. Allows us to set + * BUFFER_UNDERFLOW status (or not if packet decrypt failed + * and was dropped) */ synchronized (ioLock) { dtlsPrevDropCount = ssl.getDtlsMacDropCount(); + prevSessionTicketCount = this.sessionTicketCount; } if (this.handshakeFinished == false) { @@ -1284,12 +1302,17 @@ public class WolfSSLEngine extends SSLEngine { dtlsCurrDropCount = ssl.getDtlsMacDropCount(); } - /* Detect if we need to set BUFFER_UNDERFLOW */ + /* Detect if we need to set BUFFER_UNDERFLOW. + * If we consume data in unwrap() but it's just a session + * ticket, we don't set BUFFER_UNDERFLOW and just continue + * on to set status as OK. */ 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))) { + (this.toSend != null && this.toSend.length == 0)) + && (prevSessionTicketCount == + this.sessionTicketCount)) { if ((this.ssl.dtls() == 0) || (this.handshakeFinished && @@ -2154,6 +2177,32 @@ public class WolfSSLEngine extends SSLEngine { } } + /** + * Internal session ticket callback. Called when native wolfSSL + * receives a session ticket from peer. + * + * @param ticket byte array containing session ticket data + * + * @return 0 on success, negative value on error + */ + protected synchronized int internalSessionTicketCb(byte[] ticket) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "entered internalSessionTicketCb()"); + + if (ticket != null) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Session ticket received, length = " + ticket.length); + if (ticket.length > 0) { + this.sessionTicketCount++; + } + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Total session tickets received by this SSLEngine: " + + this.sessionTicketCount); + } + + return 0; + } + private class SendCB implements WolfSSLIOSendCallback { protected SendCB() { @@ -2180,6 +2229,17 @@ public class WolfSSLEngine extends SSLEngine { } + private class SessionTicketCB implements WolfSSLSessionTicketCallback { + + protected SessionTicketCB() { + } + + public int sessionTicketCallback(WolfSSLSession ssl, byte[] ticket, + Object engine) { + return ((WolfSSLEngine)engine).internalSessionTicketCb(ticket); + } + } + @SuppressWarnings("deprecation") @Override protected synchronized void finalize() throws Throwable {