blob: ca4756a6131c41d5a213439a3f6cb83dcd0ab255 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.io.crypto.tls;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509ExtendedTrustManager;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A custom TrustManager that supports hostname verification We attempt to perform verification
* using just the IP address first and if that fails will attempt to perform a reverse DNS lookup
* and verify using the hostname. This file has been copied from the Apache ZooKeeper project.
* @see <a href=
* "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/ZKTrustManager.java">Base
* revision</a>
*/
@InterfaceAudience.Private
public class HBaseTrustManager extends X509ExtendedTrustManager {
private static final Logger LOG = LoggerFactory.getLogger(HBaseTrustManager.class);
private final X509ExtendedTrustManager x509ExtendedTrustManager;
private final boolean hostnameVerificationEnabled;
private final boolean allowReverseDnsLookup;
private final HBaseHostnameVerifier hostnameVerifier;
/**
* Instantiate a new HBaseTrustManager.
* @param x509ExtendedTrustManager The trustmanager to use for
* checkClientTrusted/checkServerTrusted logic
* @param hostnameVerificationEnabled If true, this TrustManager should verify hostnames of peers
* when checking trust.
* @param allowReverseDnsLookup If true, we will fall back on reverse dns if resolving of
* host fails
*/
HBaseTrustManager(X509ExtendedTrustManager x509ExtendedTrustManager,
boolean hostnameVerificationEnabled, boolean allowReverseDnsLookup) {
this.x509ExtendedTrustManager = x509ExtendedTrustManager;
this.hostnameVerificationEnabled = hostnameVerificationEnabled;
this.allowReverseDnsLookup = allowReverseDnsLookup;
this.hostnameVerifier = new HBaseHostnameVerifier();
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return x509ExtendedTrustManager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType, socket);
if (hostnameVerificationEnabled) {
performHostVerification(socket.getInetAddress(), chain[0]);
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType, socket);
if (hostnameVerificationEnabled) {
performHostVerification(socket.getInetAddress(), chain[0]);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType, engine);
if (hostnameVerificationEnabled) {
try {
performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
} catch (UnknownHostException e) {
throw new CertificateException("Failed to verify host", e);
}
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
if (hostnameVerificationEnabled) {
try {
performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
} catch (UnknownHostException e) {
throw new CertificateException("Failed to verify host", e);
}
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType);
}
/**
* Compares peer's hostname with the one stored in the provided client certificate. Performs
* verification with the help of provided HostnameVerifier.
* @param inetAddress Peer's inet address.
* @param certificate Peer's certificate
* @throws CertificateException Thrown if the provided certificate doesn't match the peer
* hostname.
*/
private void performHostVerification(InetAddress inetAddress, X509Certificate certificate)
throws CertificateException {
String hostAddress = "";
String hostName = "";
try {
hostAddress = inetAddress.getHostAddress();
hostnameVerifier.verify(hostAddress, certificate);
} catch (SSLException addressVerificationException) {
// If we fail with hostAddress, we should try the hostname.
// The inetAddress may have been created with a hostname, in which case getHostName() will
// return quickly below. If not, a reverse lookup will happen, which can be expensive.
// We provide the option to skip the reverse lookup if preferring to fail fast.
// Handle logging here to aid debugging. The easiest way to check for an existing
// hostname is through toString, see javadoc.
String inetAddressString = inetAddress.toString();
if (!inetAddressString.startsWith("/")) {
LOG.debug(
"Failed to verify host address: {}, but inetAddress {} has a hostname, trying that",
hostAddress, inetAddressString, addressVerificationException);
} else if (allowReverseDnsLookup) {
LOG.debug(
"Failed to verify host address: {}, attempting to verify host name with reverse dns",
hostAddress, addressVerificationException);
} else {
LOG.debug("Failed to verify host address: {}, but reverse dns lookup is disabled",
hostAddress, addressVerificationException);
throw new CertificateException(
"Failed to verify host address, and reverse lookup is disabled",
addressVerificationException);
}
try {
hostName = inetAddress.getHostName();
hostnameVerifier.verify(hostName, certificate);
} catch (SSLException hostnameVerificationException) {
LOG.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
LOG.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
throw new CertificateException("Failed to verify both host address and host name",
hostnameVerificationException);
}
}
}
}