467702 - SslContextFactory not backward compatible
This commit is contained in:
parent
10130afa8d
commit
3f0fd550dc
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,248 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.ssl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import javax.net.ssl.SNIHostName;
|
||||
import javax.net.ssl.SNIServerName;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SocketCustomizationListener;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SniSslConnectionFactoryTest
|
||||
{
|
||||
Server _server;
|
||||
ServerConnector _connector;
|
||||
int _port;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
{
|
||||
String keystorePath = "src/test/resources/snikeystore";
|
||||
File keystoreFile = new File(keystorePath);
|
||||
if (!keystoreFile.exists())
|
||||
{
|
||||
throw new FileNotFoundException(keystoreFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
_server = new Server();
|
||||
|
||||
HttpConfiguration http_config = new HttpConfiguration();
|
||||
http_config.setSecureScheme("https");
|
||||
http_config.setSecurePort(8443);
|
||||
http_config.setOutputBufferSize(32768);
|
||||
HttpConfiguration https_config = new HttpConfiguration(http_config);
|
||||
https_config.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
|
||||
sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
|
||||
sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
|
||||
|
||||
ServerConnector https = _connector = new ServerConnector(_server,
|
||||
new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
|
||||
new HttpConnectionFactory(https_config));
|
||||
https.setPort(0);
|
||||
https.setIdleTimeout(30000);
|
||||
|
||||
_server.addConnector(https);
|
||||
|
||||
_server.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
response.setStatus(200);
|
||||
response.getWriter().write("url="+request.getRequestURI()+"\nhost="+request.getServerName());
|
||||
response.flushBuffer();
|
||||
}
|
||||
});
|
||||
|
||||
_server.start();
|
||||
_port=https.getLocalPort();
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception
|
||||
{
|
||||
_server.stop();
|
||||
_server=null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnect() throws Exception
|
||||
{
|
||||
String response= getResponse("127.0.0.1",null);
|
||||
Assert.assertThat(response,Matchers.containsString("host=127.0.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSNIConnect() throws Exception
|
||||
{
|
||||
String response;
|
||||
|
||||
response= getResponse("jetty.eclipse.org","jetty.eclipse.org");
|
||||
Assert.assertThat(response,Matchers.containsString("host=jetty.eclipse.org"));
|
||||
|
||||
response= getResponse("www.example.com","www.example.com");
|
||||
Assert.assertThat(response,Matchers.containsString("host=www.example.com"));
|
||||
|
||||
response= getResponse("foo.domain.com","*.domain.com");
|
||||
Assert.assertThat(response,Matchers.containsString("host=foo.domain.com"));
|
||||
|
||||
response= getResponse("m.san.com","san example");
|
||||
Assert.assertThat(response,Matchers.containsString("host=m.san.com"));
|
||||
|
||||
response= getResponse("www.san.com","san example");
|
||||
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
|
||||
{
|
||||
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);
|
||||
clientContextFactory.start();
|
||||
SSLSocketFactory factory = clientContextFactory.getSslContext().getSocketFactory();
|
||||
|
||||
SSLSocket sslSocket = (SSLSocket)factory.createSocket("127.0.0.1", _port);
|
||||
|
||||
if (cn!=null)
|
||||
{
|
||||
SNIHostName serverName = new SNIHostName(sniHost);
|
||||
List<SNIServerName> serverNames = new ArrayList<>();
|
||||
serverNames.add(serverName);
|
||||
|
||||
SSLParameters params = sslSocket.getSSLParameters();
|
||||
params.setServerNames(serverNames);
|
||||
sslSocket.setSSLParameters(params);
|
||||
}
|
||||
sslSocket.startHandshake();
|
||||
|
||||
|
||||
if (cn!=null)
|
||||
{
|
||||
X509Certificate cert = ((X509Certificate)sslSocket.getSession().getPeerCertificates()[0]);
|
||||
|
||||
Assert.assertThat(cert.getSubjectX500Principal().getName("CANONICAL"), Matchers.startsWith("cn="+cn));
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
sslSocket.close();
|
||||
clientContextFactory.stop();
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSocketCustomization() throws Exception
|
||||
{
|
||||
final Queue<String> history = new ConcurrentArrayQueue<>();
|
||||
|
||||
_connector.addBean(new SocketCustomizationListener()
|
||||
{
|
||||
@Override
|
||||
protected void customize(Socket socket, Class<? extends Connection> connection, boolean ssl)
|
||||
{
|
||||
history.add("customize connector "+connection+","+ssl);
|
||||
}
|
||||
});
|
||||
|
||||
_connector.getBean(SslConnectionFactory.class).addBean(new SocketCustomizationListener()
|
||||
{
|
||||
@Override
|
||||
protected void customize(Socket socket, Class<? extends Connection> connection, boolean ssl)
|
||||
{
|
||||
history.add("customize ssl "+connection+","+ssl);
|
||||
}
|
||||
});
|
||||
|
||||
_connector.getBean(HttpConnectionFactory.class).addBean(new SocketCustomizationListener()
|
||||
{
|
||||
@Override
|
||||
protected void customize(Socket socket, Class<? extends Connection> connection, boolean ssl)
|
||||
{
|
||||
history.add("customize http "+connection+","+ssl);
|
||||
}
|
||||
});
|
||||
|
||||
String response= getResponse("127.0.0.1",null);
|
||||
Assert.assertThat(response,Matchers.containsString("host=127.0.0.1"));
|
||||
|
||||
Assert.assertEquals("customize connector class org.eclipse.jetty.io.ssl.SslConnection,false",history.poll());
|
||||
Assert.assertEquals("customize ssl class org.eclipse.jetty.io.ssl.SslConnection,false",history.poll());
|
||||
Assert.assertEquals("customize connector class org.eclipse.jetty.server.HttpConnection,true",history.poll());
|
||||
Assert.assertEquals("customize http class org.eclipse.jetty.server.HttpConnection,true",history.poll());
|
||||
Assert.assertEquals(0,history.size());
|
||||
}
|
||||
|
||||
}
|
|
@ -66,7 +66,7 @@ public class SslConnectionFactoryTest
|
|||
@Before
|
||||
public void before() throws Exception
|
||||
{
|
||||
String keystorePath = "src/test/resources/snikeystore";
|
||||
String keystorePath = "src/test/resources/keystore";
|
||||
File keystoreFile = new File(keystorePath);
|
||||
if (!keystoreFile.exists())
|
||||
{
|
||||
|
@ -130,31 +130,8 @@ public class SslConnectionFactoryTest
|
|||
{
|
||||
String response;
|
||||
|
||||
response= getResponse("jetty.eclipse.org","jetty.eclipse.org");
|
||||
Assert.assertThat(response,Matchers.containsString("host=jetty.eclipse.org"));
|
||||
|
||||
response= getResponse("www.example.com","www.example.com");
|
||||
Assert.assertThat(response,Matchers.containsString("host=www.example.com"));
|
||||
|
||||
response= getResponse("foo.domain.com","*.domain.com");
|
||||
Assert.assertThat(response,Matchers.containsString("host=foo.domain.com"));
|
||||
|
||||
response= getResponse("m.san.com","san example");
|
||||
Assert.assertThat(response,Matchers.containsString("host=m.san.com"));
|
||||
|
||||
response= getResponse("www.san.com","san example");
|
||||
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"));
|
||||
response= getResponse("localhost","localhost","jetty.eclipse.org");
|
||||
Assert.assertThat(response,Matchers.containsString("host=localhost"));
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
|
@ -84,6 +84,9 @@ import org.eclipse.jetty.util.security.Password;
|
|||
* creates SSL context based on these parameters to be
|
||||
* used by the SSL connectors.
|
||||
*/
|
||||
|
||||
/**
|
||||
*/
|
||||
public class SslContextFactory extends AbstractLifeCycle
|
||||
{
|
||||
public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
|
||||
|
@ -104,6 +107,18 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
static final Logger LOG = Log.getLogger(SslContextFactory.class);
|
||||
|
||||
/*
|
||||
* @see {@link X509Certificate#getKeyUsage()}
|
||||
*/
|
||||
private static final int KEY_USAGE__KEY_CERT_SIGN=5;
|
||||
|
||||
/*
|
||||
*
|
||||
* @see {@link X509Certificate#getSubjectAlternativeNames()}
|
||||
*/
|
||||
private static final int SUBJECT_ALTERNATIVE_NAMES__DNS_NAME=2;
|
||||
|
||||
|
||||
public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
|
||||
(Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
|
||||
KeyManagerFactory.getDefaultAlgorithm() : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
|
||||
|
@ -331,7 +346,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
if (x509.getKeyUsage()!=null)
|
||||
{
|
||||
boolean[] b=x509.getKeyUsage();
|
||||
if (b[5]/* keyCertSign */)
|
||||
if (b[KEY_USAGE__KEY_CERT_SIGN])
|
||||
continue loop;
|
||||
}
|
||||
|
||||
|
@ -342,11 +357,11 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
{
|
||||
for (List<?> list : altNames)
|
||||
{
|
||||
if (((Number)list.get(0)).intValue() == 2 )
|
||||
if (((Number)list.get(0)).intValue() == SUBJECT_ALTERNATIVE_NAMES__DNS_NAME)
|
||||
{
|
||||
String cn = list.get(1).toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate san alias={} cn={} in {}",alias,cn,this);
|
||||
LOG.debug("Certificate SAN alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null)
|
||||
{
|
||||
named=true;
|
||||
|
@ -382,7 +397,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
if (name.startsWith("*."))
|
||||
_certWilds.put(name.substring(1),_certAliases.get(name));
|
||||
|
||||
LOG.info("x509={} for {}",_certAliases,this);
|
||||
LOG.info("x509={} wild={} alias={} for {}",_certAliases,_certWilds,_certAlias,this);
|
||||
|
||||
// Instantiate key and trust managers
|
||||
KeyManager[] keyManagers = getKeyManagers(keyStore);
|
||||
|
@ -598,6 +613,11 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* Set the default certificate Alias.
|
||||
* <p>This can be used if there are multiple non-SNI certificates
|
||||
* to specify the certificate that should be used, or with SNI
|
||||
* certificates to set a certificate to try if no others match
|
||||
* </p>
|
||||
* @param certAlias
|
||||
* Alias of SSL certificate for the connector
|
||||
*/
|
||||
|
@ -1042,7 +1062,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
}
|
||||
}
|
||||
|
||||
if (!_certAliases.isEmpty() || !_certWilds.isEmpty())
|
||||
if (_certAliases.isEmpty() || !_certWilds.isEmpty())
|
||||
{
|
||||
for (int idx = 0; idx < managers.length; idx++)
|
||||
{
|
||||
|
@ -1490,11 +1510,18 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
public void customize(SSLEngine sslEngine)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Customize {}",sslEngine);
|
||||
|
||||
SSLParameters sslParams = sslEngine.getSSLParameters();
|
||||
sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
|
||||
sslParams.setUseCipherSuitesOrder(_useCipherSuitesOrder);
|
||||
if (!_certAliases.isEmpty() || !_certWilds.isEmpty())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Enable SNI matching {}",sslEngine);
|
||||
sslParams.setSNIMatchers(Collections.singletonList((SNIMatcher)new AliasSNIMatcher()));
|
||||
}
|
||||
sslEngine.setSSLParameters(sslParams);
|
||||
|
||||
if (getWantClientAuth())
|
||||
|
@ -1662,16 +1689,24 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
// Try wild card matches
|
||||
String domain = _name.getAsciiName();
|
||||
domain=domain.substring(domain.indexOf('.'));
|
||||
_alias = _certWilds.get(domain);
|
||||
if (_alias!=null)
|
||||
int dot=domain.indexOf('.');
|
||||
if (dot>=0)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("wild match {}->{}",_name.getAsciiName(),_alias);
|
||||
return true;
|
||||
domain=domain.substring(dot);
|
||||
_alias = _certWilds.get(domain);
|
||||
if (_alias!=null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("wild match {}->{}",_name.getAsciiName(),_alias);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("No match for {}",_name.getAsciiName());
|
||||
|
||||
// Return true and allow the KeyManager to accept or reject when choosing a certificate.
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAlias()
|
||||
|
|
Loading…
Reference in New Issue