Merge pull request #255 from cconlon/fenrirAug11

Fenrir fixes for JNI digest bounds checks and CertManager callback lifecycle
pull/252/merge
Ruby Martin 2026-08-18 15:50:30 -05:00 committed by GitHub
commit 216df069e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 2411 additions and 41 deletions

View File

@ -142,6 +142,36 @@ project located in the wolfcrypt-jni/IDE directory.
This will ask for permissions to access the certificates in the /sdcard/
directory and then print out the server certificate information on success.
## Gradle Dependency Verification
This project pins SHA-256 checksums for all remotely downloaded Gradle build
dependencies in `gradle/verification-metadata.xml`. Gradle enforces these
automatically on every build because the file is present. If an artifact
downloaded from a repository does not match its pinned checksum, the build
fails. The Gradle distribution itself is separately pinned via
`distributionSha256Sum` in `gradle/wrapper/gradle-wrapper.properties`.
When changing the Android Gradle Plugin version or any dependency version, the
metadata file must be regenerated from a trusted network environment:
```
cd IDE/Android
./gradlew -I gradle/update-verification-metadata.gradle \
--write-verification-metadata sha256 help
```
The `update-verification-metadata.gradle` init script captures artifacts that
the Android Gradle Plugin only resolves while tasks execute (AAPT2 and the
Unified Test Platform used by instrumented tests). These would otherwise be
missing from the regenerated file and would fail verification during CI builds.
The version coordinates inside that init script must be updated to match the
new Android Gradle Plugin version, as described in the comments at the top of
the script.
Review the diff of `gradle/verification-metadata.xml` before committing an
update, and confirm new checksums come from a trusted build of the upstream
artifacts.
## Support
Please contact wolfSSL support at support@wolfssl.com with any questions or

View File

