This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f6bce1249 Make the OCSP timeout configurable
5f6bce1249 is described below

commit 5f6bce1249a5dd26a04c3ccc2744071914336af4
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]

Reply via email to