Merged branch 'jetty-9.3.x' into 'jetty-9.4.x'.

This commit is contained in:
Simone Bordet 2016-05-24 16:10:00 +02:00
commit 016a7b72a1
8 changed files with 372 additions and 1 deletions

View File

@ -57,6 +57,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Jetty;
@ -544,6 +545,7 @@ public class HttpClient extends ContainerLifeCycle
public void succeeded(List<InetSocketAddress> socketAddresses)
{
Map<String, Object> context = new HashMap<>();
context.put(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, HttpClient.this);
context.put(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY, destination);
connect(socketAddresses, 0, context);
}

View File

@ -18,10 +18,17 @@
package org.eclipse.jetty.client;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@ -86,10 +93,30 @@ public class HttpClientTLSTest
serverTLSFactory.setIncludeProtocols("TLSv1.2");
startServer(serverTLSFactory, new EmptyServerHandler());
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
serverLatch.countDown();
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
clientTLSFactory.setIncludeProtocols("TLSv1.1");
startClient(clientTLSFactory);
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
clientLatch.countDown();
}
});
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -102,6 +129,9 @@ public class HttpClientTLSTest
{
// Expected.
}
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
@Test
@ -111,10 +141,30 @@ public class HttpClientTLSTest
serverTLSFactory.setIncludeCipherSuites("TLS_RSA_WITH_AES_128_CBC_SHA");
startServer(serverTLSFactory, new EmptyServerHandler());
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
serverLatch.countDown();
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
clientTLSFactory.setExcludeCipherSuites(".*_SHA$");
startClient(clientTLSFactory);
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
clientLatch.countDown();
}
});
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -127,6 +177,9 @@ public class HttpClientTLSTest
{
// Expected.
}
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
@Test
@ -138,9 +191,29 @@ public class HttpClientTLSTest
serverTLSFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
startServer(serverTLSFactory, new EmptyServerHandler());
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
serverLatch.countDown();
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
startClient(clientTLSFactory);
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
clientLatch.countDown();
}
});
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -153,6 +226,9 @@ public class HttpClientTLSTest
{
// Expected.
}
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
@Test
@ -161,12 +237,32 @@ public class HttpClientTLSTest
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
serverLatch.countDown();
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
// TLS 1.1 protocol, but only TLS 1.2 ciphers.
clientTLSFactory.setIncludeProtocols("TLSv1.1");
clientTLSFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
startClient(clientTLSFactory);
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
clientLatch.countDown();
}
});
try
{
client.newRequest("localhost", connector.getLocalPort())
@ -179,5 +275,121 @@ public class HttpClientTLSTest
{
// Expected.
}
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
@Test
public void testHandshakeSucceeded() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
serverLatch.countDown();
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
startClient(clientTLSFactory);
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
clientLatch.countDown();
}
});
ContentResponse response = client.GET("https://localhost:" + connector.getLocalPort());
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
@Test
public void testHandshakeSucceededWithSessionResumption() throws Exception
{
SslContextFactory serverTLSFactory = createSslContextFactory();
startServer(serverTLSFactory, new EmptyServerHandler());
AtomicReference<byte[]> serverSession = new AtomicReference<>();
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
serverSession.set(event.getSSLEngine().getSession().getId());
}
});
SslContextFactory clientTLSFactory = createSslContextFactory();
startClient(clientTLSFactory);
AtomicReference<byte[]> clientSession = new AtomicReference<>();
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
clientSession.set(event.getSSLEngine().getSession().getId());
}
});
// First request primes the TLS session.
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.header(HttpHeader.CONNECTION, "close")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertNotNull(serverSession.get());
Assert.assertNotNull(clientSession.get());
connector.removeBean(connector.getBean(SslHandshakeListener.class));
client.removeBean(client.getBean(SslHandshakeListener.class));
CountDownLatch serverLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
if (Arrays.equals(serverSession.get(), event.getSSLEngine().getSession().getId()))
serverLatch.countDown();
}
});
CountDownLatch clientLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
if (Arrays.equals(clientSession.get(), event.getSSLEngine().getSession().getId()))
clientLatch.countDown();
}
});
// Second request should have the same session ID.
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.header(HttpHeader.CONNECTION, "close")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(serverLatch.await(1, TimeUnit.SECONDS));
Assert.assertTrue(clientLatch.await(1, TimeUnit.SECONDS));
}
}

View File

@ -374,6 +374,7 @@ public class HTTP2Client extends ContainerLifeCycle
context.put(SslClientConnectionFactory.SSL_CONTEXT_FACTORY_CONTEXT_KEY, sslContextFactory);
context.put(SslClientConnectionFactory.SSL_PEER_HOST_CONTEXT_KEY, address.getHostString());
context.put(SslClientConnectionFactory.SSL_PEER_PORT_CONTEXT_KEY, address.getPort());
context.putIfAbsent(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY, this);
return context;
}

View File

@ -26,6 +26,8 @@ import java.util.Map;
*/
public interface ClientConnectionFactory
{
public static final String CONNECTOR_CONTEXT_KEY = "client.connector";
/**
*
* @param endPoint the {@link org.eclipse.jetty.io.EndPoint} to link the newly created connection to

View File

@ -27,6 +27,7 @@ import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class SslClientConnectionFactory implements ClientConnectionFactory
@ -61,6 +62,10 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
SslConnection sslConnection = newSslConnection(byteBufferPool, executor, endPoint, engine);
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
endPoint.setConnection(sslConnection);
ContainerLifeCycle connector = (ContainerLifeCycle)context.get(ClientConnectionFactory.CONNECTOR_CONTEXT_KEY);
connector.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));

View File

@ -22,6 +22,8 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.net.ssl.SSLEngine;
@ -80,6 +82,8 @@ public class SslConnection extends AbstractConnection
private static final Logger LOG = Log.getLogger(SslConnection.class);
private static final ByteBuffer __FILL_CALLED_FLUSH= BufferUtil.allocate(0);
private static final ByteBuffer __FLUSH_CALLED_FILL= BufferUtil.allocate(0);
private final List<SslHandshakeListener> handshakeListeners = new ArrayList<>();
private final ByteBufferPool _bufferPool;
private final SSLEngine _sslEngine;
private final DecryptedEndPoint _decryptedEndPoint;
@ -116,6 +120,16 @@ public class SslConnection extends AbstractConnection
this._decryptedEndPoint = newDecryptedEndPoint();
}
public void addHandshakeListener(SslHandshakeListener listener)
{
handshakeListeners.add(listener);
}
public boolean removeHandshakeListener(SslHandshakeListener listener)
{
return handshakeListeners.remove(listener);
}
protected DecryptedEndPoint newDecryptedEndPoint()
{
return new DecryptedEndPoint();
@ -593,6 +607,7 @@ public class SslConnection extends AbstractConnection
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "client" : "resumed server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
// Check whether renegotiation is allowed
@ -671,13 +686,17 @@ public class SslConnection extends AbstractConnection
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
close(x);
throw x;
}
catch (SSLException x)
{
if (!_handshaken)
{
x = (SSLException)new SSLHandshakeException(x.getMessage()).initCause(x);
notifyHandshakeFailed(_sslEngine, x);
}
close(x);
throw x;
}
@ -811,7 +830,10 @@ public class SslConnection extends AbstractConnection
{
_handshaken = true;
if (LOG.isDebugEnabled())
LOG.debug("{} server handshake succeeded {}/{}", SslConnection.this, _sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "resumed client" : "server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
@ -875,6 +897,7 @@ public class SslConnection extends AbstractConnection
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
close(x);
throw x;
}
@ -961,6 +984,42 @@ public class SslConnection extends AbstractConnection
return _sslEngine.isInboundDone();
}
private void notifyHandshakeSucceeded(SSLEngine sslEngine)
{
SslHandshakeListener.Event event = null;
for (SslHandshakeListener listener : handshakeListeners)
{
if (event == null)
event = new SslHandshakeListener.Event(sslEngine);
try
{
listener.handshakeSucceeded(event);
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
}
private void notifyHandshakeFailed(SSLEngine sslEngine, Throwable failure)
{
SslHandshakeListener.Event event = null;
for (SslHandshakeListener listener : handshakeListeners)
{
if (event == null)
event = new SslHandshakeListener.Event(sslEngine);
try
{
listener.handshakeFailed(event, failure);
}
catch (Throwable x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
}
@Override
public String toString()
{

View File

@ -0,0 +1,71 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.io.ssl;
import java.util.EventListener;
import java.util.EventObject;
import javax.net.ssl.SSLEngine;
/**
* <p>Implementations of this interface are notified of TLS handshake events.</p>
* <p>Similar to {@link javax.net.ssl.HandshakeCompletedListener}, but for {@link SSLEngine}.</p>
* <p>Typical usage if to add instances of this class as beans to a server connector, or
* to a client connector.</p>
*/
public interface SslHandshakeListener extends EventListener
{
/**
* <p>Callback method invoked when the TLS handshake succeeds.</p>
*
* @param event the event object carrying information about the TLS handshake event
*/
default void handshakeSucceeded(Event event)
{
}
/**
* <p>Callback method invoked when the TLS handshake fails.</p>
*
* @param event the event object carrying information about the TLS handshake event
* @param failure the failure that caused the TLS handshake to fail
*/
default void handshakeFailed(Event event, Throwable failure)
{
}
/**
* <p>The event object carrying information about TLS handshake events.</p>
*/
public static class Event extends EventObject
{
public Event(Object source)
{
super(source);
}
/**
* @return the SSLEngine associated to the TLS handshake event
*/
public SSLEngine getSSLEngine()
{
return (SSLEngine)getSource();
}
}
}

View File

@ -24,10 +24,13 @@ import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class SslConnectionFactory extends AbstractConnectionFactory
@ -94,6 +97,22 @@ public class SslConnectionFactory extends AbstractConnectionFactory
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
}
@Override
protected AbstractConnection configure(AbstractConnection connection, Connector connector, EndPoint endPoint)
{
if (connection instanceof SslConnection)
{
SslConnection sslConnection = (SslConnection)connection;
if (connector instanceof ContainerLifeCycle)
{
ContainerLifeCycle container = (ContainerLifeCycle)connector;
container.getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
}
getBeans(SslHandshakeListener.class).forEach(sslConnection::addHandshakeListener);
}
return super.configure(connection, connector, endPoint);
}
@Override
public String toString()
{