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