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 26422e05f5 Add support for soft-fail and make it configurable
26422e05f5 is described below

commit 26422e05f54beeaa5f52300874a3e8488505cb7b
Author: Mark Thomas <[email protected]>
AuthorDate: Tue Dec 9 17:10:23 2025 +0000

    Add support for soft-fail and make it configurable
---
 java/org/apache/tomcat/util/net/SSLHostConfig.java | 12 ++++++
 java/org/apache/tomcat/util/net/SSLUtilBase.java   |  6 ++-
 .../tomcat/util/net/openssl/OpenSSLConfCmd.java    |  1 +
 .../tomcat/util/net/openssl/OpenSSLContext.java    |  2 +
 .../util/net/openssl/panama/OpenSSLContext.java    | 11 ++++-
 .../util/net/openssl/panama/OpenSSLEngine.java     | 39 ++++++++++--------
 .../apache/tomcat/util/net/ocsp/OcspBaseTest.java  | 10 +++++
 .../tomcat/util/net/ocsp/TestOcspSoftFail.java     | 48 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  6 ++-
 webapps/docs/config/http.xml                       | 10 +++++
 10 files changed, 125 insertions(+), 20 deletions(-)

diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java 
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index 319707070a..eae2d75c89 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -118,6 +118,7 @@ public class SSLHostConfig implements Serializable {
     private List<String> jsseCipherNames = null;
     private boolean honorCipherOrder = false;
     private boolean ocspEnabled = false;
+    private boolean ocspSoftFail = true;
     private final Set<String> protocols = new HashSet<>();
     // Values <0 mean use the implementation default
     private int sessionCacheSize = -1;
@@ -566,11 +567,22 @@ public class SSLHostConfig implements Serializable {
         return ocspEnabled;
     }
 
+
     public void setOcspEnabled(boolean ocspEnabled) {
         this.ocspEnabled = ocspEnabled;
     }
 
 
+    public boolean getOcspSoftFail() {
+        return ocspSoftFail;
+    }
+
+
+    public void setOcspSoftFail(boolean ocspSoftFail) {
+        this.ocspSoftFail = ocspSoftFail;
+    }
+
+
     public void setProtocols(String input) {
         protocols.clear();
         explicitlyRequestedProtocols.clear();
diff --git a/java/org/apache/tomcat/util/net/SSLUtilBase.java 
b/java/org/apache/tomcat/util/net/SSLUtilBase.java
index e2c9e141fd..d2cd27dfd7 100644
--- a/java/org/apache/tomcat/util/net/SSLUtilBase.java
+++ b/java/org/apache/tomcat/util/net/SSLUtilBase.java
@@ -539,7 +539,11 @@ public abstract class SSLUtilBase implements SSLUtil {
 
         if (sslHostConfig.getOcspEnabled()) {
             PKIXRevocationChecker revocationChecker =(PKIXRevocationChecker) 
CertPathValidator.getInstance("PKIX").getRevocationChecker();
-            
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.SOFT_FAIL));
+            if (sslHostConfig.getOcspSoftFail()) {
+                
revocationChecker.setOptions(EnumSet.of(PKIXRevocationChecker.Option.SOFT_FAIL));
+            } else {
+                revocationChecker.setOptions(Collections.emptySet());
+            }
             xparams.addCertPathChecker(revocationChecker);
             enableRevocation = true;
         }
diff --git a/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java 
b/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
index 1d2362a5ed..b799d4ecf2 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
@@ -23,6 +23,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";
 
     @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 eb58a94375..17a0dff0ad 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -367,6 +367,8 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
                         
SSLHostConfig.adjustRelativePath(sslHostConfig.getCaCertificatePath()));
                 sslHostConfig.getOpenSslConf().addCmd(new 
OpenSSLConfCmd(OpenSSLConfCmd.NO_OCSP_CHECK,
                         Boolean.toString(!sslHostConfig.getOcspEnabled())));
+                sslHostConfig.getOpenSslConf().addCmd(new 
OpenSSLConfCmd(OpenSSLConfCmd.OCSP_SOFT_FAIL,
+                        Boolean.toString(sslHostConfig.getOcspSoftFail())));
             }
 
             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 7b732432ce..645f98713d 100644
--- a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLContext.java
@@ -123,6 +123,7 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
     private boolean initialized = false;
 
     private boolean noOcspCheck = false;
+    private boolean ocspSoftFail = true;
     private int ocspTimeout = 15000;
     private int ocspVerifyFlags = 0;
     private X509TrustManager x509TrustManager;
