Fixes #3154 - Add support for javax.net.ssl.HostnameVerifier to HttpClient.
Added a SslHandshakeListener to SslConnection that performs the host name verification (only on the client) if the HostnameVerifier has been configured in SslContextFactory. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
d22ec03acc
commit
8964608bfc
|
@ -18,7 +18,6 @@
|
|||
|
||||
package org.eclipse.jetty.alpn.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -34,11 +33,10 @@ import org.eclipse.jetty.io.EndPoint;
|
|||
import org.eclipse.jetty.io.NegotiatingClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ssl.ALPNProcessor.Client;
|
||||
import org.eclipse.jetty.io.ssl.SslClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory implements SslHandshakeListener
|
||||
public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFactory
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(ALPNClientConnectionFactory.class);
|
||||
|
||||
|
@ -96,7 +94,7 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
|
|||
}
|
||||
|
||||
@Override
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||
public Connection newConnection(EndPoint endPoint, Map<String, Object> context)
|
||||
{
|
||||
SSLEngine engine = (SSLEngine)context.get(SslClientConnectionFactory.SSL_ENGINE_CONTEXT_KEY);
|
||||
for (Client processor : processors)
|
||||
|
|
|
@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
|
@ -325,7 +326,6 @@ public class HttpClientTLSTest
|
|||
@Test
|
||||
public void testHandshakeSucceededWithSessionResumption() throws Exception
|
||||
{
|
||||
|
||||
SslContextFactory serverTLSFactory = createSslContextFactory();
|
||||
startServer(serverTLSFactory, new EmptyServerHandler());
|
||||
|
||||
|
@ -405,7 +405,6 @@ public class HttpClientTLSTest
|
|||
@Test
|
||||
public void testClientRawCloseDoesNotInvalidateSession() throws Exception
|
||||
{
|
||||
|
||||
SslContextFactory serverTLSFactory = createSslContextFactory();
|
||||
startServer(serverTLSFactory, new EmptyServerHandler());
|
||||
|
||||
|
@ -527,4 +526,30 @@ public class HttpClientTLSTest
|
|||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostNameVerificationFailure() throws Exception
|
||||
{
|
||||
SslContextFactory serverTLSFactory = createSslContextFactory();
|
||||
startServer(serverTLSFactory, new EmptyServerHandler());
|
||||
|
||||
SslContextFactory clientTLSFactory = createSslContextFactory();
|
||||
// Make sure the host name is not verified at the TLS level.
|
||||
clientTLSFactory.setEndpointIdentificationAlgorithm(null);
|
||||
// Add host name verification after the TLS handshake.
|
||||
clientTLSFactory.setHostnameVerifier((host, session) -> false);
|
||||
startClient(clientTLSFactory);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.scheme(HttpScheme.HTTPS.asString())
|
||||
.send(result ->
|
||||
{
|
||||
Throwable failure = result.getFailure();
|
||||
if (failure instanceof SSLPeerUnverifiedException)
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
|
@ -100,6 +103,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
|
||||
appEndPoint.setConnection(connectionFactory.newConnection(appEndPoint, context));
|
||||
|
||||
sslConnection.addHandshakeListener(new HTTPSHandshakeListener(context));
|
||||
customize(sslConnection, context);
|
||||
|
||||
return sslConnection;
|
||||
|
@ -124,4 +128,37 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
}
|
||||
return ClientConnectionFactory.super.customize(connection, context);
|
||||
}
|
||||
|
||||
private class HTTPSHandshakeListener implements SslHandshakeListener
|
||||
{
|
||||
private final Map<String, Object> context;
|
||||
|
||||
private HTTPSHandshakeListener(Map<String, Object> context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handshakeSucceeded(Event event) throws SSLException
|
||||
{
|
||||
HostnameVerifier verifier = sslContextFactory.getHostnameVerifier();
|
||||
if (verifier != null)
|
||||
{
|
||||
String host = (String)context.get(SSL_PEER_HOST_CONTEXT_KEY);
|
||||
try
|
||||
{
|
||||
if (!verifier.verify(host, event.getSSLEngine().getSession()))
|
||||
throw new SSLPeerUnverifiedException("Host name verification failed for host: " + host);
|
||||
}
|
||||
catch (SSLException x)
|
||||
{
|
||||
throw x;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw (SSLException)new SSLPeerUnverifiedException("Host name verification failed for host: " + host).initCause(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -759,7 +759,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
}
|
||||
}
|
||||
|
||||
private void handshakeSucceeded()
|
||||
private void handshakeSucceeded() throws SSLException
|
||||
{
|
||||
if (_handshake.compareAndSet(Handshake.INITIAL, Handshake.SUCCEEDED))
|
||||
{
|
||||
|
@ -1182,7 +1182,7 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
}
|
||||
}
|
||||
|
||||
private void notifyHandshakeSucceeded(SSLEngine sslEngine)
|
||||
private void notifyHandshakeSucceeded(SSLEngine sslEngine) throws SSLException
|
||||
{
|
||||
SslHandshakeListener.Event event = null;
|
||||
for (SslHandshakeListener listener : handshakeListeners)
|
||||
|
@ -1193,6 +1193,10 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr
|
|||
{
|
||||
listener.handshakeSucceeded(event);
|
||||
}
|
||||
catch (SSLException x)
|
||||
{
|
||||
throw x;
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
LOG.info("Exception while notifying listener " + listener, x);
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.EventListener;
|
|||
import java.util.EventObject;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
/**
|
||||
* <p>Implementations of this interface are notified of TLS handshake events.</p>
|
||||
|
@ -36,7 +37,7 @@ public interface SslHandshakeListener extends EventListener
|
|||
*
|
||||
* @param event the event object carrying information about the TLS handshake event
|
||||
*/
|
||||
default void handshakeSucceeded(Event event)
|
||||
default void handshakeSucceeded(Event event) throws SSLException
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.CertPathTrustManagerParameters;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SNIHostName;
|
||||
|
@ -194,6 +195,7 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
|
|||
private int _renegotiationLimit = 5;
|
||||
private Factory _factory;
|
||||
private PKIXCertPathChecker _pkixCertPathChecker;
|
||||
private HostnameVerifier _hostnameVerifier;
|
||||
|
||||
/**
|
||||
* Construct an instance of SslContextFactory
|
||||
|
@ -1596,6 +1598,16 @@ public class SslContextFactory extends AbstractLifeCycle implements Dumpable
|
|||
_sslSessionTimeout = sslSessionTimeout;
|
||||
}
|
||||
|
||||
public HostnameVerifier getHostnameVerifier()
|
||||
{
|
||||
return _hostnameVerifier;
|
||||
}
|
||||
|
||||
public void setHostnameVerifier(HostnameVerifier hostnameVerifier)
|
||||
{
|
||||
_hostnameVerifier = hostnameVerifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password object for the given realm.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue