001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.crypto.tls; 019 020import java.io.IOException; 021import java.nio.file.Path; 022import java.nio.file.Paths; 023import java.nio.file.StandardWatchEventKinds; 024import java.nio.file.WatchEvent; 025import java.security.GeneralSecurityException; 026import java.security.KeyStore; 027import java.security.Security; 028import java.security.cert.PKIXBuilderParameters; 029import java.security.cert.X509CertSelector; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.List; 033import java.util.Objects; 034import java.util.Set; 035import java.util.concurrent.atomic.AtomicReference; 036import javax.net.ssl.CertPathTrustManagerParameters; 037import javax.net.ssl.KeyManager; 038import javax.net.ssl.KeyManagerFactory; 039import javax.net.ssl.TrustManager; 040import javax.net.ssl.TrustManagerFactory; 041import javax.net.ssl.X509ExtendedTrustManager; 042import javax.net.ssl.X509KeyManager; 043import javax.net.ssl.X509TrustManager; 044import org.apache.hadoop.conf.Configuration; 045import org.apache.hadoop.hbase.exceptions.KeyManagerException; 046import org.apache.hadoop.hbase.exceptions.SSLContextException; 047import org.apache.hadoop.hbase.exceptions.TrustManagerException; 048import org.apache.hadoop.hbase.exceptions.X509Exception; 049import org.apache.hadoop.hbase.io.FileChangeWatcher; 050import org.apache.yetus.audience.InterfaceAudience; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054import org.apache.hbase.thirdparty.com.google.common.collect.ObjectArrays; 055import org.apache.hbase.thirdparty.io.netty.handler.ssl.OpenSsl; 056import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext; 057import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContextBuilder; 058import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslProvider; 059 060/** 061 * Utility code for X509 handling Default cipher suites: Performance testing done by Facebook 062 * engineers shows that on Intel x86_64 machines, Java9 performs better with GCM and Java8 performs 063 * better with CBC, so these seem like reasonable defaults. 064 * <p/> 065 * This file has been copied from the Apache ZooKeeper project. 066 * @see <a href= 067 * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java">Base 068 * revision</a> 069 */ 070@InterfaceAudience.Private 071public final class X509Util { 072 073 private static final Logger LOG = LoggerFactory.getLogger(X509Util.class); 074 private static final char[] EMPTY_CHAR_ARRAY = new char[0]; 075 076 // 077 // Common tls configs across both server and client 078 // 079 static final String CONFIG_PREFIX = "hbase.rpc.tls."; 080 public static final String TLS_CONFIG_PROTOCOL = CONFIG_PREFIX + "protocol"; 081 public static final String TLS_CONFIG_KEYSTORE_LOCATION = CONFIG_PREFIX + "keystore.location"; 082 public static final String TLS_CONFIG_KEYSTORE_TYPE = CONFIG_PREFIX + "keystore.type"; 083 public static final String TLS_CONFIG_KEYSTORE_PASSWORD = CONFIG_PREFIX + "keystore.password"; 084 public static final String TLS_CONFIG_TRUSTSTORE_LOCATION = CONFIG_PREFIX + "truststore.location"; 085 public static final String TLS_CONFIG_TRUSTSTORE_TYPE = CONFIG_PREFIX + "truststore.type"; 086 public static final String TLS_CONFIG_TRUSTSTORE_PASSWORD = CONFIG_PREFIX + "truststore.password"; 087 public static final String TLS_CONFIG_CLR = CONFIG_PREFIX + "clr"; 088 public static final String TLS_CONFIG_OCSP = CONFIG_PREFIX + "ocsp"; 089 public static final String TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED = 090 CONFIG_PREFIX + "host-verification.reverse-dns.enabled"; 091 public static final String TLS_ENABLED_PROTOCOLS = CONFIG_PREFIX + "enabledProtocols"; 092 public static final String TLS_CIPHER_SUITES = CONFIG_PREFIX + "ciphersuites"; 093 public static final String TLS_CERT_RELOAD = CONFIG_PREFIX + "certReload"; 094 public static final String TLS_USE_OPENSSL = CONFIG_PREFIX + "useOpenSsl"; 095 public static final String DEFAULT_PROTOCOL = "TLSv1.2"; 096 097 // 098 // Server-side specific configs 099 // 100 public static final String HBASE_SERVER_NETTY_TLS_ENABLED = "hbase.server.netty.tls.enabled"; 101 public static final String HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE = 102 "hbase.server.netty.tls.client.auth.mode"; 103 public static final String HBASE_SERVER_NETTY_TLS_VERIFY_CLIENT_HOSTNAME = 104 "hbase.server.netty.tls.verify.client.hostname"; 105 public static final String HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT = 106 "hbase.server.netty.tls.supportplaintext"; 107 108 /** 109 * Set the SSL wrapSize for netty. This is only a maximum wrap size. Buffers smaller than this 110 * will not be consolidated, but buffers larger than this will be split into multiple wrap 111 * buffers. The netty default of 16k is not great for hbase which tends to return larger payloads 112 * than that, meaning most responses end up getting chunked up. This leads to more memory 113 * contention in netty's PoolArena. See https://github.com/netty/netty/pull/13551 114 */ 115 public static final String HBASE_SERVER_NETTY_TLS_WRAP_SIZE = "hbase.server.netty.tls.wrapSize"; 116 public static final int DEFAULT_HBASE_SERVER_NETTY_TLS_WRAP_SIZE = 1024 * 1024; 117 // 118 // Client-side specific configs 119 // 120 public static final String HBASE_CLIENT_NETTY_TLS_ENABLED = "hbase.client.netty.tls.enabled"; 121 public static final String HBASE_CLIENT_NETTY_TLS_VERIFY_SERVER_HOSTNAME = 122 "hbase.client.netty.tls.verify.server.hostname"; 123 public static final String HBASE_CLIENT_NETTY_TLS_HANDSHAKETIMEOUT = 124 "hbase.client.netty.tls.handshaketimeout"; 125 public static final int DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS = 5000; 126 127 private static String[] getTls13Ciphers() { 128 return new String[] { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" }; 129 } 130 131 private static String[] getGCMCiphers() { 132 return new String[] { "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 133 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 134 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" }; 135 } 136 137 private static String[] getCBCCiphers() { 138 return new String[] { "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 139 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 140 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 141 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 142 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" }; 143 } 144 145 // On Java 8, prefer CBC ciphers since AES-NI support is lacking and GCM is slower than CBC. 146 private static final String[] DEFAULT_CIPHERS_JAVA8 = 147 ObjectArrays.concat(getCBCCiphers(), getGCMCiphers(), String.class); 148 // On Java 9 and later, prefer GCM ciphers due to improved AES-NI support. 149 // Note that this performance assumption might not hold true for architectures other than x86_64. 150 private static final String[] DEFAULT_CIPHERS_JAVA9 = 151 ObjectArrays.concat(getGCMCiphers(), getCBCCiphers(), String.class); 152 private static final String[] DEFAULT_CIPHERS_JAVA11 = 153 ObjectArrays.concat(ObjectArrays.concat(getTls13Ciphers(), getGCMCiphers(), String.class), 154 getCBCCiphers(), String.class); 155 156 private static final String[] DEFAULT_CIPHERS_OPENSSL = getOpenSslFilteredDefaultCiphers(); 157 158 /** 159 * Not all of our default ciphers are available in OpenSSL. Takes our default cipher lists and 160 * filters them to only those available in OpenSsl. Prefers TLS 1.3, then GCM, then CBC because 161 * GCM tends to be better and faster, and we don't need to worry about the java8 vs 9 performance 162 * issue if OpenSSL is handling it. 163 */ 164 private static String[] getOpenSslFilteredDefaultCiphers() { 165 if (!OpenSsl.isAvailable()) { 166 return new String[0]; 167 } 168 169 Set<String> openSslSuites = OpenSsl.availableJavaCipherSuites(); 170 List<String> defaultSuites = new ArrayList<>(); 171 Arrays.stream(getTls13Ciphers()).filter(openSslSuites::contains).forEach(defaultSuites::add); 172 Arrays.stream(getGCMCiphers()).filter(openSslSuites::contains).forEach(defaultSuites::add); 173 Arrays.stream(getCBCCiphers()).filter(openSslSuites::contains).forEach(defaultSuites::add); 174 return defaultSuites.toArray(new String[0]); 175 } 176 177 /** 178 * Enum specifying the client auth requirement of server-side TLS sockets created by this 179 * X509Util. 180 * <ul> 181 * <li>NONE - do not request a client certificate.</li> 182 * <li>WANT - request a client certificate, but allow anonymous clients to connect.</li> 183 * <li>NEED - require a client certificate, disconnect anonymous clients.</li> 184 * </ul> 185 * If the config property is not set, the default value is NEED. 186 */ 187 public enum ClientAuth { 188 NONE(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.NONE), 189 WANT(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.OPTIONAL), 190 NEED(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.REQUIRE); 191 192 private final org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth; 193 194 ClientAuth(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth) { 195 this.nettyAuth = nettyAuth; 196 } 197 198 /** 199 * Converts a property value to a ClientAuth enum. If the input string is empty or null, returns 200 * <code>ClientAuth.NEED</code>. 201 * @param prop the property string. 202 * @return the ClientAuth. 203 * @throws IllegalArgumentException if the property value is not "NONE", "WANT", "NEED", or 204 * empty/null. 205 */ 206 public static ClientAuth fromPropertyValue(String prop) { 207 if (prop == null || prop.length() == 0) { 208 return NEED; 209 } 210 return ClientAuth.valueOf(prop.toUpperCase()); 211 } 212 213 public org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth toNettyClientAuth() { 214 return nettyAuth; 215 } 216 } 217 218 private X509Util() { 219 // disabled 220 } 221 222 static String[] getDefaultCipherSuites(boolean useOpenSsl) { 223 if (useOpenSsl) { 224 return DEFAULT_CIPHERS_OPENSSL; 225 } 226 return getDefaultCipherSuitesForJavaVersion(System.getProperty("java.specification.version")); 227 } 228 229 static String[] getDefaultCipherSuitesForJavaVersion(String javaVersion) { 230 Objects.requireNonNull(javaVersion); 231 232 if (javaVersion.matches("\\d+")) { 233 // Must be Java 9 or later 234 int javaVersionInt = Integer.parseInt(javaVersion); 235 if (javaVersionInt >= 11) { 236 LOG.debug( 237 "Using Java11+ optimized cipher suites for Java version {}, including TLSv1.3 support", 238 javaVersion); 239 return DEFAULT_CIPHERS_JAVA11; 240 } else { 241 LOG.debug("Using Java9+ optimized cipher suites for Java version {}", javaVersion); 242 return DEFAULT_CIPHERS_JAVA9; 243 } 244 } else if (javaVersion.startsWith("1.")) { 245 // Must be Java 1.8 or earlier 246 LOG.debug("Using Java8 optimized cipher suites for Java version {}", javaVersion); 247 return DEFAULT_CIPHERS_JAVA8; 248 } else { 249 LOG.debug("Could not parse java version {}, using Java8 optimized cipher suites", 250 javaVersion); 251 return DEFAULT_CIPHERS_JAVA8; 252 } 253 } 254 255 public static SslContext createSslContextForClient(Configuration config) 256 throws X509Exception, IOException { 257 258 SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); 259 260 boolean useOpenSsl = configureOpenSslIfAvailable(sslContextBuilder, config); 261 String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, ""); 262 char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD); 263 String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, ""); 264 265 if (keyStoreLocation.isEmpty()) { 266 LOG.warn(TLS_CONFIG_KEYSTORE_LOCATION + " not specified"); 267 } else { 268 sslContextBuilder 269 .keyManager(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType)); 270 } 271 272 String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, ""); 273 char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD); 274 String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, ""); 275 276 boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false); 277 boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false); 278 279 boolean verifyServerHostname = 280 config.getBoolean(HBASE_CLIENT_NETTY_TLS_VERIFY_SERVER_HOSTNAME, true); 281 boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true); 282 283 if (trustStoreLocation.isEmpty()) { 284 LOG.warn(TLS_CONFIG_TRUSTSTORE_LOCATION + " not specified"); 285 } else { 286 sslContextBuilder 287 .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType, 288 sslCrlEnabled, sslOcspEnabled, verifyServerHostname, allowReverseDnsLookup)); 289 } 290 291 sslContextBuilder.enableOcsp(sslOcspEnabled); 292 sslContextBuilder.protocols(getEnabledProtocols(config)); 293 sslContextBuilder.ciphers(Arrays.asList(getCipherSuites(config, useOpenSsl))); 294 295 return sslContextBuilder.build(); 296 } 297 298 /** 299 * Adds SslProvider.OPENSSL if OpenSsl is available and enabled. In order to make it available, 300 * one must ensure that a properly shaded netty-tcnative is on the classpath. Properly shaded 301 * means relocated to be prefixed with "org.apache.hbase.thirdparty" like the rest of the netty 302 * classes. We make available org.apache.hbase:hbase-openssl as a convenience module which one can 303 * use to pull in a shaded netty-tcnative statically linked against boringssl. 304 */ 305 private static boolean configureOpenSslIfAvailable(SslContextBuilder sslContextBuilder, 306 Configuration conf) { 307 if (OpenSsl.isAvailable() && conf.getBoolean(TLS_USE_OPENSSL, true)) { 308 LOG.debug("Using netty-tcnative to accelerate SSL handling"); 309 sslContextBuilder.sslProvider(SslProvider.OPENSSL); 310 return true; 311 } else { 312 if (LOG.isDebugEnabled()) { 313 LOG.debug("Using default JDK SSL provider because netty-tcnative is not {}", 314 OpenSsl.isAvailable() ? "enabled" : "available"); 315 } 316 sslContextBuilder.sslProvider(SslProvider.JDK); 317 return false; 318 } 319 } 320 321 public static SslContext createSslContextForServer(Configuration config) 322 throws X509Exception, IOException { 323 String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, ""); 324 char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD); 325 String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, ""); 326 327 if (keyStoreLocation.isEmpty()) { 328 throw new SSLContextException( 329 "Keystore is required for SSL server: " + TLS_CONFIG_KEYSTORE_LOCATION); 330 } 331 332 SslContextBuilder sslContextBuilder; 333 sslContextBuilder = SslContextBuilder 334 .forServer(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType)); 335 336 boolean useOpenSsl = configureOpenSslIfAvailable(sslContextBuilder, config); 337 String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, ""); 338 char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD); 339 String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, ""); 340 341 boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false); 342 boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false); 343 344 ClientAuth clientAuth = 345 ClientAuth.fromPropertyValue(config.get(HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE)); 346 boolean verifyClientHostname = 347 config.getBoolean(HBASE_SERVER_NETTY_TLS_VERIFY_CLIENT_HOSTNAME, true); 348 boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true); 349 350 if (trustStoreLocation.isEmpty()) { 351 LOG.warn(TLS_CONFIG_TRUSTSTORE_LOCATION + " not specified"); 352 } else { 353 sslContextBuilder 354 .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType, 355 sslCrlEnabled, sslOcspEnabled, verifyClientHostname, allowReverseDnsLookup)); 356 } 357 358 sslContextBuilder.enableOcsp(sslOcspEnabled); 359 sslContextBuilder.protocols(getEnabledProtocols(config)); 360 sslContextBuilder.ciphers(Arrays.asList(getCipherSuites(config, useOpenSsl))); 361 sslContextBuilder.clientAuth(clientAuth.toNettyClientAuth()); 362 363 return sslContextBuilder.build(); 364 } 365 366 /** 367 * Creates a key manager by loading the key store from the given file of the given type, 368 * optionally decrypting it using the given password. 369 * @param keyStoreLocation the location of the key store file. 370 * @param keyStorePassword optional password to decrypt the key store. If empty, assumes the key 371 * store is not encrypted. 372 * @param keyStoreType must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to 373 * autodetect the key store type from the file extension (e.g. .jks / 374 * .pem). 375 * @return the key manager. 376 * @throws KeyManagerException if something goes wrong. 377 */ 378 static X509KeyManager createKeyManager(String keyStoreLocation, char[] keyStorePassword, 379 String keyStoreType) throws KeyManagerException { 380 381 if (keyStorePassword == null) { 382 keyStorePassword = EMPTY_CHAR_ARRAY; 383 } 384 385 try { 386 KeyStoreFileType storeFileType = 387 KeyStoreFileType.fromPropertyValueOrFileName(keyStoreType, keyStoreLocation); 388 KeyStore ks = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType) 389 .setKeyStorePath(keyStoreLocation).setKeyStorePassword(keyStorePassword).build() 390 .loadKeyStore(); 391 392 KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); 393 kmf.init(ks, keyStorePassword); 394 395 for (KeyManager km : kmf.getKeyManagers()) { 396 if (km instanceof X509KeyManager) { 397 return (X509KeyManager) km; 398 } 399 } 400 throw new KeyManagerException("Couldn't find X509KeyManager"); 401 } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { 402 throw new KeyManagerException(e); 403 } 404 } 405 406 /** 407 * Creates a trust manager by loading the trust store from the given file of the given type, 408 * optionally decrypting it using the given password. 409 * @param trustStoreLocation the location of the trust store file. 410 * @param trustStorePassword optional password to decrypt the trust store (only applies to JKS 411 * trust stores). If empty, assumes the trust store is not encrypted. 412 * @param trustStoreType must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to 413 * autodetect the trust store type from the file extension (e.g. .jks 414 * / .pem). 415 * @param crlEnabled enable CRL (certificate revocation list) checks. 416 * @param ocspEnabled enable OCSP (online certificate status protocol) checks. 417 * @param verifyHostName if true, ssl peer hostname must match name in certificate 418 * @param allowReverseDnsLookup if true, allow falling back to reverse dns lookup in verifying 419 * hostname 420 * @return the trust manager. 421 * @throws TrustManagerException if something goes wrong. 422 */ 423 static X509TrustManager createTrustManager(String trustStoreLocation, char[] trustStorePassword, 424 String trustStoreType, boolean crlEnabled, boolean ocspEnabled, boolean verifyHostName, 425 boolean allowReverseDnsLookup) throws TrustManagerException { 426 427 if (trustStorePassword == null) { 428 trustStorePassword = EMPTY_CHAR_ARRAY; 429 } 430 431 try { 432 KeyStoreFileType storeFileType = 433 KeyStoreFileType.fromPropertyValueOrFileName(trustStoreType, trustStoreLocation); 434 KeyStore ts = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType) 435 .setTrustStorePath(trustStoreLocation).setTrustStorePassword(trustStorePassword).build() 436 .loadTrustStore(); 437 438 PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector()); 439 if (crlEnabled || ocspEnabled) { 440 pbParams.setRevocationEnabled(true); 441 System.setProperty("com.sun.net.ssl.checkRevocation", "true"); 442 if (crlEnabled) { 443 System.setProperty("com.sun.security.enableCRLDP", "true"); 444 } 445 if (ocspEnabled) { 446 Security.setProperty("ocsp.enable", "true"); 447 } 448 } else { 449 pbParams.setRevocationEnabled(false); 450 } 451 452 // Revocation checking is only supported with the PKIX algorithm 453 TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); 454 tmf.init(new CertPathTrustManagerParameters(pbParams)); 455 456 for (final TrustManager tm : tmf.getTrustManagers()) { 457 if (tm instanceof X509ExtendedTrustManager) { 458 return new HBaseTrustManager((X509ExtendedTrustManager) tm, verifyHostName, 459 allowReverseDnsLookup); 460 } 461 } 462 throw new TrustManagerException("Couldn't find X509TrustManager"); 463 } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { 464 throw new TrustManagerException(e); 465 } 466 } 467 468 private static String[] getEnabledProtocols(Configuration config) { 469 String enabledProtocolsInput = config.get(TLS_ENABLED_PROTOCOLS); 470 if (enabledProtocolsInput == null) { 471 return new String[] { config.get(TLS_CONFIG_PROTOCOL, DEFAULT_PROTOCOL) }; 472 } 473 return enabledProtocolsInput.split(","); 474 } 475 476 private static String[] getCipherSuites(Configuration config, boolean useOpenSsl) { 477 String cipherSuitesInput = config.get(TLS_CIPHER_SUITES); 478 if (cipherSuitesInput == null) { 479 return getDefaultCipherSuites(useOpenSsl); 480 } else { 481 return cipherSuitesInput.split(","); 482 } 483 } 484 485 /** 486 * Enable certificate file reloading by creating FileWatchers for keystore and truststore. 487 * AtomicReferences will be set with the new instances. resetContext - if not null - will be 488 * called when the file has been modified. 489 * @param keystoreWatcher Reference to keystoreFileWatcher. 490 * @param trustStoreWatcher Reference to truststoreFileWatcher. 491 * @param resetContext Callback for file changes. 492 */ 493 public static void enableCertFileReloading(Configuration config, 494 AtomicReference<FileChangeWatcher> keystoreWatcher, 495 AtomicReference<FileChangeWatcher> trustStoreWatcher, Runnable resetContext) 496 throws IOException { 497 String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, ""); 498 keystoreWatcher.set(newFileChangeWatcher(keyStoreLocation, resetContext)); 499 String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, ""); 500 // we are using the same callback for both. there's no reason to kick off two 501 // threads if keystore/truststore are both at the same location 502 if (!keyStoreLocation.equals(trustStoreLocation)) { 503 trustStoreWatcher.set(newFileChangeWatcher(trustStoreLocation, resetContext)); 504 } 505 } 506 507 private static FileChangeWatcher newFileChangeWatcher(String fileLocation, Runnable resetContext) 508 throws IOException { 509 if (fileLocation == null || fileLocation.isEmpty() || resetContext == null) { 510 return null; 511 } 512 final Path filePath = Paths.get(fileLocation).toAbsolutePath(); 513 Path parentPath = filePath.getParent(); 514 if (parentPath == null) { 515 throw new IOException("Key/trust store path does not have a parent: " + filePath); 516 } 517 FileChangeWatcher fileChangeWatcher = 518 new FileChangeWatcher(parentPath, Objects.toString(filePath.getFileName()), watchEvent -> { 519 handleWatchEvent(filePath, watchEvent, resetContext); 520 }); 521 fileChangeWatcher.start(); 522 return fileChangeWatcher; 523 } 524 525 /** 526 * Handler for watch events that let us know a file we may care about has changed on disk. 527 * @param filePath the path to the file we are watching for changes. 528 * @param event the WatchEvent. 529 */ 530 private static void handleWatchEvent(Path filePath, WatchEvent<?> event, Runnable resetContext) { 531 boolean shouldResetContext = false; 532 Path dirPath = filePath.getParent(); 533 if (event.kind().equals(StandardWatchEventKinds.OVERFLOW)) { 534 // If we get notified about possibly missed events, reload the key store / trust store just to 535 // be sure. 536 shouldResetContext = true; 537 } else if ( 538 event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY) 539 || event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE) 540 ) { 541 Path eventFilePath = dirPath.resolve((Path) event.context()); 542 if (filePath.equals(eventFilePath)) { 543 shouldResetContext = true; 544 } 545 } 546 // Note: we don't care about delete events 547 if (shouldResetContext) { 548 LOG.info( 549 "Attempting to reset default SSL context after receiving watch event: {} with context: {}", 550 event.kind(), event.context()); 551 resetContext.run(); 552 } else { 553 if (LOG.isDebugEnabled()) { 554 LOG.debug("Ignoring watch event and keeping previous default SSL context. " 555 + "Event kind: {} with context: {}", event.kind(), event.context()); 556 } 557 } 558 } 559}