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) {
096      try {
097        performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
098      } catch (UnknownHostException e) {
099        throw new CertificateException("Failed to verify host", e);
100      }
101    }
102  }
103
104  @Override
105  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
106    throws CertificateException {
107    x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
108    if (hostnameVerificationEnabled) {
109      try {
110        performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
111      } catch (UnknownHostException e) {
112        throw new CertificateException("Failed to verify host", e);
113      }
114    }
115  }
116
117  @Override
118  public void checkClientTrusted(X509Certificate[] chain, String authType)
119    throws CertificateException {
120    x509ExtendedTrustManager.checkClientTrusted(chain, authType);
121  }
122
123  @Override
124  public void checkServerTrusted(X509Certificate[] chain, String authType)
125    throws CertificateException {
126    x509ExtendedTrustManager.checkServerTrusted(chain, authType);
127  }
128
129  /**
130   * Compares peer's hostname with the one stored in the provided client certificate. Performs
131   * verification with the help of provided HostnameVerifier.
132   * @param inetAddress Peer's inet address.
133   * @param certificate Peer's certificate
134   * @throws CertificateException Thrown if the provided certificate doesn't match the peer
135   *                              hostname.
136   */
137  private void performHostVerification(InetAddress inetAddress, X509Certificate certificate)
138    throws CertificateException {
139    String hostAddress = "";
140    String hostName = "";
141    try {
142      hostAddress = inetAddress.getHostAddress();
143      hostnameVerifier.verify(hostAddress, certificate);
144    } catch (SSLException addressVerificationException) {
145      // If we fail with hostAddress, we should try the hostname.
146      // The inetAddress may have been created with a hostname, in which case getHostName() will
147      // return quickly below. If not, a reverse lookup will happen, which can be expensive.
148      // We provide the option to skip the reverse lookup if preferring to fail fast.
149
150      // Handle logging here to aid debugging. The easiest way to check for an existing
151      // hostname is through toString, see javadoc.
152      String inetAddressString = inetAddress.toString();
153      if (!inetAddressString.startsWith("/")) {
154        LOG.debug(
155          "Failed to verify host address: {}, but inetAddress {} has a hostname, trying that",
156          hostAddress, inetAddressString, addressVerificationException);
157      } else if (allowReverseDnsLookup) {
158        LOG.debug(
159          "Failed to verify host address: {}, attempting to verify host name with reverse dns",
160          hostAddress, addressVerificationException);
161      } else {
162        LOG.debug("Failed to verify host address: {}, but reverse dns lookup is disabled",
163          hostAddress, addressVerificationException);
164        throw new CertificateException(
165          "Failed to verify host address, and reverse lookup is disabled",
166          addressVerificationException);
167      }
168
169      try {
170        hostName = inetAddress.getHostName();
171        hostnameVerifier.verify(hostName, certificate);
172      } catch (SSLException hostnameVerificationException) {
173        LOG.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
174        LOG.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
175        throw new CertificateException("Failed to verify both host address and host name",
176          hostnameVerificationException);
177      }
178    }
179  }
180
181}