diff --git a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc index f2702996b7e..f16c8597861 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/connectors/configuring-ssl.adoc @@ -824,7 +824,7 @@ $ ____ [NOTE] The default `SslContextFactory` implementation applies the latest SSL/TLS recommendations surrounding vulnerabilities in SSL/TLS. -Check the release notes (the `VERSION.txt` found in the root of the Jetty Distribution, or the http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.eclipse.jetty%22%20AND%20a%3A%22jetty-project%22[alternate (classified 'version') artifacts for the `jetty-project` component]on Maven Central) for updates. +Check the release notes (the `VERSION.txt` found in the root of the Jetty Distribution, or the http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.eclipse.jetty%22%20AND%20a%3A%22jetty-project%22[alternate (classified 'version') artifacts for the `jetty-project` component] on Maven Central) for updates. The Java JVM also applies exclusions at the JVM level and, as such, if you have a need to enable something that is generally accepted by the industry as being insecure or vulnerable you will likely have to enable it in *both* the Java JVM and your Jetty configuration. ____ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java index df6230bca2c..aae4b2c1449 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/IdleTimeout.java @@ -40,18 +40,7 @@ public abstract class IdleTimeout private final Scheduler _scheduler; private final AtomicReference _timeout = new AtomicReference<>(); private volatile long _idleTimeout; - private volatile long _idleTimestamp = System.currentTimeMillis(); - - private final Runnable _idleTask = new Runnable() - { - @Override - public void run() - { - long idleLeft = checkIdleTimeout(); - if (idleLeft >= 0) - scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout()); - } - }; + private volatile long _idleTimestamp = System.nanoTime(); /** * @param scheduler A scheduler used to schedule checks for the idle timeout. @@ -65,22 +54,31 @@ public abstract class IdleTimeout { return _scheduler; } - - public long getIdleTimestamp() - { - return _idleTimestamp; - } + /** + * @return the period of time, in milliseconds, that this object was idle + */ public long getIdleFor() { - return System.currentTimeMillis() - getIdleTimestamp(); + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - _idleTimestamp); } + /** + * @return the idle timeout in milliseconds + * @see #setIdleTimeout(long) + */ public long getIdleTimeout() { return _idleTimeout; } + /** + *

Sets the idle timeout in milliseconds.

+ *

A value that is less than or zero disables the idle timeout checks.

+ * + * @param idleTimeout the idle timeout in milliseconds + * @see #getIdleTimeout() + */ public void setIdleTimeout(long idleTimeout) { long old = _idleTimeout; @@ -107,14 +105,21 @@ public abstract class IdleTimeout */ public void notIdle() { - _idleTimestamp = System.currentTimeMillis(); + _idleTimestamp = System.nanoTime(); + } + + private void idleCheck() + { + long idleLeft = checkIdleTimeout(); + if (idleLeft >= 0) + scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout()); } private void scheduleIdleTimeout(long delay) { Scheduler.Task newTimeout = null; if (isOpen() && delay > 0 && _scheduler != null) - newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS); + newTimeout = _scheduler.schedule(this::idleCheck, delay, TimeUnit.MILLISECONDS); Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout); if (oldTimeout != null) oldTimeout.cancel(); @@ -128,7 +133,7 @@ public abstract class IdleTimeout private void activate() { if (_idleTimeout > 0) - _idleTask.run(); + idleCheck(); } public void onClose() @@ -147,15 +152,15 @@ public abstract class IdleTimeout { if (isOpen()) { - long idleTimestamp = getIdleTimestamp(); + long idleTimestamp = _idleTimestamp; + long idleElapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTimestamp); long idleTimeout = getIdleTimeout(); - long idleElapsed = System.currentTimeMillis() - idleTimestamp; long idleLeft = idleTimeout - idleElapsed; if (LOG.isDebugEnabled()) LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft); - if (idleTimestamp != 0 && idleTimeout > 0) + if (idleTimeout > 0) { if (idleLeft <= 0) { diff --git a/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java new file mode 100644 index 00000000000..9d64da695eb --- /dev/null +++ b/jetty-websocket/jetty-websocket-tests/src/test/java/org/eclipse/jetty/websocket/tests/WebSocketNegotiationTest.java @@ -0,0 +1,171 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.websocket.tests; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.servlet.WebSocketServlet; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; + +public class WebSocketNegotiationTest +{ + public static class EchoServlet extends WebSocketServlet + { + @Override + public void configure(WebSocketServletFactory factory) + { + factory.register(EchoSocket.class); + } + } + + private Server server; + private ServerConnector connector; + private WebSocketClient client; + + @BeforeEach + public void start() throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); + contextHandler.setContextPath("/"); + contextHandler.addServlet(EchoServlet.class, "/"); + server.setHandler(contextHandler); + + client = new WebSocketClient(); + + server.start(); + client.start(); + } + + @AfterEach + public void stop() throws Exception + { + client.stop(); + server.stop(); + } + + @Test + public void testValidUpgradeRequest() throws Exception + { + Socket client = new Socket(); + client.connect(new InetSocketAddress("127.0.0.1", connector.getLocalPort())); + + HttpFields httpFields = newUpgradeRequest(null); + httpFields.remove(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL); + httpFields.add(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL, "testInvalidUpgradeRequest"); + String upgradeRequest = "GET / HTTP/1.1\r\n" + httpFields; + client.getOutputStream().write(upgradeRequest.getBytes(StandardCharsets.ISO_8859_1)); + String response = getUpgradeResponse(client.getInputStream()); + + assertThat(response, startsWith("HTTP/1.1 101 Switching Protocols")); + assertThat(response, containsString("Sec-WebSocket-Accept: +WahVcVmeMLKQUMm0fvPrjSjwzI=")); + } + + @Test + public void testInvalidUpgradeRequestNoKey() throws Exception + { + Socket client = new Socket(); + client.connect(new InetSocketAddress("127.0.0.1", connector.getLocalPort())); + + HttpFields httpFields = newUpgradeRequest(null); + httpFields.remove(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL); + httpFields.add(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL, "testInvalidUpgradeRequest"); + httpFields.remove(HttpHeader.SEC_WEBSOCKET_KEY); + + String upgradeRequest = "GET / HTTP/1.1\r\n" + httpFields; + client.getOutputStream().write(upgradeRequest.getBytes(StandardCharsets.ISO_8859_1)); + String response = getUpgradeResponse(client.getInputStream()); + + assertThat(response, containsString("400 Missing request header 'Sec-WebSocket-Key'")); + } + + + protected static HttpFields newUpgradeRequest(String extensions) + { + HttpFields fields = new HttpFields(); + fields.add(HttpHeader.HOST, "127.0.0.1"); + fields.add(HttpHeader.UPGRADE, "websocket"); + fields.add(HttpHeader.CONNECTION, "Upgrade"); + fields.add(HttpHeader.SEC_WEBSOCKET_KEY, new String(B64Code.encode("0123456701234567".getBytes()))); + fields.add(HttpHeader.SEC_WEBSOCKET_VERSION, "13"); + fields.add(HttpHeader.PRAGMA, "no-cache"); + fields.add(HttpHeader.CACHE_CONTROL, "no-cache"); + fields.add(HttpHeader.SEC_WEBSOCKET_SUBPROTOCOL, "test"); + if (extensions != null) + fields.add(HttpHeader.SEC_WEBSOCKET_EXTENSIONS, extensions); + + return fields; + } + + protected static String getUpgradeResponse(InputStream in) throws IOException + { + int state = 0; + StringBuilder buffer = new StringBuilder(); + while (state < 4) + { + int i = in.read(); + if (i < 0) + throw new EOFException(); + int b = (byte)(i & 0xff); + buffer.append((char)b); + switch (state) + { + case 0: + state = (b == '\r')?1:0; + break; + case 1: + state = (b == '\n')?2:0; + break; + case 2: + state = (b == '\r')?3:0; + break; + case 3: + state = (b == '\n')?4:0; + break; + default: + state = 0; + } + } + + return buffer.toString(); + } +} \ No newline at end of file