Skip to content

Commit

Permalink
Fixes #6034 - SslContextFactory may select a wildcard certificate dur…
Browse files Browse the repository at this point in the history
…ing SNI selection when a more specific SSL certificate is present.

Now matching certificates are sorted, non-wildcard first, so that a more specific alias is returned.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
  • Loading branch information
sbordet committed Mar 8, 2021
1 parent 217a97b commit 8de7b83
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 7 deletions.
Expand Up @@ -28,6 +28,7 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
Expand Down Expand Up @@ -58,6 +59,7 @@
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SniX509ExtendedKeyManager;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.ssl.X509;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -163,7 +165,27 @@ public void testSNIConnectNoWild() throws Exception
@Test
public void testSNIConnect() throws Exception
{
start("src/test/resources/keystore_sni.p12");
start(ssl ->
{
ssl.setKeyStorePath("src/test/resources/keystore_sni.p12");
ssl.setSNISelector((keyType, issuers, session, sniHost, certificates) ->
{
// Make sure the *.domain.com comes before sub.domain.com
// to test that we prefer more specific domains.
List<X509> sortedCertificates = certificates.stream()
// As sorted() sorts ascending, make *.domain.com the smallest.
.sorted((x509a, x509b) ->
{
if (x509a.matches("domain.com"))
return -1;
if (x509b.matches("domain.com"))
return 1;
return 0;
})
.collect(Collectors.toList());
return ssl.sniSelect(keyType, issuers, session, sniHost, sortedCertificates);
});
});

String response = getResponse("jetty.eclipse.org", "jetty.eclipse.org");
assertThat(response, Matchers.containsString("X-HOST: jetty.eclipse.org"));
Expand All @@ -174,6 +196,9 @@ public void testSNIConnect() throws Exception
response = getResponse("foo.domain.com", "*.domain.com");
assertThat(response, Matchers.containsString("X-HOST: foo.domain.com"));

response = getResponse("sub.domain.com", "sub.domain.com");
assertThat(response, Matchers.containsString("X-HOST: sub.domain.com"));

response = getResponse("m.san.com", "san example");
assertThat(response, Matchers.containsString("X-HOST: m.san.com"));

Expand Down
Binary file modified jetty-server/src/test/resources/keystore_sni.p12
Binary file not shown.
Expand Up @@ -49,6 +49,7 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
Expand All @@ -58,7 +59,6 @@
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
Expand Down Expand Up @@ -2230,7 +2230,7 @@ public void setSNISelector(SniX509ExtendedKeyManager.SniSelector sniSelector)
}

@Override
public String sniSelect(String keyType, Principal[] issuers, SSLSession session, String sniHost, Collection<X509> certificates) throws SSLHandshakeException
public String sniSelect(String keyType, Principal[] issuers, SSLSession session, String sniHost, Collection<X509> certificates)
{
if (sniHost == null)
{
Expand All @@ -2239,12 +2239,24 @@ public String sniSelect(String keyType, Principal[] issuers, SSLSession session,
}
else
{
// Match the SNI host, or let the JDK decide unless unmatched SNIs are rejected.
return certificates.stream()
// Match the SNI host.
List<X509> matching = certificates.stream()
.filter(x509 -> x509.matches(sniHost))
.findFirst()
.collect(Collectors.toList());

// No match, let the JDK decide unless unmatched SNIs are rejected.
if (matching.isEmpty())
return isSniRequired() ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;

String alias = matching.get(0).getAlias();
if (matching.size() == 1)
return alias;

// Prefer strict matches over wildcard matches.
return matching.stream()
.min(Comparator.comparingInt(cert -> cert.getWilds().size()))
.map(X509::getAlias)
.orElse(_sniRequired ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE);
.orElse(alias);
}
}

Expand Down

0 comments on commit 8de7b83

Please sign in to comment.