Merge pull request #369 from cconlon/crlVerifyCbRefLifecycle

Fix global ref lifecycle for verify and missing-CRL callbacks
pull/371/head
Ruby Martin 2026-05-14 16:22:13 -06:00 committed by GitHub
commit bf065a479f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 817 additions and 77 deletions

View File

@ -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;
}

View File

@ -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,27 +652,49 @@ 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;
/* 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);
}
}
#ifdef HAVE_CRL
/* release global CRL callback object if set */
if (g_crlCtxCbIfaceObj != NULL) {
(*jenv)->DeleteGlobalRef(jenv, g_crlCtxCbIfaceObj);
g_crlCtxCbIfaceObj = NULL;
}
#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;
}
/* release verify callback object if set before */
if (g_verifyCbIfaceObj != NULL) {
(*jenv)->DeleteGlobalRef(jenv, g_verifyCbIfaceObj);
g_verifyCbIfaceObj = NULL;
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;
}
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();
/* lookup WolfSSLMissingCRLCallback class from global object ref */
crlClass = (*jenv)->GetObjectClass(jenv, g_crlCtxCbIfaceObj);
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 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)) {

View File

@ -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();
/* lookup WolfSSLMissingCRLCallback class from global object ref */
crlClass = (*jenv)->GetObjectClass(jenv, g_crlCbIfaceObj);
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 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);

View File

@ -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

View File

@ -31,6 +31,7 @@ import org.junit.rules.TestRule;
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.WolfSSL;
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 class WolfSSLContextTest {
} 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 class WolfSSLContextTest {
}
}
/* 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<Void> 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<Void>() {
@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