Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #6099 - Cipher preference may break SNI if certificates have di… #6246

Merged
merged 1 commit into from May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -40,6 +40,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Connection;
Expand Down Expand Up @@ -489,6 +490,21 @@ protected void customize(Socket socket, Class<? extends Connection> connection,
assertEquals(0, history.size());
}

@Test
public void testSNIWithDifferentKeyTypes() throws Exception
{
// This KeyStore contains 2 certificates, one with keyAlg=EC, one with keyAlg=RSA.
start(ssl -> ssl.setKeyStorePath("src/test/resources/keystore_sni_key_types.p12"));

// Make a request with SNI = rsa.domain.com, the RSA certificate should be chosen.
HttpTester.Response response1 = HttpTester.parseResponse(getResponse("rsa.domain.com", "rsa.domain.com"));
assertEquals(HttpStatus.OK_200, response1.getStatus());

// Make a request with SNI = ec.domain.com, the EC certificate should be chosen.
HttpTester.Response response2 = HttpTester.parseResponse(getResponse("ec.domain.com", "ec.domain.com"));
assertEquals(HttpStatus.OK_200, response2.getStatus());
}

private String getResponse(String host, String cn) throws Exception
{
String response = getResponse(host, host, cn);
Expand Down
Binary file not shown.
Expand Up @@ -250,7 +250,7 @@ public interface SniSelector
* <p>Selects a certificate based on SNI information.</p>
* <p>This method may be invoked multiple times during the TLS handshake, with different parameters.
* For example, the {@code keyType} could be different, and subsequently the collection of certificates
* (because they need to match the {@code keyType}.</p>
* (because they need to match the {@code keyType}).</p>
*
* @param keyType the key algorithm type name
* @param issuers the list of acceptable CA issuer subject names or null if it does not matter which issuers are used
Expand Down
Expand Up @@ -2155,12 +2155,17 @@ public void setWantClientAuth(boolean wantClientAuth)
}

/**
* Does the default {@link #sniSelect(String, Principal[], SSLSession, String, Collection)} implementation
* require an SNI match? Note that if a non SNI handshake is accepted, requests may still be rejected
* at the HTTP level for incorrect SNI (see SecureRequestCustomizer).
* <p>Returns whether an SNI match is required when choosing the alias that
* identifies the certificate to send to the client.</p>
* <p>The exact logic to choose an alias given the SNI is configurable via
* {@link #setSNISelector(SniX509ExtendedKeyManager.SniSelector)}.</p>
* <p>The default implementation is {@link #sniSelect(String, Principal[], SSLSession, String, Collection)}
* and if SNI is not required it will delegate the TLS implementation to
* choose an alias (typically the first alias in the KeyStore).</p>
* <p>Note that if a non SNI handshake is accepted, requests may still be rejected
* at the HTTP level for incorrect SNI (see SecureRequestCustomizer).</p>
*
* @return true if no SNI match is handled as no certificate match, false if no SNI match is handled by
* delegation to the non SNI matching methods.
* @return whether an SNI match is required when choosing the alias that identifies the certificate
*/
@ManagedAttribute("Whether the TLS handshake is rejected if there is no SNI host match")
public boolean isSniRequired()
Expand All @@ -2169,14 +2174,12 @@ public boolean isSniRequired()
}

/**
* Set if the default {@link #sniSelect(String, Principal[], SSLSession, String, Collection)} implementation
* require an SNI match? Note that if a non SNI handshake is accepted, requests may still be rejected
* at the HTTP level for incorrect SNI (see SecureRequestCustomizer).
* This setting may have no effect if {@link #sniSelect(String, Principal[], SSLSession, String, Collection)} is
* overridden or a non null function is passed to {@link #setSNISelector(SniX509ExtendedKeyManager.SniSelector)}.
* <p>Sets whether an SNI match is required when choosing the alias that
* identifies the certificate to send to the client.</p>
* <p>This setting may have no effect if {@link #sniSelect(String, Principal[], SSLSession, String, Collection)} is
* overridden or a custom function is passed to {@link #setSNISelector(SniX509ExtendedKeyManager.SniSelector)}.</p>
*
* @param sniRequired true if no SNI match is handled as no certificate match, false if no SNI match is handled by
* delegation to the non SNI matching methods.
* @param sniRequired whether an SNI match is required when choosing the alias that identifies the certificate
*/
public void setSniRequired(boolean sniRequired)
{
Expand Down Expand Up @@ -2244,9 +2247,15 @@ public String sniSelect(String keyType, Principal[] issuers, SSLSession session,
.filter(x509 -> x509.matches(sniHost))
.collect(Collectors.toList());

// No match, let the JDK decide unless unmatched SNIs are rejected.
if (matching.isEmpty())
return isSniRequired() ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
{
// There is no match for this SNI among the certificates valid for
// this keyType; check if there is any certificate that matches this
// SNI, as we will likely be called again with a different keyType.
boolean anyMatching = aliasCerts().values().stream()
.anyMatch(x509 -> x509.matches(sniHost));
return isSniRequired() || anyMatching ? null : SniX509ExtendedKeyManager.SniSelector.DELEGATE;
}

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