diff --git a/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java index 4788d3d229c..44241ff3f3e 100644 --- a/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java +++ b/jetty-alpn/jetty-alpn-java-server/src/main/java/org/eclipse/jetty/alpn/java/server/JDK9ServerALPNProcessor.java @@ -25,9 +25,14 @@ import javax.net.ssl.SSLException; import org.eclipse.jetty.alpn.ALPN; import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; -public class JDK9ServerALPNProcessor implements ALPNProcessor.Server +public class JDK9ServerALPNProcessor implements ALPNProcessor.Server, SslHandshakeListener { + private static final Logger LOG = Log.getLogger(JDK9ServerALPNProcessor.class); + @Override public void configure(SSLEngine sslEngine) { @@ -38,7 +43,9 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server { try { - ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.get(sslEngine); + if (LOG.isDebugEnabled()) + LOG.debug("ALPN selecting among client{}", protocols); + ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(sslEngine); return provider.select(protocols); } catch (SSLException x) @@ -46,4 +53,21 @@ public class JDK9ServerALPNProcessor implements ALPNProcessor.Server return null; } } + + @Override + public void handshakeSucceeded(Event event) + { + ALPN.ServerProvider provider = (ALPN.ServerProvider)ALPN.remove(event.getSSLEngine()); + if (provider != null) + { + if (LOG.isDebugEnabled()) + LOG.debug("ALPN unsupported by client"); + provider.unsupported(); + } + } + + @Override + public void handshakeFailed(Event event, Throwable failure) + { + } } diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java new file mode 100644 index 00000000000..3eca874d462 --- /dev/null +++ b/jetty-alpn/jetty-alpn-java-server/src/test/java/org/eclipse/jetty/alpn/java/server/JDK9ALPNTest.java @@ -0,0 +1,166 @@ +// +// ======================================================================== +// 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.alpn.java.server; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.Test; + +public class JDK9ALPNTest +{ + private Server server; + private ServerConnector connector; + + public void startServer(Handler handler) throws Exception + { + server = new Server(); + HttpConfiguration httpConfiguration = new HttpConfiguration(); + HttpConnectionFactory h1 = new HttpConnectionFactory(httpConfiguration); + HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(httpConfiguration); + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol(h1.getProtocol()); + connector = new ServerConnector(server, newSslContextFactory(), alpn, h1, h2); + server.addConnector(connector); + server.setHandler(handler); + server.start(); + } + + private SslContextFactory newSslContextFactory() + { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4"); + sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g"); + sslContextFactory.setIncludeProtocols("TLSv1.2"); + // The mandatory HTTP/2 cipher. + sslContextFactory.setIncludeCipherSuites("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + return sslContextFactory; + } + + @Test + public void testClientNotSupportingALPNServerSpeaksDefaultProtocol() throws Exception + { + startServer(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + } + }); + + SslContextFactory sslContextFactory = new SslContextFactory(true); + sslContextFactory.start(); + SSLContext sslContext = sslContextFactory.getSslContext(); + try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", connector.getLocalPort())) + { + client.setUseClientMode(true); + client.setSoTimeout(5000); + client.startHandshake(); + + OutputStream output = client.getOutputStream(); + output.write(("" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n" + + "").getBytes(StandardCharsets.UTF_8)); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200 ")); + while (true) + { + if (reader.readLine() == null) + break; + } + } + } + + @Test + public void testClientSupportingALPNServerSpeaksNegotiatedProtocol() throws Exception + { + startServer(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + } + }); + + SslContextFactory sslContextFactory = new SslContextFactory(true); + sslContextFactory.start(); + SSLContext sslContext = sslContextFactory.getSslContext(); + try (SSLSocket client = (SSLSocket)sslContext.getSocketFactory().createSocket("localhost", connector.getLocalPort())) + { + client.setUseClientMode(true); + SSLParameters sslParameters = client.getSSLParameters(); + sslParameters.setApplicationProtocols(new String[]{"unknown/1.0", "http/1.1"}); + client.setSSLParameters(sslParameters); + client.setSoTimeout(5000); + client.startHandshake(); + + OutputStream output = client.getOutputStream(); + output.write(("" + + "GET / HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n" + + "").getBytes(StandardCharsets.UTF_8)); + output.flush(); + + InputStream input = client.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); + String line = reader.readLine(); + Assert.assertTrue(line.contains(" 200 ")); + while (true) + { + if (reader.readLine() == null) + break; + } + } + + } +} diff --git a/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties b/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties index d96a696f82e..c391f84e35b 100644 --- a/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties +++ b/jetty-alpn/jetty-alpn-java-server/src/test/resources/jetty-logging.properties @@ -1,2 +1,3 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog #org.eclipse.jetty.LEVEL=DEBUG +#org.eclipse.jetty.alpn.LEVEL=DEBUG diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java index e6c15eb092e..c755ff5b459 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java +++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnection.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.List; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; import org.eclipse.jetty.alpn.ALPN; import org.eclipse.jetty.io.EndPoint; @@ -44,7 +45,7 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements @Override public void unsupported() { - select(Collections.emptyList()); + select(Collections.emptyList()); } @Override @@ -52,8 +53,11 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements { SSLEngine sslEngine = getSSLEngine(); List serverProtocols = getProtocols(); - String tlsProtocol = sslEngine.getHandshakeSession().getProtocol(); - String tlsCipher = sslEngine.getHandshakeSession().getCipherSuite(); + SSLSession sslSession = sslEngine.getHandshakeSession(); + if (sslSession == null) + sslSession = sslEngine.getSession(); + String tlsProtocol = sslSession.getProtocol(); + String tlsCipher = sslSession.getCipherSuite(); String negotiated = null; // RFC 7301 states that the server picks the protocol @@ -83,12 +87,12 @@ public class ALPNServerConnection extends NegotiatingServerConnection implements else { if (LOG.isDebugEnabled()) - LOG.debug("{} could not negotiate protocol: C{} | S{}", this, clientProtocols, serverProtocols); + LOG.debug("{} could not negotiate protocol among client{} and server{}", this, clientProtocols, serverProtocols); throw new IllegalStateException(); } } if (LOG.isDebugEnabled()) - LOG.debug("{} protocol selected {}", this, negotiated); + LOG.debug("{} protocol selected {} among client{} and server{}", this, negotiated, clientProtocols, serverProtocols); setProtocol(negotiated); ALPN.remove(sslEngine); return negotiated; diff --git a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java index 057c41c16e1..fca7fa0f27e 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java +++ b/jetty-alpn/jetty-alpn-server/src/main/java/org/eclipse/jetty/alpn/server/ALPNServerConnectionFactory.java @@ -27,11 +27,12 @@ import javax.net.ssl.SSLEngine; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.ALPNProcessor; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NegotiatingServerConnectionFactory; import org.eclipse.jetty.util.annotation.Name; -public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory +public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFactory implements SslHandshakeListener { private final ALPNProcessor.Server alpnProcessor; @@ -59,4 +60,18 @@ public class ALPNServerConnectionFactory extends NegotiatingServerConnectionFact getALPNProcessor().configure(engine); return new ALPNServerConnection(connector, endPoint, engine, protocols, defaultProtocol); } + + @Override + public void handshakeSucceeded(Event event) + { + if (alpnProcessor instanceof SslHandshakeListener) + ((SslHandshakeListener)alpnProcessor).handshakeSucceeded(event); + } + + @Override + public void handshakeFailed(Event event, Throwable failure) + { + if (alpnProcessor instanceof SslHandshakeListener) + ((SslHandshakeListener)alpnProcessor).handshakeFailed(event, failure); + } }