diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index f6396b5d..165038de 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -43,6 +43,9 @@ /* Default wolfSSL_peek() timeout for wolfSSL_get_session(), ms */ #define WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT 2000 #endif +#ifndef WOLFSSL_MAX_SESSION_TICKET_LEN + #define WOLFSSL_MAX_SESSION_TICKET_LEN 2048 +#endif #include #include @@ -4799,6 +4802,108 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useSessionTicket return ret; } +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_getSessionTicket + (JNIEnv* jenv, jobject jcl, jlong sslPtr) +{ + jbyteArray sessionTicket = NULL; +#ifdef HAVE_SESSION_TICKET + int ret = SSL_FAILURE; + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + word32 dataSz = 0; + byte* dataBuf = NULL; + + if (jenv == NULL || ssl == NULL) { + return NULL; + } + +#if LIBWOLFSSL_VERSION_HEX <= 0x05008002 + dataSz = WOLFSSL_MAX_SESSION_TICKET_LEN; + dataBuf = (byte*)XMALLOC(dataSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (dataBuf != NULL){ + /* attempt to get ticket data and ticket size */ + ret = wolfSSL_get_SessionTicket(ssl, dataBuf, &dataSz); + + if (ret == WOLFSSL_SUCCESS && dataSz > 0){ + sessionTicket = (*jenv)->NewByteArray(jenv, dataSz); + (*jenv)->SetByteArrayRegion(jenv, sessionTicket, 0, dataSz, + (jbyte*)dataBuf); + } else if (ret == WOLFSSL_SUCCESS && dataSz == 0) { + /* no session ticket available */ + printf("No ticket available or Session " + "ticket len is greater than data buffer len\n"); + } + + XFREE(dataBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } +#else + /* get session ticket length */ + ret = wolfSSL_get_SessionTicket(ssl, dataBuf, &dataSz); + + if (ret == LENGTH_ONLY_E && dataSz > 0) { + /* allocate buffer */ + dataBuf = (byte*)XMALLOC(dataSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (dataBuf != NULL){ + /* get ticket data */ + ret = wolfSSL_get_SessionTicket(ssl, dataBuf, &dataSz); + + if (ret == WOLFSSL_SUCCESS && dataSz > 0){ + sessionTicket = (*jenv)->NewByteArray(jenv, dataSz); + (*jenv)->SetByteArrayRegion(jenv, sessionTicket, 0, dataSz, + (jbyte*)dataBuf); + } + + XFREE(dataBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + } +#endif /* LIBWOLFSSL_VERSION_HEX */ + (void)jcl; +#else + (void)jenv; + (void)jcl; + (void)sslPtr; +#endif /* HAVE_SESSION_TICKET */ + return sessionTicket; +} + +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setSessionTicket + (JNIEnv* jenv, jobject jcl, jlong sslPtr, jbyteArray dataBuf) +{ + int ret = SSL_FAILURE; +#ifdef HAVE_SESSION_TICKET + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + byte* data = NULL; + word32 dataSz = 0; + + if (jenv == NULL || ssl == NULL || dataBuf == NULL) { + return BAD_FUNC_ARG; + } + + data = (byte*)(*jenv)->GetByteArrayElements(jenv, dataBuf, NULL); + dataSz = (*jenv)->GetArrayLength(jenv, dataBuf); + + if (data != NULL && dataSz > 0) { + ret = wolfSSL_set_SessionTicket(ssl, data, dataSz); + if (ret != WOLFSSL_SUCCESS) { + (*jenv)->ThrowNew(jenv, + (*jenv)->FindClass(jenv, "java/lang/Exception"), + "failed to set session ticket!"); + } + } + else { + ret = BAD_FUNC_ARG; + } + (*jenv)->ReleaseByteArrayElements(jenv, dataBuf, + (jbyte*)data, JNI_ABORT); + (void)jcl; +#else + (void)jenv; + (void)jcl; + (void)sslPtr; + ret = NOT_COMPILED_IN; +#endif /* HAVE_SESSION_TICKET */ + return ret; +} + /* return 1 if last alert received was a close_notify alert, otherwise 0 */ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_gotCloseNotify (JNIEnv* jenv, jobject jcl, jlong sslPtr) diff --git a/native/com_wolfssl_WolfSSLSession.h b/native/com_wolfssl_WolfSSLSession.h index a841a85b..12fb1168 100644 --- a/native/com_wolfssl_WolfSSLSession.h +++ b/native/com_wolfssl_WolfSSLSession.h @@ -823,6 +823,22 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_getSNIRequest JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useSessionTicket (JNIEnv *, jobject, jlong); +/* + * Class: com_wolfssl_WolfSSLSession + * Method: getSessionTicket + * Signature: (J)[B + */ +JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_WolfSSLSession_getSessionTicket + (JNIEnv *, jobject, jlong); + +/* + * Class: com_wolfssl_WolfSSLSession + * Method: setSessionTicket + * Signature: (J[B)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setSessionTicket + (JNIEnv *, jobject, jlong, jbyteArray); + /* * Class: com_wolfssl_WolfSSLSession * Method: gotCloseNotify diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index 4fcce2ec..4e31ce30 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -676,6 +676,8 @@ private native int setTlsHmacInner(long ssl, byte[] inner, long sz, private native int useSNI(long ssl, byte type, byte[] data); private native byte[] getSNIRequest(long ssl, byte type); private native int useSessionTicket(long ssl); + private native byte[] getSessionTicket(long ssl); + private native int setSessionTicket(long ssl, byte[] ticket); private native int gotCloseNotify(long ssl); private native int sslSetAlpnProtos(long ssl, byte[] alpnProtos); private native byte[] sslGet0AlpnSelected(long ssl); @@ -5184,6 +5186,69 @@ public synchronized boolean sessionTicketsEnabled() return this.sessionTicketsEnabled; } + /** + * Get session ticket for this session if session tickets are enabled. + * + * @return session ticket as byte array, or null if not available. + * @throws IllegalStateException WolfSSLSession has been freed. + */ + public synchronized byte[] getSessionTicket() throws IllegalStateException { + + confirmObjectIsActive(); + + if (sessionTicketsEnabled()) { + + synchronized (sslLock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, + () -> "entered getSessionTicket()"); + return getSessionTicket(this.sslPtr); + } + + } else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, + () -> "session tickets not enabled, returning null"); + return null; + } + } + + /** + * Set session ticket for this session. + * + * @param sessionTicket session ticket to set for this session. + * @return WolfSSL.SSL_SUCCESS on success, otherwise negative. + * + * @throws IllegalStateException WolfSSLSession has been freed + */ + public int setSessionTicket(byte[] sessionTicket){ + int ret = WolfSSL.SSL_SUCCESS; + confirmObjectIsActive(); + if (sessionTicketsEnabled()){ + synchronized (sslLock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, + () -> "entered setSessionTicket()"); + + if (sessionTicket != null && sessionTicket.length > 0) { + ret = setSessionTicket(this.sslPtr, sessionTicket); + } else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, + () -> "session ticket is null, not setting"); + ret = WolfSSL.SSL_FAILURE; + } + + } + } else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, this.sslPtr, + () -> "session tickets not enabled"); + } + + return ret; + } + /** * Set ALPN extension protocol for this session from encoded byte array. * Calls SSL_set_alpn_protos() at native level. Format starts with diff --git a/src/test/com/wolfssl/test/WolfSSLSessionTest.java b/src/test/com/wolfssl/test/WolfSSLSessionTest.java index 4f47a99b..4b03d363 100644 --- a/src/test/com/wolfssl/test/WolfSSLSessionTest.java +++ b/src/test/com/wolfssl/test/WolfSSLSessionTest.java @@ -457,6 +457,229 @@ public void test_WolfSSLSession_useSessionTicket() System.out.println("\t\t... passed"); } + @Test + public void test_WolfSSLSession_getSetSessionTickets() + throws WolfSSLException, WolfSSLJNIException { + int ret = 0; + WolfSSLSession ssl = null; + String ticketStr = "This is a session ticket"; + byte[] ticket = null; + byte[] retrievedTicket = null; + + System.out.print("\t(get/set)SessionTicket()"); + + try { + ssl = new WolfSSLSession(ctx); + + ret = ssl.useSessionTicket(); + if (ret != WolfSSL.SSL_SUCCESS && + ret != WolfSSL.NOT_COMPILED_IN) { + System.out.println("\t\t... failed"); + fail("useSessionTicket failed"); + } + + /* set session ticket */ + ticket = ticketStr.getBytes(); + + ret = ssl.setSessionTicket(ticket); + if (ret != WolfSSL.SSL_SUCCESS && + ret != WolfSSL.NOT_COMPILED_IN) { + System.out.println("\t... failed"); + fail("setSessionTicket failed"); + } + + retrievedTicket = ssl.getSessionTicket(); + + if (retrievedTicket == null) { + System.out.println("\t... failed" ); + fail("getSessionTicket failed"); + } + + for (int i = 0; i < ticket.length; i++) { + if (ticket[i] != retrievedTicket[i]) { + System.out.println("\t... failed"); + fail("getSessionTicket failed"); + } + } + + } catch (IllegalStateException e) { + System.out.println("\t... failed"); + e.printStackTrace(); + + } finally { + if (ssl != null) { + ssl.freeSSL(); + } + } + + System.out.println("\t... passed"); + } + + public void test_WolfSSLSession_resumeWithSessionTickets() + throws WolfSSLException, WolfSSLJNIException, Exception { + int ret = 0; + int err = 0; + Socket cliSock = null; + byte[] sessionTicket = "This is a session ticket".getBytes(); + WolfSSLSession ssl = null; + + /* Create client/server WolfSSLContext objects, Server context + * must be final since used inside inner class. */ + final WolfSSLContext srvCtx; + WolfSSLContext cliCtx; + + System.out.println("\tresumeWithSessionTickets()"); + + /* Create ServerSocket first to get ephemeral port */ + final ServerSocket srvSocket = new ServerSocket(0); + final int port = srvSocket.getLocalPort(); + + srvCtx = createAndSetupWolfSSLContext(srvCert, srvKey, + WolfSSL.SSL_FILETYPE_PEM, cliCert, + WolfSSL.TLSv1_3_ServerMethod()); + cliCtx = createAndSetupWolfSSLContext(cliCert, cliKey, + WolfSSL.SSL_FILETYPE_PEM, caCert, + WolfSSL.TLSv1_3_ClientMethod()); + /* Start server, handles 1 resumption */ + try { + ExecutorService es = Executors.newSingleThreadExecutor(); + es.submit(new Callable() { + @Override + public Void call() throws Exception { + int ret; + int err; + Socket server = null; + WolfSSLSession srvSes = null; + + try { + /* Loop twice to allow handle one resumption */ + for (int i = 0; i < 2; i++) { + server = srvSocket.accept(); + srvSes = new WolfSSLSession(srvCtx); + + ret = srvSes.setFd(server); + if (ret != WolfSSL.SSL_SUCCESS) { + throw new Exception( + "WolfSSLSession.setFd() failed: " + ret); + } + + do { + ret = srvSes.accept(); + err = srvSes.getError(ret); + } while (ret != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + + if (ret != WolfSSL.SSL_SUCCESS) { + throw new Exception( + "WolfSSLSession.accept() failed: " + ret); + } + + srvSes.shutdownSSL(); + srvSes.freeSSL(); + srvSes = null; + } + + } finally { + if (srvSes != null) { + srvSes.freeSSL(); + } + if (server != null) { + server.close(); + } + } + + return null; + } + }); + + } catch (Exception e) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail(); + } + + try { + /* ------------------------------------------------------------- */ + /* Client connection #1 */ + /* ------------------------------------------------------------- */ + cliSock = new Socket("localhost", port); + ssl = new WolfSSLSession(cliCtx); + + ret = ssl.setFd(cliSock); + if (ret != WolfSSL.SSL_SUCCESS) + throw new Exception("setFd() failed"); + + do { + ret = ssl.connect(); + err = ssl.getError(ret); + } while (ret != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + + if (ret != WolfSSL.SSL_SUCCESS) + throw new Exception("Initial connect failed"); + + /* Get session ticket after handshake */ + sessionTicket = ssl.getSessionTicket(); + + assertNotNull("Session ticket was null", sessionTicket); + assertTrue("Session ticket empty", sessionTicket.length > 0); + + ssl.shutdownSSL(); + ssl.freeSSL(); + cliSock.close(); + + /* ------------------------------------------------------------- */ + /* Client connection #2, set session and try resumption */ + /* ------------------------------------------------------------- */ + cliSock = new Socket("localhost", port); + ssl = new WolfSSLSession(cliCtx); + + ret = ssl.setFd(cliSock); + if (ret != WolfSSL.SSL_SUCCESS) + throw new Exception("setFd() failed"); + + ret = ssl.setSessionTicket(sessionTicket); + if (ret != WolfSSL.SSL_SUCCESS) + throw new Exception("setSessionTicket() failed"); + + do { + ret = ssl.connect(); + err = ssl.getError(ret); + } while (ret != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + + if (ret != WolfSSL.SSL_SUCCESS) + throw new Exception("Resumption connect failed"); + + /* Check if session was resumed */ + assertEquals("Session was not resumed", 1, ssl.sessionReused()); + + ssl.shutdownSSL(); + ssl.freeSSL(); + cliSock.close(); + + } finally { + /* Free resources */ + if (ssl != null) { + ssl.freeSSL(); + } + if (cliSock != null) { + cliSock.close(); + } + if (srvSocket != null) { + srvSocket.close(); + } + if (srvCtx != null) { + srvCtx.free(); + } + } + + System.out.println("\t... passed"); + } + @Test public void test_WolfSSLSession_getPskIdentity() throws WolfSSLJNIException, WolfSSLException {