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}