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 45b49a5aa ZOOKEEPER-4955: [ADDENDUM] Refactor Ca, Cert and etc. so to 
be reusable
45b49a5aa is described below

commit 45b49a5aa9d7f2f699619eb9f486e2b89bdc19d7
Author: Kezhu Wang <kez...@apache.org>
AuthorDate: Wed Aug 27 23:51:34 2025 +0800

    ZOOKEEPER-4955: [ADDENDUM] Refactor Ca, Cert and etc. so to be reusable
    
    Reviewers: anmolnar
    Author: kezhuw
    Closes #2300 from kezhuw/ZOOKEEPER-4955-addendum-refactor-test-helpers
---
 .../java/org/apache/zookeeper/common/ssl/Ca.java   | 193 ++++++++++
 .../java/org/apache/zookeeper/common/ssl/Cert.java |  91 +++++
 .../apache/zookeeper/common/ssl/CertSigner.java    | 174 +++++++++
 .../apache/zookeeper/common/ssl/OCSPHandler.java   | 128 +++++++
 .../org/apache/zookeeper/common/ssl/PemFile.java   |  31 ++
 .../zookeeper/common/ssl/X509CertBuilder.java      |  26 ++
 .../zookeeper/server/ClientSSLRevocationTest.java  | 406 ++-------------------
 7 files changed, 679 insertions(+), 370 deletions(-)

diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Ca.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Ca.java
new file mode 100644
index 000000000..dfeea3012
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Ca.java
@@ -0,0 +1,193 @@
+/*
+ * 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.common.ssl;
+
+import com.sun.net.httpserver.HttpServer;
+import java.io.FileWriter;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.zookeeper.common.X509TestHelpers;
+import org.bouncycastle.asn1.ASN1GeneralizedTime;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
+import org.bouncycastle.openssl.MiscPEMGenerator;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.util.io.pem.PemWriter;
+
+public class Ca implements AutoCloseable {
+    public static class CaBuilder {
+        private final Path dir;
+        private String name = "CA";
+        private boolean ocsp = false;
+
+        CaBuilder(Path dir) {
+            this.dir = dir;
+        }
+
+        public CaBuilder withName(String name) {
+            this.name = Objects.requireNonNull(name);
+            return this;
+        }
+
+        public CaBuilder withOcsp() {
+            this.ocsp = true;
+            return this;
+        }
+
+        public Ca build() throws Exception {
+            KeyPair caKey = X509TestHelpers.generateRSAKeyPair();
+            X509Certificate caCert = X509TestHelpers.newSelfSignedCert(name, 
caKey);
+            if (ocsp) {
+                HttpServer ocspServer = HttpServer.create(new 
InetSocketAddress("127.0.0.1", 0), 0);
+                Ca ca = new Ca(dir, name, caKey, caCert, ocspServer);
+                ca.ocspServer.createContext("/", new OCSPHandler(ca));
+                ca.ocspServer.start();
+                return ca;
+            }
+            return new Ca(dir, name, caKey, caCert, null);
+        }
+    }
+
+    public final Path dir;
+    public final String name;
+    public final KeyPair key;
+    public final X509Certificate cert;
+    public final Map<X509Certificate, RevokedInfo> crlRevokedCerts = 
Collections.synchronizedMap(new HashMap<>());
+    public final Map<X509Certificate, RevokedInfo> ocspRevokedCerts = 
Collections.synchronizedMap(new HashMap<>());
+    public final HttpServer ocspServer;
+    public final AtomicLong crlNumber = new AtomicLong(1);
+    public final PemFile pemFile;
+
+    Ca(Path dir, String name, KeyPair key, X509Certificate cert, HttpServer 
ocspServer) throws Exception {
+        this.dir = dir;
+        this.name = name;
+        this.key = key;
+        this.cert = cert;
+        this.ocspServer = ocspServer;
+        this.pemFile = writePem();
+    }
+
+    private 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(Cert cert) throws Exception {
+        Objects.requireNonNull(cert.crl, "cert is signed with no crldp");
+        Instant now = Instant.now();
+
+        X509v2CRLBuilder builder = new 
JcaX509v2CRLBuilder(cert.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, cert.crl, StandardCopyOption.REPLACE_EXISTING, 
StandardCopyOption.ATOMIC_MOVE);
+    }
+
+    public void revoke_through_crldp(Cert 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);
+    }
+
+    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 CertSigner signer(String name) throws Exception {
+        return new CertSigner(this, name);
+    }
+
+    public Cert sign(String name) throws Exception {
+        return signer(name).sign();
+    }
+
+    public Cert sign_with_crldp(String name) throws Exception {
+        return signer(name).withCrldp().sign();
+    }
+
+    public Cert sign_with_ocsp(String name) throws Exception {
+        return signer(name).withOcsp().sign();
+    }
+
+    public static CaBuilder builder(Path dir) {
+        return new CaBuilder(dir);
+    }
+
+    public static Ca create(Path dir) throws Exception {
+        return Ca.builder(dir).build();
+    }
+
+    public static Ca create(String name, Path dir) throws Exception {
+        return Ca.builder(dir).withName(name).build();
+    }
+
+    public String getOcspAddress() {
+        if (ocspServer != null) {
+            return String.format("http://127.0.0.1:%d";, 
ocspServer.getAddress().getPort());
+        }
+        throw new IllegalStateException("No OCSP server available");
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (ocspServer != null) {
+            ocspServer.stop(0);
+        }
+    }
+}
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Cert.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Cert.java
new file mode 100644
index 000000000..d388a7a5d
--- /dev/null
+++ b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/Cert.java
@@ -0,0 +1,91 @@
+/*
+ * 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.common.ssl;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.util.Properties;
+import java.util.UUID;
+import org.apache.zookeeper.client.ZKClientConfig;
+import org.apache.zookeeper.common.X509TestHelpers;
+
+public class Cert {
+    public final String name;
+    public final KeyPair key;
+    public final X509Certificate cert;
+    public final Path dir;
+    public final Path crl;
+
+    Cert(String name, KeyPair key, X509Certificate cert, Path dir, Path crl) {
+        this.name = name;
+        this.key = key;
+        this.cert = cert;
+        this.dir = dir;
+        this.crl = crl;
+    }
+
+    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);
+    }
+
+    public Properties buildServerProperties(Ca ca) throws Exception {
+        final Properties config = new Properties();
+        config.put("clientPort", "0");
+        config.put("secureClientPort", "0");
+
+        // explicitly ipv4 to avoid dns lookup issue
+        config.put("clientPortAddress", "127.0.0.1");
+        config.put("secureClientPortAddress", "127.0.0.1");
+
+        config.put("admin.enableServer", "false");
+        config.put("admin.rateLimiterIntervalInMS", "0");
+
+        PemFile serverPem = writePem();
+
+        // TLS config fields
+        config.put("ssl.keyStore.location", serverPem.file.toString());
+        config.put("ssl.keyStore.password", serverPem.password);
+        config.put("ssl.trustStore.location", ca.pemFile.file.toString());
+
+        // Netty is required for TLS
+        config.put("serverCnxnFactory", 
org.apache.zookeeper.server.NettyServerCnxnFactory.class.getName());
+        config.put("4lw.commands.whitelist", "*");
+        return config;
+    }
+
+    public ZKClientConfig buildClientConfig(Ca ca) throws Exception {
+        PemFile pemFile = 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.pemFile.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/common/ssl/CertSigner.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/CertSigner.java
new file mode 100644
index 000000000..28ae6d61e
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/CertSigner.java
@@ -0,0 +1,174 @@
+/*
+ * 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.common.ssl;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import org.apache.zookeeper.common.X509TestHelpers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLDistPoint;
+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.KeyUsage;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+public class CertSigner {
+    private final Ca ca;
+    private final String name;
+
+    private Path crldp;
+    private boolean ocsp;
+
+    private final List<String> dnsNames = new ArrayList<>();
+    private final List<String> ipAddresses = new ArrayList<>();
+    private Duration expiration = Duration.ofDays(1);
+    private X509CertBuilder certBuilder;
+
+    CertSigner(Ca ca, String name) {
+        this.ca = ca;
+        this.name = name;
+    }
+
+    public CertSigner withCrldp() throws Exception {
+        this.crldp = Files.createTempFile(ca.dir, String.format("%s-crldp-", 
name), ".pem");
+        return this;
+    }
+
+    public CertSigner withOcsp() {
+        this.ocsp = true;
+        return this;
+    }
+
+    public CertSigner withDnsName(String name) {
+        dnsNames.add(name);
+        return this;
+    }
+
+    public CertSigner withResolvedDns(String name) throws Exception {
+        dnsNames.add(name);
+        InetAddress[] localAddresses = InetAddress.getAllByName("localhost");
+        for (InetAddress addr : localAddresses) {
+            ipAddresses.add(addr.getHostAddress());
+        }
+        return this;
+    }
+
+    public CertSigner withIpAddress(String ipAddress) {
+        ipAddresses.add(ipAddress);
+        return this;
+    }
+
+    /**
+     * Default to {@code Duration.ofDays(1)}.
+     */
+    public CertSigner withExpiration(Duration expiration) {
+        this.expiration = expiration;
+        return this;
+    }
+
+    public CertSigner withCertBuilder(X509CertBuilder certBuilder) {
+        this.certBuilder = certBuilder;
+        return this;
+    }
+
+    public Cert sign() throws Exception {
+        X509CertificateHolder holder = new JcaX509CertificateHolder(ca.cert);
+        ContentSigner signer = new 
JcaContentSignerBuilder("SHA256WithRSAEncryption").build(ca.key.getPrivate());
+
+        List<GeneralName> generalNames = new ArrayList<>();
+        for (String dnsName : dnsNames) {
+            generalNames.add(new GeneralName(GeneralName.dNSName, dnsName));
+        }
+        for (String ipAddress : ipAddresses) {
+            generalNames.add(new GeneralName(GeneralName.iPAddress, 
ipAddress));
+        }
+
+        Instant now = Instant.now();
+        KeyPair key = X509TestHelpers.generateRSAKeyPair();
+        JcaX509v3CertificateBuilder jcaX509v3CertificateBuilder = new 
JcaX509v3CertificateBuilder(
+                holder.getSubject(),
+                new BigInteger(128, new Random()),
+                Date.from(now.minus(Duration.ofSeconds(10))),
+                Date.from(now.plus(expiration)),
+                new X500Name(String.format("CN=%s", name)),
+                key.getPublic());
+        X509v3CertificateBuilder certificateBuilder = 
jcaX509v3CertificateBuilder
+                .addExtension(Extension.basicConstraints, true, new 
BasicConstraints(false))
+                .addExtension(Extension.keyUsage, true, new 
KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
+
+        if (!generalNames.isEmpty()) {
+            certificateBuilder.addExtension(
+                    Extension.subjectAlternativeName,
+                    true,
+                    new GeneralNames(generalNames.toArray(new 
GeneralName[]{})));
+        }
+
+        if (crldp != null) {
+            DistributionPointName distPointOne = new DistributionPointName(
+                    new GeneralNames(new 
GeneralName(GeneralName.uniformResourceIdentifier, "file://" + 
crldp.toAbsolutePath())));
+
+            certificateBuilder.addExtension(
+                    Extension.cRLDistributionPoints,
+                    false,
+                    new CRLDistPoint(new DistributionPoint[]{new 
DistributionPoint(distPointOne, null, null)}));
+        }
+
+        if (ocsp) {
+            certificateBuilder.addExtension(
+                    Extension.authorityInfoAccess,
+                    false,
+                    new AuthorityInformationAccess(
+                            X509ObjectIdentifiers.ocspAccessMethod,
+                            new 
GeneralName(GeneralName.uniformResourceIdentifier, ca.getOcspAddress())));
+        }
+
+        if (certBuilder != null) {
+            certBuilder.build(certificateBuilder);
+        }
+
+        X509Certificate certificate = new 
JcaX509CertificateConverter().getCertificate(certificateBuilder.build(signer));
+        Cert cert = new Cert(name, key, certificate, ca.dir, crldp);
+        if (crldp != null) {
+            ca.flush_crl(cert);
+        }
+        return cert;
+    }
+}
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/OCSPHandler.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/OCSPHandler.java
new file mode 100644
index 000000000..5cf37ac38
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/OCSPHandler.java
@@ -0,0 +1,128 @@
+/*
+ * 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.common.ssl;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Calendar;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.bouncycastle.asn1.ocsp.OCSPResponse;
+import org.bouncycastle.asn1.ocsp.OCSPResponseStatus;
+import org.bouncycastle.asn1.ocsp.RevokedInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+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.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class OCSPHandler implements HttpHandler {
+    private static final Logger LOG = 
LoggerFactory.getLogger(OCSPHandler.class);
+
+    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();
+    }
+
+}
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/PemFile.java 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/PemFile.java
new file mode 100644
index 000000000..c7f95a9e0
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/PemFile.java
@@ -0,0 +1,31 @@
+/*
+ * 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.common.ssl;
+
+import java.nio.file.Path;
+
+public class PemFile {
+    public final Path file;
+    public final String password;
+
+    public PemFile(Path file, String password) {
+        this.file = file;
+        this.password = password;
+    }
+}
diff --git 
a/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/X509CertBuilder.java
 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/X509CertBuilder.java
new file mode 100644
index 000000000..962a099fc
--- /dev/null
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/common/ssl/X509CertBuilder.java
@@ -0,0 +1,26 @@
+/*
+ * 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.common.ssl;
+
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+
+@FunctionalInterface
+public interface X509CertBuilder {
+    void build(X509v3CertificateBuilder builder) throws Exception;
+}
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
index 6e1fd2708..0471e7952 100644
--- 
a/zookeeper-server/src/test/java/org/apache/zookeeper/server/ClientSSLRevocationTest.java
+++ 
b/zookeeper-server/src/test/java/org/apache/zookeeper/server/ClientSSLRevocationTest.java
@@ -20,328 +20,34 @@
 
 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.common.ssl.Ca;
+import org.apache.zookeeper.common.ssl.Cert;
 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.BeforeEach;
 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 {
+    @BeforeEach
+    public void setup() throws Exception {
         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.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
+
         Security.setProperty("ocsp.enable", "false");
         System.clearProperty("com.sun.net.ssl.checkRevocation");
         System.clearProperty("zookeeper.ssl.crl");
@@ -351,11 +57,9 @@ public void cleanup() throws Exception {
     @Test
     public void testRevocationDisabled(@TempDir Path tmpDir) throws Exception {
         // given: crl not enabled
-        try (Ca ca = Ca.create(tmpDir)) {
-            PemFile caPem = ca.writePem();
-
+        try (Ca ca = Ca.builder(tmpDir).withOcsp().build()) {
             Cert serverCert = ca.sign_with_ocsp("server");
-            final Properties config = getServerConfig(caPem, serverCert);
+            final Properties config = serverCert.buildServerProperties(ca);
             // given: revoked server cert
             ca.revoke_through_ocsp(serverCert.cert);
             try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
@@ -366,36 +70,36 @@ public void testRevocationDisabled(@TempDir Path tmpDir) 
throws Exception {
                     .build()) {
                 server.start();
 
-                CertWithCrl client1Cert = ca.sign_with_crl("client1");
+                Cert client1Cert = ca.sign_with_crldp("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)));
+                ZKClientConfig client1Config = 
client1Cert.buildClientConfig(ca);
+                
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
             }
         }
     }
 
-    @ParameterizedTest
+    @ParameterizedTest(name = "clientRevoked = {0}")
     @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");
+            Cert server1Cert = ca.sign_with_crldp("server1");
             try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
                     .builder()
                     .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
-                    .configuration(getServerConfig(caPem, server1Cert))
+                    .configuration(server1Cert.buildServerProperties(ca))
                     .exitHandler(ExitHandler.LOG_ONLY)
                     .build()) {
                 server.start();
 
                 
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
 
-                CertWithCrl clientCert = ca.sign_with_crl("client1");
+                Cert clientCert = ca.sign_with_crldp("client1");
                 if (clientRevoked) {
                     // crl in server side is disabled, so it does not matter 
whether
                     // client cert is revoked or not.
@@ -403,7 +107,7 @@ public void testRevocationInClientUsingCrldp(boolean 
clientRevoked, @TempDir Pat
                 }
 
                 // then: ssl authentication succeed when crl is disabled
-                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
                 
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
 
                 // when: valid server cert
@@ -415,19 +119,19 @@ public void testRevocationInClientUsingCrldp(boolean 
clientRevoked, @TempDir Pat
             // 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");
+            Cert server2Cert = ca.sign_with_crldp("server2");
             ca.revoke_through_crldp(server2Cert);
             try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
                     .builder()
                     .baseDir(Files.createTempDirectory(tmpDir, "server2.data"))
-                    .configuration(getServerConfig(caPem, server2Cert))
+                    .configuration(server2Cert.buildServerProperties(ca))
                     .exitHandler(ExitHandler.LOG_ONLY)
                     .build()) {
                 server.start();
 
                 
assertTrue(ClientBase.waitForServerUp(server.getConnectionString(), 60000));
 
-                CertWithCrl clientCert = ca.sign_with_crl("client1");
+                Cert clientCert = ca.sign_with_crldp("client1");
                 if (clientRevoked) {
                     // crl in server side is disabled, so it does not matter 
whether
                     // client cert is revoked or not.
@@ -435,7 +139,7 @@ public void testRevocationInClientUsingCrldp(boolean 
clientRevoked, @TempDir Pat
                 }
 
                 // then: ssl authentication succeed when crl is disabled
-                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
                 
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, clientConfig));
 
                 // then: ssl authentication failed when crl is enabled
@@ -445,17 +149,16 @@ public void testRevocationInClientUsingCrldp(boolean 
clientRevoked, @TempDir Pat
         }
     }
 
-    @ParameterizedTest
+    @ParameterizedTest(name = "clientRevoked = {0}")
     @ValueSource(booleans = {true, false})
     public void testRevocationInClientUsingOCSP(boolean clientRevoked, 
@TempDir Path tmpDir) throws Exception {
-        try (Ca ca = Ca.create(tmpDir)) {
-            PemFile caPem = ca.writePem();
+        try (Ca ca = Ca.builder(tmpDir).withOcsp().build()) {
             // 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))
+                    .configuration(serverCert.buildServerProperties(ca))
                     .exitHandler(ExitHandler.LOG_ONLY)
                     .build()) {
                 server.start();
@@ -469,7 +172,7 @@ public void testRevocationInClientUsingOCSP(boolean 
clientRevoked, @TempDir Path
                     ca.revoke_through_ocsp(clientCert.cert);
                 }
 
-                ZKClientConfig clientConfig = getZKClientConfig(caPem, 
clientCert);
+                ZKClientConfig clientConfig = clientCert.buildClientConfig(ca);
 
                 // when: connect to serve with valid cert
                 // then: connected
@@ -493,15 +196,13 @@ public void testRevocationInClientUsingOCSP(boolean 
clientRevoked, @TempDir Path
         }
     }
 
-
-    @ParameterizedTest
+    @ParameterizedTest(name = "serverRevoked = {0}")
     @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");
+            Cert serverCert = ca.sign_with_crldp("server1");
             if (serverRevoked) {
                 // crl in client side will be disabled, so it does not matter 
whether
                 // server cert is revoked or not.
@@ -510,7 +211,7 @@ public void testRevocationInServerUsingCrldp(boolean 
serverRevoked, @TempDir Pat
             try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
                     .builder()
                     .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
-                    .configuration(getServerConfig(caPem, serverCert))
+                    .configuration(serverCert.buildServerProperties(ca))
                     .exitHandler(ExitHandler.LOG_ONLY)
                     .build()) {
                 server.start();
@@ -519,28 +220,27 @@ public void testRevocationInServerUsingCrldp(boolean 
serverRevoked, @TempDir Pat
 
                 // 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);
+                Cert client1Cert = ca.sign_with_crldp("client1");
+                ZKClientConfig client1Config = 
client1Cert.buildClientConfig(ca);
                 client1Config.setProperty("zookeeper.ssl.crl", "false");
                 
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
 
-                CertWithCrl client2Cert = ca.sign_with_crl("client2");
+                Cert client2Cert = ca.sign_with_crldp("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);
+                ZKClientConfig client2Config = 
client2Cert.buildClientConfig(ca);
                 client2Config.setProperty("zookeeper.ssl.crl", "false");
                 
assertFalse(ClientBase.waitForServerUp(server.getSecureConnectionString(), 
6000, true, client2Config));
             }
         }
     }
 
-    @ParameterizedTest
+    @ParameterizedTest(name = "serverRevoked = {0}")
     @ValueSource(booleans = {true, false})
     public void testRevocationInServerUsingOCSP(boolean serverRevoked, 
@TempDir Path tmpDir) throws Exception {
-        try (Ca ca = Ca.create(tmpDir)) {
-            PemFile caPem = ca.writePem();
+        try (Ca ca = Ca.builder(tmpDir).withOcsp().build()) {
             // given: server with crl and ocsp enabled
             System.setProperty("com.sun.net.ssl.checkRevocation", "true");
             System.setProperty("zookeeper.ssl.ocsp", "true");
@@ -553,7 +253,7 @@ public void testRevocationInServerUsingOCSP(boolean 
serverRevoked, @TempDir Path
             try (ZooKeeperServerEmbedded server = ZooKeeperServerEmbedded
                     .builder()
                     .baseDir(Files.createTempDirectory(tmpDir, "server.data"))
-                    .configuration(getServerConfig(caPem, serverCert))
+                    .configuration(serverCert.buildServerProperties(ca))
                     .exitHandler(ExitHandler.LOG_ONLY)
                     .build()) {
                 server.start();
@@ -563,7 +263,7 @@ public void testRevocationInServerUsingOCSP(boolean 
serverRevoked, @TempDir Path
                 // 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);
+                ZKClientConfig client1Config = 
client1Cert.buildClientConfig(ca);
                 client1Config.setProperty("zookeeper.ssl.crl", "false");
                 
assertTrue(ClientBase.waitForServerUp(server.getSecureConnectionString(), 6000, 
true, client1Config));
 
@@ -573,38 +273,4 @@ public void testRevocationInServerUsingOCSP(boolean 
serverRevoked, @TempDir Path
             }
         }
     }
-
-    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;
-    }
 }

Reply via email to