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.net.InetAddress;
021import java.net.Socket;
022import java.net.UnknownHostException;
023import java.security.cert.CertificateException;
024import java.security.cert.X509Certificate;
025import javax.net.ssl.SSLEngine;
026import javax.net.ssl.SSLException;
027import javax.net.ssl.X509ExtendedTrustManager;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * A custom TrustManager that supports hostname verification We attempt to perform verification
034 * using just the IP address first and if that fails will attempt to perform a reverse DNS lookup
035 * and verify using the hostname. This file has been copied from the Apache ZooKeeper project.
036 * @see <a href=
037 *      "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java">Base
038 *      revision</a>
039 */
040@InterfaceAudience.Private
041public class HBaseTrustManager extends X509ExtendedTrustManager {
042
043  private static final Logger LOG = LoggerFactory.getLogger(HBaseTrustManager.class);
044
045  private final X509ExtendedTrustManager x509ExtendedTrustManager;
046  private final boolean hostnameVerificationEnabled;
047  private final boolean allowReverseDnsLookup;
048
049  private final HBaseHostnameVerifier hostnameVerifier;
050
051  /**
052   * Instantiate a new HBaseTrustManager.
053   * @param x509ExtendedTrustManager    The trustmanager to use for
054   *                                    checkClientTrusted/checkServerTrusted logic
055   * @param hostnameVerificationEnabled If true, this TrustManager should verify hostnames of peers
056   *                                    when checking trust.
057   * @param allowReverseDnsLookup       If true, we will fall back on reverse dns if resolving of
058   *                                    host fails
059   */
060  HBaseTrustManager(X509ExtendedTrustManager x509ExtendedTrustManager,
061    boolean hostnameVerificationEnabled, boolean allowReverseDnsLookup) {
062    this.x509ExtendedTrustManager = x509ExtendedTrustManager;
063    this.hostnameVerificationEnabled = hostnameVerificationEnabled;
064    this.allowReverseDnsLookup = allowReverseDnsLookup;
065    this.hostnameVerifier = new HBaseHostnameVerifier();
066  }
067
068  @Override
069  public X509Certificate[] getAcceptedIssuers() {
070    return x509ExtendedTrustManager.getAcceptedIssuers();
071  }
072
073  @Override
074  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
075    throws CertificateException {
076    x509ExtendedTrustManager.checkClientTrusted(chain, authType, socket);
077    if (hostnameVerificationEnabled) {
078      performHostVerification(socket.getInetAddress(), chain[0]);
079    }
080  }
081
082  @Override
083  public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
084    throws CertificateException {
085    x509ExtendedTrustManager.checkServerTrusted(chain, authType, socket);
086    if (hostnameVerificationEnabled) {
087      performHostVerification(socket.getInetAddress(), chain[0]);
088    }
089  }
090
091  @Override
092  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
093    throws CertificateException {
094    x509ExtendedTrustManager.checkClientTrusted(chain, authType, engine);
095    if (hostnameVerificationEnabled && engine != null) {
096      try {
097        if (engine.getPeerHost() == null) {
098          LOG.warn(
099            "Cannot perform client hostname verification, because peer information is not available");
100          return;
101        }
102        performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
103      } catch (UnknownHostException e) {
104        throw new CertificateException("Failed to verify host", e);
105      }
106    }
107  }
108
109  @Override
110  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
111    throws CertificateException {
112    x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
113    if (hostnameVerificationEnabled) {
114      try {
115        performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
116      } catch (UnknownHostException e) {
117        throw new CertificateException("Failed to verify host", e);
118      }
119    }
120  }
121
122  @Override
123  public void checkClientTrusted(X509Certificate[] chain, String authType)
124    throws CertificateException {
125    x509ExtendedTrustManager.checkClientTrusted(chain, authType);
126  }
127
128  @Override
129  public void checkServerTrusted(X509Certificate[] chain, String authType)
130    throws CertificateException {
131    x509ExtendedTrustManager.checkServerTrusted(chain, authType);
132  }
133
134  /**
135   * Compares peer's hostname with the one stored in the provided client certificate. Performs
136   * verification with the help of provided HostnameVerifier.
137   * @param inetAddress Peer's inet address.
138   * @param certificate Peer's certificate
139   * @throws CertificateException Thrown if the provided certificate doesn't match the peer
140   *                              hostname.
141   */
142  private void performHostVerification(InetAddress inetAddress, X509Certificate certificate)
143    throws CertificateException {
144    String hostAddress = "";
145    String hostName = "";
146    try {
147      hostAddress = inetAddress.getHostAddress();
148      hostnameVerifier.verify(hostAddress, certificate);
149    } catch (SSLException addressVerificationException) {
150      // If we fail with hostAddress, we should try the hostname.
151      // The inetAddress may have been created with a hostname, in which case getHostName() will
152      // return quickly below. If not, a reverse lookup will happen, which can be expensive.
153      // We provide the option to skip the reverse lookup if preferring to fail fast.
154
155      // Handle logging here to aid debugging. The easiest way to check for an existing
156      // hostname is through toString, see javadoc.
157      String inetAddressString = inetAddress.toString();
158      if (!inetAddressString.startsWith("/")) {
159        LOG.debug(
160          "Failed to verify host address: {}, but inetAddress {} has a hostname, trying that",
161          hostAddress, inetAddressString, addressVerificationException);
162      } else if (allowReverseDnsLookup) {
163        LOG.debug(
164          "Failed to verify host address: {}, attempting to verify host name with reverse dns",
165          hostAddress, addressVerificationException);
166      } else {
167        LOG.debug("Failed to verify host address: {}, but reverse dns lookup is disabled",
168          hostAddress, addressVerificationException);
169        throw new CertificateException(
170          "Failed to verify host address, and reverse lookup is disabled",
171          addressVerificationException);
172      }
173
174      try {
175        hostName = inetAddress.getHostName();
176        hostnameVerifier.verify(hostName, certificate);
177      } catch (SSLException hostnameVerificationException) {
178        LOG.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
179        LOG.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
180        throw new CertificateException("Failed to verify both host address and host name",
181          hostnameVerificationException);
182      }
183    }
184  }
185
186}