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:
Simone Bordet 2019-02-05 15:47:34 +01:00
parent d22ec03acc
commit 8964608bfc
6 changed files with 86 additions and 9 deletions

View File

@ -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)

View File

@ -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));
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);

View File

@ -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
{
}

View File

@ -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.
*