Merge branch 'master' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project
This commit is contained in:
commit
5d451e5fec
|
@ -966,6 +966,7 @@ public class HttpClient extends ContainerLifeCycle
|
|||
engine.setUseClientMode(true);
|
||||
|
||||
SslConnection sslConnection = newSslConnection(HttpClient.this, endPoint, engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
HttpConnection connection = newHttpConnection(HttpClient.this, appEndPoint, destination);
|
||||
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.client.ssl;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.http.HttpScheme;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SslBytesClientTest extends SslBytesTest
|
||||
{
|
||||
private ExecutorService threadPool;
|
||||
private HttpClient client;
|
||||
private SslContextFactory sslContextFactory;
|
||||
private SSLServerSocket acceptor;
|
||||
private SimpleProxy proxy;
|
||||
|
||||
@Before
|
||||
public void init() throws Exception
|
||||
{
|
||||
threadPool = Executors.newCachedThreadPool();
|
||||
|
||||
client = new HttpClient(new SslContextFactory(true));
|
||||
client.setMaxConnectionsPerDestination(1);
|
||||
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
|
||||
sslContextFactory = client.getSslContextFactory();
|
||||
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
|
||||
sslContextFactory.setKeyStorePassword("storepwd");
|
||||
client.start();
|
||||
|
||||
SSLContext sslContext = sslContextFactory.getSslContext();
|
||||
acceptor = (SSLServerSocket)sslContext.getServerSocketFactory().createServerSocket(43191);
|
||||
|
||||
int serverPort = acceptor.getLocalPort();
|
||||
|
||||
proxy = new SimpleProxy(threadPool, "localhost", serverPort);
|
||||
proxy.start();
|
||||
logger.info(":{} <==> :{}", proxy.getPort(), serverPort);
|
||||
}
|
||||
|
||||
@After
|
||||
public void destroy() throws Exception
|
||||
{
|
||||
if (acceptor != null)
|
||||
acceptor.close();
|
||||
if (proxy != null)
|
||||
proxy.stop();
|
||||
if (client != null)
|
||||
client.stop();
|
||||
if (threadPool != null)
|
||||
threadPool.shutdownNow();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandshake() throws Exception
|
||||
{
|
||||
Request request = client.newRequest("localhost", proxy.getPort());
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
request.scheme(HttpScheme.HTTPS.asString()).send(listener);
|
||||
|
||||
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
|
||||
|
||||
final SSLSocket server = (SSLSocket)acceptor.accept();
|
||||
server.setUseClientMode(false);
|
||||
|
||||
Future<Object> handshake = threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
public Object call() throws Exception
|
||||
{
|
||||
server.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Client Hello
|
||||
TLSRecord record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Server Hello + Certificate + Server Done
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Client Key Exchange
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Change Cipher Spec
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Client Done
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Change Cipher Spec
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Server Done
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
|
||||
|
||||
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
// Read request
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(server.getInputStream(), "UTF-8"));
|
||||
String line = reader.readLine();
|
||||
Assert.assertTrue(line.startsWith("GET"));
|
||||
while (line.length() > 0)
|
||||
line = reader.readLine();
|
||||
|
||||
// Write response
|
||||
OutputStream output = server.getOutputStream();
|
||||
output.write(("HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"\r\n").getBytes("UTF-8"));
|
||||
output.flush();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerRenegotiation() throws Exception
|
||||
{
|
||||
Request request = client.newRequest("localhost", proxy.getPort());
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
request.scheme(HttpScheme.HTTPS.asString()).send(listener);
|
||||
|
||||
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
|
||||
|
||||
final SSLSocket server = (SSLSocket)acceptor.accept();
|
||||
server.setUseClientMode(false);
|
||||
|
||||
Future<Object> handshake = threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
public Object call() throws Exception
|
||||
{
|
||||
server.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
|
||||
|
||||
// Read request
|
||||
InputStream serverInput = server.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
|
||||
String line = reader.readLine();
|
||||
Assert.assertTrue(line.startsWith("GET"));
|
||||
while (line.length() > 0)
|
||||
line = reader.readLine();
|
||||
|
||||
OutputStream serverOutput = server.getOutputStream();
|
||||
byte[] data1 = new byte[1024];
|
||||
Arrays.fill(data1, (byte)'X');
|
||||
String content1 = new String(data1, "UTF-8");
|
||||
byte[] data2 = new byte[1024];
|
||||
Arrays.fill(data2, (byte)'Y');
|
||||
final String content2 = new String(data2, "UTF-8");
|
||||
// Write first part of the response
|
||||
serverOutput.write(("HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
|
||||
"\r\n" +
|
||||
content1).getBytes("UTF-8"));
|
||||
serverOutput.flush();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
// Renegotiate
|
||||
Future<Object> renegotiation = threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
public Object call() throws Exception
|
||||
{
|
||||
server.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Renegotiation Handshake
|
||||
TLSRecord record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Renegotiation Handshake
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Trigger a read to have the server write the final renegotiation steps
|
||||
server.setSoTimeout(100);
|
||||
try
|
||||
{
|
||||
serverInput.read();
|
||||
Assert.fail();
|
||||
}
|
||||
catch (SocketTimeoutException x)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Renegotiation Handshake
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Renegotiation Change Cipher
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Renegotiation Handshake
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
// Renegotiation Change Cipher
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.CHANGE_CIPHER_SPEC, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Renegotiation Handshake
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
Assert.assertNull(renegotiation.get(5, TimeUnit.SECONDS));
|
||||
|
||||
// Complete the response
|
||||
automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
serverOutput.write(data2);
|
||||
serverOutput.flush();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
Assert.assertEquals(data1.length + data2.length, response.getContent().length);
|
||||
|
||||
server.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerRenegotiationWhenRenegotiationIsForbidden() throws Exception
|
||||
{
|
||||
sslContextFactory.setRenegotiationAllowed(false);
|
||||
|
||||
Request request = client.newRequest("localhost", proxy.getPort());
|
||||
FutureResponseListener listener = new FutureResponseListener(request);
|
||||
request.scheme(HttpScheme.HTTPS.asString()).send(listener);
|
||||
|
||||
Assert.assertTrue(proxy.awaitClient(5, TimeUnit.SECONDS));
|
||||
|
||||
final SSLSocket server = (SSLSocket)acceptor.accept();
|
||||
server.setUseClientMode(false);
|
||||
|
||||
Future<Object> handshake = threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
public Object call() throws Exception
|
||||
{
|
||||
server.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
Assert.assertNull(handshake.get(5, TimeUnit.SECONDS));
|
||||
|
||||
// Read request
|
||||
InputStream serverInput = server.getInputStream();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(serverInput, "UTF-8"));
|
||||
String line = reader.readLine();
|
||||
Assert.assertTrue(line.startsWith("GET"));
|
||||
while (line.length() > 0)
|
||||
line = reader.readLine();
|
||||
|
||||
OutputStream serverOutput = server.getOutputStream();
|
||||
byte[] data1 = new byte[1024];
|
||||
Arrays.fill(data1, (byte)'X');
|
||||
String content1 = new String(data1, "UTF-8");
|
||||
byte[] data2 = new byte[1024];
|
||||
Arrays.fill(data2, (byte)'Y');
|
||||
final String content2 = new String(data2, "UTF-8");
|
||||
// Write first part of the response
|
||||
serverOutput.write(("HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
|
||||
"\r\n" +
|
||||
content1).getBytes("UTF-8"));
|
||||
serverOutput.flush();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
// Renegotiate
|
||||
threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
public Object call() throws Exception
|
||||
{
|
||||
server.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Renegotiation Handshake
|
||||
TLSRecord record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertNull(record);
|
||||
proxy.flushToServer(record);
|
||||
|
||||
server.close();
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.ssl;
|
||||
package org.eclipse.jetty.client.ssl;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.EOFException;
|
||||
|
@ -84,7 +84,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
private final int idleTimeout = 2000;
|
||||
private ExecutorService threadPool;
|
||||
private Server server;
|
||||
private int serverPort;
|
||||
private SslContextFactory sslContextFactory;
|
||||
private SSLContext sslContext;
|
||||
private SimpleProxy proxy;
|
||||
|
||||
|
@ -94,11 +94,10 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
threadPool = Executors.newCachedThreadPool();
|
||||
server = new Server();
|
||||
|
||||
File keyStore = MavenTestingUtils.getTestResourceFile("keystore");
|
||||
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
|
||||
sslContextFactory = new SslContextFactory();
|
||||
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
|
||||
sslContextFactory.setKeyStorePassword("storepwd");
|
||||
sslContextFactory.setKeyManagerPassword("keypwd");
|
||||
|
||||
HttpConnectionFactory httpFactory = new HttpConnectionFactory()
|
||||
{
|
||||
|
@ -204,7 +203,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
}
|
||||
});
|
||||
server.start();
|
||||
serverPort = connector.getLocalPort();
|
||||
int serverPort = connector.getLocalPort();
|
||||
|
||||
sslContext = sslContextFactory.getSslContext();
|
||||
|
||||
|
@ -1277,6 +1276,98 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
closeClient(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception
|
||||
{
|
||||
assumeJavaVersionSupportsTLSRenegotiations();
|
||||
|
||||
sslContextFactory.setRenegotiationAllowed(false);
|
||||
|
||||
final SSLSocket client = newClient();
|
||||
final OutputStream clientOutput = client.getOutputStream();
|
||||
|
||||
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
client.startHandshake();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
byte[] data1 = new byte[1024];
|
||||
Arrays.fill(data1, (byte)'X');
|
||||
String content1 = new String(data1, "UTF-8");
|
||||
byte[] data2 = new byte[1024];
|
||||
Arrays.fill(data2, (byte)'Y');
|
||||
final String content2 = new String(data2, "UTF-8");
|
||||
|
||||
// Write only part of the body
|
||||
automaticProxyFlow = proxy.startAutomaticFlow();
|
||||
clientOutput.write(("" +
|
||||
"POST / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"Content-Length: " + (content1.length() + content2.length()) + "\r\n" +
|
||||
"\r\n" +
|
||||
content1).getBytes("UTF-8"));
|
||||
clientOutput.flush();
|
||||
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
|
||||
|
||||
// Renegotiate
|
||||
threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
@Override
|
||||
public Object call() throws Exception
|
||||
{
|
||||
client.startHandshake();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Renegotiation Handshake
|
||||
TLSRecord record = proxy.readFromClient();
|
||||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Renegotiation now allowed, server has closed
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNull(record);
|
||||
|
||||
// Write the rest of the request
|
||||
threadPool.submit(new Callable<Object>()
|
||||
{
|
||||
@Override
|
||||
public Object call() throws Exception
|
||||
{
|
||||
clientOutput.write(content2.getBytes("UTF-8"));
|
||||
clientOutput.flush();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// Trying to write more application data results in an exception since the server closed
|
||||
record = proxy.readFromClient();
|
||||
proxy.flushToServer(record);
|
||||
try
|
||||
{
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToServer(record);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IOException expected)
|
||||
{
|
||||
}
|
||||
|
||||
// Check that we did not spin
|
||||
TimeUnit.MILLISECONDS.sleep(500);
|
||||
Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
|
||||
Assert.assertThat(sslFlushes.get(), Matchers.lessThan(20));
|
||||
Assert.assertThat(httpParses.get(), Matchers.lessThan(50));
|
||||
|
||||
client.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestWithBigContentWithRenegotiationInMiddleOfContent() throws Exception
|
||||
{
|
|
@ -16,7 +16,7 @@
|
|||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server.ssl;
|
||||
package org.eclipse.jetty.client.ssl;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
@ -115,8 +115,8 @@ public abstract class SslBytesTest
|
|||
|
||||
public void start() throws Exception
|
||||
{
|
||||
// serverSocket = new ServerSocket(5871);
|
||||
serverSocket = new ServerSocket(0);
|
||||
serverSocket = new ServerSocket(47009);
|
||||
// serverSocket = new ServerSocket(0);
|
||||
Thread acceptor = new Thread(this);
|
||||
acceptor.start();
|
||||
server = new Socket(serverHost, serverPort);
|
Binary file not shown.
|
@ -99,6 +99,7 @@ public class SslConnection extends AbstractConnection
|
|||
_decryptedEndPoint.getWriteFlusher().completeWrite();
|
||||
}
|
||||
};
|
||||
private boolean _renegotiationAllowed;
|
||||
|
||||
public SslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine sslEngine)
|
||||
{
|
||||
|
@ -137,6 +138,16 @@ public class SslConnection extends AbstractConnection
|
|||
return _decryptedEndPoint;
|
||||
}
|
||||
|
||||
public boolean isRenegotiationAllowed()
|
||||
{
|
||||
return _renegotiationAllowed;
|
||||
}
|
||||
|
||||
public void setRenegotiationAllowed(boolean renegotiationAllowed)
|
||||
{
|
||||
this._renegotiationAllowed = renegotiationAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen()
|
||||
{
|
||||
|
@ -242,6 +253,7 @@ public class SslConnection extends AbstractConnection
|
|||
private boolean _fillRequiresFlushToProgress;
|
||||
private boolean _flushRequiresFillToProgress;
|
||||
private boolean _cannotAcceptMoreAppDataToFlush;
|
||||
private boolean _handshaken;
|
||||
private boolean _underFlown;
|
||||
|
||||
private final Callback _writeCallback = new Callback()
|
||||
|
@ -493,15 +505,19 @@ public class SslConnection extends AbstractConnection
|
|||
if (DEBUG)
|
||||
LOG.debug("{} unwrap {}", SslConnection.this, unwrapResult);
|
||||
|
||||
Status unwrapResultStatus = unwrapResult.getStatus();
|
||||
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
|
||||
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
|
||||
|
||||
// and deal with the results
|
||||
switch (unwrapResult.getStatus())
|
||||
switch (unwrapResultStatus)
|
||||
{
|
||||
case BUFFER_OVERFLOW:
|
||||
throw new IllegalStateException();
|
||||
|
||||
case CLOSED:
|
||||
// Dang! we have to care about the handshake state specially for close
|
||||
switch (_sslEngine.getHandshakeStatus())
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// We were not handshaking, so just tell the app we are closed
|
||||
|
@ -521,10 +537,28 @@ public class SslConnection extends AbstractConnection
|
|||
throw new IllegalStateException();
|
||||
|
||||
default:
|
||||
if (unwrapResult.getStatus()==Status.BUFFER_UNDERFLOW)
|
||||
_underFlown=true;
|
||||
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
|
||||
{
|
||||
_handshaken = true;
|
||||
if (DEBUG)
|
||||
LOG.debug("{} handshake completed client-side", SslConnection.this);
|
||||
}
|
||||
|
||||
// if we produced bytes, we don't care about the handshake state for now and it can be dealt with on another call to fill or flush
|
||||
// Check whether renegotiation is allowed
|
||||
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{} renegotiation denied", SslConnection.this);
|
||||
closeInbound();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (unwrapResultStatus == Status.BUFFER_UNDERFLOW)
|
||||
_underFlown = true;
|
||||
|
||||
// If bytes were produced, don't bother with the handshake status;
|
||||
// pass the decrypted data to the application, which will perform
|
||||
// another call to fill() or flush().
|
||||
if (unwrapResult.bytesProduced() > 0)
|
||||
{
|
||||
if (app_in == buffer)
|
||||
|
@ -533,7 +567,7 @@ public class SslConnection extends AbstractConnection
|
|||
}
|
||||
|
||||
// Dang! we have to care about the handshake state
|
||||
switch (_sslEngine.getHandshakeStatus())
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// we just didn't read anything.
|
||||
|
@ -553,7 +587,7 @@ public class SslConnection extends AbstractConnection
|
|||
// we need to send some handshake data
|
||||
|
||||
// if we are called from flush
|
||||
if (buffer==__FLUSH_CALLED_FILL)
|
||||
if (buffer == __FLUSH_CALLED_FILL)
|
||||
return 0; // let it do the wrapping
|
||||
|
||||
_fillRequiresFlushToProgress = true;
|
||||
|
@ -661,7 +695,6 @@ public class SslConnection extends AbstractConnection
|
|||
|
||||
while (true)
|
||||
{
|
||||
// do the funky SSL thang!
|
||||
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
|
||||
BufferUtil.compact(_encryptedOutput);
|
||||
int pos = BufferUtil.flipToFill(_encryptedOutput);
|
||||
|
@ -672,18 +705,20 @@ public class SslConnection extends AbstractConnection
|
|||
if (wrapResult.bytesConsumed()>0)
|
||||
consumed+=wrapResult.bytesConsumed();
|
||||
|
||||
boolean all_consumed=true;
|
||||
boolean allConsumed=true;
|
||||
// clear empty buffers to prevent position creeping up the buffer
|
||||
for (ByteBuffer b : appOuts)
|
||||
{
|
||||
if (BufferUtil.isEmpty(b))
|
||||
BufferUtil.clear(b);
|
||||
else
|
||||
all_consumed=false;
|
||||
allConsumed=false;
|
||||
}
|
||||
|
||||
Status wrapResultStatus = wrapResult.getStatus();
|
||||
|
||||
// and deal with the results returned from the sslEngineWrap
|
||||
switch (wrapResult.getStatus())
|
||||
switch (wrapResultStatus)
|
||||
{
|
||||
case CLOSED:
|
||||
// The SSL engine has close, but there may be close handshake that needs to be written
|
||||
|
@ -692,32 +727,52 @@ public class SslConnection extends AbstractConnection
|
|||
_cannotAcceptMoreAppDataToFlush = true;
|
||||
getEndPoint().flush(_encryptedOutput);
|
||||
// If we failed to flush the close handshake then we will just pretend that
|
||||
// the write has progressed normally and let a subsequent call to flush (or WriteFlusher#onIncompleteFlushed)
|
||||
// to finish writing the close handshake. The caller will find out about the close on a subsequent flush or fill.
|
||||
// the write has progressed normally and let a subsequent call to flush
|
||||
// (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
|
||||
// The caller will find out about the close on a subsequent flush or fill.
|
||||
if (BufferUtil.hasContent(_encryptedOutput))
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise we have written, and the caller will close the underlying connection
|
||||
return all_consumed;
|
||||
return allConsumed;
|
||||
|
||||
case BUFFER_UNDERFLOW:
|
||||
throw new IllegalStateException();
|
||||
|
||||
default:
|
||||
if (DEBUG)
|
||||
LOG.debug("{} {} {}", this, wrapResult.getStatus(), BufferUtil.toDetailString(_encryptedOutput));
|
||||
LOG.debug("{} {} {}", this, wrapResultStatus, BufferUtil.toDetailString(_encryptedOutput));
|
||||
|
||||
if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
|
||||
{
|
||||
_handshaken = true;
|
||||
if (DEBUG)
|
||||
LOG.debug("{} handshake completed server-side", SslConnection.this);
|
||||
}
|
||||
|
||||
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
|
||||
|
||||
// Check whether renegotiation is allowed
|
||||
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
|
||||
{
|
||||
if (DEBUG)
|
||||
LOG.debug("{} renegotiation denied", SslConnection.this);
|
||||
shutdownOutput();
|
||||
BufferUtil.clear(_encryptedOutput);
|
||||
return allConsumed;
|
||||
}
|
||||
|
||||
// if we have net bytes, let's try to flush them
|
||||
if (BufferUtil.hasContent(_encryptedOutput))
|
||||
getEndPoint().flush(_encryptedOutput);
|
||||
|
||||
// But we also might have more to do for the handshaking state.
|
||||
switch (_sslEngine.getHandshakeStatus())
|
||||
switch (handshakeStatus)
|
||||
{
|
||||
case NOT_HANDSHAKING:
|
||||
// Return with the number of bytes consumed (which may be 0)
|
||||
return all_consumed&&BufferUtil.isEmpty(_encryptedOutput);
|
||||
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
|
||||
|
||||
case NEED_TASK:
|
||||
// run the task and continue
|
||||
|
@ -737,14 +792,13 @@ public class SslConnection extends AbstractConnection
|
|||
_flushRequiresFillToProgress = true;
|
||||
fill(__FLUSH_CALLED_FILL);
|
||||
// Check if after the fill() we need to wrap again
|
||||
if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)
|
||||
if (handshakeStatus == HandshakeStatus.NEED_WRAP)
|
||||
continue;
|
||||
}
|
||||
return all_consumed&&BufferUtil.isEmpty(_encryptedOutput);
|
||||
return allConsumed&&BufferUtil.isEmpty(_encryptedOutput);
|
||||
|
||||
case FINISHED:
|
||||
throw new IllegalStateException();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,10 +74,9 @@ public class SelectChannelEndPointSslTest extends SelectChannelEndPointTest
|
|||
SSLEngine engine = __sslCtxFactory.newSSLEngine();
|
||||
engine.setUseClientMode(false);
|
||||
SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
|
||||
|
||||
sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
|
||||
Connection appConnection = super.newConnection(channel,sslConnection.getDecryptedEndPoint());
|
||||
sslConnection.getDecryptedEndPoint().setConnection(appConnection);
|
||||
|
||||
return sslConnection;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.nio.channels.SocketChannel;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
|
@ -80,10 +79,9 @@ public class SslConnectionTest
|
|||
SSLEngine engine = __sslCtxFactory.newSSLEngine();
|
||||
engine.setUseClientMode(false);
|
||||
SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine);
|
||||
|
||||
sslConnection.setRenegotiationAllowed(__sslCtxFactory.isRenegotiationAllowed());
|
||||
Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint());
|
||||
sslConnection.getDecryptedEndPoint().setConnection(appConnection);
|
||||
|
||||
return sslConnection;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
engine.setUseClientMode(false);
|
||||
|
||||
SslConnection sslConnection = newSslConnection(connector, endPoint, engine);
|
||||
sslConnection.setRenegotiationAllowed(_sslContextFactory.isRenegotiationAllowed());
|
||||
configure(sslConnection, connector, endPoint);
|
||||
|
||||
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
|
||||
|
|
|
@ -307,6 +307,7 @@ public class SPDYClient
|
|||
{
|
||||
final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
|
||||
SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
|
||||
sslEndPoint.setConnection(connection);
|
||||
|
|
|
@ -41,6 +41,9 @@ import java.util.Collections;
|
|||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.net.ssl.CertPathTrustManagerParameters;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
|
@ -196,8 +199,12 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
/** EndpointIdentificationAlgorithm - when set to "HTTPS" hostname verification will be enabled */
|
||||
private String _endpointIdentificationAlgorithm = null;
|
||||
|
||||
/** Whether to blindly trust certificates */
|
||||
private boolean _trustAll;
|
||||
|
||||
/** Whether TLS renegotiation is allowed */
|
||||
private boolean _renegotiationAllowed = true;
|
||||
|
||||
/**
|
||||
* Construct an instance of SslContextFactory
|
||||
* Default constructor for use in XmlConfiguration files
|
||||
|
@ -369,6 +376,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* You can either use the exact cipher suite name or a a regular expression.
|
||||
* @param cipherSuites
|
||||
* The array of cipher suite names to exclude from
|
||||
* {@link SSLEngine#setEnabledCipherSuites(String[])}
|
||||
|
@ -399,6 +407,7 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
}
|
||||
|
||||
/**
|
||||
* You can either use the exact cipher suite name or a a regular expression.
|
||||
* @param cipherSuites
|
||||
* The array of cipher suite names to include in
|
||||
* {@link SSLEngine#setEnabledCipherSuites(String[])}
|
||||
|
@ -759,6 +768,22 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
_trustManagerFactoryAlgorithm = algorithm;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether TLS renegotiation is allowed (true by default)
|
||||
*/
|
||||
public boolean isRenegotiationAllowed()
|
||||
{
|
||||
return _renegotiationAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param renegotiationAllowed whether TLS renegotiation is allowed
|
||||
*/
|
||||
public void setRenegotiationAllowed(boolean renegotiationAllowed)
|
||||
{
|
||||
_renegotiationAllowed = renegotiationAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Path to file that contains Certificate Revocation List
|
||||
*/
|
||||
|
@ -1035,25 +1060,47 @@ public class SslContextFactory extends AbstractLifeCycle
|
|||
*/
|
||||
public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
|
||||
{
|
||||
Set<String> selected_ciphers = new LinkedHashSet<>();
|
||||
Set<String> selected_ciphers = new CopyOnWriteArraySet<>();
|
||||
|
||||
// Set the starting ciphers - either from the included or enabled list
|
||||
if (_includeCipherSuites!=null)
|
||||
{
|
||||
// Use only the supported included ciphers
|
||||
for (String cipherSuite : _includeCipherSuites)
|
||||
if(Arrays.asList(supportedCipherSuites).contains(cipherSuite))
|
||||
selected_ciphers.add(cipherSuite);
|
||||
}
|
||||
processIncludeCipherSuites(supportedCipherSuites, selected_ciphers);
|
||||
else
|
||||
selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
|
||||
|
||||
removeExcludedCipherSuites(selected_ciphers);
|
||||
|
||||
// Remove any excluded ciphers
|
||||
selected_ciphers.removeAll(_excludeCipherSuites);
|
||||
return selected_ciphers.toArray(new String[selected_ciphers.size()]);
|
||||
}
|
||||
|
||||
private void processIncludeCipherSuites(String[] supportedCipherSuites, Set<String> selected_ciphers)
|
||||
{
|
||||
for (String cipherSuite : _includeCipherSuites)
|
||||
{
|
||||
Pattern p = Pattern.compile(cipherSuite);
|
||||
for (String supportedCipherSuite : supportedCipherSuites)
|
||||
{
|
||||
Matcher m = p.matcher(supportedCipherSuite);
|
||||
if (m.matches())
|
||||
selected_ciphers.add(supportedCipherSuite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeExcludedCipherSuites(Set<String> selected_ciphers)
|
||||
{
|
||||
for (String excludeCipherSuite : _excludeCipherSuites)
|
||||
{
|
||||
Pattern excludeCipherPattern = Pattern.compile(excludeCipherSuite);
|
||||
for (String selectedCipherSuite : selected_ciphers)
|
||||
{
|
||||
Matcher m = excludeCipherPattern.matcher(selectedCipherSuite);
|
||||
if (m.matches())
|
||||
selected_ciphers.remove(selectedCipherSuite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the lifecycle has been started and throw runtime exception
|
||||
*/
|
||||
|
|
|
@ -18,15 +18,12 @@
|
|||
|
||||
package org.eclipse.jetty.util.ssl;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
|
@ -35,6 +32,12 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class SslContextFactoryTest
|
||||
{
|
||||
|
@ -189,6 +192,30 @@ public class SslContextFactoryTest
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetExcludeCipherSuitesRegex() throws Exception
|
||||
{
|
||||
cf.setExcludeCipherSuites(".*RC4.*");
|
||||
cf.start();
|
||||
SSLEngine sslEngine = cf.newSSLEngine();
|
||||
String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites();
|
||||
assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(0));
|
||||
for (String enabledCipherSuite : enabledCipherSuites)
|
||||
assertThat("CipherSuite does not contain RC4", enabledCipherSuite.contains("RC4"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetIncludeCipherSuitesRegex() throws Exception
|
||||
{
|
||||
cf.setIncludeCipherSuites(".*RC4.*");
|
||||
cf.start();
|
||||
SSLEngine sslEngine = cf.newSSLEngine();
|
||||
String[] enabledCipherSuites = sslEngine.getEnabledCipherSuites();
|
||||
assertThat("At least 1 cipherSuite is enabled", enabledCipherSuites.length, greaterThan(0));
|
||||
for (String enabledCipherSuite : enabledCipherSuites)
|
||||
assertThat("CipherSuite contains RC4", enabledCipherSuite.contains("RC4"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetIncludeCipherSuitesPreservesOrder()
|
||||
{
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.io.IOException;
|
|||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
|
@ -82,6 +81,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
|
|||
{
|
||||
SSLEngine engine = newSSLEngine(sslContextFactory,channel);
|
||||
SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
|
||||
sslConnection.setRenegotiationAllowed(sslContextFactory.isRenegotiationAllowed());
|
||||
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
|
||||
Connection connection = newUpgradeConnection(channel,sslEndPoint,connectPromise);
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.server;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -45,11 +43,15 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
|||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
* Tests various close scenarios
|
||||
*/
|
||||
@Ignore
|
||||
public class WebSocketCloseTest
|
||||
{
|
||||
@SuppressWarnings("serial")
|
||||
|
|
Loading…
Reference in New Issue