@@ -357,6 +358,8 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
             try (var localArena = Arena.ofConfined()) {
                 if (name.equals(OpenSSLConfCmd.NO_OCSP_CHECK)) {
                     ok = true;
+                } else if (name.equals(OpenSSLConfCmd.OCSP_SOFT_FAIL)) {
+                    ok = true;
                 } else if (name.equals("OCSP_TIMEOUT")) {
                     ok = true;
                 } else if (name.equals("OCSP_VERIFY_FLAGS")) {
@@ -429,7 +432,10 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
             }
             try (var localArena = Arena.ofConfined()) {
                 if (name.equals(OpenSSLConfCmd.NO_OCSP_CHECK)) {
-                    noOcspCheck = Boolean.parseBoolean(value);
+                    // Ignore - Tomcat internal - set directly
+                    rc = 1;
+                } 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);
@@ -564,6 +570,7 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
             if (value == OPTIONAL_NO_CA || !sslHostConfig.getOcspEnabled()) {
                 noOcspCheck = true;
             }
+            ocspSoftFail = sslHostConfig.getOcspSoftFail();
 
             // Set int verify_callback(int preverify_ok, X509_STORE_CTX 
*x509_ctx) callback
             SSL_CTX_set_verify(state.sslCtx, value,
@@ -1362,7 +1369,7 @@ public class OpenSSLContext implements 
org.apache.tomcat.util.net.SSLContext {
         return new OpenSSLEngine(cleaner, state.sslCtx, defaultProtocol, 
false, sessionContext, alpn, initialized,
                 sslHostConfig.getCertificateVerificationDepth(),
                 sslHostConfig.getCertificateVerification() == 
CertificateVerification.OPTIONAL_NO_CA,
-                noOcspCheck, ocspTimeout, ocspVerifyFlags);
+                noOcspCheck, ocspSoftFail, ocspTimeout, ocspVerifyFlags);
     }
 
     @Override
diff --git a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java 
b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
index 73666d347d..69fb642a86 100644
--- a/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
+++ b/java/org/apache/tomcat/util/net/openssl/panama/OpenSSLEngine.java
@@ -17,6 +17,7 @@
 package org.apache.tomcat.util.net.openssl.panama;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.foreign.Arena;
 import java.lang.foreign.MemorySegment;
@@ -177,12 +178,14 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
      * @param certificateVerificationDepth        Certificate verification 
depth
      * @param certificateVerificationOptionalNoCA Skip CA verification in 
optional mode
      * @param noOcspCheck                         Enable OCSP if true
+     * @param ocspSoftFail                        Allow OCSP checks to pass if 
the responder can't be contacted
      * @param ocspTimeout                         Timout in ms to use for OCSP 
requests
      * @param ocspVerifyFlags                     Verification flags for OCSP
      */
     OpenSSLEngine(Cleaner cleaner, MemorySegment sslCtx, String 
fallbackApplicationProtocol, boolean clientMode,
             OpenSSLSessionContext sessionContext, boolean alpn, boolean 
initialized, int certificateVerificationDepth,
-            boolean certificateVerificationOptionalNoCA, boolean noOcspCheck,  
int ocspTimeout, int ocspVerifyFlags) {
+            boolean certificateVerificationOptionalNoCA, boolean noOcspCheck, 
boolean ocspSoftFail,
+            int ocspTimeout, int ocspVerifyFlags) {
         if (sslCtx == null) {
             throw new 
IllegalArgumentException(sm.getString("engine.noSSLContext"));
         }
@@ -205,7 +208,8 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
             var internalBIO = internalBIOPointer.get(ValueLayout.ADDRESS, 0);
             var networkBIO = networkBIOPointer.get(ValueLayout.ADDRESS, 0);
             SSL_set_bio(ssl, internalBIO, internalBIO);
-            state = new EngineState(ssl, networkBIO, 
certificateVerificationDepth, noOcspCheck, ocspTimeout, ocspVerifyFlags);
+            state = new EngineState(ssl, networkBIO, 
certificateVerificationDepth, noOcspCheck, ocspSoftFail,
+                    ocspTimeout, ocspVerifyFlags);
         }
         this.fallbackApplicationProtocol = fallbackApplicationProtocol;
         this.clientMode = clientMode;
@@ -1139,7 +1143,7 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
                     (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN()) ||
                     (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY()) 
