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