Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package org.apache.hadoop.hbase.io.crypto.tls;

import java.net.InetAddress;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
Expand All @@ -28,7 +27,6 @@
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
Expand All @@ -40,12 +38,11 @@
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
import org.apache.hadoop.hbase.util.Strings;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses;

/**
* When enabled in {@link X509Util}, handles verifying that the hostname of a peer matches the
* certificate it presents.
Expand Down Expand Up @@ -112,9 +109,8 @@ public boolean verify(final String host, final SSLSession session) {
void verify(final String host, final X509Certificate cert) throws SSLException {
final List<SubjectName> subjectAlts = getSubjectAltNames(cert);
if (subjectAlts != null && !subjectAlts.isEmpty()) {
Optional<InetAddress> inetAddress = parseIpAddress(host);
if (inetAddress.isPresent()) {
matchIPAddress(host, inetAddress.get(), subjectAlts);
if (Strings.isInetAddress(host)) {
matchIPAddress(host, subjectAlts);
} else {
matchDNSName(host, subjectAlts);
}
Expand All @@ -131,14 +127,14 @@ void verify(final String host, final X509Certificate cert) throws SSLException {
}
}

private static void matchIPAddress(final String host, final InetAddress inetAddress,
final List<SubjectName> subjectAlts) throws SSLException {
private static void matchIPAddress(final String host, final List<SubjectName> subjectAlts)
throws SSLException {
for (final SubjectName subjectAlt : subjectAlts) {
if (subjectAlt.getType() == SubjectName.IP) {
Optional<InetAddress> parsed = parseIpAddress(subjectAlt.getValue());
if (parsed.filter(altAddr -> altAddr.equals(inetAddress)).isPresent()) {
return;
}
if (
subjectAlt.getType() == SubjectName.IP
&& Strings.hostnamesEqual(host, subjectAlt.getValue())
) {
return;
}
}
throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any "
Expand Down Expand Up @@ -230,32 +226,6 @@ private static String extractCN(final String subjectPrincipal) throws SSLExcepti
}
}

private static Optional<InetAddress> parseIpAddress(String host) {
host = host.trim();
// Uri strings only work for ipv6 and are wrapped with brackets
// Unfortunately InetAddresses can't handle a mixed input, so we
// check here and choose which parse method to use.
if (host.startsWith("[") && host.endsWith("]")) {
return parseIpAddressUriString(host);
} else {
return parseIpAddressString(host);
}
}

private static Optional<InetAddress> parseIpAddressUriString(String host) {
if (InetAddresses.isUriInetAddress(host)) {
return Optional.of(InetAddresses.forUriString(host));
}
return Optional.empty();
}

private static Optional<InetAddress> parseIpAddressString(String host) {
if (InetAddresses.isInetAddress(host)) {
return Optional.of(InetAddresses.forString(host));
}
return Optional.empty();
}

@SuppressWarnings("MixedMutabilityReturnType")
private static List<SubjectName> getSubjectAltNames(final X509Certificate cert) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@
package org.apache.hadoop.hbase.util;

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.yetus.audience.InterfaceAudience;

import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses;

/**
* Utility for Strings.
Expand Down Expand Up @@ -88,6 +91,51 @@ public static String domainNamePointerToHostName(String dnPtr) {
return dnPtr.endsWith(".") ? dnPtr.substring(0, dnPtr.length() - 1) : dnPtr;
}

/**
* Returns whether the given string is an IP address, including bracketed IPv6 URI form.
* @param host hostname or IP
* @return {@code true} if {@code host} is an IP address
* @throws NullPointerException if {@code host} is {@code null}
*/
public static boolean isInetAddress(String host) {
Objects.requireNonNull(host, "Hostname or IP cannot be null");
host = host.trim();
if (host.startsWith("[") && host.endsWith("]")) {
return InetAddresses.isUriInetAddress(host);
}
return InetAddresses.isInetAddress(host);
}

private static InetAddress parseInetAddress(String host) {
host = host.trim();
if (host.startsWith("[") && host.endsWith("]")) {
return InetAddresses.forUriString(host);
}
return InetAddresses.forString(host);
}

/**
* Compare two host identifiers for equality. DNS hostnames are compared case-insensitively
* because DNS labels are case-insensitive. IP address literals are compared by numeric address.
* @param left first hostname or IP
* @param right second hostname or IP
* @return {@code true} if both refer to the same host identifier
* @throws NullPointerException if either argument is {@code null}
*/
public static boolean hostnamesEqual(String left, String right) {
Objects.requireNonNull(left, "Hostname or IP cannot be null");
Objects.requireNonNull(right, "Hostname or IP cannot be null");
boolean leftIsIp = isInetAddress(left);
boolean rightIsIp = isInetAddress(right);
if (leftIsIp != rightIsIp) {
return false;
}
if (leftIsIp) {
return parseInetAddress(left).equals(parseInetAddress(right));
}
return left.equalsIgnoreCase(right);
}

/**
* Push the input string to the right by appending a character before it, usually a space.
* @param input the string to pad
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.util;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -57,6 +58,21 @@ public void testDomainNamePointerToHostName() {
assertEquals("foo.bar.com", Strings.domainNamePointerToHostName("foo.bar.com."));
}

@Test
public void testHostnamesEqual() {
assertTrue(Strings.hostnamesEqual("HOST.example.com", "host.example.com"));
assertTrue(Strings.hostnamesEqual("rs1", "RS1"));
assertFalse(Strings.hostnamesEqual("host-a.example.com", "host-b.example.com"));
assertTrue(Strings.hostnamesEqual("10.0.0.1", "10.0.0.1"));
assertFalse(Strings.hostnamesEqual("10.0.0.1", "10.0.0.2"));
assertFalse(Strings.hostnamesEqual("HOST.example.com", "10.0.0.1"));
assertTrue(Strings.hostnamesEqual("::1", "0:0:0:0:0:0:0:1"));
assertTrue(Strings.hostnamesEqual("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
assertThrows(NullPointerException.class, () -> Strings.hostnamesEqual(null, "host"));
assertThrows(NullPointerException.class, () -> Strings.hostnamesEqual("host", null));
}

@Test
public void testPadFront() {
assertEquals("ddfoo", Strings.padFront("foo", 'd', 5));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
import org.apache.hadoop.hbase.util.RetryCounter;
import org.apache.hadoop.hbase.util.RetryCounterFactory;
import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.hbase.util.Threads;
import org.apache.hadoop.hbase.util.VersionInfo;
import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
Expand Down Expand Up @@ -1432,8 +1433,9 @@ protected void handleReportForDutyResponse(final RegionServerStartupResponse c)
expectedHostName = rpcServices.getSocketAddress().getAddress().getHostAddress();
}
boolean isHostnameConsist = StringUtils.isBlank(useThisHostnameInstead)
? hostnameFromMasterPOV.equals(expectedHostName)
: hostnameFromMasterPOV.equals(useThisHostnameInstead);
? Strings.hostnamesEqual(hostnameFromMasterPOV, expectedHostName)
: Strings.hostnamesEqual(hostnameFromMasterPOV, useThisHostnameInstead);

if (!isHostnameConsist) {
String msg = "Master passed us a different hostname to use; was="
+ (StringUtils.isBlank(useThisHostnameInstead)
Expand Down