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  private static final Duration FILE_POLL_INTERVAL = Duration.ofMinutes(1);
158
159  /**
160   * Not all of our default ciphers are available in OpenSSL. Takes our default cipher lists and
161   * filters them to only those available in OpenSsl. Prefers TLS 1.3, then GCM, then CBC because
162   * GCM tends to be better and faster, and we don't need to worry about the java8 vs 9 performance
163   * issue if OpenSSL is handling it.
164   */
165  private static String[] getOpenSslFilteredDefaultCiphers() {
166    if (!OpenSsl.isAvailable()) {
167      return new String[0];
168    }
169
170    Set<String> openSslSuites = OpenSsl.availableJavaCipherSuites();
171    List<String> defaultSuites = new ArrayList<>();
172    Arrays.stream(getTls13Ciphers()).filter(openSslSuites::contains).forEach(defaultSuites::add);
173    Arrays.stream(getGCMCiphers()).filter(openSslSuites::contains).forEach(defaultSuites::add);
174    Arrays.stream(getCBCCiphers()).filter(openSslSuites::contains).forEach(defaultSuites::add);
175    return defaultSuites.toArray(new String[0]);
176  }
177
178  /**
179   * Enum specifying the client auth requirement of server-side TLS sockets created by this
180   * X509Util.
181   * <ul>
182   * <li>NONE - do not request a client certificate.</li>
183   * <li>WANT - request a client certificate, but allow anonymous clients to connect.</li>
184   * <li>NEED - require a client certificate, disconnect anonymous clients.</li>
185   * </ul>
186   * If the config property is not set, the default value is NEED.
187   */
188  public enum ClientAuth {
189    NONE(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.NONE),
190    WANT(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.OPTIONAL),
191    NEED(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.REQUIRE);
192
193    private final org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth;
194
195    ClientAuth(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth) {
196      this.nettyAuth = nettyAuth;
197    }
198
199    /**
200     * Converts a property value to a ClientAuth enum. If the input string is empty or null, returns
201     * <code>ClientAuth.NEED</code>.
202     * @param prop the property string.
203     * @return the ClientAuth.
204     * @throws IllegalArgumentException if the property value is not "NONE", "WANT", "NEED", or
205     *                                  empty/null.
206     */
207    public static ClientAuth fromPropertyValue(String prop) {
208      if (prop == null || prop.length() == 0) {
209        return NEED;
210      }
211      return ClientAuth.valueOf(prop.toUpperCase());
212    }
213
214    public org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth toNettyClientAuth() {
215      return nettyAuth;
216    }
217  }
218
219  private X509Util() {
220    // disabled
221  }
222
223  static String[] getDefaultCipherSuites(boolean useOpenSsl) {
224    if (useOpenSsl) {
225      return DEFAULT_CIPHERS_OPENSSL;
226    }
227    return getDefaultCipherSuitesForJavaVersion(System.getProperty("java.specification.version"));
228  }
229
230  static String[] getDefaultCipherSuitesForJavaVersion(String javaVersion) {
231    Objects.requireNonNull(javaVersion);
232
233    if (javaVersion.matches("\\d+")) {
234      // Must be Java 9 or later
235      int javaVersionInt = Integer.parseInt(javaVersion);
236      if (javaVersionInt >= 11) {
237        LOG.debug(
238          "Using Java11+ optimized cipher suites for Java version {}, including TLSv1.3 support",
239          javaVersion);
240        return DEFAULT_CIPHERS_JAVA11;
241      } else {
242        LOG.debug("Using Java9+ optimized cipher suites for Java version {}", javaVersion);
243        return DEFAULT_CIPHERS_JAVA9;
244      }
245    } else if (javaVersion.startsWith("1.")) {
246      // Must be Java 1.8 or earlier
247      LOG.debug("Using Java8 optimized cipher suites for Java version {}", javaVersion);
248      return DEFAULT_CIPHERS_JAVA8;
249    } else {
250      LOG.debug("Could not parse java version {}, using Java8 optimized cipher suites",
251        javaVersion);
252      return DEFAULT_CIPHERS_JAVA8;
253    }
254  }
255
256  public static SslContext createSslContextForClient(Configuration config)
257    throws X509Exception, IOException {
258
259    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
260
261    boolean useOpenSsl = configureOpenSslIfAvailable(sslContextBuilder, config);
262    String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, "");
263    char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD);
264    String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, "");
265
266    if (keyStoreLocation.isEmpty()) {
267      LOG.warn(TLS_CONFIG_KEYSTORE_LOCATION + " not specified");
268    } else {
269      sslContextBuilder
270        .keyManager(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType));
271    }
272
273    String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, "");
274    char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD);
275    String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, "");
276
277    boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false);
278    boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false);
279
280    boolean verifyServerHostname =
281      config.getBoolean(HBASE_CLIENT_NETTY_TLS_VERIFY_SERVER_HOSTNAME, true);
282    boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true);
283
284    if (trustStoreLocation.isEmpty()) {
285      LOG.warn(TLS_CONFIG_TRUSTSTORE_LOCATION + " not specified");
286    } else {
287      sslContextBuilder
288        .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType,
289          sslCrlEnabled, sslOcspEnabled, verifyServerHostname, allowReverseDnsLookup));
290    }
291
292    sslContextBuilder.enableOcsp(sslOcspEnabled);
293    sslContextBuilder.protocols(getEnabledProtocols(config));
294    sslContextBuilder.ciphers(Arrays.asList(getCipherSuites(config, useOpenSsl)));
295
296    return sslContextBuilder.build();
297  }
298
299  /**
300   * Adds SslProvider.OPENSSL if OpenSsl is available and enabled. In order to make it available,
301   * one must ensure that a properly shaded netty-tcnative is on the classpath. Properly shaded
302   * means relocated to be prefixed with "org.apache.hbase.thirdparty" like the rest of the netty
303   * classes. We make available org.apache.hbase:hbase-openssl as a convenience module which one can
304   * use to pull in a shaded netty-tcnative statically linked against boringssl.
305   */
306  private static boolean configureOpenSslIfAvailable(SslContextBuilder sslContextBuilder,
307    Configuration conf) {
308    if (OpenSsl.isAvailable() && conf.getBoolean(TLS_USE_OPENSSL, true)) {
309      LOG.debug("Using netty-tcnative to accelerate SSL handling");
310      sslContextBuilder.sslProvider(SslProvider.OPENSSL);
311      return true;
312    } else {
313      if (LOG.isDebugEnabled()) {
314        LOG.debug("Using default JDK SSL provider because netty-tcnative is not {}",
315          OpenSsl.isAvailable() ? "enabled" : "available");
316      }
317      sslContextBuilder.sslProvider(SslProvider.JDK);
318      return false;
319    }
320  }
321
322  public static SslContext createSslContextForServer(Configuration config)
323    throws X509Exception, IOException {
324    String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, "");
325    char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD);
326    String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, "");
327
328    if (keyStoreLocation.isEmpty()) {
329      throw new SSLContextException(
330        "Keystore is required for SSL server: " + TLS_CONFIG_KEYSTORE_LOCATION);
331    }
332
333    SslContextBuilder sslContextBuilder;
334    sslContextBuilder = SslContextBuilder
335      .forServer(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType));
336
337    boolean useOpenSsl = configureOpenSslIfAvailable(sslContextBuilder, config);
338    String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, "");
339    char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD);
340    String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, "");
341
342    boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false);
343    boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false);
344
345    ClientAuth clientAuth =
346      ClientAuth.fromPropertyValue(config.get(HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE));
347    boolean verifyClientHostname =
348      config.getBoolean(HBASE_SERVER_NETTY_TLS_VERIFY_CLIENT_HOSTNAME, true);
349    boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true);
350
351    if (trustStoreLocation.isEmpty()) {
352      LOG.warn(TLS_CONFIG_TRUSTSTORE_LOCATION + " not specified");
353    } else {
354      sslContextBuilder
355        .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType,
356          sslCrlEnabled, sslOcspEnabled, verifyClientHostname, allowReverseDnsLookup));
357    }
358
359    sslContextBuilder.enableOcsp(sslOcspEnabled);
360    sslContextBuilder.protocols(getEnabledProtocols(config));
361    sslContextBuilder.ciphers(Arrays.asList(getCipherSuites(config, useOpenSsl)));
362    sslContextBuilder.clientAuth(clientAuth.toNettyClientAuth());
363
364    return sslContextBuilder.build();
365  }
366
367  /**
368   * Creates a key manager by loading the key store from the given file of the given type,
369   * optionally decrypting it using the given password.
370   * @param keyStoreLocation the location of the key store file.
371   * @param keyStorePassword optional password to decrypt the key store. If empty, assumes the key
372   *                         store is not encrypted.
373   * @param keyStoreType     must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to
374   *                         autodetect the key store type from the file extension (e.g. .jks /
375   *                         .pem).
376   * @return the key manager.
377   * @throws KeyManagerException if something goes wrong.
378   */
379  static X509KeyManager createKeyManager(String keyStoreLocation, char[] keyStorePassword,
380    String keyStoreType) throws KeyManagerException {
381
382    if (keyStorePassword == null) {
383      keyStorePassword = EMPTY_CHAR_ARRAY;
384    }
385
386    try {
387      KeyStoreFileType storeFileType =
388        KeyStoreFileType.fromPropertyValueOrFileName(keyStoreType, keyStoreLocation);
389      KeyStore ks = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType)
390        .setKeyStorePath(keyStoreLocation).setKeyStorePassword(keyStorePassword).build()
391        .loadKeyStore();
392
393      KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
394      kmf.init(ks, keyStorePassword);
395
396      for (KeyManager km : kmf.getKeyManagers()) {
397        if (km instanceof X509KeyManager) {
398          return (X509KeyManager) km;
399        }
400      }
401      throw new KeyManagerException("Couldn't find X509KeyManager");
402    } catch (IOException | GeneralSecurityException | IllegalArgumentException e) {
403      throw new KeyManagerException(e);
404    }
405  }
406
407  /**
408   * Creates a trust manager by loading the trust store from the given file of the given type,
409   * optionally decrypting it using the given password.
410   * @param trustStoreLocation    the location of the trust store file.
411   * @param trustStorePassword    optional password to decrypt the trust store (only applies to JKS
412   *                              trust stores). If empty, assumes the trust store is not encrypted.
413   * @param trustStoreType        must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to
414   *                              autodetect the trust store type from the file extension (e.g. .jks
415   *                              / .pem).
416   * @param crlEnabled            enable CRL (certificate revocation list) checks.
417   * @param ocspEnabled           enable OCSP (online certificate status protocol) checks.
418   * @param verifyHostName        if true, ssl peer hostname must match name in certificate
419   * @param allowReverseDnsLookup if true, allow falling back to reverse dns lookup in verifying
420   *                              hostname
421   * @return the trust manager.
422   * @throws TrustManagerException if something goes wrong.
423   */
424  static X509TrustManager createTrustManager(String trustStoreLocation, char[] trustStorePassword,
425    String trustStoreType, boolean crlEnabled, boolean ocspEnabled, boolean verifyHostName,
426    boolean allowReverseDnsLookup) throws TrustManagerException {
427
428    if (trustStorePassword == null) {
429      trustStorePassword = EMPTY_CHAR_ARRAY;
430    }
431
432    try {
433      KeyStoreFileType storeFileType =
434        KeyStoreFileType.fromPropertyValueOrFileName(trustStoreType, trustStoreLocation);
435      KeyStore ts = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType)
436        .setTrustStorePath(trustStoreLocation).setTrustStorePassword(trustStorePassword).build()
437        .loadTrustStore();
438
439      PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
440      if (crlEnabled || ocspEnabled) {
441        pbParams.setRevocationEnabled(true);
442        System.setProperty("com.sun.net.ssl.checkRevocation", "true");
443        if (crlEnabled) {
444          System.setProperty("com.sun.security.enableCRLDP", "true");
445        }
446        if (ocspEnabled) {
447          Security.setProperty("ocsp.enable", "true");
448        }
449      } else {
450        pbParams.setRevocationEnabled(false);
451      }
452
453      // Revocation checking is only supported with the PKIX algorithm
454      TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
455      tmf.init(new CertPathTrustManagerParameters(pbParams));
456
457      for (final TrustManager tm : tmf.getTrustManagers()) {
458        if (tm instanceof X509ExtendedTrustManager) {
459          return new HBaseTrustManager((X509ExtendedTrustManager) tm, verifyHostName,
460            allowReverseDnsLookup);
461        }
462      }
463      throw new TrustManagerException("Couldn't find X509TrustManager");
464    } catch (IOException | GeneralSecurityException | IllegalArgumentException e) {
465      throw new TrustManagerException(e);
466    }
467  }
468
469  private static String[] getEnabledProtocols(Configuration config) {
470    String enabledProtocolsInput = config.get(TLS_ENABLED_PROTOCOLS);
471    if (enabledProtocolsInput == null) {
472      return new String[] { config.get(TLS_CONFIG_PROTOCOL, DEFAULT_PROTOCOL) };
473    }
474    return enabledProtocolsInput.split(",");
475  }
476
477  private static String[] getCipherSuites(Configuration config, boolean useOpenSsl) {
478    String cipherSuitesInput = config.get(TLS_CIPHER_SUITES);
479    if (cipherSuitesInput == null) {
480      return getDefaultCipherSuites(useOpenSsl);
481    } else {
482      return cipherSuitesInput.split(",");
483    }
484  }
485
486  /**
487   * Enable certificate file reloading by creating FileWatchers for keystore and truststore.
488   * AtomicReferences will be set with the new instances. resetContext - if not null - will be
489   * called when the file has been modified.
490   * @param keystoreWatcher   Reference to keystoreFileWatcher.
491   * @param trustStoreWatcher Reference to truststoreFileWatcher.
492   * @param resetContext      Callback for file changes.
493   */
494  public static void enableCertFileReloading(Configuration config,
495    AtomicReference<FileChangeWatcher> keystoreWatcher,
496    AtomicReference<FileChangeWatcher> trustStoreWatcher, Runnable resetContext)
497    throws IOException {
498    String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, "");
499    keystoreWatcher.set(newFileChangeWatcher(keyStoreLocation, resetContext));
500    String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, "");
501    // we are using the same callback for both. there's no reason to kick off two
502    // threads if keystore/truststore are both at the same location
503    if (!keyStoreLocation.equals(trustStoreLocation)) {
504      trustStoreWatcher.set(newFileChangeWatcher(trustStoreLocation, resetContext));
505    }
506  }
507
508  private static FileChangeWatcher newFileChangeWatcher(String fileLocation, Runnable resetContext)
509    throws IOException {
510    if (fileLocation == null || fileLocation.isEmpty() || resetContext == null) {
511      return null;
512    }
513    final Path filePath = Paths.get(fileLocation).toAbsolutePath();
514    FileChangeWatcher fileChangeWatcher =
515      new FileChangeWatcher(filePath, Objects.toString(filePath.getFileName()), FILE_POLL_INTERVAL,
516        watchEventFilePath -> handleWatchEvent(watchEventFilePath, resetContext));
517    fileChangeWatcher.start();
518    return fileChangeWatcher;
519  }
520
521  /**
522   * Handler for watch events that let us know a file we may care about has changed on disk.
523   */
524  private static void handleWatchEvent(Path filePath, Runnable resetContext) {
525    LOG.info("Attempting to reset default SSL context after receiving watch event on file {}",
526      filePath);
527    resetContext.run();
528  }
529}