Skip to content

Commit

Permalink
Issue #6276 - Support non-standard domains in SNI and X509.
Browse files Browse the repository at this point in the history
Fixed the non-domain SNI provider to send the server host,
not the local host (doh!).
Skip X509 matching over IP addresses when the host does
not look like an IP address, to avoid reverse DNS lookup.

Signed-off-by: Simone Bordet <[email protected]>
  • Loading branch information
sbordet committed May 19, 2021
1 parent 4264413 commit 5ab4fc9
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,12 @@
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
Expand Down Expand Up @@ -982,32 +979,24 @@ public void testForcedNonDomainSNI() throws Exception
clientTLS.setEndpointIdentificationAlgorithm("HTTPS");
startClient(clientTLS);

AtomicReference<String> hostRef = new AtomicReference<>();
clientTLS.setSNIProvider((sslEngine, serverNames) ->
{
SNIServerName sni = new SNIHostName(hostRef.get().getBytes(StandardCharsets.US_ASCII));
return Collections.singletonList(sni);
});
clientTLS.setSNIProvider(SslContextFactory.Client.SniProvider.NON_DOMAIN_SNI_PROVIDER);

// Send a request with SNI "localhost", we should get the certificate at alias=localhost.
hostRef.set("localhost");
ContentResponse response1 = client.newRequest(hostRef.get(), connector.getLocalPort())
ContentResponse response1 = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response1.getStatus());

// Send a request with SNI "127.0.0.1", we should get the certificate at alias=ip.
hostRef.set("127.0.0.1");
ContentResponse response2 = client.newRequest(hostRef.get(), connector.getLocalPort())
ContentResponse response2 = client.newRequest("127.0.0.1", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();
assertEquals(HttpStatus.OK_200, response2.getStatus());

if (Net.isIpv6InterfaceAvailable())
{
// Send a request with SNI "[::1]", we should get the certificate at alias=ip.
hostRef.set("[::1]");
ContentResponse response3 = client.newRequest(hostRef.get(), connector.getLocalPort())
ContentResponse response3 = client.newRequest("[::1]", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.send();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2246,17 +2246,19 @@ public void setSNIProvider(SniProvider sniProvider)
* <a href="https://datatracker.ietf.org/doc/html/rfc6066#section-3">TLS 1.3</a>,
* or when they are non-domain strings such as {@code "localhost"}.</p>
* <p>If you need to send custom SNI, such as a non-domain SNI or an IP address SNI,
* you can set your own SNI provider or use {@link #IP_ADDRESS_SNI_PROVIDER}.</p>
* you can set your own SNI provider or use {@link #NON_DOMAIN_SNI_PROVIDER}.</p>
*/
@FunctionalInterface
public interface SniProvider
{
/**
* <p>An SNI provider that sends, if the given {@code serverNames} list is empty,
* the host address retrieved via
* {@link InetAddress#getHostAddress() InetAddress.getLocalHost().getHostAddress()}.</p>
* <p>An SNI provider that, if the given {@code serverNames} list is empty,
* retrieves the host via {@link SSLEngine#getPeerHost()}, converts it to
* ASCII bytes, and sends it as SNI.</p>
* <p>This allows to send non-domain SNI such as {@code "localhost"} or
* IP addresses.</p>
*/
public static final SniProvider IP_ADDRESS_SNI_PROVIDER = Client::getSniServerNames;
public static final SniProvider NON_DOMAIN_SNI_PROVIDER = Client::getSniServerNames;

/**
* <p>Provides the SNI names to send to the server.</p>
Expand All @@ -2277,17 +2279,12 @@ private static List<SNIServerName> getSniServerNames(SSLEngine sslEngine, List<S
{
if (serverNames.isEmpty())
{
try
String host = sslEngine.getPeerHost();
if (host != null)
{
String address = InetAddress.getLocalHost().getHostAddress();
// Must use the byte[] constructor, because the character ':' is forbidden when
// using the String constructor (but typically present in IPv6 addresses).
return Collections.singletonList(new SNIHostName(address.getBytes(StandardCharsets.US_ASCII)));
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Could not retrieve localhost address", x);
return Collections.singletonList(new SNIHostName(host.getBytes(StandardCharsets.US_ASCII)));
}
}
return serverNames;
Expand Down
29 changes: 26 additions & 3 deletions jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,36 @@ public boolean matches(String host)
return true;
}

InetAddress address = toInetAddress(host);
if (address != null)
return _addresses.contains(address);
// Check if the host looks like an IP address to avoid
// DNS lookup for host names that did not match.
if (seemsIPAddress(host))
{
InetAddress address = toInetAddress(host);
if (address != null)
return _addresses.contains(address);
}

return false;
}

private static boolean seemsIPAddress(String host)
{
// IPv4 is just numbers and dots.
String ipv4RegExp = "[0-9\\.]+";
// IPv6 is hex and colons and possibly brackets.
String ipv6RegExp = "[0-9a-fA-F:\\[\\]]+";
return host.matches(ipv4RegExp) ||
(host.matches(ipv6RegExp) && containsAtLeastTwoColons(host));
}

private static boolean containsAtLeastTwoColons(String host)
{
int index = host.indexOf(':');
if (index >= 0)
index = host.indexOf(':', index + 1) ;
return index > 0;
}

@Override
public String toString()
{
Expand Down

0 comments on commit 5ab4fc9

Please sign in to comment.