Jetty 10.0.8 client cannot connect to a TLS1.3 website which is reachable with common browsers

762 Views Asked by At

I can reach this website with firefox, edge, chrome. But not with the following (I obfuscated the actual company address just in case that there may be a security problem with it).

    SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
    ClientConnector clientConnector = new ClientConnector();
    clientConnector.setSslContextFactory(sslContextFactory);

    HttpClient httpClient = new HttpClient(new HttpClientTransportDynamic(clientConnector));
    httpClient.start();
    ContentResponse res = httpClient.GET("https://some.safe.company.server.de");

The website requires TLS1.3. I am using Jetty 10.0.8 with Java JDK 16.0.2, so the connection should be possible, right? I get the following stack trace (again with obfuscated address):

java.util.concurrent.ExecutionException: java.io.EOFException: HttpConnectionOverHTTP@7845debe::DecryptedEndPoint@6fc8d160[{l=/192.168.145.211:59933,r=some.safe.company.server.de/129.247.33.85:443,OPEN,fill=-,flush=-,to=29960/30000}]
    at org.eclipse.jetty.client.util.FutureResponseListener.getResult(FutureResponseListener.java:113)
    at org.eclipse.jetty.client.util.FutureResponseListener.get(FutureResponseListener.java:96)
    at org.eclipse.jetty.client.HttpRequest.send(HttpRequest.java:769)
    at org.eclipse.jetty.client.HttpClient.GET(HttpClient.java:351)
    at org.eclipse.jetty.client.HttpClient.GET(HttpClient.java:336)
    >>>> httpClient.GET(...
Caused by: java.io.EOFException: HttpConnectionOverHTTP@7845debe::DecryptedEndPoint@6fc8d160[{l=/192.168.145.211:59933,r=some.safe.company.server.de/129.247.33.85:443,OPEN,fill=-,flush=-,to=29960/30000}]
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.earlyEOF(HttpReceiverOverHTTP.java:395)
    at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:1605)
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.shutdown(HttpReceiverOverHTTP.java:281)
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:197)
    at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:91)
    at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:91)
    at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:194)
    at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:530)
    at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
    at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
    at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
    at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.runTask(AdaptiveExecutionStrategy.java:412)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.consumeTask(AdaptiveExecutionStrategy.java:381)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.tryProduce(AdaptiveExecutionStrategy.java:268)
    at org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy.lambda$new$0(AdaptiveExecutionStrategy.java:138)
    at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:407)
    at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
    at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
    at java.base/java.lang.Thread.run(Thread.java:831)
1

There are 1 best solutions below

3
Joakim Erdfelt On

The HttpReceiverOverHTTP.shutdown() in your stacktrace shows that the remote side closed the connection before sending all of the response data, which results in the HttpReceiverOverHTTP.earlyEOF().

Some servers and web applications just have bad behaviors.
Not all servers behave like that, just some bad ones.

There is nothing you can do on the client side to recover from this.
You need to understand what the server is doing.

We've had one other report that is similar to your situation at https://github.com/eclipse/jetty.project/issues/6701

Here's an example of using the high level HttpClient with a dynamic connector supporting both HTTP/2 and HTTP/1.1, and forced to only use TLSv1.3. This example connects to https://api.github.com/ which implements TLS and HTTP/1.1 properly (even HTTP/2 properly).

Found at the jetty-project/embedded-jetty-cookbook repository as ClientWithDynamicConnection.java

If you don't want HTTP/2 support, then just remove the entire HttpClientTransportDynamic layer, including the ClientConnector and stick with HTTP/1.1 (you'll still need the SslContextFactory.Client tho)

package org.eclipse.jetty.cookbook;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
 * Example of using a high level HttpClient to connect to a server that
 * supports both HTTP/2 and HTTP/1.1, using TLSv1.3 only.
 */
public class ClientWithDynamicConnection
{
    public static void main(String[] args) throws Exception
    {
        SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
        sslContextFactory.setIncludeProtocols("TLSv1.3");
        ClientConnector clientConnector = new ClientConnector();
        clientConnector.setSslContextFactory(sslContextFactory);

        ClientConnectionFactory.Info h1 = HttpClientConnectionFactory.HTTP11;
        HTTP2Client http2Client = new HTTP2Client(clientConnector);
        ClientConnectionFactory.Info h2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);
        HttpClientTransportDynamic dynamicTransport = new HttpClientTransportDynamic(clientConnector, h1, h2);

        HttpClient httpClient = new HttpClient(dynamicTransport);
        try
        {
            httpClient.start();
            // To see the SslContextFactory configuration, dump the client
            System.out.printf("Dump of client: %s%n", httpClient.dump());
            ContentResponse res = httpClient.GET("https://api.github.com/zen");
            System.out.printf("response status: %d%n", res.getStatus());
            res.getHeaders().forEach((field) ->
            {
                System.out.printf("response header [%s]: %s%n", field.getName(), field.getValue());
            });
            System.out.printf("response body: %s%n", res.getContentAsString());
        }
        finally
        {
            httpClient.stop();
        }
    }
}