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

andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 770804bef ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties 
for ssl.crl and ssl.ocsp
770804bef is described below

commit 770804bef861bbfc9e150b63774f8557f1f8d995
Author: Kezhu Wang <[email protected]>
AuthorDate: Thu Aug 14 08:24:38 2025 +0800

    ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties for ssl.crl 
and ssl.ocsp
    
    ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties for ssl.crl 
and ssl.ocsp
    Refs: ZOOKEEPER-4955, apache/zookeeper#2289, stoty/zookeeper#2
    Add more comments about PKIXRevocationChecker options
    Rename test methods to reflect where revocation happens
    Refactor tests to reflect that revocation checking affects is only enforced 
in enabled side
    Fix missing fallback property for ssl.ocsp
    Reviewers: anmolnar, stoty
    Author: kezhuw
    Closes #2292 from kezhuw/ZOOKEEPER-4955-fix-interference-with-jvm-properties
---
 .../src/main/resources/markdown/zookeeperAdmin.md  |   8 +-
 .../apache/zookeeper/common/ClientX509Util.java    |   7 +-
 .../java/org/apache/zookeeper/common/X509Util.java |  45 +-
 .../server/auth/X509AuthenticationProvider.java    |   5 +-
 .../apache/zookeeper/common/X509TestContext.java   |   4 +-
 .../apache/zookeeper/common/X509TestHelpers.java   |  42 +-
 .../org/apache/zookeeper/common/X509UtilTest.java  |  49 --
 .../zookeeper/server/ClientSSLRevocationTest.java  | 610 +++++++++++++++++++++
 .../zookeeper/server/quorum/QuorumSSLTest.java     |   1 +
 9 files changed, 703 insertions(+), 68 deletions(-)

diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md 
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index a4fccf1f9..5bac829cf 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -1768,13 +1768,17 @@ and [SASL authentication for 
ZooKeeper](https://cwiki.apache.org/confluence/disp
     (Java system properties: **zookeeper.ssl.crl** and 
**zookeeper.ssl.quorum.crl**)
     **New in 3.5.5:**
     Specifies whether Certificate Revocation List is enabled in client and 
quorum TLS protocols.
-    Default: false
+    Default: jvm property "com.sun.net.ssl.checkRevocation" since 3.10.0, 
false otherwise
 
 * *ssl.ocsp* and *ssl.quorum.ocsp* :
     (Java system properties: **zookeeper.ssl.ocsp** and 
**zookeeper.ssl.quorum.ocsp**)
     **New in 3.5.5:**
     Specifies whether Online Certificate Status Protocol is enabled in client 
and quorum TLS protocols.
-    Default: false
+    **Changed in 3.10.0:**
+    Before 3.10.0, *ssl.ocsp* and *ssl.quorum.ocsp* implies *ssl.crl* and 
*ssl.quorum.crl* correspondingly.
+    After 3.10.0, one has to setup both *ssl.crl* and *ssl.ocsp* (or 
*ssl.quorum.crl* and *ssl.quorum.ocsp*)
+    to enable OCSP. This is consistent with jdk's method of [Setting up a Java 
Client to use Client-Driven 
OCSP](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ocsp.html#setting-up-a-java-client-to-use-client-driven-ocsp).
+    Default: jvm security property "ocsp.enable" since 3.10.0, false otherwise
 
 * *ssl.clientAuth* and *ssl.quorum.clientAuth* :
     (Java system properties: **zookeeper.ssl.clientAuth** and 
**zookeeper.ssl.quorum.clientAuth**)
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
index 6034b9e94..16d7f27a5 100644
--- 
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/ClientX509Util.java
@@ -23,6 +23,7 @@
 import io.netty.handler.ssl.SslContext;
 import io.netty.handler.ssl.SslContextBuilder;
 import io.netty.handler.ssl.SslProvider;
+import java.security.Security;
 import java.util.Arrays;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLEngine;
@@ -148,7 +149,7 @@ public SslContext createNettySslContextForServer(ZKConfig 
config, KeyManager key
     private SslContextBuilder handleTcnativeOcspStapling(SslContextBuilder 
builder, ZKConfig config) {
         SslProvider sslProvider = getSslProvider(config);
         boolean tcnative = sslProvider == SslProvider.OPENSSL || sslProvider 
== SslProvider.OPENSSL_REFCNT;
-        boolean ocspEnabled = config.getBoolean(getSslOcspEnabledProperty());
+        boolean ocspEnabled = config.getBoolean(getSslOcspEnabledProperty(), 
Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
 
         if (tcnative && ocspEnabled && OpenSsl.isOcspSupported()) {
             builder.enableOcsp(ocspEnabled);
@@ -201,8 +202,8 @@ private TrustManager getTrustManager(ZKConfig config) 
throws X509Exception.Trust
             getSslTruststorePasswdPathProperty());
         String trustStoreType = 
config.getProperty(getSslTruststoreTypeProperty());
 
-        boolean sslCrlEnabled = config.getBoolean(getSslCrlEnabledProperty());
-        boolean sslOcspEnabled = 
config.getBoolean(getSslOcspEnabledProperty());
+        boolean sslCrlEnabled = config.getBoolean(getSslCrlEnabledProperty(), 
Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
+        boolean sslOcspEnabled = 
config.getBoolean(getSslOcspEnabledProperty(), 
Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
         boolean sslServerHostnameVerificationEnabled = 
isServerHostnameVerificationEnabled(config);
         boolean sslClientHostnameVerificationEnabled = 
isClientHostnameVerificationEnabled(config);
 
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java 
b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
index 153a826ba..2584fecd8 100644
--- a/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java
@@ -31,11 +31,15 @@
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.Security;
+import java.security.cert.CertPathValidator;
 import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.PKIXRevocationChecker;
 import java.security.cert.X509CertSelector;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
 import javax.net.ssl.CertPathTrustManagerParameters;
@@ -392,8 +396,9 @@ public SSLContextAndOptions 
createSSLContextAndOptionsFromConfig(ZKConfig config
         String trustStorePasswordProp = 
getPasswordFromConfigPropertyOrFile(config, sslTruststorePasswdProperty, 
sslTruststorePasswdPathProperty);
         String trustStoreTypeProp = 
config.getProperty(sslTruststoreTypeProperty);
 
-        boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
-        boolean sslOcspEnabled = 
config.getBoolean(this.sslOcspEnabledProperty);
+        boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty, 
Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
+        boolean sslOcspEnabled = 
config.getBoolean(this.sslOcspEnabledProperty, 
Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
+
         boolean sslServerHostnameVerificationEnabled = 
isServerHostnameVerificationEnabled(config);
         boolean sslClientHostnameVerificationEnabled = 
isClientHostnameVerificationEnabled(config);
         boolean fipsMode = getFipsMode(config);
@@ -548,13 +553,37 @@ public static X509TrustManager createTrustManager(
         try {
             KeyStore ts = loadTrustStore(trustStoreLocation, 
trustStorePassword, trustStoreTypeProp);
             PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new 
X509CertSelector());
-            if (crlEnabled || ocspEnabled) {
-                pbParams.setRevocationEnabled(true);
-                System.setProperty("com.sun.net.ssl.checkRevocation", "true");
-                System.setProperty("com.sun.security.enableCRLDP", "true");
-                if (ocspEnabled) {
-                    Security.setProperty("ocsp.enable", "true");
+            if (crlEnabled) {
+                // See [RevocationChecker][1] for details. Basically, we are 
mimicking the legacy path,
+                // which relies significantly on jvm wide properties[2], as 
that is the path we are routing
+                // before (i.e. no explicit `PKIXRevocationChecker`).
+                //
+                // By reading but not writing these properties, we conform to 
but not interfere with what
+                // admin set while still keep backward compatibility.
+                // 1. Default "zookeeper.ssl.crl" to jvm property 
"com.sun.net.ssl.checkRevocation" if it is unset.
+                // 2. Default "zookeeper.ssl.ocsp" to jvm security property 
"ocsp.enable" if it is unset.
+                // 3. Set `Option.ONLY_END_ENTITY` for jvm security property 
"com.sun.security.onlyCheckRevocationOfEECert".
+                // 4. Don't set "com.sun.security.enableCRLDP" as it is always 
enabled in no legacy path.
+                //
+                // See also:
+                // * 
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ocsp.html
+                // * 
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
+                // * 
https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#PKIXRevocationChecker
+                //
+                // [1]: 
https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L124
+                // [2]: 
https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L179
+                Set<PKIXRevocationChecker.Option> options = new HashSet<>();
+                if (!ocspEnabled) {
+                    options.add(PKIXRevocationChecker.Option.NO_FALLBACK);
+                    options.add(PKIXRevocationChecker.Option.PREFER_CRLS);
+                }
+                if 
(Boolean.parseBoolean(Security.getProperty("com.sun.security.onlyCheckRevocationOfEECert")))
  {
+                    options.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY);
                 }
+
+                PKIXRevocationChecker revocationChecker = 
(PKIXRevocationChecker) 
CertPathValidator.getInstance("PKIX").getRevocationChecker();
+                revocationChecker.setOptions(options);
+                pbParams.addCertPathChecker(revocationChecker);
             } else {
                 pbParams.setRevocationEnabled(false);
             }
diff --git 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
index 4ea925320..8286c061c 100644
--- 
a/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
+++ 
b/zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java
@@ -18,6 +18,7 @@
 
 package org.apache.zookeeper.server.auth;
 
+import java.security.Security;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
@@ -85,8 +86,8 @@ public X509AuthenticationProvider() throws X509Exception {
                     x509Util.getSslKeystorePasswdPathProperty());
             String keyStoreTypeProp = 
config.getProperty(x509Util.getSslKeystoreTypeProperty());
 
-            boolean crlEnabled = 
Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty()));
-            boolean ocspEnabled = 
Boolean.parseBoolean(config.getProperty(x509Util.getSslOcspEnabledProperty()));
+            boolean crlEnabled = 
config.getBoolean(x509Util.getSslCrlEnabledProperty(), 
Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
+            boolean ocspEnabled = 
config.getBoolean(x509Util.getSslOcspEnabledProperty(), 
Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
             boolean hostnameVerificationEnabled = 
Boolean.parseBoolean(config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
 
             X509KeyManager km = null;
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
index f672bf468..2bb759e63 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java
@@ -80,7 +80,7 @@ public class X509TestContext {
      * @throws GeneralSecurityException
      * @throws OperatorCreationException
      */
-    private X509TestContext(File tempDir, KeyPair trustStoreKeyPair, long 
trustStoreCertExpirationMillis, String trustStorePassword, KeyPair 
keyStoreKeyPair, long keyStoreCertExpirationMillis, String keyStorePassword, 
Boolean hostnameVerification) throws IOException, GeneralSecurityException, 
OperatorCreationException {
+    private X509TestContext(File tempDir, KeyPair trustStoreKeyPair, long 
trustStoreCertExpirationMillis, String trustStorePassword, KeyPair 
keyStoreKeyPair, long keyStoreCertExpirationMillis, String keyStorePassword, 
Boolean hostnameVerification) throws Exception {
         if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
             throw new IllegalStateException("BC Security provider was not 
found");
         }
@@ -425,7 +425,7 @@ public Builder() {
          * @throws GeneralSecurityException
          * @throws OperatorCreationException
          */
-        public X509TestContext build() throws IOException, 
GeneralSecurityException, OperatorCreationException {
+        public X509TestContext build() throws Exception {
             KeyPair trustStoreKeyPair = 
X509TestHelpers.generateKeyPair(trustStoreKeyType);
             KeyPair keyStoreKeyPair = 
X509TestHelpers.generateKeyPair(keyStoreKeyType);
             return new X509TestContext(tempDir, trustStoreKeyPair, 
trustStoreCertExpirationMillis, trustStorePassword, keyStoreKeyPair, 
keyStoreCertExpirationMillis, keyStorePassword, hostnameVerification);
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
index b9f2f6db9..89907e078 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java
@@ -36,11 +36,14 @@
 import java.security.cert.X509Certificate;
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
+import java.time.Duration;
 import java.util.Date;
 import org.bouncycastle.asn1.DERIA5String;
 import org.bouncycastle.asn1.DEROctetString;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.X500NameBuilder;
+import org.bouncycastle.asn1.x500.style.BCStyle;
 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import org.bouncycastle.asn1.x509.BasicConstraints;
 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
@@ -53,6 +56,7 @@
 import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.X509v3CertificateBuilder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
 import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
 import org.bouncycastle.crypto.util.PrivateKeyFactory;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -85,6 +89,27 @@ public class X509TestHelpers {
     // Per RFC 5280 section 4.1.2.2, X509 certificates can use up to 20 bytes 
== 160 bits for serial numbers.
     private static final int SERIAL_NUMBER_MAX_BITS = 20 * Byte.SIZE;
 
+    public static X509Certificate newSelfSignedCert(String name, KeyPair 
keyPair) throws IOException, OperatorCreationException, 
GeneralSecurityException {
+        X500NameBuilder caNameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+        caNameBuilder.addRDN(BCStyle.CN, name);
+        return newSelfSignedCACert(caNameBuilder.build(), keyPair, 
Duration.ofDays(1).toMillis());
+    }
+
+    @FunctionalInterface
+    public interface CertificateCustomization {
+        void customize(X509v3CertificateBuilder builder) throws Exception;
+    }
+
+    public static X509Certificate newCert(X509Certificate caCert, KeyPair 
caKeyPair, String name, PublicKey certPublicKey, CertificateCustomization 
customization) throws Exception {
+        X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
+        nameBuilder.addRDN(BCStyle.CN, name);
+        return newCert(caCert, caKeyPair, nameBuilder.build(), certPublicKey, 
Duration.ofDays(1).toMillis(), customization);
+    }
+
+    public static X509Certificate newCert(X509Certificate caCert, KeyPair 
caKeyPair, String name, PublicKey certPublicKey) throws Exception {
+        return newCert(caCert, caKeyPair, name, certPublicKey, null);
+    }
+
     /**
      * Uses the private key of the given key pair to create a self-signed CA 
certificate with the public half of the
      * key pair and the given subject and expiration. The issuer of the new 
cert will be equal to the subject.
@@ -102,6 +127,11 @@ public class X509TestHelpers {
      */
     public static X509Certificate newSelfSignedCACert(
             X500Name subject, KeyPair keyPair, long expirationMillis) throws 
IOException, OperatorCreationException, GeneralSecurityException {
+        return newSelfSignedCACert(subject, keyPair, expirationMillis, null);
+    }
+
+    public static X509Certificate newSelfSignedCACert(
+            X500Name subject, KeyPair keyPair, long expirationMillis, 
CertificateCustomization customization) throws IOException, 
OperatorCreationException, GeneralSecurityException {
         Date now = new Date();
         X509v3CertificateBuilder builder = initCertBuilder(subject, // for 
self-signed certs, issuer == subject
                                                            now, new 
Date(now.getTime()
@@ -129,7 +159,12 @@ now, new Date(now.getTime()
      * @throws GeneralSecurityException
      */
     public static X509Certificate newCert(
-            X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, 
PublicKey certPublicKey, long expirationMillis) throws IOException, 
OperatorCreationException, GeneralSecurityException {
+            X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, 
PublicKey certPublicKey, long expirationMillis) throws Exception {
+        return newCert(caCert, caKeyPair, certSubject, certPublicKey, 
expirationMillis, null);
+    }
+
+    public static X509Certificate newCert(
+            X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, 
PublicKey certPublicKey, long expirationMillis, CertificateCustomization 
customization) throws Exception {
         if (!caKeyPair.getPublic().equals(caCert.getPublicKey())) {
             throw new IllegalArgumentException("CA private key does not match 
the public key in the CA cert");
         }
@@ -143,6 +178,9 @@ public static X509Certificate newCert(
         builder.addExtension(Extension.extendedKeyUsage, true, new 
ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, 
KeyPurposeId.id_kp_clientAuth}));
 
         builder.addExtension(Extension.subjectAlternativeName, false, 
getLocalhostSubjectAltNames());
+        if (customization != null) {
+            customization.customize(builder);
+        }
         return buildAndSignCertificate(caKeyPair.getPrivate(), builder);
     }
 
@@ -172,7 +210,7 @@ private static GeneralNames getLocalhostSubjectAltNames() 
throws UnknownHostExce
      */
     private static X509v3CertificateBuilder initCertBuilder(
             X500Name issuer, Date notBefore, Date notAfter, X500Name subject, 
PublicKey subjectPublicKey) {
-        return new X509v3CertificateBuilder(issuer, new 
BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG), notBefore, notAfter, subject, 
SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
+        return new JcaX509v3CertificateBuilder(issuer, new 
BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG), notBefore, notAfter, subject, 
SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
     }
 
     /**
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
index dd803c49f..ff054c1d6 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java
@@ -20,7 +20,6 @@
 
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import io.netty.buffer.UnpooledByteBufAllocator;
@@ -32,7 +31,6 @@
 import java.net.Socket;
 import java.nio.file.Path;
 import java.security.NoSuchAlgorithmException;
-import java.security.Security;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -90,10 +88,6 @@ public void cleanUp() {
         System.clearProperty(x509Util.getCipherSuitesProperty());
         System.clearProperty(x509Util.getSslProtocolProperty());
         
System.clearProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty());
-        System.clearProperty("com.sun.net.ssl.checkRevocation");
-        System.clearProperty("com.sun.security.enableCRLDP");
-        Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
-        Security.setProperty("com.sun.security.enableCRLDP", 
Boolean.FALSE.toString());
         System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
         System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET);
         x509Util.close();
@@ -227,49 +221,6 @@ public void testCreateSSLContextWithCustomCipherSuites(
         assertArrayEquals(customCipherSuites, 
sslSocket.getEnabledCipherSuites());
     }
 
-    // It would be great to test the value of 
PKIXBuilderParameters#setRevocationEnabled but it does not appear to be
-    // possible
-    @ParameterizedTest
-    @MethodSource("data")
-    @Timeout(value = 5)
-    public void testCRLEnabled(
-            X509KeyType caKeyType, X509KeyType certKeyType, String 
keyPassword, Integer paramIndex)
-            throws Exception {
-        init(caKeyType, certKeyType, keyPassword, paramIndex);
-        System.setProperty(x509Util.getSslCrlEnabledProperty(), "true");
-        x509Util.getDefaultSSLContext();
-        
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
-        
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
-        assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
-    }
-
-    @ParameterizedTest
-    @MethodSource("data")
-    @Timeout(value = 5)
-    public void testCRLDisabled(
-            X509KeyType caKeyType, X509KeyType certKeyType, String 
keyPassword, Integer paramIndex)
-            throws Exception {
-        init(caKeyType, certKeyType, keyPassword, paramIndex);
-        x509Util.getDefaultSSLContext();
-        
assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
-        
assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
-        assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
-    }
-
-    @ParameterizedTest
-    @MethodSource("data")
-    @Timeout(value = 5)
-    public void testOCSPEnabled(
-            X509KeyType caKeyType, X509KeyType certKeyType, String 
keyPassword, Integer paramIndex)
-            throws Exception {
-        init(caKeyType, certKeyType, keyPassword, paramIndex);
-        System.setProperty(x509Util.getSslOcspEnabledProperty(), "true");
-        x509Util.getDefaultSSLContext();
-        
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
-        
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
-        assertTrue(Boolean.valueOf(Security.getProperty("ocsp.enable")));
-    }
-
     @ParameterizedTest
     @MethodSource("data")
     @Timeout(value = 5)
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/ClientSSLRevocationTest.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ClientSSLRevocationTest.java
new file mode 100644
index 000000000..6e1fd2708
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ClientSSLRevocationTest.java
@@ -0,0 +1,610 @@
+/*
+ * 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.zookeeper.server;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.URLDecoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.security.KeyPair;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import org.apache.zookeeper.client.ZKClientConfig;
+import org.apache.zookeeper.common.X509TestHelpers;
+import org.apache.zookeeper.server.embedded.ExitHandler;
+import org.apache.zookeeper.server.embedded.ZooKeeperServerEmbedded;
+import org.apache.zookeeper.test.ClientBase;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPReq;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.Req;
+import org.bouncycastle.cert.ocsp.RevokedStatus;
+import org.bouncycastle.cert.ocsp.jcajce.JcaBasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.MiscPEMGenerator;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.bouncycastle.util.io.pem.PemWriter;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClientSSLRevocationTest {
+    private static final Logger LOG = 
LoggerFactory.getLogger(ClientSSLRevocationTest.class);
+
+    static {
+        Security.addProvider(new BouncyCastleProvider());
+    }
+
+    private static class OCSPHandler implements HttpHandler {
+        private final Ca ca;
+
+        public OCSPHandler(Ca ca) {
+            this.ca = ca;
+        }
+
+        @Override
+        public void handle(com.sun.net.httpserver.HttpExchange httpExchange) 
throws IOException {
+            byte[] responseBytes;
+            try {
+                String uri = httpExchange.getRequestURI().toString();
+                LOG.info("OCSP request: {} {}", 
httpExchange.getRequestMethod(), uri);
+                httpExchange.getRequestHeaders().entrySet().forEach((e) -> {
+                    LOG.info("OCSP request header: {} {}", e.getKey(), 
e.getValue());
+                });
+                InputStream request = httpExchange.getRequestBody();
+                byte[] requestBytes = new byte[10000];
+                int len = request.read(requestBytes);
+                LOG.info("OCSP request size {}", len);
+
+                if (len < 0) {
+                    String removedUriEncoding = 
URLDecoder.decode(uri.substring(1), "utf-8");
+                    LOG.info("OCSP request from URI no encoding {}", 
removedUriEncoding);
+                    requestBytes = 
Base64.getDecoder().decode(removedUriEncoding);
+                }
+                OCSPReq ocspRequest = new OCSPReq(requestBytes);
+                Req[] requestList = ocspRequest.getRequestList();
+                LOG.info("requestList {}", Arrays.toString(requestList));
+
+                DigestCalculator digestCalculator = new 
JcaDigestCalculatorProviderBuilder().build().get(CertificateID.HASH_SHA1);
+
+                Map<CertificateID, RevokedInfo> revokedCerts = 
ca.ocspRevokedCerts.entrySet().stream().collect(Collectors.toMap(entry -> {
+                        try {
+                            return new JcaCertificateID(digestCalculator, 
ca.cert,  entry.getKey().getSerialNumber());
+                        } catch (Exception ex) {
+                            throw new RuntimeException(ex);
+                        }
+                    }, Map.Entry::getValue));
+
+                BasicOCSPRespBuilder responseBuilder = new 
JcaBasicOCSPRespBuilder(ca.key.getPublic(), digestCalculator);
+                for (Req req : requestList) {
+                    CertificateID certId = req.getCertID();
+                    CertificateStatus certificateStatus = 
CertificateStatus.GOOD;
+                    RevokedInfo revokedInfo = revokedCerts.get(certId);
+                    if (revokedInfo != null) {
+                        certificateStatus = new RevokedStatus(revokedInfo);
+                    }
+                    responseBuilder.addResponse(certId, certificateStatus, 
null);
+                }
+
+                X509CertificateHolder[] chain = new 
X509CertificateHolder[]{new JcaX509CertificateHolder(ca.cert)};
+                ContentSigner signer = new 
JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(ca.key.getPrivate());
+                BasicOCSPResp ocspResponse = responseBuilder.build(signer, 
chain, Calendar.getInstance().getTime());
+                LOG.info("response {}", ocspResponse);
+                responseBytes = new 
OCSPRespBuilder().build(OCSPRespBuilder.SUCCESSFUL, ocspResponse).getEncoded();
+                LOG.error("OCSP server response OK");
+            } catch (Throwable exception) {
+                LOG.error("Internal OCSP server error", exception);
+                responseBytes = new OCSPResp(new OCSPResponse(new 
OCSPResponseStatus(OCSPRespBuilder.INTERNAL_ERROR), null)).getEncoded();
+            }
+
+            Headers rh = httpExchange.getResponseHeaders();
+            rh.set("Content-Type", "application/ocsp-response");
+            httpExchange.sendResponseHeaders(200, responseBytes.length);
+
+            OutputStream os = httpExchange.getResponseBody();
+            os.write(responseBytes);
+            os.close();
+        }
+
+    }
+
+    private static class PemFile {
+        private final Path file;
+        private final String password;
+
+        public PemFile(Path file, String password) {
+            this.file = file;
+            this.password = password;
+        }
+    }
+
+    private static class CertWithCrl extends Cert {
+        public final Path crl;
+
+        public CertWithCrl(String name, KeyPair key, X509Certificate cert, 
Path crl) {
+            super(name, key, cert, crl.getParent());
+            this.crl = crl;
+        }
+    }
+
+    private static class Cert {
+        public final String name;
+        public final KeyPair key;
+        public final X509Certificate cert;
+        public final Path dir;
+
+        public Cert(String name, KeyPair key, X509Certificate cert, Path dir) {
+            this.name = name;
+            this.key = key;
+            this.cert = cert;
+            this.dir = dir;
+        }
+
+        public PemFile writePem() throws Exception {
+            String password = UUID.randomUUID().toString();
+            String pem = X509TestHelpers.pemEncodeCertAndPrivateKey(cert, 
key.getPrivate(), password);
+            Path file = Files.createTempFile(dir, name, ".pem");
+            Files.write(file, pem.getBytes());
+            return new PemFile(file, password);
+        }
+    }
+
+    private static class Ca implements AutoCloseable {
+        private final String name;
+        private final KeyPair key;
+        private final X509Certificate cert;
+        private final Path dir;
+        private final Map<X509Certificate, RevokedInfo> crlRevokedCerts = 
Collections.synchronizedMap(new HashMap<>());
+        private final Map<X509Certificate, RevokedInfo> ocspRevokedCerts = 
Collections.synchronizedMap(new HashMap<>());
+        private final HttpServer ocspServer;
+        private final AtomicLong crlNumber = new AtomicLong(1);
+
+        private Ca(String name, KeyPair key, X509Certificate cert, Path dir, 
HttpServer ocspServer) throws Exception {
+            this.name = name;
+            this.key = key;
+            this.cert = cert;
+            this.dir = dir;
+            this.ocspServer = ocspServer;
+        }
+
+        public PemFile writePem() throws Exception {
+            String pem = X509TestHelpers.pemEncodeX509Certificate(cert);
+            Path file = Files.createTempFile(dir, name, ".pem");
+            Files.write(file, pem.getBytes());
+            return new PemFile(file, "");
+        }
+
+        // Check result of crldp could be cached, so use per-cert crl file.
+        public void flush_crl(Path crl) throws Exception {
+            Instant now = Instant.now();
+
+            X509v2CRLBuilder builder = new 
JcaX509v2CRLBuilder(cert.getIssuerX500Principal(), Date.from(now));
+            builder.setNextUpdate(Date.from(now.plusSeconds(2)));
+
+            builder.addExtension(Extension.authorityKeyIdentifier, false, new 
JcaX509ExtensionUtils().createAuthorityKeyIdentifier(this.cert));
+            builder.addExtension(Extension.cRLNumber, false, new 
CRLNumber(BigInteger.valueOf(crlNumber.getAndAdd(1L))));
+
+            for (Map.Entry<X509Certificate, RevokedInfo> entry : 
crlRevokedCerts.entrySet()) {
+                builder.addCRLEntry(entry.getKey().getSerialNumber(), 
entry.getValue().getRevocationTime().getDate(), CRLReason.cACompromise);
+            }
+
+            ContentSigner contentSigner = new 
JcaContentSignerBuilder("SHA256WithRSAEncryption").build(this.key.getPrivate());
+            X509CRLHolder crlHolder = builder.build(contentSigner);
+
+            Path tmpFile = Files.createTempFile(dir, "crldp-", ".pem.tmp");
+            PemWriter pemWriter = new PemWriter(new 
FileWriter(tmpFile.toFile()));
+            pemWriter.writeObject(new MiscPEMGenerator(crlHolder));
+            pemWriter.flush();
+            pemWriter.close();
+
+            Files.move(tmpFile, crl, StandardCopyOption.REPLACE_EXISTING, 
StandardCopyOption.ATOMIC_MOVE);
+        }
+
+        public void revoke_through_crldp(CertWithCrl cert) throws Exception {
+            Date now = new Date();
+            RevokedInfo revokedInfo = new RevokedInfo(new 
ASN1GeneralizedTime(now), CRLReason.lookup(CRLReason.cACompromise));
+            this.crlRevokedCerts.put(cert.cert, revokedInfo);
+            flush_crl(cert.crl);
+        }
+
+        public void revoke_through_ocsp(X509Certificate cert) throws Exception 
{
+            Date now = new Date();
+            RevokedInfo revokedInfo = new RevokedInfo(new 
ASN1GeneralizedTime(now), CRLReason.lookup(CRLReason.cACompromise));
+            this.ocspRevokedCerts.put(cert, revokedInfo);
+        }
+
+        public Cert sign(String name) throws Exception {
+            KeyPair key = X509TestHelpers.generateRSAKeyPair();
+            X509Certificate cert = X509TestHelpers.newCert(this.cert, 
this.key, name, key.getPublic());
+            return new Cert(name, key, cert, dir);
+        }
+
+        public CertWithCrl sign_with_crl(String name) throws Exception {
+            KeyPair key = X509TestHelpers.generateRSAKeyPair();
+            Path crl = Files.createTempFile(dir, String.format("%s-crldp-", 
name), ".pem");
+            X509Certificate cert = X509TestHelpers.newCert(this.cert, 
this.key, name, key.getPublic(), builder -> {
+                DistributionPointName distPointOne = new DistributionPointName(
+                        new GeneralNames(new 
GeneralName(GeneralName.uniformResourceIdentifier, "file://" + crl)));
+                builder.addExtension(
+                        Extension.cRLDistributionPoints,
+                        false,
+                        new CRLDistPoint(new DistributionPoint[]{new 
DistributionPoint(distPointOne, null, null)}));
+
+            });
+            flush_crl(crl);
+            return new CertWithCrl(name, key, cert, crl);
+        }
+
+        public Cert sign_with_ocsp(String name) throws Exception {
+            KeyPair key = X509TestHelpers.generateRSAKeyPair();
+            X509Certificate cert = X509TestHelpers.newCert(this.cert, 
this.key, name, key.getPublic(), builder -> {
+                String addr = "http://127.0.0.1:"; + 
ocspServer.getAddress().getPort();
+                builder.addExtension(
+                        Extension.authorityInfoAccess,
+                        false,
+                        new AuthorityInformationAccess(
+                                X509ObjectIdentifiers.ocspAccessMethod,
+                                new 
GeneralName(GeneralName.uniformResourceIdentifier, addr)));
+            });
+            return new Cert(name, key, cert, dir);
+        }
+
+        public static Ca create(Path dir) throws Exception {
+            return create(dir, "CA");
+        }
+
+        public static Ca create(Path dir, String name) throws Exception {
+            KeyPair caKey = X509TestHelpers.generateRSAKeyPair();
+            X509Certificate caCert = X509TestHelpers.newSelfSignedCert(name, 
caKey);
+            HttpServer ocspServer = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
+            Ca ca = new Ca(name, caKey, caCert, dir, ocspServer);
+            ca.ocspServer.createContext("/", new OCSPHandler(ca));
+            ca.ocspServer.start();
+            return ca;
+        }
+
+        @Override
+        public void close() throws Exception {
+            ocspServer.stop(0);
+        }
+    }
+
+    @AfterEach
+    public void cleanup() throws Exception {
+        Security.setProperty("ocsp.enable", "false");
+        System.clearProperty("com.sun.net.ssl.checkRevocation");
+        System.clearProperty("zookeeper.ssl.crl");
+        System.clearProperty("zookeeper.ssl.ocsp");
+    }
+
+    @Test
+    public void testRevocationDisabled(@TempDir Path tmpDir) throws Exception {
+        // given: crl not enabled
+        try (Ca ca = Ca.create(tmpDir)) {
+            PemFile caPem = ca.writePem();
+
+            Cert serverCert = ca.sign_with_ocsp("server");
+            final Properties config = getServerConfig(caPem, serverCert);
+            // given: revoked server cert
+            ca.revoke_through_ocsp(serverCert.cert);
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(config)
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                CertWithCrl client1Cert = ca.sign_with_crl("client1");
+                ca.revoke_through_crldp(client1Cert);
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                // when: connect with revoked cert.
+                // then: connected
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, getZKClientConfig(caPem, client1Cert)));
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testRevocationInClientUsingCrldp(boolean clientRevoked, 
@TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            PemFile caPem = ca.writePem();
+            // given: server cert with crldp
+            CertWithCrl server1Cert = ca.sign_with_crl("server1");
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(getServerConfig(caPem, server1Cert))
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                CertWithCrl clientCert = ca.sign_with_crl("client1");
+                if (clientRevoked) {
+                    // crl in server side is disabled, so it does not matter 
whether
+                    // client cert is revoked or not.
+                    ca.revoke_through_crldp(clientCert);
+                }
+
+                // then: ssl authentication succeed when crl is disabled
+                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
+
+                // when: valid server cert
+                // then: ssl authentication succeed when crl is enabled
+                clientConfig.setProperty("zookeeper.ssl.crl", "true");
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
+            }
+
+            // crldp check is not realtime, so we have to start a new server 
with revoked cert
+
+            // given: revoked server cert with crldp
+            CertWithCrl server2Cert = ca.sign_with_crl("server2");
+            ca.revoke_through_crldp(server2Cert);
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server2.data"))
+                    .configuration(getServerConfig(caPem, server2Cert))
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                CertWithCrl clientCert = ca.sign_with_crl("client1");
+                if (clientRevoked) {
+                    // crl in server side is disabled, so it does not matter 
whether
+                    // client cert is revoked or not.
+                    ca.revoke_through_crldp(clientCert);
+                }
+
+                // then: ssl authentication succeed when crl is disabled
+                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
+
+                // then: ssl authentication failed when crl is enabled
+                clientConfig.setProperty("zookeeper.ssl.crl", "true");
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, clientConfig));
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testRevocationInClientUsingOCSP(boolean clientRevoked, 
@TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            PemFile caPem = ca.writePem();
+            // given: server cert with ocsp
+            Cert serverCert = ca.sign_with_ocsp("server1");
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(getServerConfig(caPem, serverCert))
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                Cert clientCert = ca.sign_with_ocsp("client");
+                if (clientRevoked) {
+                    // crl in server side is disabled, so it does not matter 
whether
+                    // client cert is revoked or not.
+                    ca.revoke_through_ocsp(clientCert.cert);
+                }
+
+                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+
+                // when: connect to serve with valid cert
+                // then: connected
+                //
+                // we can't config crl using jvm properties as server will 
access them also
+                // see: https://issues.apache.org/jira/browse/ZOOKEEPER-4875
+                clientConfig.setProperty("zookeeper.ssl.crl", "true");
+                clientConfig.setProperty("zookeeper.ssl.ocsp", "true");
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
+
+                // when: server cert get revoked
+                ca.revoke_through_ocsp(serverCert.cert);
+
+                // then: ssl authentication failed when crl is enabled
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, clientConfig));
+
+                // then: ssl authentication succeed when crl is disabled
+                clientConfig.setProperty("zookeeper.ssl.crl", "false");
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
+            }
+        }
+    }
+
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testRevocationInServerUsingCrldp(boolean serverRevoked, 
@TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            PemFile caPem = ca.writePem();
+            // given: server with crl enabled
+            System.setProperty("zookeeper.ssl.crl", "true");
+            CertWithCrl serverCert = ca.sign_with_crl("server1");
+            if (serverRevoked) {
+                // crl in client side will be disabled, so it does not matter 
whether
+                // server cert is revoked or not.
+                ca.revoke_through_crldp(serverCert);
+            }
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(getServerConfig(caPem, serverCert))
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                // when: valid client cert with crldp
+                // then: ssl authentication failed when crl is enabled
+                Cert client1Cert = ca.sign_with_crl("client1");
+                ZKClientConfig client1Config = getZKClientConfig(caPem, 
client1Cert);
+                client1Config.setProperty("zookeeper.ssl.crl", "false");
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
+
+                CertWithCrl client2Cert = ca.sign_with_crl("client2");
+                ca.revoke_through_crldp(client2Cert);
+
+                // when: revoked client cert with crldp
+                // then: ssl authentication failed when crl is enabled
+                ZKClientConfig client2Config = getZKClientConfig(caPem, 
client2Cert);
+                client2Config.setProperty("zookeeper.ssl.crl", "false");
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, client2Config));
+            }
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testRevocationInServerUsingOCSP(boolean serverRevoked, 
@TempDir Path tmpDir) throws Exception {
+        try (Ca ca = Ca.create(tmpDir)) {
+            PemFile caPem = ca.writePem();
+            // given: server with crl and ocsp enabled
+            System.setProperty("com.sun.net.ssl.checkRevocation", "true");
+            System.setProperty("zookeeper.ssl.ocsp", "true");
+            Cert serverCert = ca.sign("server1");
+            if (serverRevoked) {
+                // crl in client side will be disabled, so it does not matter 
whether
+                // server cert is revoked or not.
+                ca.revoke_through_ocsp(serverCert.cert);
+            }
+            try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
+                    .builder()
+                    .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
+                    .configuration(getServerConfig(caPem, serverCert))
+                    .exitHandler(ExitHandler.LOG_ONLY)
+                    .build()) {
+                server.start();
+
+                
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
+
+                // when: valid client cert with crldp
+                // then: ssl authentication failed when crl is enabled
+                Cert client1Cert = ca.sign_with_ocsp("client1");
+                ZKClientConfig client1Config = getZKClientConfig(caPem, 
client1Cert);
+                client1Config.setProperty("zookeeper.ssl.crl", "false");
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
+
+                // ocsp is realtime, so we can reuse this client.
+                ca.revoke_through_ocsp(client1Cert.cert);
+                
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, client1Config));
+            }
+        }
+    }
+
+    private Properties getServerConfig(PemFile ca, Cert identity) throws 
Exception {
+        final Properties config = new Properties();
+        config.put("clientPort", "0");
+        config.put("secureClientPort", "0");
+        config.put("host", "localhost");
+        config.put("ticktime", "4000");
+
+        PemFile serverPem = identity.writePem();
+
+        // TLS config fields
+        //config.put("ssl.clientAuth", "need");
+        config.put("ssl.keyStore.location", serverPem.file.toString());
+        config.put("ssl.keyStore.password", serverPem.password);
+        config.put("ssl.trustStore.location", ca.file.toString());
+
+        // Netty is required for TLS
+        config.put("serverCnxnFactory", 
org.apache.zookeeper.server.NettyServerCnxnFactory.class.getName());
+        config.put("4lw.commands.whitelist", "*");
+        return config;
+    }
+
+    private ZKClientConfig getZKClientConfig(PemFile ca, Cert cert) throws 
Exception {
+        PemFile pemFile = cert.writePem();
+
+        ZKClientConfig config = new ZKClientConfig();
+        config.setProperty("zookeeper.client.secure", "true");
+        config.setProperty("zookeeper.ssl.keyStore.password", 
pemFile.password);
+        config.setProperty("zookeeper.ssl.keyStore.location", 
pemFile.file.toString());
+        config.setProperty("zookeeper.ssl.trustStore.location", 
ca.file.toString());
+        // only netty supports TLS
+        config.setProperty("zookeeper.clientCnxnSocket", 
org.apache.zookeeper.ClientCnxnSocketNetty.class.getName());
+        return config;
+    }
+}
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
index e2e1227e9..c843efd00 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/quorum/QuorumSSLTest.java
@@ -901,6 +901,7 @@ public void testOCSP(boolean fipsEnabled) throws Exception {
             assertTrue(ClientBase.waitForServerDown("127.0.0.1:" + 
clientPortQp3, CONNECTION_TIMEOUT));
 
             setSSLSystemProperties();
+            System.setProperty(quorumX509Util.getSslCrlEnabledProperty(), 
"true");
             System.setProperty(quorumX509Util.getSslOcspEnabledProperty(), 
"true");
 
             X509Certificate validCertificate = buildEndEntityCert(

Reply via email to