@ -0,0 +1,70 @@
/*
* update-verification-metadata.gradle
*
* Helper init script used when regenerating the Gradle dependency
* verification metadata file (gradle/verification-metadata.xml).
*
* The Android Gradle Plugin resolves some artifacts only while a task is
* executing, not at configuration time. Those artifacts are missed by the
* normal bootstrap command and would fail dependency verification later, for
* example AAPT2 during assemble tasks and the Unified Test Platform (UTP)
* stack during connectedDebugAndroidTest. This script declares those artifacts
* in regular configurations so the bootstrap records them. All three AAPT2
* platform classifiers are captured so Linux CI, macOS, and Windows developer
* machines all pass verification.
*
* Usage, from the IDE/Android directory:
*
* ./gradlew -I gradle/update-verification-metadata.gradle \
* --write-verification-metadata sha256 help
*
* Review the resulting gradle/verification-metadata.xml diff before
* committing it.
*
* When the Android Gradle Plugin version changes, update the
* versions below to match the new plugin:
*
* - aapt2: version is "<AGP version>-<build number>". Read it
* from aapt2_version.properties inside the AGP jar:
* unzip -p <gradle-X.Y.Z.jar> \
* com/android/build/gradle/internal/res/aapt2_version.properties
* - com.android.tools.utp artifacts: version is the AGP version
* plus 23 in the major number (AGP 8.3.1 -> 31.3.1).
* - com.google.testing.platform artifacts: version comes from the
* UtpDependency class in the AGP jar. It also appears as the
* core-proto version pulled in by the normal bootstrap.
*/
gradle.allprojects { project ->
if (project.name != 'app') {
return
}
def coords = [
'com.android.tools.build:aapt2:8.3.1-10880808:linux',
'com.android.tools.build:aapt2:8.3.1-10880808:osx',
'com.android.tools.build:aapt2:8.3.1-10880808:windows',
'com.google.testing.platform:launcher:0.0.9-alpha02',
'com.google.testing.platform:core:0.0.9-alpha02',
'com.google.testing.platform:android-driver-instrumentation:' +
'0.0.9-alpha02',
'com.google.testing.platform:android-test-plugin:0.0.9-alpha02',
'com.android.tools.utp:android-device-provider-ddmlib:31.3.1',
'com.android.tools.utp:android-device-provider-gradle:31.3.1',
'com.android.tools.utp:android-test-plugin-host-device-info:31.3.1',
'com.android.tools.utp:android-test-plugin-host-additional-' +
'test-output:31.3.1',
'com.android.tools.utp:android-test-plugin-host-apk-installer:31.3.1',
'com.android.tools.utp:android-test-plugin-host-coverage:31.3.1',
'com.android.tools.utp:android-test-plugin-host-logcat:31.3.1',
'com.android.tools.utp:android-test-plugin-host-emulator-' +
'control:31.3.1',
'com.android.tools.utp:android-test-plugin-host-retention:31.3.1',
'com.android.tools.utp:android-test-plugin-result-listener-' +
'gradle:31.3.1',
]
coords.eachWithIndex { coord, idx ->
def cfg = project.configurations.create("verifyMetadataCapture${idx}")
cfg.canBeConsumed = false
cfg.transitive = true
project.dependencies.add(cfg.name, coord)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -190,8 +190,8 @@ Java_com_wolfssl_wolfcrypt_Md5_native_1update_1internal___3BII(
data = getByteArray(env, data_buffer);
dataSz = getByteArrayLength(env, data_buffer);
if (md5 == NULL || data == NULL ||
((word32)(offset + len) > dataSz)) {
if (md5 == NULL || data == NULL || offset < 0 || len < 0 ||
(((jlong)offset + (jlong)len) > (jlong)dataSz)) {
ret = BAD_FUNC_ARG;
} else {
ret = wc_Md5Update(md5, data + offset, len);

View File

@ -319,7 +319,7 @@ Java_com_wolfssl_wolfcrypt_Sha_native_1update_1internal___3BII(
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}
else {
@ -536,7 +536,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Sha224_native_1update_1interna
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}
else {
@ -763,7 +763,7 @@ Java_com_wolfssl_wolfcrypt_Sha256_native_1update_1internal___3BII(
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}
else {
@ -969,7 +969,7 @@ Java_com_wolfssl_wolfcrypt_Sha384_native_1update_1internal___3BII(
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}
else {
@ -1176,7 +1176,7 @@ Java_com_wolfssl_wolfcrypt_Sha512_native_1update_1internal___3BII(
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}
else {
@ -1306,16 +1306,16 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Sha3_native_1init_1internal
if (ret == 0) {
switch (hashType) {
case WC_HASH_TYPE_SHA3_224:
ret = wc_InitSha3_224(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ret = wc_InitSha3_224(sha, NULL, INVALID_DEVID);
break;
case WC_HASH_TYPE_SHA3_256:
ret = wc_InitSha3_256(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ret = wc_InitSha3_256(sha, NULL, INVALID_DEVID);
break;
case WC_HASH_TYPE_SHA3_384:
ret = wc_InitSha3_384(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ret = wc_InitSha3_384(sha, NULL, INVALID_DEVID);
break;
case WC_HASH_TYPE_SHA3_512:
ret = wc_InitSha3_512(sha, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ret = wc_InitSha3_512(sha, NULL, INVALID_DEVID);
break;
default:
ret = BAD_FUNC_ARG;
@ -1471,7 +1471,7 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Sha3_native_1update_1internal_
dataSz = getByteArrayLength(env, data_buffer);
if (sha == NULL || data == NULL || offset < 0 || len < 0 ||
(word32)(offset + len) > dataSz) {
((jlong)offset + (jlong)len) > (jlong)dataSz) {
ret = BAD_FUNC_ARG;
}

View File

@ -138,6 +138,29 @@ static int addCallbackCtx(WOLFSSL_CERT_MANAGER* cm, VerifyCallbackCtx* ctx)
return 0;
}
/* Swap newCtx into the CallbackNode matching cm, in place.
*
* Caller must hold g_callbackMutex.
*
* Returns the displaced ctx, or NULL if no node matched. */
static VerifyCallbackCtx* swapCallbackCtx(WOLFSSL_CERT_MANAGER* cm,
VerifyCallbackCtx* newCtx)
{
CallbackNode* node = g_callbackList;
VerifyCallbackCtx* oldCtx = NULL;
while (node != NULL) {
if (node->cm == cm) {
oldCtx = node->ctx;
node->ctx = newCtx;
return oldCtx;
}
node = node->next;
}
return NULL;
}
/* Remove CallbackNode from global g_callbackList.
*
* Caller must hold g_callbackMutex. */
@ -170,6 +193,35 @@ static void removeCallbackCtx(WOLFSSL_CERT_MANAGER* cm)
}
}
/* Remove and free the callback context registered for cm, deleting JNI global
* reference. Used by CertManagerClearVerify and CertManagerFree.
*
* Returns 0 on success, BAD_MUTEX_E if the list mutex cannot be locked. */
static int freeCallbackCtx(JNIEnv* env, WOLFSSL_CERT_MANAGER* cm)
{
VerifyCallbackCtx* ctx = NULL;
if (wc_LockMutex(&g_callbackMutex) != 0) {
return BAD_MUTEX_E;
}
ctx = findCallbackCtx(cm);
if (ctx != NULL) {
removeCallbackCtx(cm);
}
wc_UnLockMutex(&g_callbackMutex);
if (ctx != NULL) {
if (env != NULL && ctx->callback != NULL) {
(*env)->DeleteGlobalRef(env, ctx->callback);
}
XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
}
return 0;
}
/* Extract cert DER bytes at given depth from WOLFSSL_X509_STORE_CTX into
* a new jbyteArray. Returns NULL if cert not available at depth. */
static jbyteArray getCertDerAtDepth(JNIEnv* jenv,
@ -384,10 +436,25 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManage
JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerFree
(JNIEnv* env, jclass jcl, jlong cmPtr)
{
(void)env;
WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr;
(void)jcl;
wolfSSL_CertManagerFree((WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr);
if (cm == NULL) {
return;
}
#ifndef NO_WOLFSSL_CM_VERIFY
/* Reset cm callback to NULL in wolfSSL */
wolfSSL_CertManagerSetVerify(cm, NULL);
#endif
/* Remove callback context for this manager so the list node and JNI
* global reference do not outlive it */
if (freeCallbackCtx(env, cm) != 0) {
LogStr("CertManagerFree: mutex lock failed, ctx may leak\n");
}
wolfSSL_CertManagerFree(cm);
}
JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerLoadCA
@ -915,6 +982,7 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
int ret = 0;
WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr;
VerifyCallbackCtx* ctx = NULL;
VerifyCallbackCtx* oldCtx = NULL;
JavaVM* jvm = NULL;
(void)jcl;
@ -943,18 +1011,31 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManager
}
ctx->jvm = jvm;
/* Add context to global list */
/* Swap into any existing entry for this cm so repeated SetVerify does
* not leak the prior context */
if (wc_LockMutex(&g_callbackMutex) != 0) {
(*env)->DeleteGlobalRef(env, ctx->callback);
XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
return BAD_MUTEX_E;
}
ret = addCallbackCtx(cm, ctx);
oldCtx = swapCallbackCtx(cm, ctx);
if (oldCtx == NULL) {
/* No existing entry, add new node */
ret = addCallbackCtx(cm, ctx);
}
wc_UnLockMutex(&g_callbackMutex);
if (oldCtx != NULL) {
if (oldCtx->callback != NULL) {
(*env)->DeleteGlobalRef(env, oldCtx->callback);
}
XFREE(oldCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
}
if (ret != 0) {
/* Registration failed, no callback was registered */
(*env)->DeleteGlobalRef(env, ctx->callback);
XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
return ret;
@ -979,7 +1060,6 @@ Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerClearVerify
{
#ifndef NO_WOLFSSL_CM_VERIFY
WOLFSSL_CERT_MANAGER* cm = (WOLFSSL_CERT_MANAGER*)(uintptr_t)cmPtr;
VerifyCallbackCtx* ctx = NULL;
(void)jcl;
if (env == NULL || cm == NULL) {
@ -989,30 +1069,10 @@ Java_com_wolfssl_wolfcrypt_WolfSSLCertManager_CertManagerClearVerify
/* Clear callback in wolfSSL first */
wolfSSL_CertManagerSetVerify(cm, NULL);
/* Lock mutex and find/remove callback context */
if (wc_LockMutex(&g_callbackMutex) != 0) {
if (freeCallbackCtx(env, cm) != 0) {
return BAD_MUTEX_E;
}
ctx = findCallbackCtx(cm);
if (ctx != NULL) {
/* Remove from global list */
removeCallbackCtx(cm);
}
wc_UnLockMutex(&g_callbackMutex);
/* Free context if it existed */
if (ctx != NULL) {
/* Delete global reference to callback object */
if (ctx->callback != NULL) {
(*env)->DeleteGlobalRef(env, ctx->callback);
}
/* Free context structure */
XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
}
return WOLFSSL_SUCCESS;
#else
(void)env;

View File

@ -169,9 +169,8 @@ public abstract class MessageDigest extends NativeStruct {
checkStateAndInitialize();
if (((offset + len) > data.length) || offset < 0 || len < 0) {
throw new RuntimeException(
"Invalid offset or length");
if (offset < 0 || len < 0 || len > (data.length - offset)) {
throw new RuntimeException("Invalid offset or length");
}
native_update(data, offset, len);

View File

@ -807,6 +807,7 @@ public class WolfSSLCertManager extends WolfObject {
/* free Java resources */
this.active = false;
this.cmPtr = 0;
this.verifyCallback = null;
}
}
}

View File

@ -75,6 +75,23 @@ public class Md5Test {
assertEquals(NativeStruct.NULL, new Md5().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Md5 md5 = new Md5();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
md5.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {
String[] dataVector = new String[] {

View File

@ -85,6 +85,23 @@ public class Sha224Test {
assertEquals(NativeStruct.NULL, new Sha224().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha224 sha = new Sha224();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {

View File

@ -75,6 +75,23 @@ public class Sha256Test {
assertEquals(NativeStruct.NULL, new Sha256().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha256 sha = new Sha256();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {
String[] dataVector = new String[] {

View File

@ -75,6 +75,23 @@ public class Sha384Test {
assertEquals(NativeStruct.NULL, new Sha384().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha384 sha = new Sha384();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {
String[] dataVector = new String[] { "", "c2edba56a6b82cc3",

View File

@ -75,6 +75,23 @@ public class Sha3Test {
new Sha3(Sha3.TYPE_SHA3_256).getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha3 sha = new Sha3(Sha3.TYPE_SHA3_256);
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void sha3_256HashShouldMatchUsingByteArray() {
/* Test vectors from NIST FIPS 202 - SHA-3 Standard */

View File

@ -75,6 +75,23 @@ public class Sha512Test {
assertEquals(NativeStruct.NULL, new Sha512().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha512 sha = new Sha512();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {
String[] dataVector = new String[] { "", "20580a530f01e771",

View File

@ -75,6 +75,23 @@ public class ShaTest {
assertEquals(NativeStruct.NULL, new Sha().getNativeStruct());
}
@Test
public void updateWithWrappedOffsetAndLenShouldThrow() {
Sha sha = new Sha();
byte[] data = new byte[8];
/* offset + len wraps int arithmetic, update must reject it */
try {
sha.update(data, 1, Integer.MAX_VALUE);
fail("update() should have thrown for wrapped offset + len");
} catch (IllegalStateException e) {
/* init failure is not the bounds rejection */
throw e;
} catch (RuntimeException e) {
/* expected */
}
}
@Test
public void hashShouldMatchUsingByteBuffer() throws ShortBufferException {
String[] dataVector = new String[] {

View File

@ -33,6 +33,7 @@ import org.junit.runner.Description;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
@ -180,6 +181,138 @@ public class WolfSSLCertManagerVerifyCallbackTest {
}
}
/**
* Test callback object is collectible after free(), which must release
* the native global reference to it.
*/
@Test
public void testVerifyCallbackCollectibleAfterFree() throws Exception {
WolfSSLCertManager cm = new WolfSSLCertManager();
WolfSSLCertManagerVerifyCallback cb =
new WolfSSLCertManagerVerifyCallback() {
public int verify(int preverify, int error, int errorDepth) {
return 1;
}
};
WeakReference<WolfSSLCertManagerVerifyCallback> ref =
new WeakReference<WolfSSLCertManagerVerifyCallback>(cb);
byte[] gcPressure = null;
try {
cm.setVerifyCallback(cb);
} finally {
cm.free();
}
cm = null;
cb = null;
/* Android ART does not reliably collect within the retry window
* even after the native global reference is deleted. */
if (!isAndroid()) {
/* Retry GC up to ~500ms, small allocations encourage
* collectors that treat System.gc() as advisory */
for (int i = 0; i < 50 && ref.get() != null; i++) {
gcPressure = new byte[4096];
gcPressure[0] = (byte)i;
System.gc();
Thread.sleep(10);
}
assertNull("callback not collected, native global reference " +
"leaked", ref.get());
}
}
/* Register a first callback on cm then replace with a second.
*
* Return a WeakReference to the replaced first callback. */
private static WeakReference<WolfSSLCertManagerVerifyCallback>
registerThenReplaceCallback(WolfSSLCertManager cm,
final AtomicBoolean firstInvoked,
final AtomicBoolean secondInvoked) throws Exception {
WolfSSLCertManagerVerifyCallback first =
new WolfSSLCertManagerVerifyCallback() {
public int verify(int preverify, int error, int errorDepth) {
firstInvoked.set(true);
return 1;
}
};
WeakReference<WolfSSLCertManagerVerifyCallback> firstRef =
new WeakReference<WolfSSLCertManagerVerifyCallback>(first);
cm.setVerifyCallback(first);
cm.setVerifyCallback(new WolfSSLCertManagerVerifyCallback() {
public int verify(int preverify, int error, int errorDepth) {
secondInvoked.set(true);
return 1;
}
});
return firstRef;
}
/**
* Test that a second setVerifyCallback() replaces the first callback and
* releases the native global reference to it.
*/
@Test
public void testVerifyCallbackReplaceReleasesPrevious() throws Exception {
final AtomicBoolean firstInvoked = new AtomicBoolean(false);
final AtomicBoolean secondInvoked = new AtomicBoolean(false);
WolfSSLCertManager cm = new WolfSSLCertManager();
byte[] gcPressure = null;
WeakReference<WolfSSLCertManagerVerifyCallback> firstRef = null;
byte[] caDer = readFile(caCertDer);
byte[] serverDer = readFile(serverCertDer);
try {
cm.CertManagerLoadCABuffer(caDer, caDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
firstRef = registerThenReplaceCallback(cm, firstInvoked,
secondInvoked);
try {
cm.CertManagerVerifyBuffer(serverDer, serverDer.length,
WolfCrypt.SSL_FILETYPE_ASN1);
} catch (WolfCryptException e) {
/* Verification result itself is not what we assert here */
}
assertTrue("second callback should have been invoked",
secondInvoked.get());
assertFalse("first callback should not have been invoked",
firstInvoked.get());
/* Android ART does not reliably collect within the retry
* window even after the native global reference is deleted,
* so the collectibility assert runs on non-Android JVMs only.
* The dispatch asserts above still run everywhere. */
if (!isAndroid()) {
/* Retry GC up to ~500ms, small allocations encourage
* collectors that treat System.gc() as advisory */
for (int i = 0; i < 50 && firstRef.get() != null; i++) {
gcPressure = new byte[4096];
gcPressure[0] = (byte)i;
System.gc();
Thread.sleep(10);
}
assertNull("replaced callback not collected, native " +
"global reference leaked", firstRef.get());
}
} finally {
cm.free();
}
}
/**
* Test each CertManager verification invokes its own callback, not
* the most recently registered one from another CertManager.