1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.zookeeper.common;
20 
21 import java.io.Closeable;
22 import java.io.IOException;
23 import java.lang.reflect.InvocationTargetException;
24 import java.net.Socket;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.nio.file.StandardWatchEventKinds;
28 import java.nio.file.WatchEvent;
29 import java.security.GeneralSecurityException;
30 import java.security.KeyManagementException;
31 import java.security.KeyStore;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.Security;
34 import java.security.cert.PKIXBuilderParameters;
35 import java.security.cert.X509CertSelector;
36 import java.util.Objects;
37 import java.util.concurrent.atomic.AtomicReference;
38 import java.util.function.Supplier;
39 import javax.net.ssl.CertPathTrustManagerParameters;
40 import javax.net.ssl.KeyManager;
41 import javax.net.ssl.KeyManagerFactory;
42 import javax.net.ssl.SSLContext;
43 import javax.net.ssl.SSLServerSocket;
44 import javax.net.ssl.SSLSocket;
45 import javax.net.ssl.TrustManager;
46 import javax.net.ssl.TrustManagerFactory;
47 import javax.net.ssl.X509ExtendedTrustManager;
48 import javax.net.ssl.X509KeyManager;
49 import javax.net.ssl.X509TrustManager;
50 import org.apache.zookeeper.common.X509Exception.KeyManagerException;
51 import org.apache.zookeeper.common.X509Exception.SSLContextException;
52 import org.apache.zookeeper.common.X509Exception.TrustManagerException;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55 
56 /**
57  * Utility code for X509 handling
58  *
59  * Default cipher suites:
60  *
61  *   Performance testing done by Facebook engineers shows that on Intel x86_64 machines, Java9 performs better with
62  *   GCM and Java8 performs better with CBC, so these seem like reasonable defaults.
63  */
64 public abstract class X509Util implements Closeable, AutoCloseable {
65 
66     private static final Logger LOG = LoggerFactory.getLogger(X509Util.class);
67 
68     private static final String REJECT_CLIENT_RENEGOTIATION_PROPERTY = "jdk.tls.rejectClientInitiatedRenegotiation";
69 
70     static {
71         // Client-initiated renegotiation in TLS is unsafe and
72         // allows MITM attacks, so we should disable it unless
73         // it was explicitly enabled by the user.
74         // A brief summary of the issue can be found at
75         // https://www.ietf.org/proceedings/76/slides/tls-7.pdf
76         if (System.getProperty(REJECT_CLIENT_RENEGOTIATION_PROPERTY) == null) {
77             LOG.info("Setting -D {}=true to disable client-initiated TLS renegotiation", REJECT_CLIENT_RENEGOTIATION_PROPERTY);
System.setProperty(REJECT_CLIENT_RENEGOTIATION_PROPERTY, Boolean.TRUE.toString())78             System.setProperty(REJECT_CLIENT_RENEGOTIATION_PROPERTY, Boolean.TRUE.toString());
79         }
80     }
81 
82     public static final String DEFAULT_PROTOCOL = "TLSv1.2";
getGCMCiphers()83     private static String[] getGCMCiphers() {
84         return new String[]{"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"};
85     }
86 
getCBCCiphers()87     private static String[] getCBCCiphers() {
88         return new String[]{"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"};
89     }
90 
concatArrays(String[] left, String[] right)91     private static String[] concatArrays(String[] left, String[] right) {
92         String[] result = new String[left.length + right.length];
93         System.arraycopy(left, 0, result, 0, left.length);
94         System.arraycopy(right, 0, result, left.length, right.length);
95         return result;
96     }
97 
98     // On Java 8, prefer CBC ciphers since AES-NI support is lacking and GCM is slower than CBC.
99     private static final String[] DEFAULT_CIPHERS_JAVA8 = concatArrays(getCBCCiphers(), getGCMCiphers());
100     // On Java 9 and later, prefer GCM ciphers due to improved AES-NI support.
101     // Note that this performance assumption might not hold true for architectures other than x86_64.
102     private static final String[] DEFAULT_CIPHERS_JAVA9 = concatArrays(getGCMCiphers(), getCBCCiphers());
103 
104     public static final int DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS = 5000;
105 
106     /**
107      * Enum specifying the client auth requirement of server-side TLS sockets created by this X509Util.
108      * <ul>
109      *     <li>NONE - do not request a client certificate.</li>
110      *     <li>WANT - request a client certificate, but allow anonymous clients to connect.</li>
111      *     <li>NEED - require a client certificate, disconnect anonymous clients.</li>
112      * </ul>
113      *
114      * If the config property is not set, the default value is NEED.
115      */
116     public enum ClientAuth {
117         NONE(io.netty.handler.ssl.ClientAuth.NONE),
118         WANT(io.netty.handler.ssl.ClientAuth.OPTIONAL),
119         NEED(io.netty.handler.ssl.ClientAuth.REQUIRE);
120 
121         private final io.netty.handler.ssl.ClientAuth nettyAuth;
122 
ClientAuth(io.netty.handler.ssl.ClientAuth nettyAuth)123         ClientAuth(io.netty.handler.ssl.ClientAuth nettyAuth) {
124             this.nettyAuth = nettyAuth;
125         }
126 
127         /**
128          * Converts a property value to a ClientAuth enum. If the input string is empty or null, returns
129          * <code>ClientAuth.NEED</code>.
130          * @param prop the property string.
131          * @return the ClientAuth.
132          * @throws IllegalArgumentException if the property value is not "NONE", "WANT", "NEED", or empty/null.
133          */
fromPropertyValue(String prop)134         public static ClientAuth fromPropertyValue(String prop) {
135             if (prop == null || prop.length() == 0) {
136                 return NEED;
137             }
138             return ClientAuth.valueOf(prop.toUpperCase());
139         }
140 
toNettyClientAuth()141         public io.netty.handler.ssl.ClientAuth toNettyClientAuth() {
142             return nettyAuth;
143         }
144     }
145 
146     private String sslProtocolProperty = getConfigPrefix() + "protocol";
147     private String sslEnabledProtocolsProperty = getConfigPrefix() + "enabledProtocols";
148     private String cipherSuitesProperty = getConfigPrefix() + "ciphersuites";
149     private String sslKeystoreLocationProperty = getConfigPrefix() + "keyStore.location";
150     private String sslKeystorePasswdProperty = getConfigPrefix() + "keyStore.password";
151     private String sslKeystoreTypeProperty = getConfigPrefix() + "keyStore.type";
152     private String sslTruststoreLocationProperty = getConfigPrefix() + "trustStore.location";
153     private String sslTruststorePasswdProperty = getConfigPrefix() + "trustStore.password";
154     private String sslTruststoreTypeProperty = getConfigPrefix() + "trustStore.type";
155     private String sslContextSupplierClassProperty = getConfigPrefix() + "context.supplier.class";
156     private String sslHostnameVerificationEnabledProperty = getConfigPrefix() + "hostnameVerification";
157     private String sslCrlEnabledProperty = getConfigPrefix() + "crl";
158     private String sslOcspEnabledProperty = getConfigPrefix() + "ocsp";
159     private String sslClientAuthProperty = getConfigPrefix() + "clientAuth";
160     private String sslHandshakeDetectionTimeoutMillisProperty = getConfigPrefix() + "handshakeDetectionTimeoutMillis";
161 
162     private ZKConfig zkConfig;
163     private AtomicReference<SSLContextAndOptions> defaultSSLContextAndOptions = new AtomicReference<>(null);
164 
165     private FileChangeWatcher keyStoreFileWatcher;
166     private FileChangeWatcher trustStoreFileWatcher;
167 
X509Util()168     public X509Util() {
169         this(null);
170     }
171 
X509Util(ZKConfig zkConfig)172     public X509Util(ZKConfig zkConfig) {
173         this.zkConfig = zkConfig;
174         keyStoreFileWatcher = trustStoreFileWatcher = null;
175     }
176 
getConfigPrefix()177     protected abstract String getConfigPrefix();
178 
shouldVerifyClientHostname()179     protected abstract boolean shouldVerifyClientHostname();
180 
getSslProtocolProperty()181     public String getSslProtocolProperty() {
182         return sslProtocolProperty;
183     }
184 
getSslEnabledProtocolsProperty()185     public String getSslEnabledProtocolsProperty() {
186         return sslEnabledProtocolsProperty;
187     }
188 
getCipherSuitesProperty()189     public String getCipherSuitesProperty() {
190         return cipherSuitesProperty;
191     }
192 
getSslKeystoreLocationProperty()193     public String getSslKeystoreLocationProperty() {
194         return sslKeystoreLocationProperty;
195     }
196 
getSslCipherSuitesProperty()197     public String getSslCipherSuitesProperty() {
198         return cipherSuitesProperty;
199     }
200 
getSslKeystorePasswdProperty()201     public String getSslKeystorePasswdProperty() {
202         return sslKeystorePasswdProperty;
203     }
204 
getSslKeystoreTypeProperty()205     public String getSslKeystoreTypeProperty() {
206         return sslKeystoreTypeProperty;
207     }
208 
getSslTruststoreLocationProperty()209     public String getSslTruststoreLocationProperty() {
210         return sslTruststoreLocationProperty;
211     }
212 
getSslTruststorePasswdProperty()213     public String getSslTruststorePasswdProperty() {
214         return sslTruststorePasswdProperty;
215     }
216 
getSslTruststoreTypeProperty()217     public String getSslTruststoreTypeProperty() {
218         return sslTruststoreTypeProperty;
219     }
220 
getSslContextSupplierClassProperty()221     public String getSslContextSupplierClassProperty() {
222         return sslContextSupplierClassProperty;
223     }
224 
getSslHostnameVerificationEnabledProperty()225     public String getSslHostnameVerificationEnabledProperty() {
226         return sslHostnameVerificationEnabledProperty;
227     }
228 
getSslCrlEnabledProperty()229     public String getSslCrlEnabledProperty() {
230         return sslCrlEnabledProperty;
231     }
232 
getSslOcspEnabledProperty()233     public String getSslOcspEnabledProperty() {
234         return sslOcspEnabledProperty;
235     }
236 
getSslClientAuthProperty()237     public String getSslClientAuthProperty() {
238         return sslClientAuthProperty;
239     }
240 
241     /**
242      * Returns the config property key that controls the amount of time, in milliseconds, that the first
243      * UnifiedServerSocket read operation will block for when trying to detect the client mode (TLS or PLAINTEXT).
244      *
245      * @return the config property key.
246      */
getSslHandshakeDetectionTimeoutMillisProperty()247     public String getSslHandshakeDetectionTimeoutMillisProperty() {
248         return sslHandshakeDetectionTimeoutMillisProperty;
249     }
250 
getDefaultSSLContext()251     public SSLContext getDefaultSSLContext() throws X509Exception.SSLContextException {
252         return getDefaultSSLContextAndOptions().getSSLContext();
253     }
254 
createSSLContext(ZKConfig config)255     public SSLContext createSSLContext(ZKConfig config) throws SSLContextException {
256         return createSSLContextAndOptions(config).getSSLContext();
257     }
258 
getDefaultSSLContextAndOptions()259     public SSLContextAndOptions getDefaultSSLContextAndOptions() throws X509Exception.SSLContextException {
260         SSLContextAndOptions result = defaultSSLContextAndOptions.get();
261         if (result == null) {
262             result = createSSLContextAndOptions();
263             if (!defaultSSLContextAndOptions.compareAndSet(null, result)) {
264                 // lost the race, another thread already set the value
265                 result = defaultSSLContextAndOptions.get();
266             }
267         }
268         return result;
269     }
270 
resetDefaultSSLContextAndOptions()271     private void resetDefaultSSLContextAndOptions() throws X509Exception.SSLContextException {
272         SSLContextAndOptions newContext = createSSLContextAndOptions();
273         defaultSSLContextAndOptions.set(newContext);
274     }
275 
createSSLContextAndOptions()276     private SSLContextAndOptions createSSLContextAndOptions() throws SSLContextException {
277         /*
278          * Since Configuration initializes the key store and trust store related
279          * configuration from system property. Reading property from
280          * configuration will be same reading from system property
281          */
282         return createSSLContextAndOptions(zkConfig == null ? new ZKConfig() : zkConfig);
283     }
284 
285     /**
286      * Returns the max amount of time, in milliseconds, that the first UnifiedServerSocket read() operation should
287      * block for when trying to detect the client mode (TLS or PLAINTEXT).
288      * Defaults to {@link X509Util#DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS}.
289      *
290      * @return the handshake detection timeout, in milliseconds.
291      */
getSslHandshakeTimeoutMillis()292     public int getSslHandshakeTimeoutMillis() {
293         try {
294             SSLContextAndOptions ctx = getDefaultSSLContextAndOptions();
295             return ctx.getHandshakeDetectionTimeoutMillis();
296         } catch (SSLContextException e) {
297             LOG.error("Error creating SSL context and options", e);
298             return DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS;
299         } catch (Exception e) {
300             LOG.error("Error parsing config property {}", getSslHandshakeDetectionTimeoutMillisProperty(), e);
301             return DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS;
302         }
303     }
304 
305     @SuppressWarnings("unchecked")
createSSLContextAndOptions(ZKConfig config)306     public SSLContextAndOptions createSSLContextAndOptions(ZKConfig config) throws SSLContextException {
307         final String supplierContextClassName = config.getProperty(sslContextSupplierClassProperty);
308         if (supplierContextClassName != null) {
309             LOG.debug("Loading SSLContext supplier from property '{}'", sslContextSupplierClassProperty);
310 
311             try {
312                 Class<?> sslContextClass = Class.forName(supplierContextClassName);
313                 Supplier<SSLContext> sslContextSupplier = (Supplier<SSLContext>) sslContextClass.getConstructor().newInstance();
314                 return new SSLContextAndOptions(this, config, sslContextSupplier.get());
315             } catch (ClassNotFoundException
316                 | ClassCastException
317                 | NoSuchMethodException
318                 | InvocationTargetException
319                 | InstantiationException
320                 | IllegalAccessException e) {
321                 throw new SSLContextException("Could not retrieve the SSLContext from supplier source '"
322                                               + supplierContextClassName
323                                               + "' provided in the property '"
324                                               + sslContextSupplierClassProperty
325                                               + "'", e);
326             }
327         } else {
328             return createSSLContextAndOptionsFromConfig(config);
329         }
330     }
331 
createSSLContextAndOptionsFromConfig(ZKConfig config)332     public SSLContextAndOptions createSSLContextAndOptionsFromConfig(ZKConfig config) throws SSLContextException {
333         KeyManager[] keyManagers = null;
334         TrustManager[] trustManagers = null;
335 
336         String keyStoreLocationProp = config.getProperty(sslKeystoreLocationProperty, "");
337         String keyStorePasswordProp = config.getProperty(sslKeystorePasswdProperty, "");
338         String keyStoreTypeProp = config.getProperty(sslKeystoreTypeProperty);
339 
340         // There are legal states in some use cases for null KeyManager or TrustManager.
341         // But if a user wanna specify one, location is required. Password defaults to empty string if it is not
342         // specified by the user.
343 
344         if (keyStoreLocationProp.isEmpty()) {
345             LOG.warn("{} not specified", getSslKeystoreLocationProperty());
346         } else {
347             try {
348                 keyManagers = new KeyManager[]{createKeyManager(keyStoreLocationProp, keyStorePasswordProp, keyStoreTypeProp)};
349             } catch (KeyManagerException keyManagerException) {
350                 throw new SSLContextException("Failed to create KeyManager", keyManagerException);
351             } catch (IllegalArgumentException e) {
352                 throw new SSLContextException("Bad value for " + sslKeystoreTypeProperty + ": " + keyStoreTypeProp, e);
353             }
354         }
355 
356         String trustStoreLocationProp = config.getProperty(sslTruststoreLocationProperty, "");
357         String trustStorePasswordProp = config.getProperty(sslTruststorePasswdProperty, "");
358         String trustStoreTypeProp = config.getProperty(sslTruststoreTypeProperty);
359 
360         boolean sslCrlEnabled = config.getBoolean(this.sslCrlEnabledProperty);
361         boolean sslOcspEnabled = config.getBoolean(this.sslOcspEnabledProperty);
362         boolean sslServerHostnameVerificationEnabled = config.getBoolean(this.getSslHostnameVerificationEnabledProperty(), true);
363         boolean sslClientHostnameVerificationEnabled = sslServerHostnameVerificationEnabled && shouldVerifyClientHostname();
364 
365         if (trustStoreLocationProp.isEmpty()) {
366             LOG.warn("{} not specified", getSslTruststoreLocationProperty());
367         } else {
368             try {
369                 trustManagers = new TrustManager[]{createTrustManager(trustStoreLocationProp, trustStorePasswordProp, trustStoreTypeProp, sslCrlEnabled, sslOcspEnabled, sslServerHostnameVerificationEnabled, sslClientHostnameVerificationEnabled)};
370             } catch (TrustManagerException trustManagerException) {
371                 throw new SSLContextException("Failed to create TrustManager", trustManagerException);
372             } catch (IllegalArgumentException e) {
373                 throw new SSLContextException("Bad value for "
374                                               + sslTruststoreTypeProperty
375                                               + ": "
376                                               + trustStoreTypeProp, e);
377             }
378         }
379 
380         String protocol = config.getProperty(sslProtocolProperty, DEFAULT_PROTOCOL);
381         try {
382             SSLContext sslContext = SSLContext.getInstance(protocol);
383             sslContext.init(keyManagers, trustManagers, null);
384             return new SSLContextAndOptions(this, config, sslContext);
385         } catch (NoSuchAlgorithmException | KeyManagementException sslContextInitException) {
386             throw new SSLContextException(sslContextInitException);
387         }
388     }
389 
loadKeyStore( String keyStoreLocation, String keyStorePassword, String keyStoreTypeProp)390     public static KeyStore loadKeyStore(
391         String keyStoreLocation,
392         String keyStorePassword,
393         String keyStoreTypeProp) throws IOException, GeneralSecurityException {
394         KeyStoreFileType storeFileType = KeyStoreFileType.fromPropertyValueOrFileName(keyStoreTypeProp, keyStoreLocation);
395         return FileKeyStoreLoaderBuilderProvider
396             .getBuilderForKeyStoreFileType(storeFileType)
397             .setKeyStorePath(keyStoreLocation)
398             .setKeyStorePassword(keyStorePassword)
399             .build()
400             .loadKeyStore();
401     }
402 
loadTrustStore( String trustStoreLocation, String trustStorePassword, String trustStoreTypeProp)403     public static KeyStore loadTrustStore(
404         String trustStoreLocation,
405         String trustStorePassword,
406         String trustStoreTypeProp) throws IOException, GeneralSecurityException {
407         KeyStoreFileType storeFileType = KeyStoreFileType.fromPropertyValueOrFileName(trustStoreTypeProp, trustStoreLocation);
408         return FileKeyStoreLoaderBuilderProvider
409             .getBuilderForKeyStoreFileType(storeFileType)
410             .setTrustStorePath(trustStoreLocation)
411             .setTrustStorePassword(trustStorePassword)
412             .build()
413             .loadTrustStore();
414     }
415 
416     /**
417      * Creates a key manager by loading the key store from the given file of
418      * the given type, optionally decrypting it using the given password.
419      * @param keyStoreLocation the location of the key store file.
420      * @param keyStorePassword optional password to decrypt the key store. If
421      *                         empty, assumes the key store is not encrypted.
422      * @param keyStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If null,
423      *                         attempts to autodetect the key store type from
424      *                         the file extension (e.g. .jks / .pem).
425      * @return the key manager.
426      * @throws KeyManagerException if something goes wrong.
427      */
createKeyManager( String keyStoreLocation, String keyStorePassword, String keyStoreTypeProp)428     public static X509KeyManager createKeyManager(
429         String keyStoreLocation,
430         String keyStorePassword,
431         String keyStoreTypeProp) throws KeyManagerException {
432         if (keyStorePassword == null) {
433             keyStorePassword = "";
434         }
435         try {
436             KeyStore ks = loadKeyStore(keyStoreLocation, keyStorePassword, keyStoreTypeProp);
437             KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
438             kmf.init(ks, keyStorePassword.toCharArray());
439 
440             for (KeyManager km : kmf.getKeyManagers()) {
441                 if (km instanceof X509KeyManager) {
442                     return (X509KeyManager) km;
443                 }
444             }
445             throw new KeyManagerException("Couldn't find X509KeyManager");
446         } catch (IOException | GeneralSecurityException | IllegalArgumentException e) {
447             throw new KeyManagerException(e);
448         }
449     }
450 
451     /**
452      * Creates a trust manager by loading the trust store from the given file
453      * of the given type, optionally decrypting it using the given password.
454      * @param trustStoreLocation the location of the trust store file.
455      * @param trustStorePassword optional password to decrypt the trust store
456      *                           (only applies to JKS trust stores). If empty,
457      *                           assumes the trust store is not encrypted.
458      * @param trustStoreTypeProp must be JKS, PEM, PKCS12, BCFKS or null. If
459      *                           null, attempts to autodetect the trust store
460      *                           type from the file extension (e.g. .jks / .pem).
461      * @param crlEnabled enable CRL (certificate revocation list) checks.
462      * @param ocspEnabled enable OCSP (online certificate status protocol)
463      *                    checks.
464      * @param serverHostnameVerificationEnabled if true, verify hostnames of
465      *                                          remote servers that client
466      *                                          sockets created by this
467      *                                          X509Util connect to.
468      * @param clientHostnameVerificationEnabled if true, verify hostnames of
469      *                                          remote clients that server
470      *                                          sockets created by this
471      *                                          X509Util accept connections
472      *                                          from.
473      * @return the trust manager.
474      * @throws TrustManagerException if something goes wrong.
475      */
createTrustManager( String trustStoreLocation, String trustStorePassword, String trustStoreTypeProp, boolean crlEnabled, boolean ocspEnabled, final boolean serverHostnameVerificationEnabled, final boolean clientHostnameVerificationEnabled)476     public static X509TrustManager createTrustManager(
477         String trustStoreLocation,
478         String trustStorePassword,
479         String trustStoreTypeProp,
480         boolean crlEnabled,
481         boolean ocspEnabled,
482         final boolean serverHostnameVerificationEnabled,
483         final boolean clientHostnameVerificationEnabled) throws TrustManagerException {
484         if (trustStorePassword == null) {
485             trustStorePassword = "";
486         }
487         try {
488             KeyStore ts = loadTrustStore(trustStoreLocation, trustStorePassword, trustStoreTypeProp);
489             PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
490             if (crlEnabled || ocspEnabled) {
491                 pbParams.setRevocationEnabled(true);
492                 System.setProperty("com.sun.net.ssl.checkRevocation", "true");
493                 System.setProperty("com.sun.security.enableCRLDP", "true");
494                 if (ocspEnabled) {
495                     Security.setProperty("ocsp.enable", "true");
496                 }
497             } else {
498                 pbParams.setRevocationEnabled(false);
499             }
500 
501             // Revocation checking is only supported with the PKIX algorithm
502             TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
503             tmf.init(new CertPathTrustManagerParameters(pbParams));
504 
505             for (final TrustManager tm : tmf.getTrustManagers()) {
506                 if (tm instanceof X509ExtendedTrustManager) {
507                     return new ZKTrustManager((X509ExtendedTrustManager) tm, serverHostnameVerificationEnabled, clientHostnameVerificationEnabled);
508                 }
509             }
510             throw new TrustManagerException("Couldn't find X509TrustManager");
511         } catch (IOException | GeneralSecurityException | IllegalArgumentException e) {
512             throw new TrustManagerException(e);
513         }
514     }
515 
createSSLSocket()516     public SSLSocket createSSLSocket() throws X509Exception, IOException {
517         return getDefaultSSLContextAndOptions().createSSLSocket();
518     }
519 
createSSLSocket(Socket socket, byte[] pushbackBytes)520     public SSLSocket createSSLSocket(Socket socket, byte[] pushbackBytes) throws X509Exception, IOException {
521         return getDefaultSSLContextAndOptions().createSSLSocket(socket, pushbackBytes);
522     }
523 
createSSLServerSocket()524     public SSLServerSocket createSSLServerSocket() throws X509Exception, IOException {
525         return getDefaultSSLContextAndOptions().createSSLServerSocket();
526     }
527 
createSSLServerSocket(int port)528     public SSLServerSocket createSSLServerSocket(int port) throws X509Exception, IOException {
529         return getDefaultSSLContextAndOptions().createSSLServerSocket(port);
530     }
531 
getDefaultCipherSuites()532     static String[] getDefaultCipherSuites() {
533         return getDefaultCipherSuitesForJavaVersion(System.getProperty("java.specification.version"));
534     }
535 
getDefaultCipherSuitesForJavaVersion(String javaVersion)536     static String[] getDefaultCipherSuitesForJavaVersion(String javaVersion) {
537         Objects.requireNonNull(javaVersion);
538         if (javaVersion.matches("\\d+")) {
539             // Must be Java 9 or later
540             LOG.debug("Using Java9+ optimized cipher suites for Java version {}", javaVersion);
541             return DEFAULT_CIPHERS_JAVA9;
542         } else if (javaVersion.startsWith("1.")) {
543             // Must be Java 1.8 or earlier
544             LOG.debug("Using Java8 optimized cipher suites for Java version {}", javaVersion);
545             return DEFAULT_CIPHERS_JAVA8;
546         } else {
547             LOG.debug("Could not parse java version {}, using Java8 optimized cipher suites", javaVersion);
548             return DEFAULT_CIPHERS_JAVA8;
549         }
550     }
551 
newFileChangeWatcher(String fileLocation)552     private FileChangeWatcher newFileChangeWatcher(String fileLocation) throws IOException {
553         if (fileLocation == null || fileLocation.isEmpty()) {
554             return null;
555         }
556         final Path filePath = Paths.get(fileLocation).toAbsolutePath();
557         Path parentPath = filePath.getParent();
558         if (parentPath == null) {
559             throw new IOException("Key/trust store path does not have a parent: " + filePath);
560         }
561         return new FileChangeWatcher(parentPath, watchEvent -> {
562             handleWatchEvent(filePath, watchEvent);
563         });
564     }
565 
566     /**
567      * Enables automatic reloading of the trust store and key store files when they change on disk.
568      *
569      * @throws IOException if creating the FileChangeWatcher objects fails.
570      */
enableCertFileReloading()571     public void enableCertFileReloading() throws IOException {
572         LOG.info("enabling cert file reloading");
573         ZKConfig config = zkConfig == null ? new ZKConfig() : zkConfig;
574         FileChangeWatcher newKeyStoreFileWatcher = newFileChangeWatcher(config.getProperty(sslKeystoreLocationProperty));
575         if (newKeyStoreFileWatcher != null) {
576             // stop old watcher if there is one
577             if (keyStoreFileWatcher != null) {
578                 keyStoreFileWatcher.stop();
579             }
580             keyStoreFileWatcher = newKeyStoreFileWatcher;
581             keyStoreFileWatcher.start();
582         }
583         FileChangeWatcher newTrustStoreFileWatcher = newFileChangeWatcher(config.getProperty(sslTruststoreLocationProperty));
584         if (newTrustStoreFileWatcher != null) {
585             // stop old watcher if there is one
586             if (trustStoreFileWatcher != null) {
587                 trustStoreFileWatcher.stop();
588             }
589             trustStoreFileWatcher = newTrustStoreFileWatcher;
590             trustStoreFileWatcher.start();
591         }
592     }
593 
594     /**
595      * Disables automatic reloading of the trust store and key store files when they change on disk.
596      * Stops background threads and closes WatchService instances.
597      */
598     @Override
close()599     public void close() {
600         if (keyStoreFileWatcher != null) {
601             keyStoreFileWatcher.stop();
602             keyStoreFileWatcher = null;
603         }
604         if (trustStoreFileWatcher != null) {
605             trustStoreFileWatcher.stop();
606             trustStoreFileWatcher = null;
607         }
608     }
609 
610     /**
611      * Handler for watch events that let us know a file we may care about has changed on disk.
612      *
613      * @param filePath the path to the file we are watching for changes.
614      * @param event    the WatchEvent.
615      */
handleWatchEvent(Path filePath, WatchEvent<?> event)616     private void handleWatchEvent(Path filePath, WatchEvent<?> event) {
617         boolean shouldResetContext = false;
618         Path dirPath = filePath.getParent();
619         if (event.kind().equals(StandardWatchEventKinds.OVERFLOW)) {
620             // If we get notified about possibly missed events, reload the key store / trust store just to be sure.
621             shouldResetContext = true;
622         } else if (event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)
623                    || event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE)) {
624             Path eventFilePath = dirPath.resolve((Path) event.context());
625             if (filePath.equals(eventFilePath)) {
626                 shouldResetContext = true;
627             }
628         }
629         // Note: we don't care about delete events
630         if (shouldResetContext) {
631             LOG.debug(
632                 "Attempting to reset default SSL context after receiving watch event: {} with context: {}",
633                 event.kind(),
634                 event.context());
635             try {
636                 this.resetDefaultSSLContextAndOptions();
637             } catch (SSLContextException e) {
638                 throw new RuntimeException(e);
639             }
640         } else {
641             LOG.debug(
642                 "Ignoring watch event and keeping previous default SSL context. Event kind: {} with context: {}",
643                 event.kind(),
644                 event.context());
645         }
646     }
647 
648 }
649