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 <[email protected]>
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;
- }
}