This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new dc14d147b6 Make the OCSP timeout configurable
dc14d147b6 is described below
commit dc14d147b6005e02a477062cad76a6a661c8b270
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Dec 9 17:23:55 2025 +0000
Make the OCSP timeout configurable
---
java/org/apache/tomcat/util/net/SSLHostConfig.java | 11 ++
.../tomcat/util/net/openssl/OpenSSLConfCmd.java | 1 +
.../tomcat/util/net/openssl/OpenSSLContext.java | 2 +
.../util/net/openssl/panama/OpenSSLContext.java | 8 +-
.../apache/tomcat/util/net/ocsp/OcspBaseTest.java | 10 ++
.../tomcat/util/net/ocsp/TestOcspTimeout.java | 65 ++++++++++++
.../net/ocsp/TesterOcspResponderNoResponse.java | 113 +++++++++++++++++++++
7 files changed, 207 insertions(+), 3 deletions(-)
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index eae2d75c89..817faa1ad8 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -119,6 +119,7 @@ public class SSLHostConfig implements Serializable {
private boolean honorCipherOrder = false;
private boolean ocspEnabled = false;
private boolean ocspSoftFail = true;
+ private int ocspTimeout = 15000;
private final Set<String> protocols = new HashSet<>();
// Values <0 mean use the implementation default
private int sessionCacheSize = -1;
@@ -583,6 +584,16 @@ public class SSLHostConfig implements Serializable {
}
+ public int getOcspTimeout() {
+ return ocspTimeout;
+ }
+
+
+ public void setOcspTimeout(int ocspTimeout) {
+ this.ocspTimeout = ocspTimeout;
+ }
+
+
public void setProtocols(String input) {
protocols.clear();
explicitlyRequestedProtocols.clear();
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
index b799d4ecf2..9680020f0a 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
@@ -24,6 +24,7 @@ public class OpenSSLConfCmd implements Serializable {
// Tomcat / Tomcat Native custom commands. Used internally by Tomcat. Not
intended for direct use by users.
public static final String NO_OCSP_CHECK = "NO_OCSP_CHECK";
public static final String OCSP_SOFT_FAIL = "OCSP_SOFT_FAIL";
+ public static final String OCSP_TIMEOUT = "OCSP_TIMEOUT";
@Serial
private static final long serialVersionUID = 1L;
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
index 17a0dff0ad..da0e88734d 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -369,6 +369,8 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
Boolean.toString(!sslHostConfig.getOcspEnabled())));
sslHostConfig.getOpenSslConf().addCmd(new
OpenSSLConfCmd(OpenSSLConfCmd.OCSP_SOFT_FAIL,
Boolean.toString(sslHostConfig.getOcspSoftFail())));
+ sslHostConfig.getOpenSslConf().addCmd(new
OpenSSLConfCmd(OpenSSLConfCmd.OCSP_TIMEOUT,
+ Integer.toString(sslHostConfig.getOcspTimeout())));
}
if (negotiableProtocols != null && !negotiableProtocols.isEmpty())
{
diff --git a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
index 645f98713d..48f0845b17 100644
--- a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
@@ -124,6 +124,7 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
private boolean noOcspCheck = false;
private boolean ocspSoftFail = true;
+ // 15s default - same as JSSE
private int ocspTimeout = 15000;
private int ocspVerifyFlags = 0;
private X509TrustManager x509TrustManager;
@@ -360,7 +361,7 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
ok = true;
} else if (name.equals(OpenSSLConfCmd.OCSP_SOFT_FAIL)) {
ok = true;
- } else if (name.equals("OCSP_TIMEOUT")) {
+ } else if (name.equals(OpenSSLConfCmd.OCSP_TIMEOUT)) {
ok = true;
} else if (name.equals("OCSP_VERIFY_FLAGS")) {
ok = true;
@@ -437,8 +438,8 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
} else if (name.equals(OpenSSLConfCmd.OCSP_SOFT_FAIL)) {
// Ignore - Tomcat internal - set directly
rc = 1;
- } else if (name.equals("OCSP_TIMEOUT")) {
- ocspTimeout = Integer.parseInt(value);
+ } else if (name.equals(OpenSSLConfCmd.OCSP_TIMEOUT)) {
+ // Ignore - Tomcat internal - set directly
rc = 1;
} else if (name.equals("OCSP_VERIFY_FLAGS")) {
ocspVerifyFlags = Integer.parseInt(value);
@@ -571,6 +572,7 @@ public class OpenSSLContext implements
org.apache.tomcat.util.net.SSLContext {
noOcspCheck = true;
}
ocspSoftFail = sslHostConfig.getOcspSoftFail();
+ ocspTimeout = sslHostConfig.getOcspTimeout();
// Set int verify_callback(int preverify_ok, X509_STORE_CTX
*x509_ctx) callback
SSL_CTX_set_verify(state.sslCtx, value,
diff --git a/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
b/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
index 46d059c056..cf6a171ceb 100644
--- a/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
+++ b/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
@@ -150,6 +150,16 @@ public class OcspBaseTest extends TomcatBaseTest {
sslHostConfig.setOcspSoftFail(softFail.booleanValue());
}
+ /*
+ * Use shorter timeout to speed up test.
+ *
+ * Note: JSSE timeout set earlier as it requires setting a system
property that is read once in a static
+ * initializer.
+ */
+ if (useOpenSSLTrust) {
+ sslHostConfig.setOcspTimeout(1000);
+ }
+
tomcat.start();
int rc = getUrl("https://localhost:" + getPort() + "/simple", new
ByteChunk(), false);
diff --git a/test/org/apache/tomcat/util/net/ocsp/TestOcspTimeout.java
b/test/org/apache/tomcat/util/net/ocsp/TestOcspTimeout.java
new file mode 100644
index 0000000000..e3034ac956
--- /dev/null
+++ b/test/org/apache/tomcat/util/net/ocsp/TestOcspTimeout.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.ocsp;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/*
+ * The timeout for reading an OCSP response is 15s by default for both JSSE
and OpenSSL.
+ */
+@RunWith(Parameterized.class)
+public class TestOcspTimeout extends OcspBaseTest {
+
+ private static TesterOcspResponderNoResponse ocspResponder;
+
+ @BeforeClass
+ public static void startOcspResponder() {
+ /*
+ * Use shorter timeout to speed up test.
+ *
+ * Note: OpenSSL timeout set later as it requires access to
SSLHostConfig.
+ */
+ System.setProperty("com.sun.security.ocsp.readtimeout", "1000ms");
+ ocspResponder = new TesterOcspResponderNoResponse();
+ ocspResponder.start();
+ }
+
+
+ @AfterClass
+ public static void stopOcspResponder() {
+ ocspResponder.stop();
+ ocspResponder = null;
+ }
+
+
+ @Test
+ public void testTimeoutWithSoftFail() throws Exception {
+ doTest(false, false, ClientCertificateVerification.ENABLED, false,
Boolean.TRUE);
+ }
+
+
+ @Test(expected = SSLHandshakeException.class)
+ public void testTimeoutWithoutSoftFail() throws Exception {
+ doTest(false, false, ClientCertificateVerification.ENABLED, false,
Boolean.FALSE);
+ }
+}
diff --git
a/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderNoResponse.java
b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderNoResponse.java
new file mode 100644
index 0000000000..da65588ba0
--- /dev/null
+++ b/test/org/apache/tomcat/util/net/ocsp/TesterOcspResponderNoResponse.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.net.ocsp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import javax.net.ServerSocketFactory;
+
+import org.junit.Assert;
+
+/*
+ * An OCSP responder that swallows any input received and never responds. Use
to test timeouts.
+ */
+public class TesterOcspResponderNoResponse {
+
+ private ServerRunnable sr;
+
+ public void start() {
+ if (sr != null) {
+ throw new IllegalStateException("Already started");
+ }
+
+ sr = new ServerRunnable();
+ Thread t = new Thread(sr);
+ t.start();
+
+ Assert.assertTrue(sr.isAlive());
+ }
+
+ public void stop() {
+ if (sr == null) {
+ throw new IllegalStateException("Not started");
+ }
+ sr.stop();
+ }
+
+
+ private static class ServerRunnable implements Runnable {
+
+ private volatile boolean alive = true;
+ private volatile ServerSocket serverSocket;
+
+ @Override
+ public void run() {
+ try {
+ serverSocket =
ServerSocketFactory.getDefault().createServerSocket();
+ serverSocket.bind(new InetSocketAddress("localhost", 8888));
+
+ while (alive) {
+ Socket socket = serverSocket.accept();
+ Thread t = new Thread(new SwallowRunnable(socket));
+ t.start();
+ }
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ public void stop() {
+ try {
+ serverSocket.close();
+ alive = false;
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ public boolean isAlive() {
+ return alive;
+ }
+ }
+
+
+ private static class SwallowRunnable implements Runnable {
+
+ private final Socket socket;
+
+ SwallowRunnable(Socket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ byte[] buf = new byte[1024];
+ try (InputStream os = socket.getInputStream()) {
+ // Read until the client closes the socket
+ while (os.read(buf) > 0) {
+ // Ignore any data read
+ }
+ } catch (IOException ignore) {
+ // Ignore
+ }
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]