diff --git a/.github/workflows/linux-common.yml b/.github/workflows/linux-common.yml index 75d987ea..05a9577a 100644 --- a/.github/workflows/linux-common.yml +++ b/.github/workflows/linux-common.yml @@ -15,6 +15,9 @@ on: wolfssl_configure: required: true type: string + javash_cflags: + required: false + type: string jobs: build_wolfssljni: @@ -51,7 +54,7 @@ jobs: echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$GITHUB_WORKSPACE/build-dir/lib" >> "$GITHUB_ENV" - name: Build JNI library - run: ./java.sh $GITHUB_WORKSPACE/build-dir + run: CFLAGS=${{ inputs.javah_cflags }} ./java.sh $GITHUB_WORKSPACE/build-dir - name: Build JAR (ant) run: ant - name: Run Java tests (ant test) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 64236be0..c255f186 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -120,6 +120,27 @@ jobs: jdk_version: ${{ matrix.jdk_version }} wolfssl_configure: ${{ matrix.wolfssl_configure }} + # -------------------- WOLFJNI_USE_IO_SELECT sanity check -------------------- + # Only check one Linux and Mac JDK version as a sanity check. + # Using Zulu, but this can be expanded if needed. + linux-zulu-ioselect: + strategy: + matrix: + os: [ 'ubuntu-latest', 'macos-latest' ] + jdk_version: [ '11' ] + wolfssl_configure: [ + '--enable-jni', + ] + javash_cflags: [ '-DWOLFJNI_USE_IO_SELECT' ] + name: ${{ matrix.os }} (Zulu JDK ${{ matrix.jdk_version }}, ${{ matrix.wolfssl_configure}}, ${{ matrix.javash_cflags }}) + uses: ./.github/workflows/linux-common.yml + with: + os: ${{ matrix.os }} + jdk_distro: "zulu" + jdk_version: ${{ matrix.jdk_version }} + wolfssl_configure: ${{ matrix.wolfssl_configure }} + javash_cflags: ${{ matrix.javash_cflags }} + # ------------------ Facebook Infer static analysis ------------------- # Run Facebook infer over PR code, only running on Linux with one # JDK/version for now. diff --git a/README.md b/README.md index 2336829a..d47efc42 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,44 @@ $ ./examples/provider/ServerJSSE.sh $ ./examples/provider/ClientJSSE.sh ``` +### java.sh Script Options + +The `java.sh` script compiles the native JNI sources into a shared library named +either `libwolfssljni.so` (Linux/Unix) or `libwolfssljni.dylib` (MacOS). +Compiling on Linux/Unix and Mac OSX are currently supported. + +This script will attempt to auto-detect the `JAVA_HOME` location if not set. +To explicitly use a Java home location, set the `JAVA_HOME` environment variable +prior to running this script. + +This script will try to link against a wolfSSL library installed to the +default location of `/usr/local`. This script accepts two arguments on the +command line. The first argument can point to a custom wolfSSL installation +location. A custom install location would match the directory set at wolfSSL +`./configure --prefix=`. + +The second argument represents the wolfSSL library name that should be +linked against. This is helpful if a non-standard library name has been +used with wolfSSL, for example the `./configure --with-libsuffix` option +has been used to add a suffix to the wolfSSL library name. Note that to +use this argument, an installation location must be specified via the +first argument. + +For example, if wolfSSL was configured with `--with-libsuffix=jsse`, then +this script could be called like so using the default installation +path of `/usr/local`: + +``` +java.sh /usr/local wolfssljsse +``` + +`java.sh` can use preset `CFLAGS` defines, if set in the environment variable +prior to running the script, for example: + +``` +CFLAGS=-DWOLFJNI_USE_IO_SELECT java.sh +``` + ## Building with Maven wolfJSSE supports building and packaging with Maven, for those projects that diff --git a/java.sh b/java.sh index 1bfe18ed..aa07d8dc 100755 --- a/java.sh +++ b/java.sh @@ -75,7 +75,6 @@ if [ "$OS" == "Darwin" ] ; then javaIncludes="-I$javaHome/include -I$javaHome/include/darwin -I$WOLFSSL_INSTALL_DIR/include" javaLibs="-dynamiclib" jniLibName="libwolfssljni.dylib" - cflags="" elif [ "$OS" == "Linux" ] ; then echo " Detected Linux host OS" if [ -z $javaHome ]; then @@ -88,7 +87,6 @@ elif [ "$OS" == "Linux" ] ; then javaIncludes="-I$javaHome/include -I$javaHome/include/linux -I$WOLFSSL_INSTALL_DIR/include" javaLibs="-shared" jniLibName="libwolfssljni.so" - cflags="" if [ "$ARCH" == "x86_64" ] ; then fpic="-fPIC" else @@ -108,18 +106,18 @@ then mkdir ./lib fi -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSL.c -o ./native/com_wolfssl_WolfSSL.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLSession.c -o ./native/com_wolfssl_WolfSSLSession.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLContext.c -o ./native/com_wolfssl_WolfSSLContext.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_wolfcrypt_RSA.c -o ./native/com_wolfssl_wolfcrypt_RSA.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_wolfcrypt_ECC.c -o ./native/com_wolfssl_wolfcrypt_ECC.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_wolfcrypt_EccKey.c -o ./native/com_wolfssl_wolfcrypt_EccKey.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLCertManager.c -o ./native/com_wolfssl_WolfSSLCertManager.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLCertRequest.c -o ./native/com_wolfssl_WolfSSLCertRequest.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLCertificate.c -o ./native/com_wolfssl_WolfSSLCertificate.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLX509Name.c -o ./native/com_wolfssl_WolfSSLX509Name.o $javaIncludes -gcc -Wall -c $fpic $cflags ./native/com_wolfssl_WolfSSLX509StoreCtx.c -o ./native/com_wolfssl_WolfSSLX509StoreCtx.o $javaIncludes -gcc -Wall $javaLibs $cflags -o ./lib/$jniLibName ./native/com_wolfssl_WolfSSL.o ./native/com_wolfssl_WolfSSLSession.o ./native/com_wolfssl_WolfSSLContext.o ./native/com_wolfssl_wolfcrypt_RSA.o ./native/com_wolfssl_wolfcrypt_ECC.o ./native/com_wolfssl_wolfcrypt_EccKey.o ./native/com_wolfssl_WolfSSLCertManager.o ./native/com_wolfssl_WolfSSLCertRequest.o ./native/com_wolfssl_WolfSSLCertificate.o ./native/com_wolfssl_WolfSSLX509Name.o ./native/com_wolfssl_WolfSSLX509StoreCtx.o -L$WOLFSSL_INSTALL_DIR/lib -L$WOLFSSL_INSTALL_DIR/lib64 -l$WOLFSSL_LIBNAME +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSL.c -o ./native/com_wolfssl_WolfSSL.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLSession.c -o ./native/com_wolfssl_WolfSSLSession.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLContext.c -o ./native/com_wolfssl_WolfSSLContext.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_wolfcrypt_RSA.c -o ./native/com_wolfssl_wolfcrypt_RSA.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_wolfcrypt_ECC.c -o ./native/com_wolfssl_wolfcrypt_ECC.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_wolfcrypt_EccKey.c -o ./native/com_wolfssl_wolfcrypt_EccKey.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLCertManager.c -o ./native/com_wolfssl_WolfSSLCertManager.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLCertRequest.c -o ./native/com_wolfssl_WolfSSLCertRequest.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLCertificate.c -o ./native/com_wolfssl_WolfSSLCertificate.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLX509Name.c -o ./native/com_wolfssl_WolfSSLX509Name.o $javaIncludes +gcc -Wall -c $fpic $CFLAGS ./native/com_wolfssl_WolfSSLX509StoreCtx.c -o ./native/com_wolfssl_WolfSSLX509StoreCtx.o $javaIncludes +gcc -Wall $javaLibs $CFLAGS -o ./lib/$jniLibName ./native/com_wolfssl_WolfSSL.o ./native/com_wolfssl_WolfSSLSession.o ./native/com_wolfssl_WolfSSLContext.o ./native/com_wolfssl_wolfcrypt_RSA.o ./native/com_wolfssl_wolfcrypt_ECC.o ./native/com_wolfssl_wolfcrypt_EccKey.o ./native/com_wolfssl_WolfSSLCertManager.o ./native/com_wolfssl_WolfSSLCertRequest.o ./native/com_wolfssl_WolfSSLCertificate.o ./native/com_wolfssl_WolfSSLX509Name.o ./native/com_wolfssl_WolfSSLX509StoreCtx.o -L$WOLFSSL_INSTALL_DIR/lib -L$WOLFSSL_INSTALL_DIR/lib64 -l$WOLFSSL_LIBNAME if [ $? != 0 ]; then echo "Error creating native JNI library" exit 1 diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index a7216d72..3e88820d 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -72,8 +72,17 @@ static jobject g_crlCbIfaceObj; * function calls. Stored inside WOLFSSL app data, set with * wolfSSL_set_app_data(), retrieved with wolfSSL_get_app_data(). * Global callback objects are created with NewGlobalRef(), then freed - * inside freeSSL() with DeleteGlobalRef(). */ + * inside freeSSL() with DeleteGlobalRef(). + * + * interruptFds[2] is a pipe() used for non-Windows platforms. This pipe is + * used to interrupt threads blocked inside select()/poll() when a separate + * Java thread calls close() on the SSLSocket. */ typedef struct SSLAppData { + int threadsInPoll; /* number of threads in poll/select() */ +#ifndef USE_WINDOWS_API + int interruptFds[2]; /* pipe for interrupting socketSelect() */ + wolfSSL_Mutex* pollCountLock; /* lock around threadsInPoll */ +#endif wolfSSL_Mutex* jniSessLock; /* WOLFSSL session lock */ jobject* g_verifySSLCbIfaceObj; /* Java verify callback [global ref] */ } SSLAppData; @@ -191,13 +200,116 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) return retval; } +#ifndef USE_WINDOWS_API + +/* Close interrupt pipe() descriptors and reset back to -1. */ +static void closeInterruptPipe(SSLAppData* appData) +{ + if (appData != NULL) { + if (appData->interruptFds[0] != -1) { + close(appData->interruptFds[0]); + appData->interruptFds[0] = -1; + } + if (appData->interruptFds[1] != -1) { + close(appData->interruptFds[1]); + appData->interruptFds[1] = -1; + } + } +} + +/* Signal to threads blocked in select() or poll() to wake up, by writing + * one byte to the appData.interruptFds[1] pipe. */ +static void writeToInterruptPipe(SSLAppData* appData) +{ + if (appData != NULL) { + if (appData->interruptFds[1] != -1) { + write(appData->interruptFds[1], "1", 1); + } + } +} + +#endif /* !USE_WINDOWS_API */ + +/* Return number of threads waiting in poll()/select() */ +static int threadsWaitingInPollSelect(SSLAppData* appData) +{ + int ret = 0; +#ifndef USE_WINDOWS_API + wolfSSL_Mutex* pollCountLock = NULL; + + if (appData != NULL) { + pollCountLock = appData->pollCountLock; + if (pollCountLock != NULL) { + if (wc_LockMutex(pollCountLock) == 0) { + ret = appData->threadsInPoll; + wc_UnLockMutex(pollCountLock); + } + } + } +#endif + return ret; +} + +static void incrementThreadPollCount(SSLAppData* appData) +{ +#ifndef USE_WINDOWS_API + wolfSSL_Mutex* pollCountLock = NULL; + + if (appData == NULL) { + return; + } + + pollCountLock = appData->pollCountLock; + if (pollCountLock == NULL) { + return; + } + + if (wc_LockMutex(pollCountLock) != 0) { + return; + } + + appData->threadsInPoll++; + + wc_UnLockMutex(pollCountLock); +#endif +} + +static void decrementThreadPollCount(SSLAppData* appData) +{ +#ifndef USE_WINDOWS_API + wolfSSL_Mutex* pollCountLock = NULL; + + if (appData == NULL) { + return; + } + + pollCountLock = appData->pollCountLock; + if (pollCountLock == NULL) { + return; + } + + if (wc_LockMutex(pollCountLock) != 0) { + return; + } + + if (appData->threadsInPoll > 0) { + appData->threadsInPoll--; + } + + wc_UnLockMutex(pollCountLock); +#endif +} + JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_newSSL - (JNIEnv* jenv, jobject jcl, jlong ctx) + (JNIEnv* jenv, jobject jcl, jlong ctx, jboolean withIOPipe) { int ret; jlong sslPtr = 0; jobject* g_cachedSSLObj = NULL; wolfSSL_Mutex* jniSessLock = NULL; +#ifndef USE_WINDOWS_API + wolfSSL_Mutex* pollCountLock = NULL; +#endif SSLAppData* appData = NULL; if (jenv == NULL) { @@ -251,11 +363,71 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_newSSL wc_InitMutex(jniSessLock); appData->jniSessLock = jniSessLock; + /* set up interrupt pipe for SSLSocket.close() to use if/when needed. + * currently only non-Windows platforms supported due to Windows not + * supporting direct/same pipe() operation. Make read pipe non + * blocking since byte read from it could have already been taken + * out by either reader/writer thread before the other has a chance + * to read it. But, we only use it for waking us up and don't care + * much about actually reading the byte passed over the pipe. */ + appData->threadsInPoll = 0; +#ifndef USE_WINDOWS_API + pollCountLock = (wolfSSL_Mutex*)XMALLOC(sizeof(wolfSSL_Mutex), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (pollCountLock == NULL) { + printf("error mallocing pollCountLock in newSSL for SSLAppData\n"); + (*jenv)->DeleteGlobalRef(jenv, *g_cachedSSLObj); + XFREE(jniSessLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g_cachedSSLObj, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free((WOLFSSL*)(uintptr_t)sslPtr); + return SSL_FAILURE; + } + wc_InitMutex(pollCountLock); + appData->pollCountLock = pollCountLock; + + appData->interruptFds[0] = -1; + appData->interruptFds[1] = -1; + + if (withIOPipe == JNI_TRUE) { + ret = pipe(appData->interruptFds); + if (ret == -1) { + printf("error setting up pipe() for interruptFds[] in newSSL\n"); + (*jenv)->DeleteGlobalRef(jenv, *g_cachedSSLObj); + XFREE(pollCountLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(jniSessLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g_cachedSSLObj, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free((WOLFSSL*)(uintptr_t)sslPtr); + return SSL_FAILURE; + } + + ret = fcntl(appData->interruptFds[0], F_SETFL, + fcntl(appData->interruptFds[0], F_GETFL, 0) | O_NONBLOCK); + if (ret < 0) { + printf("error setting interruptFds[0] non-blocking in newSSL\n"); + closeInterruptPipe(appData); + (*jenv)->DeleteGlobalRef(jenv, *g_cachedSSLObj); + XFREE(pollCountLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(jniSessLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(g_cachedSSLObj, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free((WOLFSSL*)(uintptr_t)sslPtr); + return SSL_FAILURE; + } + } +#endif /* !USE_WINDOWS_API */ + /* cache associated WolfSSLSession jobject in native WOLFSSL */ ret = wolfSSL_set_jobject((WOLFSSL*)(uintptr_t)sslPtr, g_cachedSSLObj); if (ret != SSL_SUCCESS) { printf("error storing jobject in wolfSSL native session\n"); (*jenv)->DeleteGlobalRef(jenv, *g_cachedSSLObj); + #ifndef USE_WINDOWS_API + closeInterruptPipe(appData); + XFREE(pollCountLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + #endif + XFREE(jniSessLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(g_cachedSSLObj, NULL, DYNAMIC_TYPE_TMP_BUFFER); wolfSSL_free((WOLFSSL*)(uintptr_t)sslPtr); @@ -267,6 +439,10 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_newSSL (WOLFSSL*)(uintptr_t)sslPtr, appData) != SSL_SUCCESS) { printf("error setting WOLFSSL app data in newSSL\n"); (*jenv)->DeleteGlobalRef(jenv, *g_cachedSSLObj); + #ifndef USE_WINDOWS_API + closeInterruptPipe(appData); + XFREE(pollCountLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + #endif XFREE(jniSessLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); XFREE(g_cachedSSLObj, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -637,16 +813,21 @@ enum { * supported, since not supported on Java Socket. * @param rx set to 1 to monitor readability on socket descriptor, * otherwise 0 to monitor writability + * @param shutdown Is this being called from shutdownSSL()? Don't select + * on interruptFds in that case, since we already know + * we are closing in that case. * * @return possible return values are: * WOLFJNI_IO_EVENT_FAIL * WOLFJNI_IO_EVENT_ERROR + * WOLFJNI_IO_EVENT_FD_CLOSED * WOLFJNI_IO_EVENT_TIMEOUT * WOLFJNI_IO_EVENT_RECV_READY * WOLFJNI_IO_EVENT_SEND_READY * WOLFJNI_IO_EVENT_INVALID_TIMEOUT */ -static int socketSelect(int sockfd, int timeout_ms, int rx) +static int socketSelect(SSLAppData* appData, int sockfd, int timeout_ms, int rx, + int shutdown) { fd_set fds, errfds; fd_set* recvfds = NULL; @@ -654,20 +835,39 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) int nfds = sockfd + 1; int result = 0; struct timeval timeout; +#ifndef USE_WINDOWS_API + char tmpBuf[1]; +#endif /* Java Socket does not support negative timeouts, sanitize */ if (timeout_ms < 0) { return WOLFJNI_IO_EVENT_INVALID_TIMEOUT; } + if (appData == NULL) { + return WOLFJNI_IO_EVENT_ERROR; + } + #ifndef USE_WINDOWS_API do { #endif timeout.tv_sec = timeout_ms / 1000; timeout.tv_usec = (timeout_ms % 1000) * 1000; + /* file/socket descriptors */ FD_ZERO(&fds); FD_SET(sockfd, &fds); +#ifndef USE_WINDOWS_API + if ((shutdown == 0) && (appData->interruptFds[0] != -1)) { + FD_SET(appData->interruptFds[0], &fds); + /* nfds should be set to the highest number descriptor plus 1 */ + if (appData->interruptFds[0] > sockfd) { + nfds = appData->interruptFds[0] + 1; + } + } +#endif /* !USE_WINDOWS_API */ + + /* error descriptors */ FD_ZERO(&errfds); FD_SET(sockfd, &errfds); @@ -677,11 +877,13 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) sendfds = &fds; } + incrementThreadPollCount(appData); if (timeout_ms == 0) { result = select(nfds, recvfds, sendfds, &errfds, NULL); } else { result = select(nfds, recvfds, sendfds, &errfds, &timeout); } + decrementThreadPollCount(appData); if (result == 0) { return WOLFJNI_IO_EVENT_TIMEOUT; @@ -692,9 +894,26 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) } else { return WOLFJNI_IO_EVENT_SEND_READY; } - } else if (FD_ISSET(sockfd, &errfds)) { + } + else if (FD_ISSET(sockfd, &errfds)) { return WOLFJNI_IO_EVENT_ERROR; } +#ifndef USE_WINDOWS_API + else if ((shutdown == 0) && (appData->interruptFds[0] != -1) && + (FD_ISSET(appData->interruptFds[0], &fds))) { + /* We got interrupted by our interrupt fd, due to a Java + * thread calling SSLSocket.close(). Try to read byte that + * was placed on our interruptFds[0] descriptor, but not + * an error if not there. Another read/write() may have + * already read it off. We just want to be interrupted, + * byte value does not matter. */ + do { + read(appData->interruptFds[0], tmpBuf, 1); + } while (errno == EINTR); + + return WOLFJNI_IO_EVENT_FD_CLOSED; + } +#endif /* !USE_WINDOWS_API */ } #ifndef USE_WINDOWS_API @@ -725,6 +944,9 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) * otherwise 0 to ignore readability events * @param tx set to 1 to monitor writability on socket descriptor, * otherwise 0 to ignore writability events + * @param shutdown Is this being called from shutdownSSL()? Don't poll + * on interruptFds in that case, since we already know + * we are closing in that case. * * @return possible return values are: * WOLFJNI_IO_EVENT_FAIL @@ -736,11 +958,18 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) * WOLFJNI_IO_EVENT_POLLHUP * WOLFJNI_IO_EVENT_INVALID_TIMEOUT */ -static int socketPoll(int sockfd, int timeout_ms, int rx, int tx) +static int socketPoll(SSLAppData* appData, int sockfd, int timeout_ms, + int rx, int tx, int shutdown) { int ret; + int nfds = 2; int timeout; - struct pollfd fds[1]; + struct pollfd fds[2]; + char tmpBuf[1]; + + if (appData == NULL) { + return WOLFJNI_IO_EVENT_ERROR; + } /* Sanitize timeout and convert from Java to poll() expectations */ timeout = timeout_ms; @@ -750,35 +979,62 @@ static int socketPoll(int sockfd, int timeout_ms, int rx, int tx) timeout = -1; } + /* fd for socket I/O */ fds[0].fd = sockfd; fds[0].events = 0; if (tx) { - fds[0].events |= POLLOUT; + fds[0].events |= POLLOUT | POLLPRI; } if (rx) { - fds[0].events |= POLLIN; + fds[0].events |= POLLIN | POLLPRI; + } + + if ((shutdown == 0) && (appData->interruptFds[0] != -1)) { + /* fd for interrupt / signaling SSLSocket.close() */ + fds[1].fd = appData->interruptFds[0]; + fds[1].events = POLLIN | POLLPRI; + } + else { + nfds--; } do { - ret = poll(fds, 1, timeout); + incrementThreadPollCount(appData); + ret = poll(fds, nfds, timeout); + decrementThreadPollCount(appData); if (ret == 0) { return WOLFJNI_IO_EVENT_TIMEOUT; - } else if (ret > 0) { - if (fds[0].revents & POLLIN || + } + else if (ret > 0) { + if ((shutdown == 0) && (appData->interruptFds[0] != -1) && + (fds[1].revents & POLLIN)) { + /* received data on interrupt pipe, read and return + * that descriptor is closed (closing) */ + do { + read(appData->interruptFds[0], tmpBuf, 1); + } while (errno == EINTR); + + return WOLFJNI_IO_EVENT_FD_CLOSED; + } + else if (fds[0].revents & POLLIN || fds[0].revents & POLLPRI) { /* read possible */ return WOLFJNI_IO_EVENT_RECV_READY; - } else if (fds[0].revents & POLLOUT) { /* write possible */ + } + else if (fds[0].revents & POLLOUT) { /* write possible */ return WOLFJNI_IO_EVENT_SEND_READY; - } else if (fds[0].revents & POLLNVAL) { /* fd not open */ + } + else if (fds[0].revents & POLLNVAL) { /* fd not open */ return WOLFJNI_IO_EVENT_FD_CLOSED; - } else if (fds[0].revents & POLLERR) { /* exceptional error */ + } + else if (fds[0].revents & POLLERR) { /* exceptional error */ return WOLFJNI_IO_EVENT_ERROR; - } else if (fds[0].revents & POLLHUP) { /* sock disconnected */ + } + else if (fds[0].revents & POLLHUP) { /* sock disconnected */ return WOLFJNI_IO_EVENT_POLLHUP; } } @@ -795,7 +1051,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_connect { int ret = 0, err = 0, sockfd = 0; int pollRx = 0; +#if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) int pollTx = 0; +#endif wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -852,14 +1110,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_connect if (err == SSL_ERROR_WANT_READ) { pollRx = 1; } + #if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) else if (err == SSL_ERROR_WANT_WRITE) { pollTx = 1; } + #endif #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) - ret = socketSelect(sockfd, (int)timeout, pollRx); + ret = socketSelect(appData, sockfd, (int)timeout, pollRx, 0); #else - ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + ret = socketPoll(appData, sockfd, (int)timeout, pollRx, pollTx, 0); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -898,7 +1158,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write byte* data = NULL; int ret = SSL_FAILURE, err, sockfd; int pollRx = 0; +#if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) int pollTx = 0; +#endif wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -963,14 +1225,17 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write if (err == SSL_ERROR_WANT_READ) { pollRx = 1; } + #if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) else if (err == SSL_ERROR_WANT_WRITE) { pollTx = 1; } + #endif #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) - ret = socketSelect(sockfd, (int)timeout, pollRx); + ret = socketSelect(appData, sockfd, (int)timeout, pollRx, 0); #else - ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + ret = socketPoll(appData, sockfd, (int)timeout, pollRx, + pollTx, 0); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -1017,7 +1282,9 @@ static int SSLReadNonblockingWithSelectPoll(WOLFSSL* ssl, byte* out, { int size, ret, err, sockfd; int pollRx = 0; +#if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) int pollTx = 0; +#endif wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; @@ -1065,14 +1332,16 @@ static int SSLReadNonblockingWithSelectPoll(WOLFSSL* ssl, byte* out, if (err == SSL_ERROR_WANT_READ) { pollRx = 1; } + #if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) else if (err == SSL_ERROR_WANT_WRITE) { pollTx = 1; } + #endif #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) - ret = socketSelect(sockfd, timeout, pollRx); + ret = socketSelect(appData, sockfd, timeout, pollRx, 0); #else - ret = socketPoll(sockfd, timeout, pollRx, pollTx); + ret = socketPoll(appData, sockfd, timeout, pollRx, pollTx, 0); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -1152,7 +1421,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read__JLjava_nio_ByteBuff jint position; jint limit; jboolean hasArray; - jbyteArray bufArr; + jbyteArray bufArr = NULL; (void)jcl; @@ -1304,7 +1573,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_accept { int ret = 0, err, sockfd; int pollRx = 0; +#if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) int pollTx = 0; +#endif wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -1361,14 +1632,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_accept if (err == SSL_ERROR_WANT_READ) { pollRx = 1; } + #if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) else if (err == SSL_ERROR_WANT_WRITE) { pollTx = 1; } + #endif #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) - ret = socketSelect(sockfd, (int)timeout, pollRx); + ret = socketSelect(appData, sockfd, (int)timeout, pollRx, 0); #else - ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + ret = socketPoll(appData, sockfd, (int)timeout, pollRx, pollTx, 0); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -1441,6 +1714,16 @@ JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLSession_freeSSL XFREE(g_cachedVerifyCb, NULL, DYNAMIC_TYPE_TMP_BUFFER); g_cachedVerifyCb = NULL; } +#ifndef USE_WINDOWS_API + if (appData->pollCountLock != NULL) { + wc_FreeMutex(appData->pollCountLock); + XFREE(appData->pollCountLock, NULL, DYNAMIC_TYPE_TMP_BUFFER); + appData->pollCountLock = NULL; + } + + /* close pipe() descriptors */ + closeInterruptPipe(appData); +#endif /* free appData */ XFREE(appData, NULL, DYNAMIC_TYPE_TMP_BUFFER); appData = NULL; @@ -1553,7 +1836,9 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_shutdownSSL { int ret = 0, err, sockfd; int pollRx = 0; +#if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) int pollTx = 0; +#endif wolfSSL_Mutex* jniSessLock; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -1610,14 +1895,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_shutdownSSL if (err == SSL_ERROR_WANT_READ) { pollRx = 1; } + #if !defined(WOLFJNI_USE_IO_SELECT) && !defined(USE_WINDOWS_API) else if (err == SSL_ERROR_WANT_WRITE) { pollTx = 1; } + #endif #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) - ret = socketSelect(sockfd, (int)timeout, pollRx); + ret = socketSelect(appData, sockfd, (int)timeout, pollRx, 1); #else - ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + ret = socketPoll(appData, sockfd, (int)timeout, pollRx, pollTx, 1); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -1822,11 +2109,11 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_get1Session #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) /* Default to select() on Windows or if WOLFJNI_USE_IO_SELECT */ - ret = socketSelect(sockfd, - (int)WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT, 1); - #else - ret = socketPoll(sockfd, + ret = socketSelect(appData, sockfd, (int)WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT, 1, 0); + #else + ret = socketPoll(appData, sockfd, + (int)WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT, 1, 0, 0); #endif if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || (ret == WOLFJNI_IO_EVENT_SEND_READY)) { @@ -3558,12 +3845,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getSide (void)jenv; (void)jcl; -#ifdef ATOMIC_USER /* wolfSSL checks ssl for NULL */ return wolfSSL_GetSide((WOLFSSL*)(uintptr_t)ssl); -#else - return NOT_COMPILED_IN; -#endif } JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_isTLSv1_11 @@ -5445,6 +5728,51 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_hasTicket #endif } +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_interruptBlockedIO + (JNIEnv* jenv, jobject jcl, jlong sslPtr) +{ +#ifndef USE_WINDOWS_API + SSLAppData* appData = NULL; + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; +#endif + (void)jenv; + (void)jcl; + +#ifndef USE_WINDOWS_API + /* get session mutex from SSL app data */ + appData = (SSLAppData*)wolfSSL_get_app_data(ssl); + if (appData == NULL) { + return WOLFSSL_FAILURE; + } + + /* signal any blocked threads in select()/poll() to wake up, so we + * don't have a deadlock when trying to lock jniSessLock next */ + writeToInterruptPipe(appData); + writeToInterruptPipe(appData); + +#endif /* USE_WINDOWS_API */ + + return SSL_SUCCESS; +} + +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getThreadsBlockedInPoll + (JNIEnv* jenv, jobject jcl, jlong sslPtr) +{ + SSLAppData* appData = NULL; + WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; + + (void)jenv; + (void)jcl; + + /* get session mutex from SSL app data */ + appData = (SSLAppData*)wolfSSL_get_app_data(ssl); + if (appData == NULL) { + return 0; + } + + return (jint)threadsWaitingInPollSelect(appData); +} + JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLSession_setSSLIORecv (JNIEnv* jenv, jobject jcl, jlong sslPtr) { diff --git a/native/com_wolfssl_WolfSSLSession.h b/native/com_wolfssl_WolfSSLSession.h index 639db768..260032ca 100644 --- a/native/com_wolfssl_WolfSSLSession.h +++ b/native/com_wolfssl_WolfSSLSession.h @@ -10,10 +10,10 @@ extern "C" { /* * Class: com_wolfssl_WolfSSLSession * Method: newSSL - * Signature: (J)J + * Signature: (JZ)J */ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_newSSL - (JNIEnv *, jobject, jlong); + (JNIEnv *, jobject, jlong, jboolean); /* * Class: com_wolfssl_WolfSSLSession @@ -879,6 +879,22 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_useSupportedCurve JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_hasTicket (JNIEnv *, jobject, jlong); +/* + * Class: com_wolfssl_WolfSSLSession + * Method: interruptBlockedIO + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_interruptBlockedIO + (JNIEnv *, jobject, jlong); + +/* + * Class: com_wolfssl_WolfSSLSession + * Method: getThreadsBlockedInPoll + * Signature: (J)I + */ +JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getThreadsBlockedInPoll + (JNIEnv *, jobject, jlong); + #ifdef __cplusplus } #endif diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index 8090a53d..e34891ec 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -102,6 +102,13 @@ public class WolfSSLSession { /** * Creates a new SSL/TLS session. * + * Native session created also creates JNI SSLAppData for usage + * internal to wolfSSL JNI. This constructor creates a default + * pipe() to use for interrupting threads waiting in select()/poll() + * when close() is called. To skip creation of this pipe() use + * the WolfSSLSession(WolfSSLContext ctx, boolean setupIOPipe) + * constructor with 'setupIOPipe' set to false. + * * @param ctx WolfSSLContext object used to create SSL session. * * @throws com.wolfssl.WolfSSLException if session object creation @@ -109,13 +116,61 @@ public class WolfSSLSession { */ public WolfSSLSession(WolfSSLContext ctx) throws WolfSSLException { - sslPtr = newSSL(ctx.getContextPtr()); + sslPtr = newSSL(ctx.getContextPtr(), true); if (sslPtr == 0) { throw new WolfSSLException("Failed to create SSL Object"); } WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, - WolfSSLDebug.INFO, sslPtr, "creating new WolfSSLSession"); + WolfSSLDebug.INFO, sslPtr, + "creating new WolfSSLSession (with I/O pipe)"); + + synchronized (stateLock) { + this.active = true; + } + + /* save context reference for I/O callbacks from JNI */ + this.ctx = ctx; + } + + /** + * Creates a new SSL/TLS session. + * + * Native session created also creates JNI SSLAppData for usage + * internal to wolfSSL JNI. A pipe() can be created internally to wolfSSL + * JNI to use for interrupting threads waiting in select()/poll() + * when close() is called. To skip creation of this pipe(), set + * 'setupIOPipe' to false. + * + * It is generally recommended to have wolfSSL JNI create the native + * pipe(), unless you will be operating over non-Socket I/O. For example, + * when this WolfSSLSession is being created from the JSSE level + * SSLEngine class. + * + * @param ctx WolfSSLContext object used to create SSL session. + * @param setupIOPipe true to create internal IO pipe(), otherwise + * false + * + * @throws com.wolfssl.WolfSSLException if session object creation + * failed. + */ + public WolfSSLSession(WolfSSLContext ctx, boolean setupIOPipe) + throws WolfSSLException { + + sslPtr = newSSL(ctx.getContextPtr(), false); + if (sslPtr == 0) { + throw new WolfSSLException("Failed to create SSL Object"); + } + + if (setupIOPipe) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, sslPtr, + "creating new WolfSSLSession (with I/O pipe)"); + } else { + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, sslPtr, + "creating new WolfSSLSession (without I/O pipe)"); + } synchronized (stateLock) { this.active = true; @@ -240,7 +295,7 @@ private synchronized void confirmObjectIsActive() /* ------------------ native method declarations -------------------- */ - private native long newSSL(long ctx); + private native long newSSL(long ctx, boolean withIOPipe); private native int setFd(long ssl, Socket sd, int type); private native int setFd(long ssl, DatagramSocket sd, int type); private native int useCertificateFile(long ssl, String file, int format); @@ -357,6 +412,8 @@ private native int setTlsHmacInner(long ssl, byte[] inner, long sz, private native int set1SigAlgsList(long ssl, String list); private native int useSupportedCurve(long ssl, int name); private native int hasTicket(long session); + private native int interruptBlockedIO(long ssl); + private native int getThreadsBlockedInPoll(long ssl); /* ------------------- session-specific methods --------------------- */ @@ -4125,6 +4182,49 @@ public synchronized void setIOSend(WolfSSLIOSendCallback callback) } } + /** + * Interrupt native I/O operations blocked inside select()/poll(). + * + * This is used by wolfJSSE when SSLSocket.close() is called, to wake up + * threads that are blocked in select()/poll(). + * + * @return WolfSSL.SSL_SUCCESS on success, negative on error. + * + * @throws IllegalStateException WolfSSLSession has been freed + */ + public synchronized int interruptBlockedIO() + throws IllegalStateException { + + confirmObjectIsActive(); + + /* Not synchronizing on sslLock, since we want to interrupt threads + * blocked on I/O operations, which will already hold sslLock */ + + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, "entered interruptBlockedIO()"); + + return interruptBlockedIO(this.sslPtr); + } + + /** + * Get count of threads currently blocked in select() or poll() + * at the native JNI level. + * + * @return count of threads waiting in select() or poll() + * + * @throws IllegalStateException WolfSSLSession has been freed + */ + public synchronized int getThreadsBlockedInPoll() + throws IllegalStateException { + + confirmObjectIsActive(); + + WolfSSLDebug.log(getClass(), WolfSSLDebug.Component.JNI, + WolfSSLDebug.INFO, "entered getThreadsBlockedInPoll()"); + + return getThreadsBlockedInPoll(this.sslPtr); + } + /** * Use SNI name with this session. * diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java index 9b3f2305..b2699402 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java @@ -313,7 +313,7 @@ private void initSSL() throws WolfSSLException, WolfSSLJNIException { } /* will throw WolfSSLException if issue creating WOLFSSL */ - ssl = new WolfSSLSession(ctx); + ssl = new WolfSSLSession(ctx, false); enableExtraDebug(); enableIODebug(); diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java index d29278ce..293d94dd 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java @@ -2011,6 +2011,10 @@ public synchronized void close() throws IOException { handshakeFinished = this.handshakeComplete; } + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "signaling any blocked I/O threads to wake up"); + ssl.interruptBlockedIO(); + /* Try TLS shutdown procedure, only if handshake has finished */ if (ssl != null && handshakeFinished) { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, @@ -2036,12 +2040,15 @@ public synchronized void close() throws IOException { } try { + /* Use SO_LINGER value when calling + * shutdown here, since we are closing the + * socket */ if (this.socket != null) { ret = ssl.shutdownSSL( - this.socket.getSoTimeout()); + this.socket.getSoLinger()); } else { ret = ssl.shutdownSSL( - super.getSoTimeout()); + super.getSoLinger()); } WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, @@ -2065,9 +2072,7 @@ public synchronized void close() throws IOException { /* Release native verify callback (JNI global) */ this.EngineHelper.unsetVerifyCallback(); - /* Connection is closed, free native WOLFSSL session - * to release native memory earlier than garbage - * collector might with finalize(). */ + /* Close ConsumedRecvCtx data stream */ Object readCtx = this.ssl.getIOReadCtx(); if (readCtx != null && readCtx instanceof ConsumedRecvCtx) { @@ -2076,14 +2081,6 @@ public synchronized void close() throws IOException { "calling ConsumedRecvCtx.closeDataStreams()"); rctx.closeDataStreams(); } - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "calling this.ssl.freeSSL()"); - this.ssl.freeSSL(); - this.ssl = null; - - /* Reset internal WolfSSLEngineHelper to null */ - this.EngineHelper.clearObjectState(); - this.EngineHelper = null; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "thread exiting handshakeLock (shutdown)"); @@ -2112,6 +2109,33 @@ public synchronized void close() throws IOException { this.outStream = null; } } + + /* Free this.ssl here instead of above for use cases + * where a SSLSocket is created then closed()'d before + * connected or handshake is done. freeSSL() will + * release interruptFds[] pipe() and free up descriptor. */ + synchronized (ioLock) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "thread got ioLock (freeSSL)"); + + /* Connection is closed, free native WOLFSSL session + * to release native memory earlier than garbage + * collector might with finalize(), if we don't + * have any threads still waiting in poll/select. */ + if (this.ssl.getThreadsBlockedInPoll() == 0) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "calling this.ssl.freeSSL()"); + this.ssl.freeSSL(); + this.ssl = null; + } + + /* Reset internal WolfSSLEngineHelper to null */ + this.EngineHelper.clearObjectState(); + this.EngineHelper = null; + + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "thread exiting ioLock (shutdown)"); + } /* ioLock */ } if (this.autoClose) { @@ -2687,7 +2711,11 @@ public synchronized int read(byte[] b, int off, int len) throw e; } catch (IllegalStateException e) { - throw new IOException(e); + /* SSLSocket.close() may have already called freeSSL(), + * thus causing a 'WolfSSLSession object has been freed' + * IllegalStateException to be thrown from + * WolfSSLSession.read(). Return as a SocketException here. */ + throw new SocketException(e.getMessage()); } /* return number of bytes read */ diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java index d21a83a7..9405ea19 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLSocketTest.java @@ -59,6 +59,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLParameters; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -116,6 +117,8 @@ public void testSessionResumptionWithTicketEnabled(); public void testDoubleSocketClose(); public void testSocketConnectException(); + public void testSocketCloseInterruptsWrite(); + public void testSocketCloseInterruptsRead(); */ public class WolfSSLSocketTest { @@ -465,6 +468,8 @@ public void testEnabledSupportedCurvesProperty() throws Exception { TestClient client = null; Exception srvException = null; Exception cliException = null; + CountDownLatch sDoneLatch = null; + CountDownLatch cDoneLatch = null; System.out.print("\twolfjsse.enabledSupportedCurves"); @@ -485,11 +490,19 @@ public void testEnabledSupportedCurvesProperty() throws Exception { TestArgs sArgs = new TestArgs(null, null, true, true, true, null); TestArgs cArgs = new TestArgs(null, null, false, false, true, null); - server = new TestServer(this.ctx, ss, sArgs, 1); + + sDoneLatch = new CountDownLatch(1); + cDoneLatch = new CountDownLatch(1); + + server = new TestServer(this.ctx, ss, sArgs, 1, sDoneLatch); server.start(); - client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); + cDoneLatch.await(); + sDoneLatch.await(); + srvException = server.getException(); if (srvException != null) { Security.setProperty("wolfjsse.enabledSupportedCurves", @@ -527,11 +540,19 @@ public void testEnabledSupportedCurvesProperty() throws Exception { TestArgs sArgs = new TestArgs(null, null, true, true, true, null); TestArgs cArgs = new TestArgs(null, null, false, false, true, null); - server = new TestServer(this.ctx, ss, sArgs, 1); + + sDoneLatch = new CountDownLatch(1); + cDoneLatch = new CountDownLatch(1); + + server = new TestServer(this.ctx, ss, sArgs, 1, sDoneLatch); server.start(); - client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); + cDoneLatch.await(); + sDoneLatch.await(); + srvException = server.getException(); if (srvException != null) { Security.setProperty("wolfjsse.enabledSupportedCurves", @@ -569,11 +590,19 @@ public void testEnabledSupportedCurvesProperty() throws Exception { TestArgs sArgs = new TestArgs(null, null, true, true, true, null); TestArgs cArgs = new TestArgs(null, null, false, false, true, null); - server = new TestServer(this.ctx, ss, sArgs, 1); + + sDoneLatch = new CountDownLatch(1); + cDoneLatch = new CountDownLatch(1); + + server = new TestServer(this.ctx, ss, sArgs, 1, sDoneLatch); server.start(); - client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); + cDoneLatch.await(); + sDoneLatch.await(); + srvException = server.getException(); if (srvException != null) { Security.setProperty("wolfjsse.enabledSupportedCurves", @@ -600,7 +629,9 @@ public void testEnabledSupportedCurvesProperty() throws Exception { } } - /* Test with invalid property entries */ + /* Test with invalid property entries. + * Only need to start client thread, since it throws exception + * before connecting to server. */ { Security.setProperty("wolfjsse.enabledSupportedCurves", "badone, badtwo"); @@ -609,19 +640,15 @@ public void testEnabledSupportedCurvesProperty() throws Exception { ss = (SSLServerSocket)ctx.getServerSocketFactory() .createServerSocket(0); - TestArgs sArgs = new TestArgs(null, null, true, true, true, null); TestArgs cArgs = new TestArgs(null, null, false, false, true, null); - server = new TestServer(this.ctx, ss, sArgs, 1); - server.start(); - client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + + cDoneLatch = new CountDownLatch(1); + + client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); - srvException = server.getException(); - if (srvException != null) { - Security.setProperty("wolfjsse.enabledSupportedCurves", - originalProperty); - throw srvException; - } + cDoneLatch.await(); cliException = client.getException(); if (cliException != null) { @@ -630,7 +657,7 @@ public void testEnabledSupportedCurvesProperty() throws Exception { try { client.join(1000); - server.join(1000); + //server.join(1000); } catch (InterruptedException e) { System.out.println("interrupt happened"); @@ -656,6 +683,9 @@ public void testEnabledSupportedCurvesProperty() throws Exception { @Test public void testClientServerThreaded() throws Exception { + CountDownLatch sDoneLatch = null; + CountDownLatch cDoneLatch = null; + System.out.print("\tTesting basic client/server"); /* create new CTX */ @@ -668,12 +698,18 @@ public void testClientServerThreaded() throws Exception { TestArgs sArgs = new TestArgs(null, null, true, true, true, null); TestArgs cArgs = new TestArgs(null, null, false, false, true, null); - TestServer server = new TestServer(this.ctx, ss, sArgs, 1); + sDoneLatch = new CountDownLatch(1); + cDoneLatch = new CountDownLatch(1); + + TestServer server = new TestServer(this.ctx, ss, sArgs, 1, sDoneLatch); server.start(); - TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); + cDoneLatch.await(); + sDoneLatch.await(); Exception srvException = server.getException(); if (srvException != null) { @@ -700,6 +736,9 @@ public void testClientServerThreaded() throws Exception { public void alpnClientServerRunner(TestArgs sArgs, TestArgs cArgs, boolean expectingException) throws Exception { + CountDownLatch sDoneLatch = null; + CountDownLatch cDoneLatch = null; + if (sArgs == null || cArgs == null) { throw new Exception("client/server TestArgs can not be null"); } @@ -710,12 +749,19 @@ public void alpnClientServerRunner(TestArgs sArgs, TestArgs cArgs, SSLServerSocket ss = (SSLServerSocket)ctx.getServerSocketFactory() .createServerSocket(0); - TestServer server = new TestServer(this.ctx, ss, sArgs, 1); + sDoneLatch = new CountDownLatch(1); + cDoneLatch = new CountDownLatch(1); + + TestServer server = new TestServer(this.ctx, ss, sArgs, 1, sDoneLatch); server.start(); - TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs); + TestClient client = new TestClient(this.ctx, ss.getLocalPort(), cArgs, + cDoneLatch); client.start(); + cDoneLatch.await(); + sDoneLatch.await(); + try { client.join(1000); server.join(1000); @@ -988,7 +1034,7 @@ public void testExtendedThreadingUse() /* This test hangs on Android, marking TODO for later investigation. Seems to be * something specific to the test code, not library proper. */ - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { System.out.println("\t... skipped"); return; } @@ -2746,6 +2792,271 @@ public Void call() throws Exception { System.out.println("\t... passed"); } + /* Test timeout set to 10000 ms (10 sec) in case inerrupt code is not + * working as expected, we will see the timeout as a hard error that + * this test has failed */ + @Test(timeout = 10000) + public void testSocketCloseInterruptsWrite() throws Exception { + + String protocol = null; + SSLServerSocketFactory ssf = null; + SSLServerSocket ss = null; + SSLSocketFactory sf = null; + boolean passed = false; + + System.out.print("\tTesting close/write interrupt"); + + /* pipe() interrupt mechamism not implemented for Windows yet since + * Windows does not support Unix/Linux pipe(). Re-enable this test + * for Windows when that support has been added */ + if (WolfSSLTestFactory.isWindows()) { + System.out.println("\t... skipped"); + return; + } + + if (WolfSSL.TLSv12Enabled()) { + protocol = "TLSv1.2"; + } else if (WolfSSL.TLSv11Enabled()) { + protocol = "TLSv1.1"; + } else if (WolfSSL.TLSv1Enabled()) { + protocol = "TLSv1.0"; + } else { + System.out.println("\t... skipped"); + return; + } + + /* create new CTX */ + this.ctx = tf.createSSLContext(protocol, ctxProvider); + + /* create SSLServerSocket first to get ephemeral port */ + ss = (SSLServerSocket)ctx.getServerSocketFactory() + .createServerSocket(0); + + final SSLSocket cs = (SSLSocket)ctx.getSocketFactory().createSocket(); + cs.connect(new InetSocketAddress(ss.getLocalPort())); + + final SSLSocket server = (SSLSocket)ss.accept(); + final CountDownLatch closeLatch = new CountDownLatch(1); + + ExecutorService es = Executors.newSingleThreadExecutor(); + Future serverFuture = es.submit(new Callable() { + @Override + public Void call() throws Exception { + try { + server.startHandshake(); + + boolean doClose = closeLatch.await(90L, TimeUnit.SECONDS); + if (!doClose) { + /* Return without closing, latch not hit within + * time limit */ + return null; + } + + /* Sleep so write thread has a chance to do some + * writing before interrupt */ + Thread.sleep(1000); + cs.setSoLinger(true, 5); + cs.close(); + + } catch (SSLException e) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Server thread got SSLException when not expected"); + } + return null; + } + }); + + byte[] tmpArr = new byte[1024]; + Arrays.fill(tmpArr, (byte)0xA2); + OutputStream out = cs.getOutputStream(); + + try { + try { + cs.startHandshake(); + out.write(tmpArr); + } + catch (Exception e) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Exception from first out.write() when not expected"); + } + + try { + /* signal server thread to try and close socket */ + closeLatch.countDown(); + + /* keep writing, we should get interrupted */ + while (true) { + out.write(tmpArr); + } + + } catch (SocketException e) { + /* We expect SocketException with this message, error if + * different than expected */ + if (!e.getMessage().contains("Socket fd closed during poll")) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Incorrect SocketException thrown by client"); + throw e; + } + + passed = true; + } + } + finally { + es.shutdown(); + serverFuture.get(); + if (!cs.isClosed()) { + cs.close(); + } + if (!server.isClosed()) { + server.close(); + } + if (!ss.isClosed()) { + ss.close(); + } + } + + if (passed) { + System.out.println("\t... passed"); + } + } + + /* Test timeout set to 10000 ms (10 sec) in case inerrupt code is not + * working as expected, we will see the timeout as a hard error that + * this test has failed */ + @Test(timeout = 10000) + public void testSocketCloseInterruptsRead() throws Exception { + + int ret = 0; + String protocol = null; + SSLServerSocketFactory ssf = null; + SSLServerSocket ss = null; + SSLSocketFactory sf = null; + boolean passed = false; + + System.out.print("\tTesting close/read interrupt"); + + /* pipe() interrupt mechamism not implemented for Windows yet since + * Windows does not support Unix/Linux pipe(). Re-enable this test + * for Windows when that support has been added */ + if (WolfSSLTestFactory.isWindows()) { + System.out.println("\t... skipped"); + return; + } + + if (WolfSSL.TLSv12Enabled()) { + protocol = "TLSv1.2"; + } else if (WolfSSL.TLSv11Enabled()) { + protocol = "TLSv1.1"; + } else if (WolfSSL.TLSv1Enabled()) { + protocol = "TLSv1.0"; + } else { + System.out.println("\t... skipped"); + return; + } + + /* create new CTX */ + this.ctx = tf.createSSLContext(protocol, ctxProvider); + + /* create SSLServerSocket first to get ephemeral port */ + ss = (SSLServerSocket)ctx.getServerSocketFactory() + .createServerSocket(0); + + final SSLSocket cs = (SSLSocket)ctx.getSocketFactory().createSocket(); + cs.connect(new InetSocketAddress(ss.getLocalPort())); + + final SSLSocket server = (SSLSocket)ss.accept(); + final CountDownLatch closeLatch = new CountDownLatch(1); + + ExecutorService es = Executors.newSingleThreadExecutor(); + Future serverFuture = es.submit(new Callable() { + @Override + public Void call() throws Exception { + try { + server.startHandshake(); + + boolean doClose = closeLatch.await(90L, TimeUnit.SECONDS); + if (!doClose) { + /* Return without closing, latch not hit within + * time limit */ + return null; + } + + /* Sleep to let client thread hit read call */ + Thread.sleep(1000); + cs.setSoLinger(true, 5); + cs.close(); + + } catch (SSLException e) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Server thread got SSLException when not expected"); + } + return null; + } + }); + + byte[] tmpArr = new byte[1024]; + InputStream in = cs.getInputStream(); + + try { + try { + cs.startHandshake(); + } + catch (Exception e) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Exception from startHandshake() when not expected"); + } + + try { + /* signal server thread to try and close socket */ + closeLatch.countDown(); + + while (true) { + ret = in.read(tmpArr, 0, tmpArr.length); + if (ret == -1) { + /* end of stream */ + break; + } + } + + } catch (SocketException e) { + /* We expect SocketException with this message, error if + * different than expected */ + if (!e.getMessage().contains("Socket is closed") && + !e.getMessage().contains("Connection already shutdown") && + !e.getMessage().contains("object has been freed")) { + System.out.println("\t... failed"); + e.printStackTrace(); + fail("Incorrect SocketException thrown by client"); + throw e; + } + } + + passed = true; + } + finally { + es.shutdown(); + serverFuture.get(); + if (!cs.isClosed()) { + cs.close(); + } + if (!server.isClosed()) { + server.close(); + } + if (!ss.isClosed()) { + ss.close(); + } + } + + if (passed) { + System.out.println("\t... passed"); + } + } + @Test public void testSocketMethodsAfterClose() throws Exception { @@ -3089,13 +3400,15 @@ protected class TestServer extends Thread private int numConnections = 1; WolfSSLSocketTest wst; SSLServerSocket ss = null; + CountDownLatch doneLatch = null; public TestServer(SSLContext ctx, SSLServerSocket ss, - TestArgs args, int numConnections) { + TestArgs args, int numConnections, CountDownLatch doneLatch) { this.ctx = ctx; this.ss = ss; this.args = args; this.numConnections = numConnections; + this.doneLatch = doneLatch; } @@ -3175,6 +3488,8 @@ public void run() { } catch (Exception e) { this.exception = e; + } finally { + this.doneLatch.countDown(); } } @@ -3202,11 +3517,14 @@ protected class TestClient extends Thread private Exception exception = null; private TestArgs args = null; WolfSSLSocketTest wst; + CountDownLatch doneLatch = null; - public TestClient(SSLContext ctx, int port, TestArgs args) { + public TestClient(SSLContext ctx, int port, TestArgs args, + CountDownLatch doneLatch) { this.ctx = ctx; this.srvPort = port; this.args = args; + this.doneLatch = doneLatch; } @Override @@ -3271,6 +3589,8 @@ public void run() { } catch (Exception e) { this.exception = e; + } finally { + this.doneLatch.countDown(); } } diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java index 5c4634e7..2b66c431 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTestFactory.java @@ -1052,13 +1052,24 @@ protected String[] getAlias() throws KeyStoreException, IOException, * Test if the env is Android * @return true if is Android system */ - protected boolean isAndroid() { + protected static boolean isAndroid() { if (System.getProperty("java.runtime.name").contains("Android")) { return true; } return false; } + /** + * Test if the env is Windows. + * @return true if Windows, otherwise false + */ + protected static boolean isWindows() { + if (System.getProperty("os.name").startsWith("Windows")) { + return true; + } + return false; + } + /** * Check if Security property contains a specific value. * diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java index 2ef210b1..ec792054 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLTrustX509Test.java @@ -121,7 +121,7 @@ public void testCAParsing() System.out.print("\tTesting parse all.jks"); - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { /* @TODO finding that BKS has different order of certs */ pass("\t... skipped"); return; @@ -186,7 +186,7 @@ public void testUseBeforeInit() System.out.print("\tTesting use before init()"); - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { /* @TODO finding that BKS has different order of certs */ pass("\t... skipped"); return; @@ -242,7 +242,7 @@ public void testServerParsing() System.out.print("\tTesting parsing server.jks"); - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { /* @TODO finding that BKS has different order of certs */ pass("\t... skipped"); return; @@ -312,7 +312,7 @@ public void testCAParsingMixed() System.out.print("\tTesting parse all_mixed.jks"); - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { /* @TODO finding that BKS has different order of certs */ pass("\t... skipped"); return; @@ -570,7 +570,7 @@ public void testCheckServerTrustedWithChain() String eccInt1Cert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1Cert = "/sdcard/" + rsaInt1Cert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -697,7 +697,7 @@ public void testCheckServerTrustedWithBadChainCert() "examples/certs/intermediate/ca-int-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1CertWrong = "/sdcard/" + rsaInt1CertWrong; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -825,7 +825,7 @@ public void testCheckServerTrustedWithWrongChain() String eccInt1Cert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1Cert = "/sdcard/" + rsaInt1Cert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -948,7 +948,7 @@ public void testCheckServerTrustedMissingChain() "examples/certs/intermediate/server-int-ecc-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -1054,7 +1054,7 @@ public void testCheckServerTrustedWithChainWrongOrder() String eccInt1Cert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1Cert = "/sdcard/" + rsaInt1Cert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -1180,7 +1180,7 @@ public void testCheckServerTrustedWithChainReturnsChain() String eccInt1Cert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; String eccInt2Cert = "examples/certs/intermediate/ca-int2-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1Cert = "/sdcard/" + rsaInt1Cert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; @@ -1340,7 +1340,7 @@ public void testCheckServerTrustedWithDuplicatedRootInChain() String eccInt1Cert = "examples/certs/intermediate/ca-int-ecc-cert.pem"; String eccRootCert = "examples/certs/ca-ecc-cert.pem"; - if (tf.isAndroid()) { + if (WolfSSLTestFactory.isAndroid()) { rsaServerCert = "/sdcard/" + rsaServerCert; rsaInt1Cert = "/sdcard/" + rsaInt1Cert; rsaInt2Cert = "/sdcard/" + rsaInt2Cert; diff --git a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java index 7cc255c1..fc5edd79 100644 --- a/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java +++ b/src/test/com/wolfssl/provider/jsse/test/WolfSSLX509Test.java @@ -363,7 +363,7 @@ public void testVerifyProvider() { break; } } - if (sigProvider == null || tf.isAndroid()) { + if (sigProvider == null || WolfSSLTestFactory.isAndroid()) { pass("\t\t\t... skipped"); return; } @@ -511,7 +511,7 @@ public void testGetters() } /* Android KeyStore formats x509 getName() differently than peer getName() */ - if (!tf.isAndroid()) { + if (!WolfSSLTestFactory.isAndroid()) { if (!x509.getSubjectDN().getName().equals( peer.getSubjectDN().getName())) { error("\t\t... failed");