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