||
                     (errnum == X509_V_ERR_CERT_UNTRUSTED()) || (errnum == 
X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE());
-            if (verifyErrorIsOptional && (state.certificateVerifyMode == 
OpenSSLContext.OPTIONAL_NO_CA)) {
+            if ((verifyErrorIsOptional || errnum == X509_V_OK()) && 
(state.certificateVerifyMode == OpenSSLContext.OPTIONAL_NO_CA)) {
                 ok = 1;
                 openssl_h_Compatibility.SSL_set_verify_result(state.ssl, 
X509_V_OK());
             }
@@ -1166,11 +1170,9 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
                  * issuer may be missing/untrusted. Fail in that case.
                  */
                 if (verifyErrorIsOptional) {
-                    if (state.certificateVerifyMode != 
OpenSSLContext.OPTIONAL_NO_CA) {
-                        X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_APPLICATION_VERIFICATION());
-                        errnum = X509_V_ERR_APPLICATION_VERIFICATION();
-                        ok = 0;
-                    }
+                    X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_APPLICATION_VERIFICATION());
+                    errnum = X509_V_ERR_APPLICATION_VERIFICATION();
+                    ok = 0;
                 } else {
                     int ocspResponse = processOCSP(state, x509ctx);
                     if (ocspResponse == V_OCSP_CERTSTATUS_REVOKED()) {
@@ -1179,8 +1181,7 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
                         X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_CERT_REVOKED());
                     } else if (ocspResponse == V_OCSP_CERTSTATUS_UNKNOWN()) {
                         errnum = X509_STORE_CTX_get_error(x509ctx);
-                        if (errnum != X509_V_ERR_UNABLE_TO_GET_CRL() &&
-                                (errnum == 
X509_V_ERR_APPLICATION_VERIFICATION() || errnum != 0)) {
+                        if (errnum != 0 && !(state.ocspSoftFail && errnum == 
X509_V_ERR_UNABLE_TO_GET_CRL())) {
                             ok = 0;
                         }
                     }
@@ -1359,7 +1360,10 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
             var nativeResponseData = 
localArena.allocateFrom(ValueLayout.JAVA_BYTE, responseData);
             var nativeResponseDataPointer = 
localArena.allocateFrom(ValueLayout.ADDRESS, nativeResponseData);
             ocspResponse = d2i_OCSP_RESPONSE(MemorySegment.NULL, 
nativeResponseDataPointer, responseData.length);
-            if (!MemorySegment.NULL.equals(ocspResponse)) {
+            if (MemorySegment.NULL.equals(ocspResponse)) {
+                // Failed to get a valid response
+                X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_APPLICATION_VERIFICATION());
+            } else {
                 if (OCSP_response_status(ocspResponse) == 
OCSP_RESPONSE_STATUS_SUCCESSFUL()) {
                     basicResponse = OCSP_response_get1_basic(ocspResponse);
                     if (OCSP_check_nonce(ocspRequest, basicResponse) == 0) {
@@ -1396,13 +1400,14 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
                     return status;
                 }
             }
+        } catch (IOException ioe) {
+            // Timeout or network error. Responder is not available.
+            log.warn(sm.getString("engine.ocspRequestError", url.toString()), 
ioe);
+            X509_STORE_CTX_set_error(x509ctx, X509_V_ERR_UNABLE_TO_GET_CRL());
         } catch (Exception e) {
             log.warn(sm.getString("engine.ocspRequestError", url.toString()), 
e);
+            X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_APPLICATION_VERIFICATION());
         } finally {
-            if (MemorySegment.NULL.equals(ocspResponse)) {
-                // Failed to get a valid response
-                X509_STORE_CTX_set_error(x509ctx, 
X509_V_ERR_APPLICATION_VERIFICATION());
-            }
             OCSP_CERTID_free(certId);
             OCSP_BASICRESP_free(basicResponse);
             OCSP_RESPONSE_free(ocspResponse);
@@ -1706,6 +1711,7 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
         private final MemorySegment networkBIO;
         private final int certificateVerificationDepth;
         private final boolean noOcspCheck;
+        private final boolean ocspSoftFail;
         private final int ocspTimeout;
         private final int ocspVerifyFlags;
 
@@ -1714,10 +1720,11 @@ public final class OpenSSLEngine extends SSLEngine 
implements SSLUtil.ProtocolIn
         private int handshakeCount = 0;
 
         private EngineState(MemorySegment ssl, MemorySegment networkBIO, int 
certificateVerificationDepth,
-                boolean noOcspCheck, int ocspTimeout, int ocspVerifyFlags) {
+                boolean noOcspCheck, boolean ocspSoftFail, int ocspTimeout, 
int ocspVerifyFlags) {
             states.put(Long.valueOf(ssl.address()), this);
             this.certificateVerificationDepth = certificateVerificationDepth;
             this.noOcspCheck = noOcspCheck;
+            this.ocspSoftFail = ocspSoftFail;
             this.ocspTimeout = ocspTimeout;
             this.ocspVerifyFlags = ocspVerifyFlags;
             // Use another arena to avoid keeping a reference through segments
diff --git a/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java 
b/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
index 0421dab3b6..46d059c056 100644
--- a/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
+++ b/test/org/apache/tomcat/util/net/ocsp/OcspBaseTest.java
@@ -101,6 +101,12 @@ public class OcspBaseTest extends TomcatBaseTest {
 
     protected void doTest(boolean clientCertValid, boolean serverCertValid, 
ClientCertificateVerification verifyClientCert,
             boolean verifyServerCert) throws Exception {
+        doTest(clientCertValid, serverCertValid, verifyClientCert, 
verifyServerCert, null);
+    }
+
+
+    protected void doTest(boolean clientCertValid, boolean serverCertValid, 
ClientCertificateVerification verifyClientCert,
+            boolean verifyServerCert, Boolean softFail) throws Exception {
 
         Assume.assumeFalse(!useOpenSSLTrust && verifyClientCert == 
ClientCertificateVerification.OPTIONAL_NO_CA);
 
@@ -140,6 +146,10 @@ public class OcspBaseTest extends TomcatBaseTest {
             TesterSupport.configureClientSsl(verifyServerCert, 
TesterSupport.CLIENT_CRL_JKS);
         }
 
+        if (softFail != null) {
+            sslHostConfig.setOcspSoftFail(softFail.booleanValue());
+        }
+
         tomcat.start();
 
         int rc = getUrl("https://localhost:"; + getPort() + "/simple", new 
ByteChunk(), false);
diff --git a/test/org/apache/tomcat/util/net/ocsp/TestOcspSoftFail.java 
b/test/org/apache/tomcat/util/net/ocsp/TestOcspSoftFail.java
new file mode 100644
index 0000000000..7252f9de7e
--- /dev/null
+++ b/test/org/apache/tomcat/util/net/ocsp/TestOcspSoftFail.java
@@ -0,0 +1,48 @@
+/*
+ * 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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/*
+ * Only looking to test the Tomcat (server) side configuration of soft fail.
+ */
+@RunWith(Parameterized.class)
+public class TestOcspSoftFail extends OcspBaseTest {
+
+    @Test
+    public void testNoResponderDefaultSoftFail() throws Exception {
+        // Default behaviour should be the same for all configurations and 
equivalent to enabled.
+        doTest(false, false, ClientCertificateVerification.ENABLED, false, 
null);
+    }
+
+
+    @Test
+    public void testNoResponderWithSoftFail() throws Exception {
+        doTest(false, false, ClientCertificateVerification.ENABLED, false, 
Boolean.TRUE);
+    }
+
+
+    @Test(expected = SSLHandshakeException.class)
+    public void testNoResponderWithoutSoftFail() throws Exception {
+        doTest(false, false, ClientCertificateVerification.ENABLED, false, 
Boolean.FALSE);
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 2a5a889281..30d5e1c5dc 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -282,9 +282,13 @@
       <add>
         Add OCSP support to JSSE based TLS connectors and make the use of OCSP
         configurable per connector for both JSSE and OpenSSL based TLS
-        implementations and align the checks performed by OpenSSL with those
+        implementations. Align the checks performed by OpenSSL with those
         performed by JSSE. (markt)
       </add>
+      <add>
+        Add support for soft failure of OCSP checks with soft failure support
+        disabled by default. (markt)
+      </add>
     </changelog>
   </subsection>
   <subsection name="Jasper">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 77ed17c417..3b6669d3b4 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1362,6 +1362,16 @@
       used.</p>
     </attribute>
 
+    <attribute name="ocspSoftFail" required="false">
+      <p>By default, if an OCSP check fails for any reason the associated TLS
+      handhskae will also fail and a TLS connection will not be established. If
+      this attribute is set to <code>true</code>, OCSP checks that fail but do
+      not return an explicit failure status to Tomcat (e.g. the OCSP check 
times
+      out) will not cause the TLS handshake to fail.</p>
+      <p>If not specified, the default value of <code>false</code> will be
+      used.</p>
+    </attribute>
+
     <attribute name="protocols" required="false">
       <p>The names of the protocols to support when communicating with clients.
       This should be a list of any combination of the following:


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to