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}