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 {