Skip to content

Commit 770804b

Browse files
authored
ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties for ssl.crl and ssl.ocsp
ZOOKEEPER-4955: Fix intererence with jvm wide ssl properties for ssl.crl and ssl.ocsp Refs: ZOOKEEPER-4955, #2289, stoty#2 Add more comments about PKIXRevocationChecker options Rename test methods to reflect where revocation happens Refactor tests to reflect that revocation checking affects is only enforced in enabled side Fix missing fallback property for ssl.ocsp Reviewers: anmolnar, stoty Author: kezhuw Closes #2292 from kezhuw/ZOOKEEPER-4955-fix-interference-with-jvm-properties
1 parent bec08df commit 770804b

File tree

9 files changed

+703
-68
lines changed

9 files changed

+703
-68
lines changed

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,13 +1768,17 @@ and [SASL authentication for ZooKeeper](https://cwiki.apache.org/confluence/disp
17681768
(Java system properties: **zookeeper.ssl.crl** and **zookeeper.ssl.quorum.crl**)
17691769
**New in 3.5.5:**
17701770
Specifies whether Certificate Revocation List is enabled in client and quorum TLS protocols.
1771-
Default: false
1771+
Default: jvm property "com.sun.net.ssl.checkRevocation" since 3.10.0, false otherwise
17721772

17731773
* *ssl.ocsp* and *ssl.quorum.ocsp* :
17741774
(Java system properties: **zookeeper.ssl.ocsp** and **zookeeper.ssl.quorum.ocsp**)
17751775
**New in 3.5.5:**
17761776
Specifies whether Online Certificate Status Protocol is enabled in client and quorum TLS protocols.
1777-
Default: false
1777+
**Changed in 3.10.0:**
1778+
Before 3.10.0, *ssl.ocsp* and *ssl.quorum.ocsp* implies *ssl.crl* and *ssl.quorum.crl* correspondingly.
1779+
After 3.10.0, one has to setup both *ssl.crl* and *ssl.ocsp* (or *ssl.quorum.crl* and *ssl.quorum.ocsp*)
1780+
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).
1781+
Default: jvm security property "ocsp.enable" since 3.10.0, false otherwise
17781782

