Merge branch 'master' into release-9.3.3
This commit is contained in:
commit
f5d1fb1058
|
@ -18,11 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.io;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -43,7 +38,6 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory.Helper;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.OS;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
@ -51,6 +45,11 @@ import org.eclipse.jetty.util.IO;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IOTest
|
||||
{
|
||||
@Test
|
||||
|
@ -437,9 +436,9 @@ public class IOTest
|
|||
connector.bind(null);
|
||||
InetSocketAddress addr=(InetSocketAddress)connector.getLocalAddress();
|
||||
Future<AsynchronousSocketChannel> acceptor = connector.accept();
|
||||
|
||||
|
||||
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
|
||||
|
||||
|
||||
client.connect(new InetSocketAddress("127.0.0.1",addr.getPort())).get(5, TimeUnit.SECONDS);
|
||||
|
||||
AsynchronousSocketChannel server = acceptor.get(5, TimeUnit.SECONDS);
|
||||
|
@ -457,14 +456,14 @@ public class IOTest
|
|||
|
||||
Assert.assertEquals(ByteBuffer.wrap(data), read);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGatherWrite() throws Exception
|
||||
{
|
||||
File dir = MavenTestingUtils.getTargetTestingDir();
|
||||
if (!dir.exists())
|
||||
dir.mkdir();
|
||||
|
||||
|
||||
File file = File.createTempFile("test",".txt",dir);
|
||||
file.deleteOnExit();
|
||||
FileChannel out = FileChannel.open(file.toPath(),
|
||||
|
@ -472,7 +471,7 @@ public class IOTest
|
|||
StandardOpenOption.READ,
|
||||
StandardOpenOption.WRITE,
|
||||
StandardOpenOption.DELETE_ON_CLOSE);
|
||||
|
||||
|
||||
ByteBuffer[] buffers = new ByteBuffer[4096];
|
||||
long expected=0;
|
||||
for (int i=0;i<buffers.length;i++)
|
||||
|
@ -480,22 +479,12 @@ public class IOTest
|
|||
buffers[i]=BufferUtil.toBuffer(i);
|
||||
expected+=buffers[i].remaining();
|
||||
}
|
||||
|
||||
|
||||
long wrote = IO.write(out,buffers,0,buffers.length);
|
||||
|
||||
|
||||
assertEquals(expected,wrote);
|
||||
|
||||
for (int i=0;i<buffers.length;i++)
|
||||
assertEquals(0,buffers[i].remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDomain()
|
||||
{
|
||||
assertTrue(IO.isInDomain("foo.com","foo.com"));
|
||||
assertTrue(IO.isInDomain("www.foo.com","foo.com"));
|
||||
assertFalse(IO.isInDomain("foo.com","bar.com"));
|
||||
assertFalse(IO.isInDomain("www.foo.com","bar.com"));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,40 +29,40 @@ import org.eclipse.jetty.http.BadMessageException;
|
|||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection;
|
||||
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.ssl.SniX509ExtendedKeyManager;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.ssl.X509;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Customizer that extracts the attribute from an {@link SSLContext}
|
||||
/**
|
||||
* <p>Customizer that extracts the attribute from an {@link SSLContext}
|
||||
* and sets them on the request with {@link ServletRequest#setAttribute(String, Object)}
|
||||
* according to Servlet Specification Requirements.
|
||||
* according to Servlet Specification Requirements.</p>
|
||||
*/
|
||||
public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SecureRequestCustomizer.class);
|
||||
|
||||
|
||||
/**
|
||||
* The name of the SSLSession attribute that will contain any cached information.
|
||||
*/
|
||||
public static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
|
||||
|
||||
private boolean _sniHostCheck;
|
||||
|
||||
|
||||
|
||||
|
||||
public SecureRequestCustomizer()
|
||||
{
|
||||
this(true);
|
||||
}
|
||||
|
||||
|
||||
public SecureRequestCustomizer(boolean sniHostCheck)
|
||||
{
|
||||
_sniHostCheck=sniHostCheck;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
|
||||
{
|
||||
|
@ -77,10 +77,9 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* Customise the request attributes to be set for SSL requests. <br>
|
||||
* The requirements of the Servlet specs are:
|
||||
/**
|
||||
* <p>Customizes the request attributes to be set for SSL requests.</p>
|
||||
* <p>The requirements of the Servlet specs are:</p>
|
||||
* <ul>
|
||||
* <li> an attribute named "javax.servlet.request.ssl_session_id" of type
|
||||
* String (since Servlet Spec 3.0).</li>
|
||||
|
@ -95,8 +94,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param request
|
||||
* HttpRequest to be customised.
|
||||
* @param request HttpRequest to be customized.
|
||||
*/
|
||||
public void customize(SSLEngine sslEngine, Request request)
|
||||
{
|
||||
|
@ -105,19 +103,20 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
|
||||
if (_sniHostCheck)
|
||||
{
|
||||
String sniName = (String)sslSession.getValue("org.eclipse.jetty.util.ssl.sniname");
|
||||
if (sniName!=null && !sniName.equalsIgnoreCase(request.getServerName()))
|
||||
String name = request.getServerName();
|
||||
@SuppressWarnings("unchecked")
|
||||
X509 x509 = (X509)sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509);
|
||||
|
||||
if (x509!=null && !x509.matches(name))
|
||||
{
|
||||
String wild=(String)sslSession.getValue("org.eclipse.jetty.util.ssl.sniwild");
|
||||
String name=request.getServerName();
|
||||
if (wild==null || !IO.isInDomain(name,wild))
|
||||
{
|
||||
LOG.warn("Host does not match SNI Name: {}/{}!={}",sniName,wild,request.getServerName());
|
||||
throw new BadMessageException(400,"Host does not match SNI");
|
||||
}
|
||||
LOG.warn("Host {} does not match SNI {}",name,x509);
|
||||
throw new BadMessageException(400,"Host does not match SNI");
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Host {} matched SNI {}",name,x509);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
String cipherSuite=sslSession.getCipherSuite();
|
||||
|
@ -132,9 +131,9 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
certs=cachedInfo.getCerts();
|
||||
idStr=cachedInfo.getIdStr();
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
keySize=new Integer(SslContextFactory.deduceKeyLength(cipherSuite));
|
||||
keySize=SslContextFactory.deduceKeyLength(cipherSuite);
|
||||
certs=SslContextFactory.getCertChain(sslSession);
|
||||
byte[] bytes = sslSession.getId();
|
||||
idStr = TypeUtil.toHexString(bytes);
|
||||
|
@ -160,10 +159,7 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
{
|
||||
return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Simple bundle of information that is cached in the SSLSession. Stores the
|
||||
* effective keySize and the client certificate chain.
|
||||
|
@ -196,7 +192,4 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer
|
|||
return _idStr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import org.eclipse.jetty.server.Request;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.Jetty;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
|
@ -134,7 +133,7 @@ public class DefaultHandler extends AbstractHandler
|
|||
{
|
||||
writer.write("<li><a href=\"");
|
||||
if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
|
||||
writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
|
||||
writer.write(request.getScheme()+"://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
|
||||
writer.write(context.getContextPath());
|
||||
if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
|
||||
writer.write("/");
|
||||
|
@ -163,7 +162,7 @@ public class DefaultHandler extends AbstractHandler
|
|||
}
|
||||
|
||||
writer.write("</ul><hr>");
|
||||
|
||||
|
||||
baseRequest.getHttpChannel().getHttpConfiguration()
|
||||
.writePoweredBy(writer,"<a href=\"http://eclipse.org/jetty\"><img border=0 src=\"/favicon.ico\"/></a> ","<hr/>\n");
|
||||
|
||||
|
@ -174,7 +173,7 @@ public class DefaultHandler extends AbstractHandler
|
|||
{
|
||||
writer.writeTo(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -21,10 +21,13 @@ package org.eclipse.jetty.server.ssl;
|
|||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
|
@ -50,6 +53,7 @@ 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.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.After;
|
||||
|
@ -96,9 +100,10 @@ public class SniSslConnectionFactoryTest
|
|||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setStatus(200);
|
||||
response.getWriter().write("url=" + request.getRequestURI() + "\nhost=" + request.getServerName());
|
||||
response.flushBuffer();
|
||||
response.setHeader("X-URL", request.getRequestURI());
|
||||
response.setHeader("X-HOST", request.getServerName());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -117,7 +122,7 @@ public class SniSslConnectionFactoryTest
|
|||
public void testConnect() throws Exception
|
||||
{
|
||||
String response = getResponse("127.0.0.1", null);
|
||||
Assert.assertThat(response, Matchers.containsString("host=127.0.0.1"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: 127.0.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -142,39 +147,39 @@ public class SniSslConnectionFactoryTest
|
|||
// The first entry in the keystore is www.example.com, and it will
|
||||
// be returned by default, so make sure that here we don't ask for it.
|
||||
String response = getResponse("jetty.eclipse.org", "jetty.eclipse.org");
|
||||
Assert.assertThat(response, Matchers.containsString("host=jetty.eclipse.org"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: jetty.eclipse.org"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSNIConnect() throws Exception
|
||||
{
|
||||
String response = getResponse("jetty.eclipse.org", "jetty.eclipse.org");
|
||||
Assert.assertThat(response, Matchers.containsString("host=jetty.eclipse.org"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: jetty.eclipse.org"));
|
||||
|
||||
response = getResponse("www.example.com", "www.example.com");
|
||||
Assert.assertThat(response, Matchers.containsString("host=www.example.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: www.example.com"));
|
||||
|
||||
response = getResponse("foo.domain.com", "*.domain.com");
|
||||
Assert.assertThat(response, Matchers.containsString("host=foo.domain.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: foo.domain.com"));
|
||||
|
||||
response = getResponse("m.san.com", "san example");
|
||||
Assert.assertThat(response, Matchers.containsString("host=m.san.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: m.san.com"));
|
||||
|
||||
response = getResponse("www.san.com", "san example");
|
||||
Assert.assertThat(response, Matchers.containsString("host=www.san.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: www.san.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildSNIConnect() throws Exception
|
||||
{
|
||||
String response = getResponse("domain.com", "www.domain.com", "*.domain.com");
|
||||
Assert.assertThat(response, Matchers.containsString("host=www.domain.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: www.domain.com"));
|
||||
|
||||
response = getResponse("domain.com", "domain.com", "*.domain.com");
|
||||
Assert.assertThat(response, Matchers.containsString("host=domain.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: domain.com"));
|
||||
|
||||
response = getResponse("www.domain.com", "www.domain.com", "*.domain.com");
|
||||
Assert.assertThat(response, Matchers.containsString("host=www.domain.com"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-HOST: www.domain.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -185,11 +190,138 @@ public class SniSslConnectionFactoryTest
|
|||
Assert.assertThat(response, Matchers.containsString("Host does not match SNI"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameConnectionRequestsForManyDomains() throws Exception
|
||||
{
|
||||
SslContextFactory clientContextFactory = new SslContextFactory(true);
|
||||
clientContextFactory.start();
|
||||
SSLSocketFactory factory = clientContextFactory.getSslContext().getSocketFactory();
|
||||
try (SSLSocket sslSocket = (SSLSocket)factory.createSocket("127.0.0.1", _port))
|
||||
{
|
||||
SNIHostName serverName = new SNIHostName("m.san.com");
|
||||
SSLParameters params = sslSocket.getSSLParameters();
|
||||
params.setServerNames(Collections.singletonList(serverName));
|
||||
sslSocket.setSSLParameters(params);
|
||||
sslSocket.startHandshake();
|
||||
|
||||
// The first request binds the socket to an alias.
|
||||
String request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: m.san.com\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output = sslSocket.getOutputStream();
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
InputStream input = sslSocket.getInputStream();
|
||||
String response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
|
||||
// Same socket, send a request for a different domain but same alias.
|
||||
request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: www.san.com\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
|
||||
// Same socket, send a request for a different domain but different alias.
|
||||
request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: www.example.com\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 400 "));
|
||||
Assert.assertThat(response, Matchers.containsString("Host does not match SNI"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
clientContextFactory.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSameConnectionRequestsForManyWildDomains() throws Exception
|
||||
{
|
||||
SslContextFactory clientContextFactory = new SslContextFactory(true);
|
||||
clientContextFactory.start();
|
||||
SSLSocketFactory factory = clientContextFactory.getSslContext().getSocketFactory();
|
||||
try (SSLSocket sslSocket = (SSLSocket)factory.createSocket("127.0.0.1", _port))
|
||||
{
|
||||
SNIHostName serverName = new SNIHostName("www.domain.com");
|
||||
SSLParameters params = sslSocket.getSSLParameters();
|
||||
params.setServerNames(Collections.singletonList(serverName));
|
||||
sslSocket.setSSLParameters(params);
|
||||
sslSocket.startHandshake();
|
||||
|
||||
String request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: www.domain.com\r\n" +
|
||||
"\r\n";
|
||||
OutputStream output = sslSocket.getOutputStream();
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
InputStream input = sslSocket.getInputStream();
|
||||
String response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
|
||||
// Now, on the same socket, send a request for a different valid domain.
|
||||
request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: assets.domain.com\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 200 "));
|
||||
|
||||
// Now make a request for an invalid domain for this connection.
|
||||
request = "" +
|
||||
"GET /ctx/path HTTP/1.1\r\n" +
|
||||
"Host: www.example.com\r\n" +
|
||||
"\r\n";
|
||||
output.write(request.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
response = response(input);
|
||||
Assert.assertTrue(response.startsWith("HTTP/1.1 400 "));
|
||||
Assert.assertThat(response, Matchers.containsString("Host does not match SNI"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
clientContextFactory.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private String response(InputStream input) throws IOException
|
||||
{
|
||||
Utf8StringBuilder buffer = new Utf8StringBuilder();
|
||||
int crlfs = 0;
|
||||
while (true)
|
||||
{
|
||||
int read = input.read();
|
||||
Assert.assertTrue(read >= 0);
|
||||
buffer.append((byte)read);
|
||||
crlfs = (read == '\r' || read == '\n') ? crlfs + 1 : 0;
|
||||
if (crlfs == 4)
|
||||
break;
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
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"));
|
||||
Assert.assertThat(response, Matchers.startsWith("HTTP/1.1 200 "));
|
||||
Assert.assertThat(response, Matchers.containsString("X-URL: /ctx/path"));
|
||||
return response;
|
||||
}
|
||||
|
||||
|
@ -198,33 +330,34 @@ public class SniSslConnectionFactoryTest
|
|||
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)
|
||||
try (SSLSocket sslSocket = (SSLSocket)factory.createSocket("127.0.0.1", _port))
|
||||
{
|
||||
SNIHostName serverName = new SNIHostName(sniHost);
|
||||
List<SNIServerName> serverNames = new ArrayList<>();
|
||||
serverNames.add(serverName);
|
||||
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);
|
||||
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));
|
||||
}
|
||||
|
||||
String response = "GET /ctx/path HTTP/1.0\r\nHost: " + reqHost + ":" + _port + "\r\n\r\n";
|
||||
sslSocket.getOutputStream().write(response.getBytes(StandardCharsets.ISO_8859_1));
|
||||
return IO.toString(sslSocket.getInputStream());
|
||||
}
|
||||
sslSocket.startHandshake();
|
||||
|
||||
if (cn != null)
|
||||
finally
|
||||
{
|
||||
X509Certificate cert = ((X509Certificate)sslSocket.getSession().getPeerCertificates()[0]);
|
||||
|
||||
Assert.assertThat(cert.getSubjectX500Principal().getName("CANONICAL"), Matchers.startsWith("cn=" + cn));
|
||||
clientContextFactory.stop();
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -260,7 +393,7 @@ public class SniSslConnectionFactoryTest
|
|||
});
|
||||
|
||||
String response = getResponse("127.0.0.1", null);
|
||||
Assert.assertThat(response, Matchers.containsString("host=127.0.0.1"));
|
||||
Assert.assertThat(response, Matchers.containsString("X-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());
|
||||
|
|
|
@ -43,10 +43,10 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* Provides stream handling utilities in
|
||||
* singleton Threadpool implementation accessed by static members.
|
||||
*/
|
||||
public class IO
|
||||
public class IO
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(IO.class);
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
public final static String
|
||||
CRLF = "\015\012";
|
||||
|
@ -80,9 +80,9 @@ public class IO
|
|||
this.read=read;
|
||||
this.write=write;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
/*
|
||||
* @see java.lang.Runnable#run()
|
||||
*/
|
||||
public void run()
|
||||
|
@ -109,19 +109,19 @@ public class IO
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** Copy Stream in to Stream out until EOF or exception.
|
||||
* @param in the input stream to read from (until EOF)
|
||||
* @param out the output stream to write to
|
||||
* @throws IOException if unable to copy streams
|
||||
* @throws IOException if unable to copy streams
|
||||
*/
|
||||
public static void copy(InputStream in, OutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
copy(in,out,-1);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** Copy Reader to Writer out until EOF or exception.
|
||||
* @param in the read to read from (until EOF)
|
||||
|
@ -133,7 +133,7 @@ public class IO
|
|||
{
|
||||
copy(in,out,-1);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
|
||||
* @param in the stream to read from
|
||||
|
@ -145,20 +145,20 @@ public class IO
|
|||
OutputStream out,
|
||||
long byteCount)
|
||||
throws IOException
|
||||
{
|
||||
{
|
||||
byte buffer[] = new byte[bufferSize];
|
||||
int len=bufferSize;
|
||||
|
||||
|
||||
if (byteCount>=0)
|
||||
{
|
||||
while (byteCount>0)
|
||||
{
|
||||
int max = byteCount<bufferSize?(int)byteCount:bufferSize;
|
||||
len=in.read(buffer,0,max);
|
||||
|
||||
|
||||
if (len==-1)
|
||||
break;
|
||||
|
||||
|
||||
byteCount -= len;
|
||||
out.write(buffer,0,len);
|
||||
}
|
||||
|
@ -173,8 +173,8 @@ public class IO
|
|||
out.write(buffer,0,len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
/** Copy Reader to Writer for byteCount bytes or until EOF or exception.
|
||||
* @param in the Reader to read from
|
||||
|
@ -186,10 +186,10 @@ public class IO
|
|||
Writer out,
|
||||
long byteCount)
|
||||
throws IOException
|
||||
{
|
||||
{
|
||||
char buffer[] = new char[bufferSize];
|
||||
int len=bufferSize;
|
||||
|
||||
|
||||
if (byteCount>=0)
|
||||
{
|
||||
while (byteCount>0)
|
||||
|
@ -197,11 +197,11 @@ public class IO
|
|||
if (byteCount<bufferSize)
|
||||
len=in.read(buffer,0,(int)byteCount);
|
||||
else
|
||||
len=in.read(buffer,0,bufferSize);
|
||||
|
||||
len=in.read(buffer,0,bufferSize);
|
||||
|
||||
if (len==-1)
|
||||
break;
|
||||
|
||||
|
||||
byteCount -= len;
|
||||
out.write(buffer,0,len);
|
||||
}
|
||||
|
@ -253,7 +253,7 @@ public class IO
|
|||
}
|
||||
else
|
||||
to.mkdirs();
|
||||
|
||||
|
||||
File[] files = from.listFiles();
|
||||
if (files!=null)
|
||||
{
|
||||
|
@ -266,7 +266,7 @@ public class IO
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static void copyFile(File from,File to) throws IOException
|
||||
{
|
||||
|
@ -276,7 +276,7 @@ public class IO
|
|||
copy(in,out);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Read input stream to string.
|
||||
* @param in the stream to read from (until EOF)
|
||||
|
@ -288,13 +288,13 @@ public class IO
|
|||
{
|
||||
return toString(in,(Charset)null);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Read input stream to string.
|
||||
* @param in the stream to read from (until EOF)
|
||||
* @param encoding the encoding to use (can be null to use default Charset)
|
||||
* @return the String parsed from the stream
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
*/
|
||||
public static String toString(InputStream in,String encoding)
|
||||
throws IOException
|
||||
|
@ -306,7 +306,7 @@ public class IO
|
|||
* @param in the stream to read from (until EOF)
|
||||
* @param encoding the Charset to use (can be null to use default Charset)
|
||||
* @return the String parsed from the stream
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
*/
|
||||
public static String toString(InputStream in, Charset encoding)
|
||||
throws IOException
|
||||
|
@ -322,7 +322,7 @@ public class IO
|
|||
/** Read input stream to string.
|
||||
* @param in the reader to read from (until EOF)
|
||||
* @return the String parsed from the reader
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
* @throws IOException if unable to read the stream (or handle the charset)
|
||||
*/
|
||||
public static String toString(Reader in)
|
||||
throws IOException
|
||||
|
@ -351,7 +351,7 @@ public class IO
|
|||
}
|
||||
return file.delete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes an arbitrary closable, and logs exceptions at ignore level
|
||||
*
|
||||
|
@ -379,7 +379,7 @@ public class IO
|
|||
{
|
||||
close((Closeable)is);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* closes an output stream, and logs exceptions
|
||||
*
|
||||
|
@ -392,7 +392,7 @@ public class IO
|
|||
|
||||
/**
|
||||
* closes a reader, and logs exceptions
|
||||
*
|
||||
*
|
||||
* @param reader the reader to close
|
||||
*/
|
||||
public static void close(Reader reader)
|
||||
|
@ -402,14 +402,14 @@ public class IO
|
|||
|
||||
/**
|
||||
* closes a writer, and logs exceptions
|
||||
*
|
||||
*
|
||||
* @param writer the writer to close
|
||||
*/
|
||||
public static void close(Writer writer)
|
||||
{
|
||||
close((Closeable)writer);
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public static byte[] readBytes(InputStream in)
|
||||
throws IOException
|
||||
|
@ -426,7 +426,7 @@ public class IO
|
|||
* This method wraps a gather write with a loop that handles the limitations of some operating systems that have a
|
||||
* limit on the number of buffers written. The method loops on the write until either all the content is written or
|
||||
* no progress is made.
|
||||
*
|
||||
*
|
||||
* @param out
|
||||
* The GatheringByteChannel to write to
|
||||
* @param buffers
|
||||
|
@ -446,14 +446,14 @@ public class IO
|
|||
{
|
||||
// Write as much as we can
|
||||
long wrote=out.write(buffers,offset,length);
|
||||
|
||||
|
||||
// If we can't write any more, give up
|
||||
if (wrote==0)
|
||||
break;
|
||||
|
||||
|
||||
// count the total
|
||||
total+=wrote;
|
||||
|
||||
|
||||
// Look for unwritten content
|
||||
for (int i=offset;i<buffers.length;i++)
|
||||
{
|
||||
|
@ -467,25 +467,10 @@ public class IO
|
|||
}
|
||||
length=0;
|
||||
}
|
||||
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param name A host name like www.foo.com
|
||||
* @param domain A domain name like foo.com
|
||||
* @return True if the host name is in the domain name
|
||||
*/
|
||||
public static boolean isInDomain(String name, String domain)
|
||||
{
|
||||
if (!name.endsWith(domain))
|
||||
return false;
|
||||
if (name.length()==domain.length())
|
||||
return true;
|
||||
return name.charAt(name.length()-domain.length()-1)=='.';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return An outputstream to nowhere
|
||||
|
@ -496,17 +481,17 @@ public class IO
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
/**
|
||||
* @return An outputstream to nowhere
|
||||
*/
|
||||
public static InputStream getClosedStream()
|
||||
{
|
||||
return __closedStream;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private static class NullOS extends OutputStream
|
||||
private static class NullOS extends OutputStream
|
||||
{
|
||||
@Override
|
||||
public void close(){}
|
||||
|
@ -521,10 +506,10 @@ public class IO
|
|||
}
|
||||
private static NullOS __nullStream = new NullOS();
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private static class ClosedIS extends InputStream
|
||||
private static class ClosedIS extends InputStream
|
||||
{
|
||||
@Override
|
||||
public int read() throws IOException
|
||||
|
@ -533,28 +518,28 @@ public class IO
|
|||
}
|
||||
}
|
||||
private static ClosedIS __closedStream = new ClosedIS();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
/**
|
||||
* @return An writer to nowhere
|
||||
*/
|
||||
public static Writer getNullWriter()
|
||||
{
|
||||
return __nullWriter;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
/**
|
||||
* @return An writer to nowhere
|
||||
*/
|
||||
public static PrintWriter getNullPrintWriter()
|
||||
{
|
||||
return __nullPrintWriter;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private static class NullWrite extends Writer
|
||||
private static class NullWrite extends Writer
|
||||
{
|
||||
@Override
|
||||
public void close(){}
|
||||
|
|
|
@ -41,9 +41,8 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
||||
{
|
||||
public static final String SNI_NAME = "org.eclipse.jetty.util.ssl.sniname";
|
||||
public static final String SNI_WILD = "org.eclipse.jetty.util.ssl.sniwild";
|
||||
public static final String NO_MATCHERS="No Matchers";
|
||||
public static final String SNI_X509 = "org.eclipse.jetty.util.ssl.snix509";
|
||||
private static final String NO_MATCHERS = "no_matchers";
|
||||
private static final Logger LOG = Log.getLogger(SniX509ExtendedKeyManager.class);
|
||||
|
||||
private final X509ExtendedKeyManager _delegate;
|
||||
|
@ -72,10 +71,9 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
if (aliases==null || aliases.length==0)
|
||||
return null;
|
||||
|
||||
// Look for an SNI alias
|
||||
String alias=null;
|
||||
// Look for the SNI information.
|
||||
String host=null;
|
||||
String wild=null;
|
||||
X509 x509=null;
|
||||
if (matchers!=null)
|
||||
{
|
||||
for (SNIMatcher m : matchers)
|
||||
|
@ -83,28 +81,25 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
if (m instanceof SslContextFactory.AliasSNIMatcher)
|
||||
{
|
||||
SslContextFactory.AliasSNIMatcher matcher = (SslContextFactory.AliasSNIMatcher)m;
|
||||
alias=matcher.getAlias();
|
||||
host=matcher.getServerName();
|
||||
wild=matcher.getWildDomain();
|
||||
host=matcher.getHost();
|
||||
x509=matcher.getX509();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("matched {}/{} from {}",alias,host,Arrays.asList(aliases));
|
||||
LOG.debug("Matched {} with {} from {}",host,x509,Arrays.asList(aliases));
|
||||
|
||||
// Check if the SNI selected alias is allowable
|
||||
if (alias!=null)
|
||||
if (x509!=null)
|
||||
{
|
||||
for (String a:aliases)
|
||||
{
|
||||
if (a.equals(alias))
|
||||
if (a.equals(x509.getAlias()))
|
||||
{
|
||||
session.putValue(SNI_NAME,host);
|
||||
if (wild!=null)
|
||||
session.putValue(SNI_WILD,wild);
|
||||
return alias;
|
||||
session.putValue(SNI_X509,x509);
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -121,7 +116,7 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
if (alias==NO_MATCHERS)
|
||||
alias=_delegate.chooseServerAlias(keyType,issuers,socket);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("chose {}/{} on {}",alias,keyType,socket);
|
||||
LOG.debug("Chose alias {}/{} on {}",alias,keyType,socket);
|
||||
return alias;
|
||||
}
|
||||
|
||||
|
@ -132,7 +127,7 @@ public class SniX509ExtendedKeyManager extends X509ExtendedKeyManager
|
|||
if (alias==NO_MATCHERS)
|
||||
alias=_delegate.chooseEngineServerAlias(keyType,issuers,engine);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("chose {}/{} on {}",alias,keyType,engine);
|
||||
LOG.debug("Chose alias {}/{} on {}",alias,keyType,engine);
|
||||
return alias;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ import java.util.Arrays;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -48,8 +47,6 @@ import java.util.Set;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.net.ssl.CertPathTrustManagerParameters;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
|
@ -70,8 +67,8 @@ import javax.net.ssl.TrustManager;
|
|||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
@ -110,18 +107,6 @@ 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"));
|
||||
|
@ -149,7 +134,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
private final Set<String> _excludeCipherSuites = new LinkedHashSet<>();
|
||||
|
||||
/** Included cipher suites. */
|
||||
private final List<String> _includeCipherSuites = new ArrayList<String>();
|
||||
private final List<String> _includeCipherSuites = new ArrayList<>();
|
||||
private boolean _useCipherSuitesOrder=true;
|
||||
|
||||
/** Cipher comparator for ordering ciphers */
|
||||
|
@ -167,8 +152,9 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
/** SSL certificate alias */
|
||||
private String _certAlias;
|
||||
private final Map<String,String> _certAliases = new HashMap<>();
|
||||
private final Map<String,String> _certWilds = new HashMap<>();
|
||||
private final Map<String,X509> _aliasX509 = new HashMap<>();
|
||||
private final Map<String,X509> _certHosts = new HashMap<>();
|
||||
private final Map<String,X509> _certWilds = new HashMap<>();
|
||||
|
||||
/** Truststore path */
|
||||
private Resource _trustStoreResource;
|
||||
|
@ -304,6 +290,16 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
_cipherComparator = cipherComparator;
|
||||
}
|
||||
|
||||
public Set<String> getAliases()
|
||||
{
|
||||
return Collections.unmodifiableSet(_aliasX509.keySet());
|
||||
}
|
||||
|
||||
public X509 getX509(String alias)
|
||||
{
|
||||
return _aliasX509.get(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the SSLContext object and start the lifecycle
|
||||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
||||
|
@ -343,35 +339,8 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
Collection<? extends CRL> crls = loadCRL(_crlPath);
|
||||
|
||||
if (_validateCerts && keyStore != null)
|
||||
{
|
||||
if (_certAlias==null)
|
||||
{
|
||||
for (Enumeration<String> e=keyStore.aliases(); _certAlias==null && e.hasMoreElements(); )
|
||||
{
|
||||
String alias=e.nextElement();
|
||||
Certificate c =keyStore.getCertificate(alias);
|
||||
if (c!=null && "X.509".equals(c.getType()))
|
||||
_certAlias=alias;
|
||||
}
|
||||
}
|
||||
|
||||
Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
|
||||
if (cert==null || !"X.509".equals(cert.getType()))
|
||||
{
|
||||
throw new Exception("No X.509 certificate in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
|
||||
}
|
||||
|
||||
CertificateValidator validator = new CertificateValidator(trustStore, crls);
|
||||
validator.setMaxCertPathLength(_maxCertPathLength);
|
||||
validator.setEnableCRLDP(_enableCRLDP);
|
||||
validator.setEnableOCSP(_enableOCSP);
|
||||
validator.setOcspResponderURL(_ocspResponderURL);
|
||||
validator.validate(keyStore, cert);
|
||||
}
|
||||
|
||||
// Look for X.509 certificates to create alias map
|
||||
_certAliases.clear();
|
||||
_certHosts.clear();
|
||||
if (keyStore!=null)
|
||||
{
|
||||
for (String alias : Collections.list(keyStore.aliases()))
|
||||
|
@ -379,65 +348,38 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
Certificate certificate = keyStore.getCertificate(alias);
|
||||
if (certificate!=null && "X.509".equals(certificate.getType()))
|
||||
{
|
||||
X509Certificate x509 = (X509Certificate)certificate;
|
||||
X509Certificate x509C = (X509Certificate)certificate;
|
||||
|
||||
// Exclude certificates with special uses
|
||||
if (x509.getKeyUsage()!=null)
|
||||
if (X509.isCertSign(x509C))
|
||||
{
|
||||
boolean[] b=x509.getKeyUsage();
|
||||
if (b[KEY_USAGE__KEY_CERT_SIGN])
|
||||
continue;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Skipping "+x509C);
|
||||
continue;
|
||||
}
|
||||
X509 x509 = new X509(alias,x509C);
|
||||
_aliasX509.put(alias,x509);
|
||||
|
||||
if (_validateCerts)
|
||||
{
|
||||
CertificateValidator validator = new CertificateValidator(trustStore, crls);
|
||||
validator.setMaxCertPathLength(_maxCertPathLength);
|
||||
validator.setEnableCRLDP(_enableCRLDP);
|
||||
validator.setEnableOCSP(_enableOCSP);
|
||||
validator.setOcspResponderURL(_ocspResponderURL);
|
||||
validator.validate(keyStore, x509C); // TODO what about truststore?
|
||||
}
|
||||
|
||||
// Look for alternative name extensions
|
||||
boolean named=false;
|
||||
Collection<List<?>> altNames = x509.getSubjectAlternativeNames();
|
||||
if (altNames!=null)
|
||||
{
|
||||
for (List<?> list : altNames)
|
||||
{
|
||||
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);
|
||||
if (cn!=null)
|
||||
{
|
||||
named=true;
|
||||
_certAliases.put(cn,alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG.info("x509={} for {}",x509,this);
|
||||
|
||||
// If no names found, look up the cn from the subject
|
||||
if (!named)
|
||||
{
|
||||
LdapName name=new LdapName(x509.getSubjectX500Principal().getName(X500Principal.RFC2253));
|
||||
for (Rdn rdn : name.getRdns())
|
||||
{
|
||||
if (rdn.getType().equalsIgnoreCase("cn"))
|
||||
{
|
||||
String cn = rdn.getValue().toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate cn alias={} cn={} in {}",alias,cn,this);
|
||||
if (cn!=null && cn.contains(".") && !cn.contains(" "))
|
||||
_certAliases.put(cn,alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String h:x509.getHosts())
|
||||
_certHosts.put(h,x509);
|
||||
for (String w:x509.getWilds())
|
||||
_certWilds.put(w,x509);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find wild aliases
|
||||
_certWilds.clear();
|
||||
for (String name : _certAliases.keySet())
|
||||
if (name.startsWith("*."))
|
||||
_certWilds.put(name.substring(2),_certAliases.get(name));
|
||||
|
||||
LOG.info("x509={} wild={} alias={} for {}",_certAliases,_certWilds,_certAlias,this);
|
||||
|
||||
// Instantiate key and trust managers
|
||||
KeyManager[] keyManagers = getKeyManagers(keyStore);
|
||||
TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
|
||||
|
@ -462,7 +404,6 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
LOG.debug("Selected Protocols {} of {}",Arrays.asList(_selectedProtocols),Arrays.asList(sslEngine.getSupportedProtocols()));
|
||||
LOG.debug("Selected Ciphers {} of {}",Arrays.asList(_selectedCipherSuites),Arrays.asList(sslEngine.getSupportedCipherSuites()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -470,8 +411,9 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
{
|
||||
_factory = null;
|
||||
super.doStop();
|
||||
_certAliases.clear();
|
||||
_certHosts.clear();
|
||||
_certWilds.clear();
|
||||
_aliasX509.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1140,7 +1082,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
}
|
||||
}
|
||||
|
||||
if (!_certAliases.isEmpty() || !_certWilds.isEmpty())
|
||||
if (!_certHosts.isEmpty() || !_certWilds.isEmpty())
|
||||
{
|
||||
for (int idx = 0; idx < managers.length; idx++)
|
||||
{
|
||||
|
@ -1615,7 +1557,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
SSLParameters sslParams = sslEngine.getSSLParameters();
|
||||
sslParams.setEndpointIdentificationAlgorithm(_endpointIdentificationAlgorithm);
|
||||
sslParams.setUseCipherSuitesOrder(_useCipherSuitesOrder);
|
||||
if (!_certAliases.isEmpty() || !_certWilds.isEmpty())
|
||||
if (!_certHosts.isEmpty() || !_certWilds.isEmpty())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Enable SNI matching {}",sslEngine);
|
||||
|
@ -1727,8 +1669,6 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
_trustStoreResource);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected class Factory
|
||||
{
|
||||
final KeyStore _keyStore;
|
||||
|
@ -1752,11 +1692,10 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
|
||||
class AliasSNIMatcher extends SNIMatcher
|
||||
{
|
||||
private String _alias;
|
||||
private String _wild;
|
||||
private SNIHostName _name;
|
||||
private String _host;
|
||||
private X509 _x509;
|
||||
|
||||
protected AliasSNIMatcher()
|
||||
AliasSNIMatcher()
|
||||
{
|
||||
super(StandardConstants.SNI_HOST_NAME);
|
||||
}
|
||||
|
@ -1764,66 +1703,57 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
@Override
|
||||
public boolean matches(SNIServerName serverName)
|
||||
{
|
||||
LOG.debug("matches={} for {}",serverName,this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI matching for {}",serverName);
|
||||
|
||||
if (serverName instanceof SNIHostName)
|
||||
{
|
||||
_name=(SNIHostName)serverName;
|
||||
|
||||
// If we don't have a SNI name, or didn't see any certificate aliases,
|
||||
// just say true as it will either somehow work or fail elsewhere
|
||||
if (_certAliases.size()==0)
|
||||
return true;
|
||||
String host = _host = ((SNIHostName)serverName).getAsciiName();
|
||||
host=StringUtil.asciiToLowerCase(host);
|
||||
|
||||
// Try an exact match
|
||||
_alias = _certAliases.get(_name.getAsciiName());
|
||||
if (_alias!=null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("matched {}->{}",_name.getAsciiName(),_alias);
|
||||
return true;
|
||||
}
|
||||
_x509 = _certHosts.get(host);
|
||||
|
||||
// Try wild card matches
|
||||
String domain = _name.getAsciiName();
|
||||
_alias = _certWilds.get(domain);
|
||||
if (_alias==null)
|
||||
// Else try an exact wild match
|
||||
if (_x509==null)
|
||||
{
|
||||
int dot=domain.indexOf('.');
|
||||
if (dot>=0)
|
||||
_x509 = _certWilds.get(host);
|
||||
|
||||
// Else try an 1 deep wild match
|
||||
if (_x509==null)
|
||||
{
|
||||
domain=domain.substring(dot+1);
|
||||
_alias = _certWilds.get(domain);
|
||||
int dot=host.indexOf('.');
|
||||
if (dot>=0)
|
||||
{
|
||||
String domain=host.substring(dot+1);
|
||||
_x509 = _certWilds.get(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_alias!=null)
|
||||
{
|
||||
_wild=domain;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("wild match {}->{}",_name.getAsciiName(),_alias);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI matched {}->{}",host,_x509);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("SNI no match for {}", serverName);
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("No match for {}",_name.getAsciiName());
|
||||
|
||||
// Return true and allow the KeyManager to accept or reject when choosing a certificate.
|
||||
// If we don't have a SNI host, or didn't see any certificate aliases,
|
||||
// just say true as it will either somehow work or fail elsewhere.
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getAlias()
|
||||
public String getHost()
|
||||
{
|
||||
return _alias;
|
||||
return _host;
|
||||
}
|
||||
|
||||
public String getWildDomain()
|
||||
public X509 getX509()
|
||||
{
|
||||
return _wild;
|
||||
}
|
||||
|
||||
public String getServerName()
|
||||
{
|
||||
return _name==null?null:_name.getAsciiName();
|
||||
return _x509;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// 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.util.ssl;
|
||||
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import javax.naming.ldap.Rdn;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class X509
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(X509.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 boolean isCertSign(X509Certificate x509)
|
||||
{
|
||||
boolean[] key_usage=x509.getKeyUsage();
|
||||
return key_usage!=null && key_usage[KEY_USAGE__KEY_CERT_SIGN];
|
||||
}
|
||||
|
||||
private final X509Certificate _x509;
|
||||
private final String _alias;
|
||||
private final List<String> _hosts=new ArrayList<>();
|
||||
private final List<String> _wilds=new ArrayList<>();
|
||||
|
||||
public X509(String alias,X509Certificate x509) throws CertificateParsingException, InvalidNameException
|
||||
{
|
||||
_alias=alias;
|
||||
_x509 = x509;
|
||||
|
||||
// Look for alternative name extensions
|
||||
boolean named=false;
|
||||
Collection<List<?>> altNames = x509.getSubjectAlternativeNames();
|
||||
if (altNames!=null)
|
||||
{
|
||||
for (List<?> list : altNames)
|
||||
{
|
||||
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);
|
||||
if (cn!=null)
|
||||
{
|
||||
named=true;
|
||||
addName(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no names found, look up the CN from the subject
|
||||
if (!named)
|
||||
{
|
||||
LdapName name=new LdapName(x509.getSubjectX500Principal().getName(X500Principal.RFC2253));
|
||||
for (Rdn rdn : name.getRdns())
|
||||
{
|
||||
if (rdn.getType().equalsIgnoreCase("CN"))
|
||||
{
|
||||
String cn = rdn.getValue().toString();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Certificate CN alias={} CN={} in {}",alias,cn,this);
|
||||
if (cn!=null && cn.contains(".") && !cn.contains(" "))
|
||||
addName(cn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void addName(String cn)
|
||||
{
|
||||
cn=StringUtil.asciiToLowerCase(cn);
|
||||
if (cn.startsWith("*."))
|
||||
_wilds.add(cn.substring(2));
|
||||
else
|
||||
_hosts.add(cn);
|
||||
}
|
||||
|
||||
public String getAlias()
|
||||
{
|
||||
return _alias;
|
||||
}
|
||||
|
||||
public X509Certificate getCertificate()
|
||||
{
|
||||
return _x509;
|
||||
}
|
||||
|
||||
public Set<String> getHosts()
|
||||
{
|
||||
return new HashSet<>(_hosts);
|
||||
}
|
||||
|
||||
public Set<String> getWilds()
|
||||
{
|
||||
return new HashSet<>(_wilds);
|
||||
}
|
||||
|
||||
public boolean matches(String host)
|
||||
{
|
||||
host=StringUtil.asciiToLowerCase(host);
|
||||
if (_hosts.contains(host) || _wilds.contains(host))
|
||||
return true;
|
||||
|
||||
int dot = host.indexOf('.');
|
||||
if (dot>=0)
|
||||
{
|
||||
String domain=host.substring(dot+1);
|
||||
if (_wilds.contains(domain))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("%s@%x(%s,h=%s,w=%s)",
|
||||
getClass().getSimpleName(),
|
||||
hashCode(),
|
||||
_alias,
|
||||
_hosts,
|
||||
_wilds);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,9 @@
|
|||
package org.eclipse.jetty.util.ssl;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -221,4 +223,45 @@ public class SslContextFactoryTest
|
|||
assertNotNull(cf.getExcludeCipherSuites());
|
||||
assertNotNull(cf.getIncludeCipherSuites());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSNICertificates() throws Exception
|
||||
{
|
||||
Resource keystoreResource = Resource.newSystemResource("snikeystore");
|
||||
|
||||
cf.setKeyStoreResource(keystoreResource);
|
||||
cf.setKeyStorePassword("storepwd");
|
||||
cf.setKeyManagerPassword("keypwd");
|
||||
|
||||
cf.start();
|
||||
|
||||
assertThat(cf.getAliases(),containsInAnyOrder("jetty","other","san","wild"));
|
||||
|
||||
assertThat(cf.getX509("jetty").getHosts(),containsInAnyOrder("jetty.eclipse.org"));
|
||||
assertTrue(cf.getX509("jetty").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("jetty").matches("JETTY.Eclipse.Org"));
|
||||
assertFalse(cf.getX509("jetty").matches("m.jetty.eclipse.org"));
|
||||
assertFalse(cf.getX509("jetty").matches("eclipse.org"));
|
||||
|
||||
assertThat(cf.getX509("other").getHosts(),containsInAnyOrder("www.example.com"));
|
||||
assertTrue(cf.getX509("other").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("other").matches("www.example.com"));
|
||||
assertFalse(cf.getX509("other").matches("eclipse.org"));
|
||||
|
||||
assertThat(cf.getX509("san").getHosts(),containsInAnyOrder("www.san.com","m.san.com"));
|
||||
assertTrue(cf.getX509("san").getWilds().isEmpty());
|
||||
assertTrue(cf.getX509("san").matches("www.san.com"));
|
||||
assertTrue(cf.getX509("san").matches("m.san.com"));
|
||||
assertFalse(cf.getX509("san").matches("other.san.com"));
|
||||
assertFalse(cf.getX509("san").matches("san.com"));
|
||||
assertFalse(cf.getX509("san").matches("eclipse.org"));
|
||||
|
||||
assertTrue(cf.getX509("wild").getHosts().isEmpty());
|
||||
assertThat(cf.getX509("wild").getWilds(),containsInAnyOrder("domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("www.domain.com"));
|
||||
assertTrue(cf.getX509("wild").matches("other.domain.com"));
|
||||
assertFalse(cf.getX509("wild").matches("foo.bar.domain.com"));
|
||||
assertFalse(cf.getX509("wild").matches("other.com"));
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -413,7 +413,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} onClose()",policy.getBehavior());
|
||||
super.onClose();
|
||||
// ioState.onDisconnected();
|
||||
ioState.onDisconnected();
|
||||
flusher.close();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -35,7 +37,9 @@ import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
|||
import org.eclipse.jetty.websocket.common.CloseInfo;
|
||||
import org.eclipse.jetty.websocket.common.OpCode;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||
import org.eclipse.jetty.websocket.common.WebSocketSession;
|
||||
import org.eclipse.jetty.websocket.common.events.AbstractEventDriver;
|
||||
import org.eclipse.jetty.websocket.common.frames.TextFrame;
|
||||
import org.eclipse.jetty.websocket.common.test.BlockheadClient;
|
||||
import org.eclipse.jetty.websocket.server.helper.RFCSocket;
|
||||
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
|
||||
|
@ -44,7 +48,6 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
|||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -79,10 +82,16 @@ public class WebSocketCloseTest
|
|||
@SuppressWarnings("serial")
|
||||
public static class CloseServlet extends WebSocketServlet implements WebSocketCreator
|
||||
{
|
||||
private WebSocketServerFactory serverFactory;
|
||||
|
||||
@Override
|
||||
public void configure(WebSocketServletFactory factory)
|
||||
{
|
||||
factory.setCreator(this);
|
||||
if (factory instanceof WebSocketServerFactory)
|
||||
{
|
||||
this.serverFactory = (WebSocketServerFactory)factory;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,10 +108,58 @@ public class WebSocketCloseTest
|
|||
closeSocket = new FastFailSocket();
|
||||
return closeSocket;
|
||||
}
|
||||
|
||||
if (req.hasSubProtocol("container"))
|
||||
{
|
||||
closeSocket = new ContainerSocket(serverFactory);
|
||||
return closeSocket;
|
||||
}
|
||||
return new RFCSocket();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Message, return container information
|
||||
*/
|
||||
public static class ContainerSocket extends AbstractCloseSocket
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(WebSocketCloseTest.ContainerSocket.class);
|
||||
private final WebSocketServerFactory container;
|
||||
private Session session;
|
||||
|
||||
public ContainerSocket(WebSocketServerFactory container)
|
||||
{
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message)
|
||||
{
|
||||
LOG.debug("onWebSocketText({})",message);
|
||||
if (message.equalsIgnoreCase("openSessions"))
|
||||
{
|
||||
Set<WebSocketSession> sessions = container.getOpenSessions();
|
||||
|
||||
StringBuilder ret = new StringBuilder();
|
||||
ret.append("openSessions.size=").append(sessions.size()).append('\n');
|
||||
int idx = 0;
|
||||
for (WebSocketSession sess : sessions)
|
||||
{
|
||||
ret.append('[').append(idx++).append("] ").append(sess.toString()).append('\n');
|
||||
}
|
||||
session.getRemote().sendStringByFuture(ret.toString());
|
||||
}
|
||||
session.close(StatusCode.NORMAL,"ContainerSocket");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(Session sess)
|
||||
{
|
||||
LOG.debug("onWebSocketConnect({})",sess);
|
||||
this.session = sess;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On Connect, close socket
|
||||
*/
|
||||
|
@ -155,7 +212,9 @@ public class WebSocketCloseTest
|
|||
|
||||
/**
|
||||
* Test fast close (bug #403817)
|
||||
* @throws Exception on test failure
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testFastClose() throws Exception
|
||||
|
@ -171,22 +230,24 @@ public class WebSocketCloseTest
|
|||
// Verify that client got close frame
|
||||
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
|
||||
WebSocketFrame frame = frames.poll();
|
||||
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
CloseInfo close = new CloseInfo(frame);
|
||||
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
|
||||
|
||||
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
|
||||
|
||||
// Notify server of close handshake
|
||||
client.write(close.asFrame()); // respond with close
|
||||
|
||||
|
||||
// ensure server socket got close event
|
||||
Assert.assertThat("Fast Close Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
Assert.assertThat("Fast Close.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL));
|
||||
assertThat("Fast Close Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
assertThat("Fast Close.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fast fail (bug #410537)
|
||||
* @throws Exception on test failure
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testFastFail() throws Exception
|
||||
|
@ -203,16 +264,129 @@ public class WebSocketCloseTest
|
|||
|
||||
EventQueue<WebSocketFrame> frames = client.readFrames(1,1,TimeUnit.SECONDS);
|
||||
WebSocketFrame frame = frames.poll();
|
||||
Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
CloseInfo close = new CloseInfo(frame);
|
||||
Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
|
||||
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR));
|
||||
|
||||
client.write(close.asFrame()); // respond with close
|
||||
|
||||
// ensure server socket got close event
|
||||
Assert.assertThat("Fast Fail Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
Assert.assertThat("Fast Fail.statusCode",closeSocket.closeStatusCode,is(StatusCode.SERVER_ERROR));
|
||||
Assert.assertThat("Fast Fail.errors",closeSocket.errors.size(),is(1));
|
||||
assertThat("Fast Fail Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
assertThat("Fast Fail.statusCode",closeSocket.closeStatusCode,is(StatusCode.SERVER_ERROR));
|
||||
assertThat("Fast Fail.errors",closeSocket.errors.size(),is(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test session open session cleanup (bug #474936)
|
||||
*
|
||||
* @throws Exception
|
||||
* on test failure
|
||||
*/
|
||||
@Test
|
||||
public void testOpenSessionCleanup() throws Exception
|
||||
{
|
||||
fastFail();
|
||||
fastClose();
|
||||
dropConnection();
|
||||
|
||||
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
{
|
||||
client.setProtocols("container");
|
||||
client.setTimeout(1,TimeUnit.SECONDS);
|
||||
client.connect();
|
||||
client.sendStandardRequest();
|
||||
client.expectUpgradeResponse();
|
||||
|
||||
TextFrame text = new TextFrame();
|
||||
text.setPayload("openSessions");
|
||||
client.write(text);
|
||||
|
||||
EventQueue<WebSocketFrame> frames = client.readFrames(2,1,TimeUnit.SECONDS);
|
||||
WebSocketFrame frame = frames.poll();
|
||||
assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.TEXT));
|
||||
|
||||
String resp = frame.getPayloadAsUTF8();
|
||||
assertThat("Should only have 1 open session",resp,containsString("openSessions.size=1\n"));
|
||||
|
||||
frame = frames.poll();
|
||||
assertThat("frames[1].opcode",frame.getOpCode(),is(OpCode.CLOSE));
|
||||
CloseInfo close = new CloseInfo(frame);
|
||||
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
|
||||
client.write(close.asFrame()); // respond with close
|
||||
|
||||
// ensure server socket got close event
|
||||
assertThat("Open Sessions Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
assertThat("Open Sessions.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL));
|
||||
assertThat("Open Sessions.errors",closeSocket.errors.size(),is(0));
|
||||
}
|
||||
}
|
||||
|
||||
private void fastClose() throws Exception
|
||||
{
|
||||
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
{
|
||||
client.setProtocols("fastclose");
|
||||
client.setTimeout(1,TimeUnit.SECONDS);
|
||||
try (StacklessLogging scope = new StacklessLogging(WebSocketSession.class))
|
||||
{
|
||||
client.connect();
|
||||
client.sendStandardRequest();
|
||||
client.expectUpgradeResponse();
|
||||
|
||||
client.readFrames(1,1,TimeUnit.SECONDS);
|
||||
|
||||
CloseInfo close = new CloseInfo(StatusCode.NORMAL,"Normal");
|
||||
assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL));
|
||||
|
||||
// Notify server of close handshake
|
||||
client.write(close.asFrame()); // respond with close
|
||||
|
||||
// ensure server socket got close event
|
||||
assertThat("Fast Close Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
assertThat("Fast Close.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fastFail() throws Exception
|
||||
{
|
||||
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
{
|
||||
client.setProtocols("fastfail");
|
||||
client.setTimeout(1,TimeUnit.SECONDS);
|
||||
try (StacklessLogging scope = new StacklessLogging(WebSocketSession.class))
|
||||
{
|
||||
client.connect();
|
||||
client.sendStandardRequest();
|
||||
client.expectUpgradeResponse();
|
||||
|
||||
client.readFrames(1,1,TimeUnit.SECONDS);
|
||||
|
||||
CloseInfo close = new CloseInfo(StatusCode.NORMAL,"Normal");
|
||||
client.write(close.asFrame()); // respond with close
|
||||
|
||||
// ensure server socket got close event
|
||||
assertThat("Fast Fail Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true));
|
||||
assertThat("Fast Fail.statusCode",closeSocket.closeStatusCode,is(StatusCode.SERVER_ERROR));
|
||||
assertThat("Fast Fail.errors",closeSocket.errors.size(),is(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dropConnection() throws Exception
|
||||
{
|
||||
try (BlockheadClient client = new BlockheadClient(server.getServerUri()))
|
||||
{
|
||||
client.setProtocols("container");
|
||||
client.setTimeout(1,TimeUnit.SECONDS);
|
||||
try (StacklessLogging scope = new StacklessLogging(WebSocketSession.class))
|
||||
{
|
||||
client.connect();
|
||||
client.sendStandardRequest();
|
||||
client.expectUpgradeResponse();
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue