Skip to content

Commit 8802d12

Browse files
committed
JNI/JCE: wrap wolfIO_SetTimeout(), add wolfjce.ioTimeout system property
1 parent 55040a9 commit 8802d12

9 files changed

Lines changed: 612 additions & 1 deletion

File tree

README_JCE.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,21 @@ programatically for JCE provider customization:
107107
| System Property | Default | To Enable | Description |
108108
| --- | --- | --- | --- |
109109
| wolfjce.debug | "false" | "true" | Enable wolfJCE debug logging |
110+
| wolfjce.ioTimeout | UNSET | Integer (seconds) | I/O timeout for OCSP and CRL HTTP operations (0-3600) |
111+
112+
**wolfjce.ioTimeout** - sets the I/O timeout (in seconds) used by native wolfSSL
113+
for HTTP-based OCSP lookups and CRL fetching. Wraps native `wolfIO_SetTimeout()`.
114+
Requires native wolfSSL to be compiled with `HAVE_IO_TIMEOUT`. Valid values are
115+
0 to 3600 inclusive (1 hour). A value of 0 disables the timeout (default
116+
behavior). If the property is not set, no timeout is applied. This
117+
property is read during `PKIXRevocationChecker.init()`, which occurs at
118+
certificate path validation time. This means the property can be set or changed
119+
after provider registration and will be picked up on the next validation.
120+
Invalid values (non-numeric, negative, exceeding 3600) will cause revocation
121+
checker initialization to fail with `CertPathValidatorException`. This property
122+
replaces the Sun-specific `com.sun.security.ocsp.timeout` and
123+
`com.sun.security.crl.timeout` properties (which use milliseconds) with a single
124+
wolfJCE-specific property in seconds that applies to both OCSP and CRL operations.
110125

111126
### Algorithm Support:
112127
---------

jni/include/com_wolfssl_wolfcrypt_WolfCrypt.h

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jni/jni_wolfcrypt.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
#include <wolfssl/wolfcrypt/coding.h>
3333
#include <wolfssl/wolfcrypt/asn_public.h>
3434
#include <wolfssl/wolfcrypt/error-crypt.h>
35+
#include <wolfssl/ssl.h>
36+
#include <wolfssl/wolfio.h>
3537
#include <com_wolfssl_wolfcrypt_WolfCrypt.h>
3638
#include <wolfcrypt_jni_error.h>
3739

@@ -612,3 +614,30 @@ JNIEXPORT jbyteArray JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_wcPubKeyPemToD
612614
#endif /* !NO_ASN && !WOLFSSL_NO_PEM && !NO_CODING) */
613615
}
614616

617+
JNIEXPORT jboolean JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_IoTimeoutEnabled
618+
(JNIEnv* env, jclass jcl)
619+
{
620+
(void)env;
621+
(void)jcl;
622+
623+
#ifdef HAVE_IO_TIMEOUT
624+
return JNI_TRUE;
625+
#else
626+
return JNI_FALSE;
627+
#endif
628+
}
629+
630+
JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_WolfCrypt_nativeSetIOTimeout
631+
(JNIEnv* env, jclass jcl, jint timeoutSec)
632+
{
633+
(void)jcl;
634+
635+
#ifdef HAVE_IO_TIMEOUT
636+
wolfIO_SetTimeout(timeoutSec);
637+
(void)env;
638+
#else
639+
(void)timeoutSec;
640+
throwNotCompiledInException(env);
641+
#endif
642+
}
643+

src/main/java/com/wolfssl/provider/jce/WolfCryptPKIXRevocationChecker.java

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ public class WolfCryptPKIXRevocationChecker extends PKIXRevocationChecker {
8080
/* Trust anchors for determining if issuer is a trust anchor */
8181
private Set<TrustAnchor> trustAnchors;
8282

83+
/* Last applied I/O timeout value from wolfjce.ioTimeout property.
84+
* Used to skip redundant JNI calls when multiple checkers or
85+
* repeated init() calls read the same property value. wolfIO_SetTimeout()
86+
* sets a global value, so all checkers in the JVM share the same timeout.
87+
* Integer.MIN_VALUE indicates no timeout has been applied yet. */
88+
private static volatile int lastAppliedIOTimeout =
89+
Integer.MIN_VALUE;
90+
8391
/**
8492
* Create new WolfCryptPKIXRevocationChecker.
8593
*/
@@ -147,6 +155,10 @@ public void init(boolean forward) throws CertPathValidatorException {
147155
this.initialized = true;
148156
this.softFailExceptions.clear();
149157

158+
/* Set wolfSSL I/O timeout for HTTP-based operations (OCSP lookups,
159+
* CRL fetching) if 'wolfjce.ioTimeout' System property is set. */
160+
setIOTimeoutFromProperty();
161+
150162
/* Verify we have OCSP support if needed */
151163
if (!options.contains(Option.PREFER_CRLS)) {
152164
if (!WolfCrypt.OcspEnabled()) {
@@ -591,6 +603,77 @@ public List<CertPathValidatorException> getSoftFailExceptions() {
591603
return Collections.unmodifiableList(this.softFailExceptions);
592604
}
593605

606+
/**
607+
* Read and apply wolfjce.ioTimeout system property.
608+
*
609+
* Sets the native wolfSSL I/O timeout via wolfIO_SetTimeout()
610+
* if the property is set and valid. If the property is set but
611+
* contains an invalid value, throws CertPathValidatorException
612+
* to fail revocation checker initialization.
613+
*
614+
* Note: The native timeout is a global (process-wide) setting
615+
* shared by all threads and validations in the JVM. To reduce
616+
* redundant JNI calls, the parsed value is compared against
617+
* the last applied value and the native call is skipped if
618+
* unchanged.
619+
*
620+
* @throws CertPathValidatorException if property value is
621+
* invalid (not a number, negative, exceeds max, or
622+
* HAVE_IO_TIMEOUT not compiled in)
623+
*/
624+
private void setIOTimeoutFromProperty() throws CertPathValidatorException {
625+
626+
int timeoutSec;
627+
String ioTimeout;
628+
629+
try {
630+
ioTimeout = System.getProperty("wolfjce.ioTimeout");
631+
} catch (SecurityException e) {
632+
/* SecurityManager blocked property access, treat as
633+
* property not set and continue without timeout */
634+
return;
635+
}
636+
637+
if (ioTimeout == null) {
638+
return;
639+
}
640+
final String trimmed = ioTimeout.trim();
641+
if (trimmed.isEmpty()) {
642+
return;
643+
}
644+
645+
try {
646+
timeoutSec = Integer.parseInt(trimmed);
647+
648+
/* Skip JNI call if value unchanged from last apply */
649+
if (timeoutSec != lastAppliedIOTimeout) {
650+
WolfCrypt.setIOTimeout(timeoutSec);
651+
lastAppliedIOTimeout = timeoutSec;
652+
653+
WolfCryptDebug.log(
654+
WolfCryptPKIXRevocationChecker.class,
655+
WolfCryptDebug.INFO,
656+
() -> "wolfjce.ioTimeout set to " +
657+
trimmed + " seconds");
658+
}
659+
660+
} catch (NumberFormatException e) {
661+
throw new CertPathValidatorException(
662+
"Invalid wolfjce.ioTimeout value: " + trimmed +
663+
", must be integer seconds: " + e.getMessage(), e);
664+
665+
} catch (IllegalArgumentException e) {
666+
throw new CertPathValidatorException(
667+
"Invalid wolfjce.ioTimeout value: " + trimmed +
668+
": " + e.getMessage(), e);
669+
670+
} catch (WolfCryptException e) {
671+
throw new CertPathValidatorException(
672+
"wolfjce.ioTimeout set but native wolfSSL not " +
673+
"compiled with HAVE_IO_TIMEOUT: " + e.getMessage(), e);
674+
}
675+
}
676+
594677
/**
595678
* Clone this revocation checker.
596679
*

src/main/java/com/wolfssl/wolfcrypt/WolfCrypt.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ public class WolfCrypt extends WolfObject {
134134
private static native byte[] wcKeyPemToDer(byte[] pem, String password);
135135
private static native byte[] wcCertPemToDer(byte[] pem);
136136
private static native byte[] wcPubKeyPemToDer(byte[] pem);
137+
private static native void nativeSetIOTimeout(int timeoutSec);
137138

138139
/* Public mappings of some SSL/TLS level enums/defines */
139140
/** wolfSSL file type: PEM */
@@ -192,6 +193,51 @@ public class WolfCrypt extends WolfObject {
192193
*/
193194
public static native boolean Base16Enabled();
194195

196+
/**
197+
* Tests if I/O timeout (HAVE_IO_TIMEOUT) has been enabled in wolfSSL.
198+
*
199+
* @return true if enabled, otherwise false if not compiled in
200+
*/
201+
public static native boolean IoTimeoutEnabled();
202+
203+
/** Maximum allowed I/O timeout value in seconds (1 hour) */
204+
private static final int MAX_IO_TIMEOUT_SEC = 3600;
205+
206+
/**
207+
* Set the I/O timeout used by native wolfSSL for HTTP-based operations
208+
* including OCSP lookups and CRL fetching.
209+
*
210+
* Wraps native wolfIO_SetTimeout(). Requires native wolfSSL to be
211+
* compiled with HAVE_IO_TIMEOUT.
212+
*
213+
* This sets a global (library-wide) timeout value in native
214+
* wolfSSL. All threads and certificate validations in the same
215+
* JVM share this single timeout setting.
216+
*
217+
* @param timeoutSec timeout value in seconds, 0 to 3600 inclusive.
218+
* A value of 0 disables the timeout (default behavior).
219+
*
220+
* @throws WolfCryptException if HAVE_IO_TIMEOUT is not compiled
221+
* into native wolfSSL
222+
* @throws IllegalArgumentException if timeoutSec is negative or
223+
* exceeds 3600 seconds
224+
*/
225+
public static void setIOTimeout(int timeoutSec) {
226+
227+
if (timeoutSec < 0) {
228+
throw new IllegalArgumentException(
229+
"Timeout value must not be negative");
230+
}
231+
232+
if (timeoutSec > MAX_IO_TIMEOUT_SEC) {
233+
throw new IllegalArgumentException(
234+
"Timeout value must not exceed " +
235+
MAX_IO_TIMEOUT_SEC + " seconds");
236+
}
237+
238+
nativeSetIOTimeout(timeoutSec);
239+
}
240+
195241
/**
196242
* Constant time byte array comparison.
197243
*

src/test/java/com/wolfssl/provider/jce/test/WolfCryptPKIXRevocationCheckerTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import static org.junit.Assert.*;
2525

26+
import org.junit.After;
2627
import org.junit.Assume;
2728
import org.junit.BeforeClass;
2829
import org.junit.Test;
@@ -68,6 +69,15 @@ public class WolfCryptPKIXRevocationCheckerTest {
6869
@Rule(order = Integer.MIN_VALUE)
6970
public TestRule testWatcher = TimedTestWatcher.create();
7071

72+
/**
73+
* Clean up wolfjce.ioTimeout system property after each
74+
* test to avoid affecting other tests.
75+
*/
76+
@After
77+
public void clearIOTimeoutProperty() {
78+
System.clearProperty("wolfjce.ioTimeout");
79+
}
80+
7181
/**
7282
* Test if this environment is Android.
7383
* @return true if Android, otherwise false
@@ -1023,5 +1033,65 @@ public void testRevocationCheckerInitClearsExceptions() throws Exception {
10231033

10241034
cm.free();
10251035
}
1036+
1037+
@Test(timeout = 15000)
1038+
public void testRevocationCheckerIOTimeoutLowValue()
1039+
throws Exception {
1040+
1041+
if (!WolfCrypt.OcspEnabled()) {
1042+
/* Skip test if OCSP not compiled in */
1043+
return;
1044+
}
1045+
1046+
if (!WolfCrypt.IoTimeoutEnabled()) {
1047+
/* Skip test if HAVE_IO_TIMEOUT not compiled in */
1048+
return;
1049+
}
1050+
1051+
CertPathValidator cpv = CertPathValidator.getInstance("PKIX", provider);
1052+
WolfCryptPKIXRevocationChecker checker =
1053+
(WolfCryptPKIXRevocationChecker)cpv.getRevocationChecker();
1054+
1055+
/* Load certs */
1056+
FileInputStream fis = new FileInputStream(caCertDer);
1057+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
1058+
X509Certificate caCert = (X509Certificate)cf.generateCertificate(fis);
1059+
fis.close();
1060+
1061+
fis = new FileInputStream(serverCertDer);
1062+
X509Certificate serverCert =
1063+
(X509Certificate)cf.generateCertificate(fis);
1064+
fis.close();
1065+
1066+
/* Set 1 second I/O timeout via system property */
1067+
System.setProperty("wolfjce.ioTimeout", "1");
1068+
1069+
/* Set SOFT_FAIL and override OCSP URL to non-routable address.
1070+
* 198.51.100.1 (TEST-NET-2, RFC 5737) is not routable, so TCP connect
1071+
* will hang until timeout kicks in, rather than getting an immediate
1072+
* connection refused like localhost would. */
1073+
Set<Option> options = EnumSet.of(Option.SOFT_FAIL);
1074+
checker.setOptions(options);
1075+
checker.setOcspResponder(new URI("http://198.51.100.1:12345"));
1076+
1077+
/* Create CertManager, load CA, and init */
1078+
WolfSSLCertManager cm = new WolfSSLCertManager();
1079+
cm.CertManagerLoadCA(caCert);
1080+
checker.setCertManager(cm);
1081+
checker.init(false);
1082+
1083+
/* Time the check() call. With 1 second timeout and a non-routable OCSP
1084+
* URL, should complete quickly. */
1085+
long startMs = System.currentTimeMillis();
1086+
checker.check(serverCert, null);
1087+
long elapsedMs = System.currentTimeMillis() - startMs;
1088+
1089+
/* Verify check completed within reasonable time.
1090+
* Allow 10 sec margin for system overhead/etc. */
1091+
assertTrue("OCSP check with 1s timeout took " + elapsedMs +
1092+
"ms, expected < 10000ms", elapsedMs < 10000);
1093+
1094+
cm.free();
1095+
}
10261096
}
10271097

0 commit comments

Comments
 (0)