17791783
* *ssl.clientAuth* and *ssl.quorum.clientAuth* :
17801784
(Java system properties: **zookeeper.ssl.clientAuth** and **zookeeper.ssl.quorum.clientAuth**)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.netty.handler.ssl.SslContext;
2424
import io.netty.handler.ssl.SslContextBuilder;
2525
import io.netty.handler.ssl.SslProvider;
26+
import java.security.Security;
2627
import java.util.Arrays;
2728
import javax.net.ssl.KeyManager;
2829
import javax.net.ssl.SSLEngine;
@@ -148,7 +149,7 @@ public SslContext createNettySslContextForServer(ZKConfig config, KeyManager key
148149
private SslContextBuilder handleTcnativeOcspStapling(SslContextBuilder builder, ZKConfig config) {
149150
SslProvider sslProvider = getSslProvider(config);
150151
boolean tcnative = sslProvider == SslProvider.OPENSSL || sslProvider == SslProvider.OPENSSL_REFCNT;
151-
boolean ocspEnabled = config.getBoolean(getSslOcspEnabledProperty());
152+
boolean ocspEnabled = config.getBoolean(getSslOcspEnabledProperty(), Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
152153

153154
if (tcnative && ocspEnabled && OpenSsl.isOcspSupported()) {
154155
builder.enableOcsp(ocspEnabled);
@@ -201,8 +202,8 @@ private TrustManager getTrustManager(ZKConfig config) throws X509Exception.Trust
201202
getSslTruststorePasswdPathProperty());
202203
String trustStoreType = config.getProperty(getSslTruststoreTypeProperty());
203204

204-
boolean sslCrlEnabled = config.getBoolean(getSslCrlEnabledProperty());
205-
boolean sslOcspEnabled = config.getBoolean(getSslOcspEnabledProperty());
205+
boolean sslCrlEnabled = config.getBoolean(getSslCrlEnabledProperty(), Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
206+
boolean sslOcspEnabled = config.getBoolean(getSslOcspEnabledProperty(), Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
206207
boolean sslServerHostnameVerificationEnabled = isServerHostnameVerificationEnabled(config);
207208
boolean sslClientHostnameVerificationEnabled = isClientHostnameVerificationEnabled(config);
208209

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

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,15 @@
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;
42+
import java.util.Set;
3943
import java.util.concurrent.atomic.AtomicReference;
4044
import java.util.function.Supplier;
4145
import javax.net.ssl.CertPathTrustManagerParameters;
@@ -392,8 +396,9 @@ public SSLContextAndOptions createSSLContextAndOptionsFromConfig(ZKConfig config
392396
String trustStorePasswordProp = getPasswordFromConfigPropertyOrFile(config, sslTruststorePasswdProperty, sslTruststorePasswdPathProperty);
393397
String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty);
394398

395-
boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
396-
boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty);
399+
boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty, Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
400+
boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty, Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
401+
397402
boolean sslServerHostnameVerificationEnabled = isServerHostnameVerificationEnabled(config);
398403
boolean sslClientHostnameVerificationEnabled = isClientHostnameVerificationEnabled(config);
399404
boolean fipsMode = getFipsMode(config);
@@ -548,13 +553,37 @@ public static X509TrustManager createTrustManager(
548553
try {
549554
KeyStore ts = loadTrustStore(trustStoreLocation, trustStorePassword, trustStoreTypeProp);
550555
PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
551-
if (crlEnabled || ocspEnabled) {
552-
pbParams.setRevocationEnabled(true);
553-
System.setProperty("com.sun.net.ssl.checkRevocation", "true");
554-
System.setProperty("com.sun.security.enableCRLDP", "true");
555-
if (ocspEnabled) {
556-
Security.setProperty("ocsp.enable", "true");
556+
if (crlEnabled) {
557+
// See [RevocationChecker][1] for details. Basically, we are mimicking the legacy path,
558+
// which relies significantly on jvm wide properties[2], as that is the path we are routing
559+
// before (i.e. no explicit `PKIXRevocationChecker`).
560+
//
561+
// By reading but not writing these properties, we conform to but not interfere with what
562+
// admin set while still keep backward compatibility.
563+
// 1. Default "zookeeper.ssl.crl" to jvm property "com.sun.net.ssl.checkRevocation" if it is unset.
564+
// 2. Default "zookeeper.ssl.ocsp" to jvm security property "ocsp.enable" if it is unset.
565+
// 3. Set `Option.ONLY_END_ENTITY` for jvm security property "com.sun.security.onlyCheckRevocationOfEECert".
566+
// 4. Don't set "com.sun.security.enableCRLDP" as it is always enabled in no legacy path.
567+
//
568+
// See also:
569+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ocsp.html
570+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html
571+
// * https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#PKIXRevocationChecker
572+
//
573+
// [1]: https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L124
574+
// [2]: https://github.com/openjdk/jdk/blob/jdk-11%2B28/src/java.base/share/classes/sun/security/provider/certpath/RevocationChecker.java#L179
575+
Set<PKIXRevocationChecker.Option> options = new HashSet<>();
576+
if (!ocspEnabled) {
577+
options.add(PKIXRevocationChecker.Option.NO_FALLBACK);
578+
options.add(PKIXRevocationChecker.Option.PREFER_CRLS);
579+
}
580+
if (Boolean.parseBoolean(Security.getProperty("com.sun.security.onlyCheckRevocationOfEECert"))) {
581+
options.add(PKIXRevocationChecker.Option.ONLY_END_ENTITY);
557582
}
583+
584+
PKIXRevocationChecker revocationChecker = (PKIXRevocationChecker) CertPathValidator.getInstance("PKIX").getRevocationChecker();
585+
revocationChecker.setOptions(options);
586+
pbParams.addCertPathChecker(revocationChecker);
558587
} else {
559588
pbParams.setRevocationEnabled(false);
560589
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
package org.apache.zookeeper.server.auth;
2020

21+
import java.security.Security;
2122
import java.security.cert.Certificate;
2223
import java.security.cert.CertificateException;
2324
import java.security.cert.X509Certificate;
@@ -85,8 +86,8 @@ public X509AuthenticationProvider() throws X509Exception {
8586
x509Util.getSslKeystorePasswdPathProperty());
8687
String keyStoreTypeProp = config.getProperty(x509Util.getSslKeystoreTypeProperty());
8788

88-
boolean crlEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslCrlEnabledProperty()));
89-
boolean ocspEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslOcspEnabledProperty()));
89+
boolean crlEnabled = config.getBoolean(x509Util.getSslCrlEnabledProperty(), Boolean.getBoolean("com.sun.net.ssl.checkRevocation"));
90+
boolean ocspEnabled = config.getBoolean(x509Util.getSslOcspEnabledProperty(), Boolean.parseBoolean(Security.getProperty("ocsp.enable")));
9091
boolean hostnameVerificationEnabled = Boolean.parseBoolean(config.getProperty(x509Util.getSslHostnameVerificationEnabledProperty()));
9192

9293
X509KeyManager km = null;

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
/**

0 commit comments

Comments
 (0)