430951 Support SNI with ExtendedSslContextFactory
refactored common code in SniX509ExtendedKeyManager added sniHostCheck code to ensure request host is the same as SNI host
This commit is contained in:
parent
9eb2cb4c0f
commit
c3577bbbb0
|
@ -70,7 +70,11 @@
|
||||||
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
<Arg><Ref refid="httpConfig"/></Arg>
|
<Arg><Ref refid="httpConfig"/></Arg>
|
||||||
<Call name="addCustomizer">
|
<Call name="addCustomizer">
|
||||||
<Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.SecureRequestCustomizer">
|
||||||
|
<Property name="jetty.ssl.sniHostCheck" default="true"/>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
</Call>
|
</Call>
|
||||||
</New>
|
</New>
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,9 @@ http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-server/
|
||||||
## Thread priority delta to give to acceptor threads
|
## Thread priority delta to give to acceptor threads
|
||||||
# jetty.ssl.acceptorPriorityDelta=0
|
# jetty.ssl.acceptorPriorityDelta=0
|
||||||
|
|
||||||
|
## Whether request host names are checked to match any SNI names
|
||||||
|
# jetty.ssl.sniHostCheck=true
|
||||||
|
|
||||||
### SslContextFactory Configuration
|
### SslContextFactory Configuration
|
||||||
|
|
||||||
## Keystore file path (relative to $jetty.base)
|
## Keystore file path (relative to $jetty.base)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import javax.servlet.DispatcherType;
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpGenerator;
|
import org.eclipse.jetty.http.HttpGenerator;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
@ -353,7 +354,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (EofException|QuietServletException e)
|
catch (EofException|QuietServletException|BadMessageException e)
|
||||||
{
|
{
|
||||||
error=true;
|
error=true;
|
||||||
LOG.debug(e);
|
LOG.debug(e);
|
||||||
|
@ -465,7 +466,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
|
_response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
|
||||||
_response.sendError(500, x.getMessage());
|
|
||||||
|
if (x instanceof BadMessageException)
|
||||||
|
{
|
||||||
|
BadMessageException bme = (BadMessageException)x;
|
||||||
|
_response.sendError(bme.getCode(), bme.getReason());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_response.sendError(500, x.getClass().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||||
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
|
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
|
||||||
|
@ -48,6 +49,19 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
*/
|
*/
|
||||||
public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
|
public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
|
||||||
|
|
||||||
|
private boolean _sniHostCheck;
|
||||||
|
|
||||||
|
|
||||||
|
public SecureRequestCustomizer()
|
||||||
|
{
|
||||||
|
this(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecureRequestCustomizer(boolean sniHostCheck)
|
||||||
|
{
|
||||||
|
_sniHostCheck=sniHostCheck;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
|
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
|
||||||
{
|
{
|
||||||
|
@ -88,6 +102,16 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||||
request.setScheme(HttpScheme.HTTPS.asString());
|
request.setScheme(HttpScheme.HTTPS.asString());
|
||||||
SSLSession sslSession = sslEngine.getSession();
|
SSLSession sslSession = sslEngine.getSession();
|
||||||
|
|
||||||
|
if (_sniHostCheck)
|
||||||
|
{
|
||||||
|
String sniName = (String)sslSession.getValue("org.eclipse.jetty.util.ssl.sniname");
|
||||||
|
if (sniName!=null && !sniName.equalsIgnoreCase(request.getServerName()))
|
||||||
|
{
|
||||||
|
LOG.warn("Host does not match SNI Name: {}!={}",sniName,request.getServerName());
|
||||||
|
throw new BadMessageException(400,"Host does not match SNI");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String cipherSuite=sslSession.getCipherSuite();
|
String cipherSuite=sslSession.getCipherSuite();
|
||||||
|
|
|
@ -1261,7 +1261,7 @@ public class RequestTest
|
||||||
{
|
{
|
||||||
long start=System.currentTimeMillis();
|
long start=System.currentTimeMillis();
|
||||||
String response = _connector.getResponses(request);
|
String response = _connector.getResponses(request);
|
||||||
assertThat(response,Matchers.containsString("Form too many keys"));
|
assertThat(response,Matchers.containsString("IllegalStateException"));
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
assertTrue((now-start)<5000);
|
assertTrue((now-start)<5000);
|
||||||
}
|
}
|
||||||
|
@ -1307,7 +1307,7 @@ public class RequestTest
|
||||||
{
|
{
|
||||||
long start=System.currentTimeMillis();
|
long start=System.currentTimeMillis();
|
||||||
String response = _connector.getResponses(request);
|
String response = _connector.getResponses(request);
|
||||||
assertTrue(response.contains("Form too large:"));
|
assertTrue(response.contains("IllegalStateException"));
|
||||||
long now=System.currentTimeMillis();
|
long now=System.currentTimeMillis();
|
||||||
assertTrue((now-start)<5000);
|
assertTrue((now-start)<5000);
|
||||||
}
|
}
|
||||||
|
@ -1414,7 +1414,7 @@ public class RequestTest
|
||||||
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
|
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
|
||||||
|
|
||||||
//We should get an error when we getParams if there was a problem parsing the multipart
|
//We should get an error when we getParams if there was a problem parsing the multipart
|
||||||
String field1 = request.getParameter("xxx");
|
request.getParameter("xxx");
|
||||||
//A 200 response is actually wrong here
|
//A 200 response is actually wrong here
|
||||||
}
|
}
|
||||||
catch (RuntimeException e)
|
catch (RuntimeException e)
|
||||||
|
|
|
@ -125,7 +125,9 @@ public class SslConnectionFactoryTest
|
||||||
@Test
|
@Test
|
||||||
public void testSNIConnect() throws Exception
|
public void testSNIConnect() throws Exception
|
||||||
{
|
{
|
||||||
String response= getResponse("jetty.eclipse.org","jetty.eclipse.org");
|
String response;
|
||||||
|
|
||||||
|
response= getResponse("jetty.eclipse.org","jetty.eclipse.org");
|
||||||
Assert.assertThat(response,Matchers.containsString("host=jetty.eclipse.org"));
|
Assert.assertThat(response,Matchers.containsString("host=jetty.eclipse.org"));
|
||||||
|
|
||||||
response= getResponse("www.example.com","www.example.com");
|
response= getResponse("www.example.com","www.example.com");
|
||||||
|
@ -139,10 +141,29 @@ public class SslConnectionFactoryTest
|
||||||
|
|
||||||
response= getResponse("www.san.com","san example");
|
response= getResponse("www.san.com","san example");
|
||||||
Assert.assertThat(response,Matchers.containsString("host=www.san.com"));
|
Assert.assertThat(response,Matchers.containsString("host=www.san.com"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBadSNIConnect() throws Exception
|
||||||
|
{
|
||||||
|
String response;
|
||||||
|
|
||||||
|
response= getResponse("www.example.com","some.other.com","www.example.com");
|
||||||
|
Assert.assertThat(response,Matchers.containsString("HTTP/1.1 400 "));
|
||||||
|
Assert.assertThat(response,Matchers.containsString("Host does not match SNI"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private String getResponse(String host,String cn) throws Exception
|
private String getResponse(String host,String cn) throws Exception
|
||||||
|
{
|
||||||
|
String response = getResponse(host,host,cn);
|
||||||
|
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
|
||||||
|
Assert.assertThat(response,Matchers.containsString("url=/ctx/path"));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResponse(String sniHost,String reqHost, String cn) throws Exception
|
||||||
{
|
{
|
||||||
SslContextFactory clientContextFactory = new SslContextFactory(true);
|
SslContextFactory clientContextFactory = new SslContextFactory(true);
|
||||||
clientContextFactory.start();
|
clientContextFactory.start();
|
||||||
|
@ -152,7 +173,7 @@ public class SslConnectionFactoryTest
|
||||||
|
|
||||||
if (cn!=null)
|
if (cn!=null)
|
||||||
{
|
{
|
||||||
SNIHostName serverName = new SNIHostName(host);
|
SNIHostName serverName = new SNIHostName(sniHost);
|
||||||
List<SNIServerName> serverNames = new ArrayList<>();
|
List<SNIServerName> serverNames = new ArrayList<>();
|
||||||
serverNames.add(serverName);
|
serverNames.add(serverName);
|
||||||
|
|
||||||
|
@ -170,10 +191,8 @@ public class SslConnectionFactoryTest
|
||||||
Assert.assertThat(cert.getSubjectX500Principal().getName("CANONICAL"), Matchers.startsWith("cn="+cn));
|
Assert.assertThat(cert.getSubjectX500Principal().getName("CANONICAL"), Matchers.startsWith("cn="+cn));
|
||||||
}
|
}
|
||||||
|
|
||||||
sslSocket.getOutputStream().write(("GET /ctx/path HTTP/1.0\r\nHost: "+host+":"+_port+"\r\n\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
sslSocket.getOutputStream().write(("GET /ctx/path HTTP/1.0\r\nHost: "+reqHost+":"+_port+"\r\n\r\n").getBytes(StandardCharsets.ISO_8859_1));
|
||||||
String response = IO.toString(sslSocket.getInputStream());
|
String response = IO.toString(sslSocket.getInputStream());
|
||||||
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
|
|
||||||
Assert.assertThat(response,Matchers.containsString("url=/ctx/path"));
|
|
||||||
|
|
||||||
sslSocket.close();
|
sslSocket.close();
|
||||||
clientContextFactory.stop();
|
clientContextFactory.stop();
|
||||||
|
|
|
@ -223,9 +223,9 @@ public class ExtendedSslContextFactory extends SslContextFactory
|
||||||
return _alias;
|
return _alias;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SNIHostName getServerName()
|
public String getServerName()
|
||||||
{
|
{
|
||||||
return _name;
|
return _name==null?null:_name.getAsciiName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.Collection;
|
||||||
|
|
||||||
import javax.net.ssl.SNIMatcher;
|
import javax.net.ssl.SNIMatcher;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||||
{
|
{
|
||||||
static final Logger LOG = Log.getLogger(SniX509ExtendedKeyManager.class);
|
static final Logger LOG = Log.getLogger(SniX509ExtendedKeyManager.class);
|
||||||
|
public final static String SNI_NAME = "org.eclipse.jetty.util.ssl.sniname";
|
||||||
|
public final static String NO_MATCHERS="No Matchers";
|
||||||
private final X509ExtendedKeyManager _delegate;
|
private final X509ExtendedKeyManager _delegate;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -55,7 +58,6 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||||
{
|
{
|
||||||
_delegate = keyManager;
|
_delegate = keyManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
|
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
|
||||||
|
@ -68,88 +70,64 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||||
{
|
{
|
||||||
return _delegate.chooseEngineClientAlias(keyType,issuers,engine);
|
return _delegate.chooseEngineClientAlias(keyType,issuers,engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String chooseServerAlias(String keyType, Principal[] issuers, Collection<SNIMatcher> matchers, SSLSession session)
|
||||||
|
{
|
||||||
|
// Look for the aliases that are suitable for the keytype and issuers
|
||||||
|
String[] aliases = _delegate.getServerAliases(keyType,issuers);
|
||||||
|
if (aliases==null || aliases.length==0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Look for an SNI alias
|
||||||
|
String alias=null;
|
||||||
|
String host=null;
|
||||||
|
if (matchers!=null)
|
||||||
|
{
|
||||||
|
for (SNIMatcher m : matchers)
|
||||||
|
{
|
||||||
|
if (m instanceof ExtendedSslContextFactory.AliasSNIMatcher)
|
||||||
|
{
|
||||||
|
ExtendedSslContextFactory.AliasSNIMatcher matcher = (ExtendedSslContextFactory.AliasSNIMatcher)m;
|
||||||
|
alias=matcher.getAlias();
|
||||||
|
host=matcher.getServerName();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("choose {} from {}",alias,Arrays.asList(aliases));
|
||||||
|
|
||||||
|
// Check if the SNI selected alias is allowable
|
||||||
|
if (alias!=null)
|
||||||
|
{
|
||||||
|
for (String a:aliases)
|
||||||
|
{
|
||||||
|
if (a.equals(alias))
|
||||||
|
{
|
||||||
|
session.putValue(SNI_NAME,host);
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return NO_MATCHERS;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
|
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
|
||||||
{
|
{
|
||||||
// Look for the aliases that are suitable for the keytype and issuers
|
SSLSocket sslSocket = (SSLSocket)socket;
|
||||||
String[] aliases = _delegate.getServerAliases(keyType,issuers);
|
|
||||||
|
|
||||||
if (aliases==null || aliases.length==0)
|
String alias = chooseServerAlias(keyType,issuers,sslSocket.getSSLParameters().getSNIMatchers(),sslSocket.getHandshakeSession());
|
||||||
return null;
|
return alias==NO_MATCHERS?_delegate.chooseServerAlias(keyType,issuers,socket):alias;
|
||||||
|
|
||||||
// Look for an SNI alias
|
|
||||||
String alias=null;
|
|
||||||
Collection<SNIMatcher> matchers = ((SSLSocket)socket).getSSLParameters().getSNIMatchers();
|
|
||||||
if (matchers!=null)
|
|
||||||
{
|
|
||||||
for (SNIMatcher m : matchers)
|
|
||||||
{
|
|
||||||
if (m instanceof ExtendedSslContextFactory.AliasSNIMatcher)
|
|
||||||
{
|
|
||||||
alias=((ExtendedSslContextFactory.AliasSNIMatcher)m).getAlias();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("choose {} from {}",alias,Arrays.asList(aliases));
|
|
||||||
|
|
||||||
// Check if the SNI selected alias is allowable
|
|
||||||
if (alias!=null)
|
|
||||||
{
|
|
||||||
for (String a:aliases)
|
|
||||||
{
|
|
||||||
if (a.equals(alias))
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _delegate.chooseServerAlias(keyType,issuers,socket);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
|
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
|
||||||
{
|
{
|
||||||
// Look for the aliases that are suitable for the keytype and issuers
|
String alias = chooseServerAlias(keyType,issuers,engine.getSSLParameters().getSNIMatchers(),engine.getHandshakeSession());
|
||||||
String[] aliases = _delegate.getServerAliases(keyType,issuers);
|
return alias==NO_MATCHERS?_delegate.chooseEngineServerAlias(keyType,issuers,engine):alias;
|
||||||
if (aliases==null || aliases.length==0)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// Look for an SNI alias
|
|
||||||
String alias=null;
|
|
||||||
Collection<SNIMatcher> matchers = engine.getSSLParameters().getSNIMatchers();
|
|
||||||
if (matchers!=null)
|
|
||||||
{
|
|
||||||
for (SNIMatcher m : matchers)
|
|
||||||
{
|
|
||||||
if (m instanceof ExtendedSslContextFactory.AliasSNIMatcher)
|
|
||||||
{
|
|
||||||
alias=((ExtendedSslContextFactory.AliasSNIMatcher)m).getAlias();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("choose {} from {}",alias,Arrays.asList(aliases));
|
|
||||||
|
|
||||||
// Check if the SNI selected alias is allowable
|
|
||||||
if (alias!=null)
|
|
||||||
{
|
|
||||||
for (String a:aliases)
|
|
||||||
{
|
|
||||||
if (a.equals(alias))
|
|
||||||
return alias;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _delegate.chooseEngineServerAlias(keyType,issuers,engine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -175,6 +153,4 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||||
{
|
{
|
||||||
return _delegate.getServerAliases(keyType,issuers);
|
return _delegate.getServerAliases(keyType,issuers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue