This is an automated email from the ASF dual-hosted git repository.
markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 566a25a3bd Add support for soft-fail and make it configurable
566a25a3bd is described below
commit 566a25a3bd0c869283376d4864a913792f6ecce6
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 | 38 ++++++++++-------
.../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(+), 19 deletions(-)
diff --git a/java/org/apache/tomcat/util/net/SSLHostConfig.java
b/java/org/apache/tomcat/util/net/SSLHostConfig.java
index 717afe015a..82ccab6f2b 100644
--- a/java/org/apache/tomcat/util/net/SSLHostConfig.java
+++ b/java/org/apache/tomcat/util/net/SSLHostConfig.java
@@ -116,6 +116,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;
@@ -621,11 +622,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 537781a4c5..041ed09140 100644
--- a/java/org/apache/tomcat/util/net/SSLUtilBase.java
+++ b/java/org/apache/tomcat/util/net/SSLUtilBase.java
@@ -540,7 +540,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 c9068a2359..b29d52f59a 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLConfCmd.java
@@ -22,6 +22,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";
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 635803a16a..03a01a003c 100644
--- a/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
+++ b/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java
@@ -387,6 +387,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 0e38a0e543..bd001fdf03 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,7 +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;
}
}
@@ -1357,7 +1359,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) {
@@ -1394,13 +1399,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);
@@ -1704,6 +1710,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;
@@ -1712,10 +1719,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 0615286f8a..43ee788d92 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 17ab442447..830c641728 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -171,9 +171,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="Cluster">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 7ccaaa5a23..b0e1102163 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -1578,6 +1578,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]