From 547ebe7236fee5b85888a808922e33c03ee3df23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Tue, 7 Apr 2026 11:48:01 +0000 Subject: [PATCH] 8381316: HttpClient / Http3: poor exception messages on SSL handshake errors Reviewed-by: dfuchs --- .../sun/security/ssl/QuicTLSEngineImpl.java | 4 +- .../net/http/quic/TerminationCause.java | 5 +- .../net/httpclient/InvalidSSLContextTest.java | 62 +++++++++++-------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java index 74975fc1e5b..b5f1eff179c 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -660,7 +660,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { } Alert alert = ((QuicEngineOutputRecord) conContext.outputRecord).getAlert(); - throw new QuicTransportException(alert.description, keySpace, 0, + throw new QuicTransportException(e.getMessage(), keySpace, 0, BASE_CRYPTO_ERROR + alert.id, e); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/TerminationCause.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/TerminationCause.java index 9e441cf7873..df8c229a000 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/TerminationCause.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/TerminationCause.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -128,6 +128,9 @@ public abstract sealed class TerminationCause { ? new IOException("connection terminated") : new IOException(fallbackExceptionMsg); } else if (original instanceof QuicTransportException qte) { + if (qte.getCause() instanceof IOException ioe) { + return ioe; + } return new IOException(qte.getMessage()); } else if (original instanceof IOException ioe) { return ioe; diff --git a/test/jdk/java/net/httpclient/InvalidSSLContextTest.java b/test/jdk/java/net/httpclient/InvalidSSLContextTest.java index 0027a7956d5..bbbd1e486ca 100644 --- a/test/jdk/java/net/httpclient/InvalidSSLContextTest.java +++ b/test/jdk/java/net/httpclient/InvalidSSLContextTest.java @@ -23,10 +23,12 @@ /* * @test + * @bug 8381316 * @summary Test to ensure the HTTP client throws an appropriate SSL exception * when SSL context is not valid. - * @library /test/lib + * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.test.lib.net.SimpleSSLContext + * jdk.httpclient.test.lib.common.HttpServerAdapters * @run junit/othervm -Djdk.internal.httpclient.debug=true InvalidSSLContextTest */ @@ -44,14 +46,18 @@ import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; import java.net.http.HttpClient; import java.net.http.HttpClient.Version; +import java.net.http.HttpOption; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; + +import jdk.httpclient.test.lib.common.HttpServerAdapters.HttpTestServer; import jdk.test.lib.net.SimpleSSLContext; import static java.net.http.HttpClient.Builder.NO_PROXY; -import static java.net.http.HttpClient.Version.HTTP_1_1; -import static java.net.http.HttpClient.Version.HTTP_2; +import static java.net.http.HttpClient.Version.*; +import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; +import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderForH3; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -64,25 +70,29 @@ public class InvalidSSLContextTest { private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); static volatile SSLServerSocket sslServerSocket; static volatile String uri; + private static HttpTestServer h3Server; + private static String h3Uri; public static Object[][] versions() { return new Object[][]{ - { HTTP_1_1 }, - { HTTP_2 } + { HTTP_1_1, uri }, + { HTTP_2 , uri }, + { HTTP_3 , h3Uri } }; } @ParameterizedTest @MethodSource("versions") - public void testSync(Version version) throws Exception { + public void testSync(Version version, String uri) throws Exception { // client-side uses a different context to that of the server-side - HttpClient client = HttpClient.newBuilder() + HttpClient client = createClientBuilderForH3() .proxy(NO_PROXY) .sslContext(SSLContext.getDefault()) .build(); HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) .version(version) + .setOption(HttpOption.H3_DISCOVERY, HTTP_3_URI_ONLY) .build(); try { @@ -90,21 +100,22 @@ public class InvalidSSLContextTest { Assertions.fail("UNEXPECTED response" + response); } catch (IOException ex) { System.out.println("Caught expected: " + ex); - assertExceptionOrCause(SSLException.class, ex); + assertException(SSLException.class, ex); } } @ParameterizedTest @MethodSource("versions") - public void testAsync(Version version) throws Exception { + public void testAsync(Version version, String uri) throws Exception { // client-side uses a different context to that of the server-side - HttpClient client = HttpClient.newBuilder() + HttpClient client = createClientBuilderForH3() .proxy(NO_PROXY) .sslContext(SSLContext.getDefault()) .build(); HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) .version(version) + .setOption(HttpOption.H3_DISCOVERY, HTTP_3_URI_ONLY) .build(); assertExceptionally(SSLException.class, @@ -123,26 +134,20 @@ public class InvalidSSLContextTest { if (cause == null) { Assertions.fail("Unexpected null cause: " + error); } - assertExceptionOrCause(clazz, cause); + System.out.println("Caught expected: " + cause); + assertException(clazz, cause); } else { - assertExceptionOrCause(clazz, error); + System.out.println("Caught expected: " + error); + assertException(clazz, error); } return null; }).join(); } - static void assertExceptionOrCause(Class clazz, Throwable t) { - if (t == null) { - Assertions.fail("Expected " + clazz + ", caught nothing"); - } - final Throwable original = t; - do { - if (clazz.isInstance(t)) { - return; // found - } - } while ((t = t.getCause()) != null); - original.printStackTrace(System.out); - Assertions.fail("Expected " + clazz + "in " + original); + static void assertException(Class clazz, Throwable t) { + Assertions.assertInstanceOf(clazz, t); + Assertions.assertTrue(t.getMessage().contains("unable to find valid certification path to requested target"), + "Unexpected exception message: " + t); } @BeforeAll @@ -159,7 +164,7 @@ public class InvalidSSLContextTest { Thread t = new Thread("SSL-Server-Side") { @Override public void run() { - while (true) { + while (!sslServerSocket.isClosed()) { try { SSLSocket s = (SSLSocket) sslServerSocket.accept(); System.out.println("SERVER: accepted: " + s); @@ -177,7 +182,6 @@ public class InvalidSSLContextTest { if (!sslServerSocket.isClosed()) { throw new UncheckedIOException(e); } - break; } catch (InterruptedException ie) { throw new RuntimeException(ie); } @@ -185,10 +189,16 @@ public class InvalidSSLContextTest { } }; t.start(); + + h3Server = HttpTestServer.create(HTTP_3_URI_ONLY, sslContext); + h3Server.addHandler((exchange) -> exchange.sendResponseHeaders(200, 0), "/hello"); + h3Server.start(); + h3Uri = "https://" + h3Server.serverAuthority() + "/hello"; } @AfterAll public static void teardown() throws Exception { + h3Server.stop(); sslServerSocket.close(); } }