Skip to content

Commit e907bac

Browse files
authored
ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties for ssl.crl and ssl.ocsp (#2339)
This is a cherry-pick commit from 770804b and 45b49a5.
1 parent 927c097 commit e907bac

15 files changed

Lines changed: 1019 additions & 61 deletions

File tree

zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,6 +1779,10 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
17791779
(Java system properties: **zookeeper.ssl.ocsp** and **zookeeper.ssl.quorum.ocsp**)
17801780
**New in 3.5.5:**
17811781
Specifies whether Online Certificate Status Protocol is enabled in client and quorum TLS protocols.
1782+
**Changed in next feature version:**
1783+
Currently, *ssl.ocsp* and *ssl.quorum.ocsp* implies *ssl.crl* and *ssl.quorum.crl* correspondingly.
1784+
In next feature release, one has to setup both *ssl.crl* and *ssl.ocsp* (or *ssl.quorum.crl* and *ssl.quorum.ocsp*)
1785+
to enable OCSP. This is consistent with jdk's method of [Setting up a Java Client to use Client-Driven OCSP](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ocsp.html#setting-up-a-java-client-to-use-client-driven-ocsp).
17821786
Default: false
17831787

17841788
* *ssl.clientAuth* and *ssl.quorum.clientAuth* :

zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@
3131
import java.security.KeyStore;
3232
import java.security.NoSuchAlgorithmException;
3333
import java.security.Security;
34+
import java.security.cert.CertPathValidator;
3435
import java.security.cert.PKIXBuilderParameters;
36+
import java.security.cert.PKIXRevocationChecker;
3537
import java.security.cert.X509CertSelector;
3638
import java.util.ArrayList;
3739
import java.util.Arrays;
40+
import java.util.HashSet;
3841
import java.util.List;
3942
import java.util.Objects;
43+
import java.util.Set;
4044
import java.util.concurrent.atomic.AtomicReference;
4145
import java.util.function.Supplier;
4246
import java.util.stream.Collectors;
@@ -598,12 +602,36 @@ public static X509TrustManager createTrustManager(
598602
KeyStore ts = loadTrustStore(trustStoreLocation, trustStorePassword, trustStoreTypeProp);
599603
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
600604
if (crlEnabled || ocspEnabled) {
601-
pbParams.setRevocationEnabled(true);
602-
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
603-
System.setProperty("com.sun.security.enableCRLDP", "true");
604-
if (ocspEnabled) {
605-
Security.setProperty("ocsp.enable", "true");
605+
// See [RevocationChecker][1] for details. Basically, we are mimicking the legacy path,
606+
// which relies significantly on jvm wide properties[2], as that is the path we are routing
607+
// before (i.e. no explicit `PKIXRevocationChecker`).
608+
//
609+
// By reading but not writing these properties, we conform to but not interfere with what
610+
// admin set while still keep backward compatibility.
611+
// 1. Default "zookeeper.ssl.crl" to jvm property "com.sun.net.ssl.checkRevocation" if it is unset in upcoming feature version.
612+
// 2. Default "zookeeper.ssl.ocsp" to jvm security property "ocsp.enable" if it is unset in upcoming feature version.
613+
// 3. Set `Option.ONLY_END_ENTITY` for jvm security property "com.sun.security.onlyCheckRevocationOfEECert".
614+
// 4. Don't set "com.sun.security.enableCRLDP" as it is always enabled in no legacy path.
615+
//
616+
// See also:
617+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ocsp.html
618+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
619+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#PKIXRevocationChecker
620+
//
621+
// [1]: https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L124
622+
// [2]: https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L179
623+
Set<PKIXRevocationChecker.Option> options = new HashSet<>();
624+
if (!ocspEnabled) {
625+
options.add(PKIXRevocationChecker.Option.NO_FALLBACK);
626+
options.add(PKIXRevocationChecker.Option.PREFER_CRLS);
606627
}
628+
if (Boolean.parseBoolean(Security.getProperty("com.sun.security.onlyCheckRevocationOfEECert"))) {
629+
options.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY);
630+
}
631+
632+
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) CertPathValidator.getInstance("PKIX").getRevocationChecker();
633+
revocationChecker.setOptions(options);
634+
pbParams.addCertPathChecker(revocationChecker);
607635
} else {
608636
pbParams.setRevocationEnabled(false);
609637
}

zookeeper-server/src/main/java/org/apache/zookeeper/server/auth/X509AuthenticationProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ public X509AuthenticationProvider() throws X509Exception {
8585
x509Util.getSslKeystorePasswdPathProperty());
8686
String keyStoreTypeProp = config.getProperty(x509Util.getSslKeystoreTypeProperty());
8787

88-
boolean crlEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty()));
89-
boolean ocspEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslOcspEnabledProperty()));
88+
boolean crlEnabled = config.getBoolean(x509Util.getSslCrlEnabledProperty());
89+
boolean ocspEnabled = config.getBoolean(x509Util.getSslOcspEnabledProperty());
9090
boolean hostnameVerificationEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
9191
boolean clientHostnameVerificationEnabled = x509Util.isClientHostnameVerificationEnabled(config);
9292
boolean allowReverseDnsLookup = Boolean.parseBoolean(config.getProperty(x509Util.getSslAllowReverseDnsLookupProperty()));

zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public class X509TestContext {
8080
* @throws GeneralSecurityException
8181
* @throws OperatorCreationException
8282
*/
83-
private X509TestContext(File tempDir, KeyPair trustStoreKeyPair, long trustStoreCertExpirationMillis, String trustStorePassword, KeyPair keyStoreKeyPair, long keyStoreCertExpirationMillis, String keyStorePassword, Boolean hostnameVerification) throws IOException, GeneralSecurityException, OperatorCreationException {
83+
private X509TestContext(File tempDir, KeyPair trustStoreKeyPair, long trustStoreCertExpirationMillis, String trustStorePassword, KeyPair keyStoreKeyPair, long keyStoreCertExpirationMillis, String keyStorePassword, Boolean hostnameVerification) throws Exception {
8484
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
8585
throw new IllegalStateException("BC Security provider was not found");
8686
}
@@ -425,7 +425,7 @@ public Builder() {
425425
* @throws GeneralSecurityException
426426
* @throws OperatorCreationException
427427
*/
428-
public X509TestContext build() throws IOException, GeneralSecurityException, OperatorCreationException {
428+
public X509TestContext build() throws Exception {
429429
KeyPair trustStoreKeyPair = X509TestHelpers.generateKeyPair(trustStoreKeyType);
430430
KeyPair keyStoreKeyPair = X509TestHelpers.generateKeyPair(keyStoreKeyType);
431431
return new X509TestContext(tempDir, trustStoreKeyPair, trustStoreCertExpirationMillis, trustStorePassword, keyStoreKeyPair, keyStoreCertExpirationMillis, keyStorePassword, hostnameVerification);

zookeeper-server/src/test/java/org/apache/zookeeper/common/X509TestHelpers.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@
3636
import java.security.cert.X509Certificate;
3737
import java.security.spec.ECGenParameterSpec;
3838
import java.security.spec.RSAKeyGenParameterSpec;
39+
import java.time.Duration;
3940
import java.util.Date;
4041
import org.bouncycastle.asn1.DERIA5String;
4142
import org.bouncycastle.asn1.DEROctetString;
4243
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
4344
import org.bouncycastle.asn1.x500.X500Name;
45+
import org.bouncycastle.asn1.x500.X500NameBuilder;
46+
import org.bouncycastle.asn1.x500.style.BCStyle;
4447
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
4548
import org.bouncycastle.asn1.x509.BasicConstraints;
4649
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
@@ -53,6 +56,7 @@
5356
import org.bouncycastle.cert.X509CertificateHolder;
5457
import org.bouncycastle.cert.X509v3CertificateBuilder;
5558
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
59+
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
5660
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
5761
import org.bouncycastle.crypto.util.PrivateKeyFactory;
5862
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@@ -85,6 +89,27 @@ public class X509TestHelpers {
8589
// Per RFC 5280 section 4.1.2.2, X509 certificates can use up to 20 bytes == 160 bits for serial numbers.
8690
private static final int SERIAL_NUMBER_MAX_BITS = 20 * Byte.SIZE;
8791

92+
public static X509Certificate newSelfSignedCert(String name, KeyPair keyPair) throws IOException, OperatorCreationException, GeneralSecurityException {
93+
X500NameBuilder caNameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
94+
caNameBuilder.addRDN(BCStyle.CN, name);
95+
return newSelfSignedCACert(caNameBuilder.build(), keyPair, Duration.ofDays(1).toMillis());
96+
}
97+
98+
@FunctionalInterface
99+
public interface CertificateCustomization {
100+
void customize(X509v3CertificateBuilder builder) throws Exception;
101+
}
102+
103+
public static X509Certificate newCert(X509Certificate caCert, KeyPair caKeyPair, String name, PublicKey certPublicKey, CertificateCustomization customization) throws Exception {
104+
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
105+
nameBuilder.addRDN(BCStyle.CN, name);
106+
return newCert(caCert, caKeyPair, nameBuilder.build(), certPublicKey, Duration.ofDays(1).toMillis(), customization);
107+
}
108+
109+
public static X509Certificate newCert(X509Certificate caCert, KeyPair caKeyPair, String name, PublicKey certPublicKey) throws Exception {
110+
return newCert(caCert, caKeyPair, name, certPublicKey, null);
111+
}
112+
88113
/**
89114
* Uses the private key of the given key pair to create a self-signed CA certificate with the public half of the
90115
* key pair and the given subject and expiration. The issuer of the new cert will be equal to the subject.
@@ -102,6 +127,11 @@ public class X509TestHelpers {
102127
*/
103128
public static X509Certificate newSelfSignedCACert(
104129
X500Name subject, KeyPair keyPair, long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
130+
return newSelfSignedCACert(subject, keyPair, expirationMillis, null);
131+
}
132+
133+
public static X509Certificate newSelfSignedCACert(
134+
X500Name subject, KeyPair keyPair, long expirationMillis, CertificateCustomization customization) throws IOException, OperatorCreationException, GeneralSecurityException {
105135
Date now = new Date();
106136
X509v3CertificateBuilder builder = initCertBuilder(subject, // for self-signed certs, issuer == subject
107137
now, new Date(now.getTime()
@@ -129,7 +159,12 @@ now, new Date(now.getTime()
129159
* @throws GeneralSecurityException
130160
*/
131161
public static X509Certificate newCert(
132-
X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis) throws IOException, OperatorCreationException, GeneralSecurityException {
162+
X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis) throws Exception {
163+
return newCert(caCert, caKeyPair, certSubject, certPublicKey, expirationMillis, null);
164+
}
165+
166+
public static X509Certificate newCert(
167+
X509Certificate caCert, KeyPair caKeyPair, X500Name certSubject, PublicKey certPublicKey, long expirationMillis, CertificateCustomization customization) throws Exception {
133168
if (!caKeyPair.getPublic().equals(caCert.getPublicKey())) {
134169
throw new IllegalArgumentException("CA private key does not match the public key in the CA cert");
135170
}
@@ -143,6 +178,9 @@ public static X509Certificate newCert(
143178
builder.addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth}));
144179

145180
builder.addExtension(Extension.subjectAlternativeName, false, getLocalhostSubjectAltNames());
181+
if (customization != null) {
182+
customization.customize(builder);
183+
}
146184
return buildAndSignCertificate(caKeyPair.getPrivate(), builder);
147185
}
148186

@@ -172,7 +210,7 @@ private static GeneralNames getLocalhostSubjectAltNames() throws UnknownHostExce
172210
*/
173211
private static X509v3CertificateBuilder initCertBuilder(
174212
X500Name issuer, Date notBefore, Date notAfter, X500Name subject, PublicKey subjectPublicKey) {
175-
return new X509v3CertificateBuilder(issuer, new BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG), notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
213+
return new JcaX509v3CertificateBuilder(issuer, new BigInteger(SERIAL_NUMBER_MAX_BITS, PRNG), notBefore, notAfter, subject, SubjectPublicKeyInfo.getInstance(subjectPublicKey.getEncoded()));
176214
}
177215

178216
/**

zookeeper-server/src/test/java/org/apache/zookeeper/common/X509UtilTest.java

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
2222
import static org.junit.jupiter.api.Assertions.assertEquals;
23-
import static org.junit.jupiter.api.Assertions.assertFalse;
2423
import static org.junit.jupiter.api.Assertions.assertThrows;
2524
import static org.junit.jupiter.api.Assertions.assertTrue;
2625
import io.netty.buffer.UnpooledByteBufAllocator;
@@ -32,7 +31,6 @@
3231
import java.net.Socket;
3332
import java.nio.file.Path;
3433
import java.security.NoSuchAlgorithmException;
35-
import java.security.Security;
3634
import java.util.Arrays;
3735
import java.util.List;
3836
import java.util.concurrent.Callable;
@@ -90,10 +88,6 @@ public void cleanUp() {
9088
System.clearProperty(x509Util.getCipherSuitesProperty());
9189
System.clearProperty(x509Util.getSslProtocolProperty());
9290
System.clearProperty(x509Util.getSslHandshakeDetectionTimeoutMillisProperty());
93-
System.clearProperty("com.sun.net.ssl.checkRevocation");
94-
System.clearProperty("com.sun.security.enableCRLDP");
95-
Security.setProperty("ocsp.enable", Boolean.FALSE.toString());
96-
Security.setProperty("com.sun.security.enableCRLDP", Boolean.FALSE.toString());
9791
System.clearProperty(ServerCnxnFactory.ZOOKEEPER_SERVER_CNXN_FACTORY);
9892
System.clearProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET);
9993
x509Util.close();
@@ -227,49 +221,6 @@ public void testCreateSSLContextWithCustomCipherSuites(
227221
assertArrayEquals(customCipherSuites, sslSocket.getEnabledCipherSuites());
228222
}
229223

230-
// It would be great to test the value of PKIXBuilderParameters#setRevocationEnabled but it does not appear to be
231-
// possible
232-
@ParameterizedTest
233-
@MethodSource("data")
234-
@Timeout(value = 5)
235-
public void testCRLEnabled(
236-
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
237-
throws Exception {
238-
init(caKeyType, certKeyType, keyPassword, paramIndex);
239-
System.setProperty(x509Util.getSslCrlEnabledProperty(), "true");
240-
x509Util.getDefaultSSLContext();
241-
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
242-
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
243-
assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
244-
}
245-
246-
@ParameterizedTest
247-
@MethodSource("data")
248-
@Timeout(value = 5)
249-
public void testCRLDisabled(
250-
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
251-
throws Exception {
252-
init(caKeyType, certKeyType, keyPassword, paramIndex);
253-
x509Util.getDefaultSSLContext();
254-
assertFalse(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
255-
assertFalse(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
256-
assertFalse(Boolean.valueOf(Security.getProperty("ocsp.enable")));
257-
}
258-
259-
@ParameterizedTest
260-
@MethodSource("data")
261-
@Timeout(value = 5)
262-
public void testOCSPEnabled(
263-
X509KeyType caKeyType, X509KeyType certKeyType, String keyPassword, Integer paramIndex)
264-
throws Exception {
265-
init(caKeyType, certKeyType, keyPassword, paramIndex);
266-
System.setProperty(x509Util.getSslOcspEnabledProperty(), "true");
267-
x509Util.getDefaultSSLContext();
268-
assertTrue(Boolean.valueOf(System.getProperty("com.sun.net.ssl.checkRevocation")));
269-
assertTrue(Boolean.valueOf(System.getProperty("com.sun.security.enableCRLDP")));
270-
assertTrue(Boolean.valueOf(Security.getProperty("ocsp.enable")));
271-
}
272-
273224
@ParameterizedTest
274225
@MethodSource("data")
275226
@Timeout(value = 5)

0 commit comments

Comments
 (0)