diff --git a/native/com_wolfssl_WolfSSL.c b/native/com_wolfssl_WolfSSL.c index 54e022bd..138bbe6c 100644 --- a/native/com_wolfssl_WolfSSL.c +++ b/native/com_wolfssl_WolfSSL.c @@ -71,6 +71,10 @@ jmethodID g_bufferArrayOffsetMethodId = NULL; jmethodID g_bufferSetPositionMethodId = NULL; jmethodID g_verifyCallbackMethodId = NULL; +/* WOLFSSL_CTX ex_data slot for per-context verify callback jobject. + * -1 means not allocated. Populated in Java_com_wolfssl_WolfSSL_init(). */ +int g_verifyCbCtxExDataIdx = -1; + #ifdef HAVE_FIPS /* global object ref for FIPS error callback */ static jobject g_fipsCbIfaceObj; @@ -215,6 +219,19 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) (*env)->DeleteLocalRef(env, byteBufferClass); (*env)->DeleteLocalRef(env, verifyClass); + /* Initialize the mutex serializing verify callback jobject reads/updates. + * Done here to guarantee race-free init. */ + if (NativeVerifyCbMutexInit() != 0) { + return JNI_ERR; + } + + /* Initialize the mutex serializing missing-CRL callback jobject + * reads/updates. */ + if (NativeCrlCbMutexInit() != 0) { + NativeVerifyCbMutexFree(); + return JNI_ERR; + } + return JNI_VERSION_1_6; } @@ -230,6 +247,16 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) return; } + /* Release process global jobject refs at CTX and session levels. */ + NativeWolfSSLContextCleanup(env); + NativeWolfSSLSessionCleanup(env); + + /* Free the verify callback synchronization mutex. */ + NativeVerifyCbMutexFree(); + + /* Free the missing-CRL callback synchronization mutex. */ + NativeCrlCbMutexFree(); + /* Clear cached method ID */ g_sslIORecvMethodId = NULL; g_sslIORecvMethodId_BB = NULL; @@ -431,7 +458,16 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSL_init #endif /* HAVE_FIPS && HAVE_FIPS_VERSION == 5 */ if (ret == 0) { - return (jint)wolfSSL_Init(); + ret = (int)wolfSSL_Init(); + if (ret == WOLFSSL_SUCCESS) { + /* Allocate the per-context verify callback ex_data slot if not + * allocated yet. Guarded by g_verifyCbMutex (initialized in + * JNI_OnLoad()). */ + if (NativeVerifyCbSlotEnsure() != 0) { + ret = WOLFSSL_FAILURE; + } + } + return (jint)ret; } else { return (jint)WOLFSSL_FAILURE; } diff --git a/native/com_wolfssl_WolfSSLContext.c b/native/com_wolfssl_WolfSSLContext.c index 06c9a378..19913777 100644 --- a/native/com_wolfssl_WolfSSLContext.c +++ b/native/com_wolfssl_WolfSSLContext.c @@ -35,13 +35,167 @@ #include "com_wolfssl_globals.h" #include "com_wolfssl_WolfSSLContext.h" -/* global object refs for verify, CRL callbacks */ -static jobject g_verifyCbIfaceObj; +/* Process-global mutex serializing reads/updates of the per-WOLFSSL_CTX + * verify callback jobject (stored in WOLFSSL_CTX ex_data at + * g_verifyCbCtxExDataIdx). + * + * setVerify and freeContext take the mutex while updating ex_data, then + * release it before calling DeleteGlobalRef on the prior ref. Any thread + * already inside NativeVerifyCallback's local-ref window completes safely. + * + * Initialized in Java_com_wolfssl_WolfSSL_init, freed in JNI_OnUnload. */ +static wolfSSL_Mutex g_verifyCbMutex; +static int g_verifyCbMutexInit = 0; + +int NativeVerifyCbMutexInit(void) +{ + if (g_verifyCbMutexInit) { + return 0; + } + if (wc_InitMutex(&g_verifyCbMutex) != 0) { + return -1; + } + g_verifyCbMutexInit = 1; + + return 0; +} + +void NativeVerifyCbMutexFree(void) +{ + if (g_verifyCbMutexInit) { + wc_FreeMutex(&g_verifyCbMutex); + g_verifyCbMutexInit = 0; + } +} + +int NativeVerifyCbLock(void) +{ + int rc; + + if (!g_verifyCbMutexInit) { + return 0; + } + + rc = wc_LockMutex(&g_verifyCbMutex); + if (rc != 0) { + WOLFSSL_MSG("Failed to lock verify callback mutex"); + } + return rc; +} + +int NativeVerifyCbUnlock(void) +{ + int rc; + + if (!g_verifyCbMutexInit) { + return 0; + } + + rc = wc_UnLockMutex(&g_verifyCbMutex); + if (rc != 0) { + WOLFSSL_MSG("Failed to unlock verify callback mutex"); + } + return rc; +} + +int NativeVerifyCbSlotEnsure(void) +{ + int idx; + int rc = 0; + + /* Mutex should have been initialized in JNI_OnLoad() */ + if (!g_verifyCbMutexInit) { + WOLFSSL_MSG("Verify callback mutex not initialized"); + return -1; + } + + if (NativeVerifyCbLock() != 0) { + return -1; + } + + if (g_verifyCbCtxExDataIdx < 0) { + idx = wolfSSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (idx < 0) { + WOLFSSL_MSG("Failed to allocate verify callback ex_data index"); + rc = -1; + } + else { + g_verifyCbCtxExDataIdx = idx; + } + } + + (void)NativeVerifyCbUnlock(); + return rc; +} + +/* The CTX-level missing-CRL callback below is process-global. wolfSSL + * CbMissingCRL signature has no SSL/CTX pointer, so native wolfSSL cannot + * route per instance. Access is serialized by g_crlCbMutex (shared with the + * session-level g_crlCbIfaceObj in com_wolfssl_WolfSSLSession.c). */ #ifdef HAVE_CRL static jobject g_crlCtxCbIfaceObj; #endif +/* Process-global mutex covering both CTX-level and session-level missing CRL + * callback jobjects. Initialized in Java_com_wolfssl_WolfSSL_init, freed in + * JNI_OnUnload. */ +static wolfSSL_Mutex g_crlCbMutex; +static int g_crlCbMutexInit = 0; + +int NativeCrlCbMutexInit(void) +{ + if (g_crlCbMutexInit) { + return 0; + } + + if (wc_InitMutex(&g_crlCbMutex) != 0) { + return -1; + } + + g_crlCbMutexInit = 1; + + return 0; +} + +void NativeCrlCbMutexFree(void) +{ + if (g_crlCbMutexInit) { + wc_FreeMutex(&g_crlCbMutex); + g_crlCbMutexInit = 0; + } +} + +int NativeCrlCbLock(void) +{ + int rc; + + if (!g_crlCbMutexInit) { + return 0; + } + + rc = wc_LockMutex(&g_crlCbMutex); + if (rc != 0) { + WOLFSSL_MSG("Failed to lock missing-CRL callback mutex"); + } + return rc; +} + +int NativeCrlCbUnlock(void) +{ + int rc; + + if (!g_crlCbMutexInit) { + return 0; + } + + rc = wc_UnLockMutex(&g_crlCbMutex); + if (rc != 0) { + WOLFSSL_MSG("Failed to unlock missing-CRL callback mutex"); + } + return rc; +} + /* custom I/O native fn prototypes */ int NativeIORecvCb(WOLFSSL *ssl, char *buf, int sz, void *ctx); int NativeIOSendCb(WOLFSSL *ssl, char *buf, int sz, void *ctx); @@ -498,26 +652,48 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_useCertificateChainFile #endif } +/* Release process-global CRL ctx callback ref. Called from JNI_OnUnload() */ +void NativeWolfSSLContextCleanup(JNIEnv* jenv) +{ +#ifdef HAVE_CRL + jobject prior = NULL; +#endif + + if (jenv == NULL) { + return; + } +#ifdef HAVE_CRL + (void)NativeCrlCbLock(); + prior = g_crlCtxCbIfaceObj; + g_crlCtxCbIfaceObj = NULL; + (void)NativeCrlCbUnlock(); + + if (prior != NULL) { + (*jenv)->DeleteGlobalRef(jenv, prior); + } +#endif +} + JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_freeContext (JNIEnv* jenv, jobject jcl, jlong ctxPtr) { WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr; - (void)jenv; + jobject verifyCb = NULL; (void)jcl; - /* release verify callback object if set */ - if (g_verifyCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_verifyCbIfaceObj); - g_verifyCbIfaceObj = NULL; - } - -#ifdef HAVE_CRL - /* release global CRL callback object if set */ - if (g_crlCtxCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_crlCtxCbIfaceObj); - g_crlCtxCbIfaceObj = NULL; + /* Capture and clear the per-CTX verify callback jobject under lock. */ + if (ctx != NULL && jenv != NULL && g_verifyCbCtxExDataIdx >= 0) { + (void)NativeVerifyCbLock(); + verifyCb = (jobject)wolfSSL_CTX_get_ex_data(ctx, + g_verifyCbCtxExDataIdx); + if (verifyCb != NULL) { + (void)wolfSSL_CTX_set_ex_data(ctx, g_verifyCbCtxExDataIdx, NULL); + } + (void)NativeVerifyCbUnlock(); + if (verifyCb != NULL) { + (*jenv)->DeleteGlobalRef(jenv, verifyCb); + } } -#endif /* HAVE_CRL */ /* wolfSSL checks for null pointer */ wolfSSL_CTX_free(ctx); @@ -527,31 +703,63 @@ JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLContext_setVerify(JNIEnv* jenv, jobject jcl, jlong ctxPtr, jint mode, jobject callbackIface) { WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr; + jobject prior = NULL; + jobject newRef = NULL; (void)jcl; - if (jenv == NULL) { + if (jenv == NULL || ctx == NULL) { + return; + } + + if (g_verifyCbCtxExDataIdx < 0) { + /* ex_data slot was not allocated at init, cannot store per-CTX cb */ + throwWolfSSLJNIException(jenv, + "Verify callback ex_data slot not allocated"); return; } - /* release verify callback object if set before */ - if (g_verifyCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_verifyCbIfaceObj); - g_verifyCbIfaceObj = NULL; + if (callbackIface != NULL) { + newRef = (*jenv)->NewGlobalRef(jenv, callbackIface); + if (newRef == NULL) { + throwWolfSSLJNIException(jenv, + "Error storing verify callback global reference"); + return; + } } + (void)NativeVerifyCbLock(); + + /* Capture the prior per-CTX verify callback object */ + prior = (jobject)wolfSSL_CTX_get_ex_data(ctx, g_verifyCbCtxExDataIdx); + if (!callbackIface) { + /* Unregister the native verify callback first so wolfSSL stops + * routing handshakes through it, then clear the slot. */ wolfSSL_CTX_set_verify(ctx, mode, NULL); + (void)wolfSSL_CTX_set_ex_data(ctx, g_verifyCbCtxExDataIdx, NULL); } else { - /* store Java verify Interface object */ - g_verifyCbIfaceObj = (*jenv)->NewGlobalRef(jenv, callbackIface); - if (g_verifyCbIfaceObj == NULL) { - printf("error storing global callback interface\n"); + if (wolfSSL_CTX_set_ex_data(ctx, g_verifyCbCtxExDataIdx, newRef) + != WOLFSSL_SUCCESS) { + (void)NativeVerifyCbUnlock(); + /* prior (if any) is still stored in ctx ex_data because the + * set_ex_data call failed. Leave it there for the next setter + * or freeContext() to release. */ + (*jenv)->DeleteGlobalRef(jenv, newRef); + throwWolfSSLJNIException(jenv, + "Error storing verify callback in ctx ex_data"); + return; } - - /* set verify mode, register Java callback with wolfSSL */ + /* Ensure the native verify callback is registered. */ wolfSSL_CTX_set_verify(ctx, mode, NativeVerifyCallback); } + + (void)NativeVerifyCbUnlock(); + + /* Release the prior global ref outside the lock */ + if (prior != NULL) { + (*jenv)->DeleteGlobalRef(jenv, prior); + } } int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) @@ -564,6 +772,9 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) jclass verifyClass = NULL; jmethodID verifyMethod = NULL; jobjectRefType refcheck; + WOLFSSL* ssl = NULL; + WOLFSSL_CTX* ctx = NULL; + jobject verifyCbObj = NULL; if (!g_vm) { /* we can't throw an exception yet, so just return 0 (failure) */ @@ -596,12 +807,43 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) return -103; } - /* check if our stored object reference is valid */ - refcheck = (*jenv)->GetObjectRefType(jenv, g_verifyCbIfaceObj); - if (refcheck == 2) { + /* Locate the per-context verify callback jobject via + * store->ssl->ctx->ex_data. The jobject is owned by the WOLFSSL_CTX + * that produced this handshake, so each context fires its own user + * callback. If the slot is empty or any lookup step fails, fall + * through to verifyCbObj == NULL below and fail closed. */ + if (g_verifyCbCtxExDataIdx >= 0 && store != NULL) { + ssl = (WOLFSSL*)wolfSSL_X509_STORE_CTX_get_ex_data(store, + wolfSSL_get_ex_data_X509_STORE_CTX_idx()); + if (ssl != NULL) { + ctx = wolfSSL_get_SSL_CTX(ssl); + } + } + if (ctx != NULL) { + (void)NativeVerifyCbLock(); + verifyCbObj = (jobject)wolfSSL_CTX_get_ex_data(ctx, + g_verifyCbCtxExDataIdx); + if (verifyCbObj != NULL) { + verifyCbObj = (*jenv)->NewLocalRef(jenv, verifyCbObj); + } + (void)NativeVerifyCbUnlock(); + } + if (verifyCbObj == NULL) { + /* No Java callback registered. Fail closed without throwing. */ + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return 0; + } + + /* check if our stored object reference is valid (non-zero ref type + * covers local, global, or weak global; verifyCbObj is a local ref + * promoted from the per-CTX global ref under g_verifyCbMutex) */ + refcheck = (*jenv)->GetObjectRefType(jenv, verifyCbObj); + if (refcheck != 0) { /* lookup WolfSSLVerifyCallback class from global object ref */ - verifyClass = (*jenv)->GetObjectClass(jenv, g_verifyCbIfaceObj); + verifyClass = (*jenv)->GetObjectClass(jenv, verifyCbObj); if (!verifyClass) { if ((*jenv)->ExceptionOccurred(jenv)) { (*jenv)->ExceptionDescribe(jenv); @@ -630,7 +872,7 @@ int NativeVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) return -105; } - retval = (*jenv)->CallIntMethod(jenv, g_verifyCbIfaceObj, + retval = (*jenv)->CallIntMethod(jenv, verifyCbObj, verifyMethod, preverify_ok, (jlong)(uintptr_t)store); if ((*jenv)->ExceptionOccurred(jenv)) { @@ -1767,7 +2009,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setCRLCb { #ifdef HAVE_CRL int ret = 0; - jclass excClass; + jobject newRef = NULL; + jobject prior = NULL; WOLFSSL_CTX* ctx = (WOLFSSL_CTX*)(uintptr_t)ctxPtr; (void)jcl; @@ -1775,29 +2018,36 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLContext_setCRLCb return (jint)BAD_FUNC_ARG; } - /* release global CRL callback object if set */ - if (g_crlCtxCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_crlCtxCbIfaceObj); - g_crlCtxCbIfaceObj = NULL; - } - if (cb != NULL) { - /* store Java CRL callback Interface object */ - g_crlCtxCbIfaceObj = (*jenv)->NewGlobalRef(jenv, cb); - - if (!g_crlCtxCbIfaceObj) { - excClass = (*jenv)->FindClass(jenv, - "com/wolfssl/WolfSSLJNIException"); + newRef = (*jenv)->NewGlobalRef(jenv, cb); + if (newRef == NULL) { if ((*jenv)->ExceptionOccurred(jenv)) { (*jenv)->ExceptionDescribe(jenv); (*jenv)->ExceptionClear(jenv); } - (*jenv)->ThrowNew(jenv, excClass, - "error storing global missing CTX CRL callback interface"); + throwWolfSSLJNIException(jenv, + "error storing global missing CTX CRL callback interface"); + return (jint)SSL_FAILURE; } + } + /* Swap the global pointer under g_crlCbMutex */ + (void)NativeCrlCbLock(); + prior = g_crlCtxCbIfaceObj; + g_crlCtxCbIfaceObj = newRef; + (void)NativeCrlCbUnlock(); + + if (prior != NULL) { + (*jenv)->DeleteGlobalRef(jenv, prior); + } + + if (cb != NULL) { ret = wolfSSL_CTX_SetCRL_Cb(ctx, NativeCtxMissingCRLCallback); } + else { + /* Unregister native wolfSSL CRL callback */ + ret = wolfSSL_CTX_SetCRL_Cb(ctx, NULL); + } return (jint)ret; #else @@ -1821,6 +2071,7 @@ void NativeCtxMissingCRLCallback(const char* url) jmethodID crlMethod; jstring missingUrl = NULL; jobjectRefType refcheck; + jobject crlCbObj = NULL; /* get JNIEnv from JavaVM */ vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6)); @@ -1850,12 +2101,31 @@ void NativeCtxMissingCRLCallback(const char* url) return; } - /* check if our stored object reference is valid */ - refcheck = (*jenv)->GetObjectRefType(jenv, g_crlCtxCbIfaceObj); - if (refcheck == 2) { + /* Promote process-global jobject to a local ref under g_crlCbMutex. Local + * ref keeps underlying object alive even if a concurrent setCRLCb() + * clears g_crlCtxCbIfaceObj and deletes the prior global ref immediately + * after we release the lock. */ + (void)NativeCrlCbLock(); + if (g_crlCtxCbIfaceObj != NULL) { + crlCbObj = (*jenv)->NewLocalRef(jenv, g_crlCtxCbIfaceObj); + } + (void)NativeCrlCbUnlock(); + + if (crlCbObj == NULL) { + /* No Java callback registered, drop silently. */ + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return; + } + + /* GetObjectRefType returns non-zero for local/global/weak refs; + * crlCbObj is a local ref promoted from a global ref under lock. */ + refcheck = (*jenv)->GetObjectRefType(jenv, crlCbObj); + if (refcheck != 0) { - /* lookup WolfSSLMissingCRLCallback class from global object ref */ - crlClass = (*jenv)->GetObjectClass(jenv, g_crlCtxCbIfaceObj); + /* lookup WolfSSLMissingCRLCallback class from local object ref */ + crlClass = (*jenv)->GetObjectClass(jenv, crlCbObj); if (!crlClass) { (*jenv)->ThrowNew(jenv, excClass, "Can't get native WolfSSLMissingCRLCallback class reference"); @@ -1883,7 +2153,7 @@ void NativeCtxMissingCRLCallback(const char* url) /* create jstring from char* */ missingUrl = (*jenv)->NewStringUTF(jenv, url); - (*jenv)->CallVoidMethod(jenv, g_crlCtxCbIfaceObj, crlMethod, + (*jenv)->CallVoidMethod(jenv, crlCbObj, crlMethod, missingUrl); if ((*jenv)->ExceptionOccurred(jenv)) { diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index a6b7b8c4..d30be444 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -73,7 +73,9 @@ int NativeSessionTicketCb(WOLFSSL *ssl, const unsigned char* ticket, #endif #ifdef HAVE_CRL -/* global object refs for CRL callback */ +/* Process-global jobject for the missing-CRL callback registered via + * Java_com_wolfssl_WolfSSLSession_setCRLCb(). wolfSSL CbMissingCRL has no + * SSL/CTX pointer, so native wolfSSL cannot route per-instance. */ static jobject g_crlCbIfaceObj; #endif @@ -1756,6 +1758,30 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_accept return ret; } +/* Release process-global session-level missing-CRL callback ref. Called + * from JNI_OnUnload() in com_wolfssl_WolfSSL.c so the global is torn + * down at native library unload. */ +void NativeWolfSSLSessionCleanup(JNIEnv* jenv) +{ +#ifdef HAVE_CRL + jobject prior = NULL; +#endif + + if (jenv == NULL) { + return; + } +#ifdef HAVE_CRL + (void)NativeCrlCbLock(); + prior = g_crlCbIfaceObj; + g_crlCbIfaceObj = NULL; + (void)NativeCrlCbUnlock(); + + if (prior != NULL) { + (*jenv)->DeleteGlobalRef(jenv, prior); + } +#endif +} + JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLSession_freeSSL (JNIEnv* jenv, jobject jcl, jlong sslPtr) { @@ -1857,14 +1883,6 @@ JNIEXPORT void JNICALL Java_com_wolfssl_WolfSSLSession_freeSSL return; } -#ifdef HAVE_CRL - /* release global CRL callback ref if registered */ - if (g_crlCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_crlCbIfaceObj); - g_crlCbIfaceObj = NULL; - } -#endif - #if defined(HAVE_PK_CALLBACKS) #ifdef HAVE_ECC /* free ECC sign callback CTX global reference if set */ @@ -3919,6 +3937,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setCRLCb { #ifdef HAVE_CRL int ret = 0; + jobject newRef = NULL; + jobject prior = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; (void)jcl; @@ -3932,23 +3952,36 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_setCRLCb return SSL_FAILURE; } - /* release global CRL callback ref if already registered */ - if (g_crlCbIfaceObj != NULL) { - (*jenv)->DeleteGlobalRef(jenv, g_crlCbIfaceObj); - g_crlCbIfaceObj = NULL; - } - + /* Allocate the new global ref outside the lock to avoid holding + * g_crlCbMutex across the JNI allocation. */ if (cb != NULL) { - /* store Java CRL callback Interface object */ - g_crlCbIfaceObj = (*jenv)->NewGlobalRef(jenv, cb); - if (g_crlCbIfaceObj == NULL) { + newRef = (*jenv)->NewGlobalRef(jenv, cb); + if (newRef == NULL) { throwWolfSSLException(jenv, "Error storing global missingCRLCallback interface"); return SSL_FAILURE; } + } + + /* Swap global pointer under g_crlCbMutex. The prior ref is deleted + * after the unlock so any in-flight NativeMissingCRLCallback that already + * promoted the global to a local ref can complete safely. */ + (void)NativeCrlCbLock(); + prior = g_crlCbIfaceObj; + g_crlCbIfaceObj = newRef; + (void)NativeCrlCbUnlock(); + + if (prior != NULL) { + (*jenv)->DeleteGlobalRef(jenv, prior); + } + if (cb != NULL) { ret = wolfSSL_SetCRL_Cb(ssl, NativeMissingCRLCallback); } + else { + /* Unregister native wolfSSL CRL callback */ + ret = wolfSSL_SetCRL_Cb(ssl, NULL); + } return ret; #else @@ -3971,6 +4004,7 @@ void NativeMissingCRLCallback(const char* url) jmethodID crlMethod = NULL; jobjectRefType refcheck; jstring missingUrl = NULL; + jobject crlCbObj = NULL; /* get JNIEnv from JavaVM */ vmret = (int)((*g_vm)->GetEnv(g_vm, (void**) &jenv, JNI_VERSION_1_6)); @@ -3990,12 +4024,29 @@ void NativeMissingCRLCallback(const char* url) return; } - /* check if our stored object reference is valid */ - refcheck = (*jenv)->GetObjectRefType(jenv, g_crlCbIfaceObj); - if (refcheck == 2) { + /* Promote the global jobject to a local ref under g_crlCbMutex so we keep + * the object alive even if a concurrent setCRLCb clears the global and + * deletes the previous ref immediately after we unlock. */ + (void)NativeCrlCbLock(); + if (g_crlCbIfaceObj != NULL) { + crlCbObj = (*jenv)->NewLocalRef(jenv, g_crlCbIfaceObj); + } + (void)NativeCrlCbUnlock(); + + if (crlCbObj == NULL) { + /* No Java callback registered, drop silently. */ + if (needsDetach) { + (*g_vm)->DetachCurrentThread(g_vm); + } + return; + } + + /* GetObjectRefType returns non-zero for local/global/weak refs. */ + refcheck = (*jenv)->GetObjectRefType(jenv, crlCbObj); + if (refcheck != 0) { - /* lookup WolfSSLMissingCRLCallback class from global object ref */ - crlClass = (*jenv)->GetObjectClass(jenv, g_crlCbIfaceObj); + /* lookup WolfSSLMissingCRLCallback class from local object ref */ + crlClass = (*jenv)->GetObjectClass(jenv, crlCbObj); if (!crlClass) { throwWolfSSLException(jenv, "Can't get native WolfSSLMissingCRLCallback class reference"); @@ -4023,7 +4074,7 @@ void NativeMissingCRLCallback(const char* url) /* create jstring from char* */ missingUrl = (*jenv)->NewStringUTF(jenv, url); - (*jenv)->CallVoidMethod(jenv, g_crlCbIfaceObj, crlMethod, missingUrl); + (*jenv)->CallVoidMethod(jenv, crlCbObj, crlMethod, missingUrl); if ((*jenv)->ExceptionOccurred(jenv)) { (*jenv)->ExceptionDescribe(jenv); diff --git a/native/com_wolfssl_globals.h b/native/com_wolfssl_globals.h index 2fd20f13..c95949d8 100644 --- a/native/com_wolfssl_globals.h +++ b/native/com_wolfssl_globals.h @@ -46,6 +46,12 @@ extern jmethodID g_bufferArrayOffsetMethodId; /* ByteBuffer.arrayOffset() */ extern jmethodID g_bufferSetPositionMethodId; /* ByteBuffer.position(int) */ extern jmethodID g_verifyCallbackMethodId; /* WolfSSLVerifyCallback.verifyCallback */ +/* WOLFSSL_CTX ex_data index used to store the per-WolfSSLContext jobject ref + * to the user WolfSSLVerifyCallback. Allocated once in + * Java_com_wolfssl_WolfSSL_init via wolfSSL_CTX_get_ex_new_index(). A + * negative value means the slot has not yet been allocated. */ +extern int g_verifyCbCtxExDataIdx; + /* struct to hold I/O class, object refs */ typedef struct { int active; @@ -61,4 +67,27 @@ unsigned int NativePskServerCb(WOLFSSL* ssl, const char* identity, void throwWolfSSLJNIException(JNIEnv* jenv, const char* msg); void throwWolfSSLException(JNIEnv* jenv, const char* msg); +/* Release process-global jobject refs. Called from JNI_OnUnload() */ +void NativeWolfSSLContextCleanup(JNIEnv* jenv); +void NativeWolfSSLSessionCleanup(JNIEnv* jenv); + +/* Initialize/free the process-global mutex around verify callback */ +int NativeVerifyCbMutexInit(void); +void NativeVerifyCbMutexFree(void); + +/* Lock/unlock the verify callback mutex. Logs on failure. */ +int NativeVerifyCbLock(void); +int NativeVerifyCbUnlock(void); + +/* Ensure verify callback slot is allocated */ +int NativeVerifyCbSlotEnsure(void); + +/* Initialize/free the process-global mutex around missing-CRL callback */ +int NativeCrlCbMutexInit(void); +void NativeCrlCbMutexFree(void); + +/* Lock/unlock the missing-CRL callback mutex. */ +int NativeCrlCbLock(void); +int NativeCrlCbUnlock(void); + #endif diff --git a/src/test/com/wolfssl/test/WolfSSLContextTest.java b/src/test/com/wolfssl/test/WolfSSLContextTest.java index e17180cd..0bd97eab 100644 --- a/src/test/com/wolfssl/test/WolfSSLContextTest.java +++ b/src/test/com/wolfssl/test/WolfSSLContextTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.Socket; import java.net.ServerSocket; import java.nio.ByteBuffer; @@ -46,6 +47,8 @@ import com.wolfssl.WolfSSLContext; import com.wolfssl.WolfSSLException; import com.wolfssl.WolfSSLJNIException; +import com.wolfssl.WolfSSLVerifyCallback; +import com.wolfssl.WolfSSLMissingCRLCallback; import com.wolfssl.WolfSSLPskClientCallback; import com.wolfssl.WolfSSLPskServerCallback; import com.wolfssl.WolfSSLSession; @@ -74,6 +77,12 @@ public class WolfSSLContextTest { WolfSSLContext ctx; + /* Hold a strong reference to a WolfSSL instance for the lifetime of + * this test class so wolfSSL_Init() runs before any test executes. + * Required when surefire/Maven runs this class outside of + * WolfSSLTestSuite, whose @BeforeClass would otherwise create one. */ + private static WolfSSL sslLib = null; + @BeforeClass public static void loadLibrary() throws WolfSSLException { System.out.println("WolfSSLContext Class"); @@ -83,6 +92,7 @@ public static void loadLibrary() throws WolfSSLException { } catch (UnsatisfiedLinkError ule) { fail("failed to load native JNI library"); } + sslLib = new WolfSSL(); cliCert = WolfSSLTestCommon.getPath(cliCert); cliKey = WolfSSLTestCommon.getPath(cliKey); @@ -671,6 +681,350 @@ public void test_WolfSSLContext_setMinDHKeySize() { } } + /* Custom verify callback class so each test instance is its own Java + * object (and therefore its own JNI global ref). */ + private static class CountingVerifyCb implements WolfSSLVerifyCallback { + public int calls = 0; + public int verifyCallback(int preverify_ok, long x509StorePtr) { + calls++; + return preverify_ok; + } + } + + /* Exercises setVerify ref lifecycle with multiple WolfSSLContext instances + * each holding their own callback. Goes through the install, replace, + * unregister, and re-install paths, frees one context while the other is + * still live, and forces GC to surface any dangling-ref issues under + * -Xcheck:jni or Android checked JNI. + * + * The Java callback objects are referenced at the end of the test + * to keep them strongly reachable for the duration of the test. */ + @Test + public void test_WolfSSLContext_setVerifyMultipleContexts() + throws WolfSSLException { + + /* Each context owns the WOLFSSL_METHOD it is constructed with and + * frees it during free(), so allocate a method per context. */ + long methodA = WolfSSL.SSLv23_ServerMethod(); + long methodB = WolfSSL.SSLv23_ServerMethod(); + Assume.assumeTrue("SSLv23 server method not available", + methodA != 0 && methodA != WolfSSL.NOT_COMPILED_IN && + methodB != 0 && methodB != WolfSSL.NOT_COMPILED_IN); + + WolfSSLContext ctxA = new WolfSSLContext(methodA); + WolfSSLContext ctxB = new WolfSSLContext(methodB); + CountingVerifyCb cbA = new CountingVerifyCb(); + CountingVerifyCb cbB = new CountingVerifyCb(); + + try { + /* Register distinct callbacks on each ctx. */ + ctxA.setVerify(WolfSSL.SSL_VERIFY_PEER, cbA); + ctxB.setVerify(WolfSSL.SSL_VERIFY_PEER, cbB); + + /* Replace ctxA's callback with a fresh one, exercises the + * prior-ref-release path on the same ctx. */ + CountingVerifyCb cbA2 = new CountingVerifyCb(); + ctxA.setVerify(WolfSSL.SSL_VERIFY_PEER, cbA2); + + /* Unregister ctxB's callback (null path). */ + ctxB.setVerify(WolfSSL.SSL_VERIFY_PEER, null); + + /* Re-register on ctxB after a null pass. */ + ctxB.setVerify(WolfSSL.SSL_VERIFY_PEER, cbB); + + /* Free ctxA first while ctxB's callback is still live. */ + ctxA.free(); + ctxA = null; + + /* ctxB should still be functional (no exception thrown). */ + ctxB.setVerify(WolfSSL.SSL_VERIFY_PEER, null); + + /* Touch the cb references at the end to defeat any compiler level + * liveness shortening that would let GC collect them mid-test. */ + assertNotNull(cbA); + assertNotNull(cbA2); + assertNotNull(cbB); + } finally { + if (ctxA != null) { + ctxA.free(); + } + if (ctxB != null) { + ctxB.free(); + } + } + } + + /* Custom missing CRL callback class so each test instance is its own + * Java object (and own JNI global ref). */ + private static class CountingMissingCRLCb + implements WolfSSLMissingCRLCallback { + public int calls = 0; + public void missingCRLCallback(String url) { + calls++; + } + } + + /* Exercises setCRLCb ref lifecycle for both WolfSSLContext.setCRLCb() and + * WolfSSLSession.setCRLCb(). The two globals (g_crlCtxCbIfaceObj and + * g_crlCbIfaceObj) are protected by a single process-global mutex + * (g_crlCbMutex). This test does install / replace / unregister / + * re-install / free paths and forces GC. + * + * Java callback objects are referenced at the end of the test to keep + * them strongly reachable for the duration of the test. */ + @Test + public void test_WolfSSLContext_setCRLCbMultipleContexts() + throws Exception { + + /* WolfSSLSession construction requires a CTX with cert and key loaded, + * so this test is gated on RSA and filesystem support. */ + Assume.assumeTrue(WolfSSL.RsaEnabled() && WolfSSL.FileSystemEnabled()); + Assume.assumeTrue(WolfSSL.TLSv12Enabled()); + + WolfSSLContext ctxA = createCtx(cliCert, cliKey, caCert, + WolfSSL.TLSv1_2_ClientMethod()); + WolfSSLContext ctxB = createCtx(cliCert, cliKey, caCert, + WolfSSL.TLSv1_2_ClientMethod()); + WolfSSLSession sesA = null; + WolfSSLSession sesB = null; + CountingMissingCRLCb cbA = new CountingMissingCRLCb(); + CountingMissingCRLCb cbB = new CountingMissingCRLCb(); + CountingMissingCRLCb cbA2 = new CountingMissingCRLCb(); + CountingMissingCRLCb sesCbA = new CountingMissingCRLCb(); + CountingMissingCRLCb sesCbB = new CountingMissingCRLCb(); + + try { + /* Check for HAVE_CRL by attempting a CTX-level register. Skip the + * test if CRL is not compiled in. */ + int probe = ctxA.setCRLCb(cbA); + Assume.assumeTrue("CRL not compiled in", + probe != WolfSSL.NOT_COMPILED_IN); + + ctxB.setCRLCb(cbB); + + /* Replace ctxA callback with a fresh one, exercises the + * prior-ref-release path on the same ctx. */ + ctxA.setCRLCb(cbA2); + + /* Unregister ctxB callback (null path, should also unregister + * the native wolfSSL CRL cb). */ + ctxB.setCRLCb(null); + + /* Re-register on ctxB after a null pass. */ + ctxB.setCRLCb(cbB); + + /* Session-level setCRLCb, same pattern on a session created + * from each context. */ + sesA = new WolfSSLSession(ctxA); + sesB = new WolfSSLSession(ctxB); + sesA.setCRLCb(sesCbA); + sesB.setCRLCb(sesCbB); + sesA.setCRLCb(null); + sesA.setCRLCb(sesCbA); + + /* Force a GC pass to show any dangling refs in checked-JNI + * environments before tearing down. */ + System.gc(); + try { + Thread.sleep(10); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + + /* Free sesA session, then ctxA, while ctxB and sesB still hold + * live callbacks. */ + sesA.freeSSL(); + sesA = null; + ctxA.free(); + ctxA = null; + + /* ctxB should still be functional (no exception thrown). */ + ctxB.setCRLCb(null); + sesB.setCRLCb(null); + + /* Touch the cb references at the end to defeat any compiler + * level liveness shortening that would let GC collect them + * mid-test. */ + assertNotNull(cbA); + assertNotNull(cbA2); + assertNotNull(cbB); + assertNotNull(sesCbA); + assertNotNull(sesCbB); + + /* No handshake is done in this test, so no missing-CRL callback + * should have fired. A non-zero count would mean native callback + * got wired to the wrong jobject or leaked across contexts. */ + assertEquals("cbA missingCRLCallback fired unexpectedly", + 0, cbA.calls); + assertEquals("cbA2 missingCRLCallback fired unexpectedly", + 0, cbA2.calls); + assertEquals("cbB missingCRLCallback fired unexpectedly", + 0, cbB.calls); + assertEquals("sesCbA missingCRLCallback fired unexpectedly", + 0, sesCbA.calls); + assertEquals("sesCbB missingCRLCallback fired unexpectedly", + 0, sesCbB.calls); + + } finally { + if (sesA != null) { + sesA.freeSSL(); + } + if (sesB != null) { + sesB.freeSSL(); + } + if (ctxA != null) { + ctxA.free(); + } + if (ctxB != null) { + ctxB.free(); + } + } + } + + /* Do a real handshake on each of two client contexts and assert that each + * context's verify callback fires only for its own handshake. */ + @Test + public void test_WolfSSLContext_setVerifyHandshakeRouting() + throws Exception { + + Assume.assumeTrue(WolfSSL.RsaEnabled() && WolfSSL.FileSystemEnabled()); + Assume.assumeTrue(WolfSSL.TLSv12Enabled()); + + WolfSSLContext srvCtx = null; + WolfSSLContext cliCtxA = null; + WolfSSLContext cliCtxB = null; + ServerSocket srvSocket = null; + ExecutorService es = null; + Future srvFuture = null; + + try { + srvCtx = createCtx(svrCert, svrKey, caCert, + WolfSSL.TLSv1_2_ServerMethod()); + cliCtxA = createCtx(cliCert, cliKey, caCert, + WolfSSL.TLSv1_2_ClientMethod()); + cliCtxB = createCtx(cliCert, cliKey, caCert, + WolfSSL.TLSv1_2_ClientMethod()); + + final CountingVerifyCb cbA = new CountingVerifyCb(); + final CountingVerifyCb cbB = new CountingVerifyCb(); + cliCtxA.setVerify(WolfSSL.SSL_VERIFY_PEER, cbA); + cliCtxB.setVerify(WolfSSL.SSL_VERIFY_PEER, cbB); + + srvSocket = new ServerSocket(0); + srvSocket.setSoTimeout(10000); + final int port = srvSocket.getLocalPort(); + final ServerSocket fSrvSock = srvSocket; + final WolfSSLContext fSrvCtx = srvCtx; + + /* Server thread accepts two sequential connections. */ + es = Executors.newSingleThreadExecutor(); + srvFuture = es.submit(new Callable() { + @Override + public Void call() throws Exception { + for (int i = 0; i < 2; i++) { + Socket srv = fSrvSock.accept(); + WolfSSLSession ses = new WolfSSLSession(fSrvCtx); + try { + int r = ses.setFd(srv); + if (r != WolfSSL.SSL_SUCCESS) { + throw new Exception("srv setFd: " + r); + } + int err; + do { + r = ses.accept(); + err = ses.getError(r); + } while (r != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + if (r != WolfSSL.SSL_SUCCESS) { + throw new Exception("srv accept: " + r); + } + ses.shutdownSSL(); + } finally { + ses.freeSSL(); + srv.close(); + } + } + return null; + } + }); + + /* Client A handshake: should fire cbA only. */ + doClientHandshake(cliCtxA, port); + assertTrue("cbA not called for ctxA handshake", cbA.calls > 0); + assertEquals("cbB unexpectedly called for ctxA handshake", + 0, cbB.calls); + + int cbACallsAfterFirst = cbA.calls; + + /* Client B handshake: should fire cbB only. */ + doClientHandshake(cliCtxB, port); + assertTrue("cbB not called for ctxB handshake", cbB.calls > 0); + assertEquals("cbA called for ctxB handshake", + cbACallsAfterFirst, cbA.calls); + + srvFuture.get(5, TimeUnit.SECONDS); + } finally { + /* Close the server socket first so any pending accept() unblocks. + * Then cancel the future and force the executor to terminate. */ + if (srvSocket != null && !srvSocket.isClosed()) { + srvSocket.close(); + } + if (srvFuture != null) { + srvFuture.cancel(true); + } + if (es != null) { + es.shutdownNow(); + try { + es.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + } + } + if (cliCtxA != null) { + cliCtxA.free(); + } + if (cliCtxB != null) { + cliCtxB.free(); + } + if (srvCtx != null) { + srvCtx.free(); + } + } + } + + /* Helper method to open a TCP socket to localhost:port and complete a TLS + * handshake using the given client context. */ + private void doClientHandshake(WolfSSLContext cliCtx, int port) + throws Exception { + + final int socketTimeoutMs = 5000; + Socket sock = new Socket(); + sock.connect(new InetSocketAddress("localhost", port), socketTimeoutMs); + sock.setSoTimeout(socketTimeoutMs); + WolfSSLSession ses = new WolfSSLSession(cliCtx); + try { + int r = ses.setFd(sock); + if (r != WolfSSL.SSL_SUCCESS) { + throw new Exception("cli setFd: " + r); + } + int err; + do { + r = ses.connect(); + err = ses.getError(r); + } while (r != WolfSSL.SSL_SUCCESS && + (err == WolfSSL.SSL_ERROR_WANT_READ || + err == WolfSSL.SSL_ERROR_WANT_WRITE)); + if (r != WolfSSL.SSL_SUCCESS) { + throw new Exception("cli connect: " + r); + } + ses.shutdownSSL(); + } finally { + ses.freeSSL(); + sock.close(); + } + } + /* Context object shared between RSA sign/verify callbacks, tracks whether * callback was invoked during handshake */ class TestRsaCbCtx