From 8802d1287a0bdef14959c47eceb2e3d0e35b8f84 Mon Sep 17 00:00:00 2001 From: Chris Conlon Date: Mon, 9 Feb 2026 12:16:51 -0700 Subject: [PATCH] JNI/JCE: wrap wolfIO_SetTimeout(), add wolfjce.ioTimeout system property --- README_JCE.md | 15 ++ jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h | 18 ++ jni/jni_wolfcrypt.c | 29 ++ .../jce/WolfCryptPKIXRevocationChecker.java | 83 ++++++ .../java/com/wolfssl/wolfcrypt/WolfCrypt.java | 46 ++++ .../WolfCryptPKIXRevocationCheckerTest.java | 70 +++++ .../test/WolfCryptProviderIOTimeoutTest.java | 253 ++++++++++++++++++ .../provider/jce/test/WolfJCETestSuite.java | 3 +- .../wolfssl/wolfcrypt/test/WolfCryptTest.java | 96 +++++++ 9 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/wolfssl/provider/jce/test/WolfCryptProviderIOTimeoutTest.java diff --git a/README_JCE.md b/README_JCE.md index 76bc5d51..03b14300 100644 --- a/README_JCE.md +++ b/README_JCE.md @@ -107,6 +107,21 @@ programatically for JCE provider customization: | System Property | Default | To Enable | Description | | --- | --- | --- | --- | | wolfjce.debug | "false" | "true" | Enable wolfJCE debug logging | +| wolfjce.ioTimeout | UNSET | Integer (seconds) | I/O timeout for OCSP and CRL HTTP operations (0-3600) | + +**wolfjce.ioTimeout** - sets the I/O timeout (in seconds) used by native wolfSSL +for HTTP-based OCSP lookups and CRL fetching. Wraps native `wolfIO_SetTimeout()`. +Requires native wolfSSL to be compiled with `HAVE_IO_TIMEOUT`. Valid values are +0 to 3600 inclusive (1 hour). A value of 0 disables the timeout (default +behavior). If the property is not set, no timeout is applied. This +property is read during `PKIXRevocationChecker.init()`, which occurs at +certificate path validation time. This means the property can be set or changed +after provider registration and will be picked up on the next validation. +Invalid values (non-numeric, negative, exceeding 3600) will cause revocation +checker initialization to fail with `CertPathValidatorException`. This property +replaces the Sun-specific `com.sun.security.ocsp.timeout` and +`com.sun.security.crl.timeout` properties (which use milliseconds) with a single +wolfJCE-specific property in seconds that applies to both OCSP and CRL operations. ### Algorithm Support: --------- diff --git a/jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h b/jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h index 8a8f2faa..4842edb0 100644 --- a/jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h +++ b/jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h @@ -29,6 +29,8 @@ extern "C" { #define com_wolfssl_wolfcrypt_WolfCrypt_SIZE_OF_1024_BITS 128L #undef com_wolfssl_wolfcrypt_WolfCrypt_SIZE_OF_2048_BITS #define com_wolfssl_wolfcrypt_WolfCrypt_SIZE_OF_2048_BITS 256L +#undef com_wolfssl_wolfcrypt_WolfCrypt_MAX_IO_TIMEOUT_SEC +#define com_wolfssl_wolfcrypt_WolfCrypt_MAX_IO_TIMEOUT_SEC 3600L /* * Class: com_wolfssl_wolfcrypt_WolfCrypt * Method: getWC_HASH_TYPE_NONE @@ -181,6 +183,14 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcCertPemToDer JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcPubKeyPemToDer (JNIEnv *, jclass, jbyteArray); +/* + * Class: com_wolfssl_wolfcrypt_WolfCrypt + * Method: nativeSetIOTimeout + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_nativeSetIOTimeout + (JNIEnv *, jclass, jint); + /* * Class: com_wolfssl_wolfcrypt_WolfCrypt * Method: CrlEnabled @@ -205,6 +215,14 @@ JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_OcspEnabled JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_Base16Enabled (JNIEnv *, jclass); +/* + * Class: com_wolfssl_wolfcrypt_WolfCrypt + * Method: IoTimeoutEnabled + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_IoTimeoutEnabled + (JNIEnv *, jclass); + #ifdef __cplusplus } #endif diff --git a/jni/jni_wolfcrypt.c b/jni/jni_wolfcrypt.c index 813038a1..51d9d974 100644 --- a/jni/jni_wolfcrypt.c +++ b/jni/jni_wolfcrypt.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -612,3 +614,30 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcPubKeyPemToD #endif /* !NO_ASN && !WOLFSSL_NO_PEM && !NO_CODING) */ } +JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_IoTimeoutEnabled + (JNIEnv* env, jclass jcl) +{ + (void)env; + (void)jcl; + +#ifdef HAVE_IO_TIMEOUT + return JNI_TRUE; +#else + return JNI_FALSE; +#endif +} + +JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_nativeSetIOTimeout + (JNIEnv* env, jclass jcl, jint timeoutSec) +{ + (void)jcl; + +#ifdef HAVE_IO_TIMEOUT + wolfIO_SetTimeout(timeoutSec); + (void)env; +#else + (void)timeoutSec; + throwNotCompiledInException(env); +#endif +} + diff --git a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java index 92056480..96013260 100644 --- a/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java +++ b/src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java @@ -80,6 +80,14 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker { /* Trust anchors for determining if issuer is a trust anchor */ private Set trustAnchors; + /* Last applied I/O timeout value from wolfjce.ioTimeout property. + * Used to skip redundant JNI calls when multiple checkers or + * repeated init() calls read the same property value. wolfIO_SetTimeout() + * sets a global value, so all checkers in the JVM share the same timeout. + * Integer.MIN_VALUE indicates no timeout has been applied yet. */ + private static volatile int lastAppliedIOTimeout = + Integer.MIN_VALUE; + /** * Create new WolfCryptPKIXRevocationChecker. */ @@ -147,6 +155,10 @@ public void init(boolean forward) throws CertPathValidatorException { this.initialized = true; this.softFailExceptions.clear(); + /* Set wolfSSL I/O timeout for HTTP-based operations (OCSP lookups, + * CRL fetching) if 'wolfjce.ioTimeout' System property is set. */ + setIOTimeoutFromProperty(); + /* Verify we have OCSP support if needed */ if (!options.contains(Option.PREFER_CRLS)) { if (!WolfCrypt.OcspEnabled()) { @@ -591,6 +603,77 @@ public List getSoftFailExceptions() { return Collections.unmodifiableList(this.softFailExceptions); } + /** + * Read and apply wolfjce.ioTimeout system property. + * + * Sets the native wolfSSL I/O timeout via wolfIO_SetTimeout() + * if the property is set and valid. If the property is set but + * contains an invalid value, throws CertPathValidatorException + * to fail revocation checker initialization. + * + * Note: The native timeout is a global (process-wide) setting + * shared by all threads and validations in the JVM. To reduce + * redundant JNI calls, the parsed value is compared against + * the last applied value and the native call is skipped if + * unchanged. + * + * @throws CertPathValidatorException if property value is + * invalid (not a number, negative, exceeds max, or + * HAVE_IO_TIMEOUT not compiled in) + */ + private void setIOTimeoutFromProperty() throws CertPathValidatorException { + + int timeoutSec; + String ioTimeout; + + try { + ioTimeout = System.getProperty("wolfjce.ioTimeout"); + } catch (SecurityException e) { + /* SecurityManager blocked property access, treat as + * property not set and continue without timeout */ + return; + } + + if (ioTimeout == null) { + return; + } + final String trimmed = ioTimeout.trim(); + if (trimmed.isEmpty()) { + return; + } + + try { + timeoutSec = Integer.parseInt(trimmed); + + /* Skip JNI call if value unchanged from last apply */ + if (timeoutSec != lastAppliedIOTimeout) { + WolfCrypt.setIOTimeout(timeoutSec); + lastAppliedIOTimeout = timeoutSec; + + WolfCryptDebug.log( + WolfCryptPKIXRevocationChecker.class, + WolfCryptDebug.INFO, + () -> "wolfjce.ioTimeout set to " + + trimmed + " seconds"); + } + + } catch (NumberFormatException e) { + throw new CertPathValidatorException( + "Invalid wolfjce.ioTimeout value: " + trimmed + + ", must be integer seconds: " + e.getMessage(), e); + + } catch (IllegalArgumentException e) { + throw new CertPathValidatorException( + "Invalid wolfjce.ioTimeout value: " + trimmed + + ": " + e.getMessage(), e); + + } catch (WolfCryptException e) { + throw new CertPathValidatorException( + "wolfjce.ioTimeout set but native wolfSSL not " + + "compiled with HAVE_IO_TIMEOUT: " + e.getMessage(), e); + } + } + /** * Clone this revocation checker. * diff --git a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java index f1600e68..a5219adf 100644 --- a/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java +++ b/src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java @@ -134,6 +134,7 @@ public class WolfCrypt extends WolfObject { private static native byte[] wcKeyPemToDer(byte[] pem, String password); private static native byte[] wcCertPemToDer(byte[] pem); private static native byte[] wcPubKeyPemToDer(byte[] pem); + private static native void nativeSetIOTimeout(int timeoutSec); /* Public mappings of some SSL/TLS level enums/defines */ /** wolfSSL file type: PEM */ @@ -192,6 +193,51 @@ public class WolfCrypt extends WolfObject { */ public static native boolean Base16Enabled(); + /** + * Tests if I/O timeout (HAVE_IO_TIMEOUT) has been enabled in wolfSSL. + * + * @return true if enabled, otherwise false if not compiled in + */ + public static native boolean IoTimeoutEnabled(); + + /** Maximum allowed I/O timeout value in seconds (1 hour) */ + private static final int MAX_IO_TIMEOUT_SEC = 3600; + + /** + * Set the I/O timeout used by native wolfSSL for HTTP-based operations + * including OCSP lookups and CRL fetching. + * + * Wraps native wolfIO_SetTimeout(). Requires native wolfSSL to be + * compiled with HAVE_IO_TIMEOUT. + * + * This sets a global (library-wide) timeout value in native + * wolfSSL. All threads and certificate validations in the same + * JVM share this single timeout setting. + * + * @param timeoutSec timeout value in seconds, 0 to 3600 inclusive. + * A value of 0 disables the timeout (default behavior). + * + * @throws WolfCryptException if HAVE_IO_TIMEOUT is not compiled + * into native wolfSSL + * @throws IllegalArgumentException if timeoutSec is negative or + * exceeds 3600 seconds + */ + public static void setIOTimeout(int timeoutSec) { + + if (timeoutSec < 0) { + throw new IllegalArgumentException( + "Timeout value must not be negative"); + } + + if (timeoutSec > MAX_IO_TIMEOUT_SEC) { + throw new IllegalArgumentException( + "Timeout value must not exceed " + + MAX_IO_TIMEOUT_SEC + " seconds"); + } + + nativeSetIOTimeout(timeoutSec); + } + /** * Constant time byte array comparison. * diff --git a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java index 729c48b3..004f0d35 100644 --- a/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java +++ b/src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.*; +import org.junit.After; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -68,6 +69,15 @@ public class WolfCryptPKIXRevocationCheckerTest { @Rule(order = Integer.MIN_VALUE) public TestRule testWatcher = TimedTestWatcher.create(); + /** + * Clean up wolfjce.ioTimeout system property after each + * test to avoid affecting other tests. + */ + @After + public void clearIOTimeoutProperty() { + System.clearProperty("wolfjce.ioTimeout"); + } + /** * Test if this environment is Android. * @return true if Android, otherwise false @@ -1023,5 +1033,65 @@ public void testRevocationCheckerInitClearsExceptions() throws Exception { cm.free(); } + + @Test(timeout = 15000) + public void testRevocationCheckerIOTimeoutLowValue() + throws Exception { + + if (!WolfCrypt.OcspEnabled()) { + /* Skip test if OCSP not compiled in */ + return; + } + + if (!WolfCrypt.IoTimeoutEnabled()) { + /* Skip test if HAVE_IO_TIMEOUT not compiled in */ + return; + } + + CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider); + WolfCryptPKIXRevocationChecker checker = + (WolfCryptPKIXRevocationChecker)cpv.getRevocationChecker(); + + /* Load certs */ + FileInputStream fis = new FileInputStream(caCertDer); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate caCert = (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + fis = new FileInputStream(serverCertDer); + X509Certificate serverCert = + (X509Certificate)cf.generateCertificate(fis); + fis.close(); + + /* Set 1 second I/O timeout via system property */ + System.setProperty("wolfjce.ioTimeout", "1"); + + /* Set SOFT_FAIL and override OCSP URL to non-routable address. + * 198.51.100.1 (TEST-NET-2, RFC 5737) is not routable, so TCP connect + * will hang until timeout kicks in, rather than getting an immediate + * connection refused like localhost would. */ + Set