Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8369950

TLS connection to IPv6 address fails with BCJSSE due to IllegalArgumentException

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      OS : Ubuntu(Tried on mac and should also be reproducible on other OS)
      Java : Java 17

      A DESCRIPTION OF THE PROBLEM :
      When establishing a TLS connection to an IPv6 literal address (e.g., [2001:db8::1]) using the BCJSSE (Bouncy Castle JSSE) provider in Java 17, the connection fails with an IllegalArgumentException: Contains non-LDH ASCII characters.

      This occurs because HttpsClient always attempts to create a new SNIHostName(host) even when the host string contains non-LDH characters such as colons and brackets ([, ]). The SNIHostName constructor prohibits these characters per RFC 1123 rules, resulting in a runtime exception and connection failure.

      REGRESSION : Last worked in version 17.0.16

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Prepare the Java Code:
      Please copy code from "Test Case Code" in IPv6HttpsClientTest.java file.
      Replace <REACHABLE_IPV6_ADDRESS> in the code with a valid and reachable IPv6 address of an HTTPS server.

      2. Compile the Code:
      Compile the IPv6HttpsClientTest.java file using JDK 17.
      Run with Default JDK Providers (Verification Step):
      Locate your java.security file. On macOS, it's typically at /Library/Java/JavaVirtualMachines/openjdk-jdk-17.jdk/Contents/Home/conf/security/java.security. Adjust the path for your specific JDK installation.
      Run the compiled program using the default security providers:
      bash java -Djava.security.properties==/Library/Java/JavaVirtualMachines/openjdk-jdk-17.jdk/Contents/Home/conf/security/java.security IPv6HttpsClientTest
      Expected Result: The program should run successfully, establishing the TLS connection and printing "Response Body (first 200 chars):" followed by the beginning of the response.

      3. Configure for Bouncy Castle Providers:
      Create a temporary directory, e.g., /tmp/java-security.
      Copy the original java.security file to this temporary location:
      bash cp /Library/Java/JavaVirtualMachines/openjdk-jdk-17.jdk/Contents/Home/conf/security/java.security /tmp/java-security/java.security.modified

      4. Modify java.security.modified:
      Open /tmp/java-security/java.security.modified in a text editor.
      Remove or comment out all existing security.provider.<n>=... lines.
      Add the following lines at the top of the provider list to register BCFIPS, BCJSSE, and SUN providers in this order:
      security.provider.1=org.bouncycastle.jce.provider.BouncyCastleFipsProvider security.provider.2=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider security.provider.3=sun.security.provider.Sun

      diff for reference


      diff java.security java.security.modified
      66,78c66,68
      < security.provider.1=SUN
      < security.provider.2=SunRsaSign
      < security.provider.3=SunEC
      < security.provider.4=SunJSSE
      < security.provider.5=SunJCE
      < security.provider.6=SunJGSS
      < security.provider.7=SunSASL
      < security.provider.8=XMLDSig
      < security.provider.9=SunPCSC
      < security.provider.10=JdkLDAP
      < security.provider.11=JdkSASL
      < security.provider.12=Apple
      < security.provider.13=SunPKCS11
      ---
      > security.provider.1=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider fips:BCFIPS
      > security.provider.2=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All};
      > security.provider.3=SUN

      5. Download Bouncy Castle JARs:
      Download the necessary Bouncy Castle JARs from www.bouncycastle.org.
      Place these JARs in a known directory (e.g., /path/to/bouncycastle/libs).
      Run with Modified Providers (Fails):
      Run IPv6HttpsClientTest again, this time pointing to your modified java.security file and including the Bouncy Castle JARs in the classpath:
      bash java -Djava.security.properties==/tmp/java-security/java.security.modified -cp ".:/path/to/bouncycastle/libs/*" IPv6HttpsClientTest
      (Adjust /path/to/bouncycastle/libs to your actual path).
      Actual Result: The program fails with the following output:
      Connection failed due to exception : java.lang.RuntimeException: java.lang.IllegalArgumentException: Contains non-LDH ASCII characters


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The program should run successfully, establishing the TLS connection and printing "Response Body (first 200 chars):" followed by the beginning of the response.
      ACTUAL -
      The program fails with the following output:
      Connection failed due to exception : java.lang.RuntimeException: java.lang.IllegalArgumentException: Contains non-LDH ASCII characters

      ---------- BEGIN SOURCE ----------
      import javax.net.ssl.HttpsURLConnection;
      import javax.net.ssl.SSLContext;
      import javax.net.ssl.SSLEngine;
      import javax.net.ssl.SSLHandshakeException;
      import javax.net.ssl.TrustManager;
      import javax.net.ssl.X509ExtendedTrustManager;
      import java.io.BufferedReader;
      import java.io.InputStreamReader;
      import java.net.Socket;
      import java.net.URL;
      import java.security.cert.X509Certificate;
      import java.util.stream.Collectors;

      public class IPv6HttpsClientTest {

          private static final int PORT = 443;
          private static final String PATH = "/";
          private static final String IPV6_LITERAL_HOST = "<REACHABLE_IPV6_ADDRESS>";

          public static void main(String[] args) {
              String urlString = "https://[" + IPV6_LITERAL_HOST + "]:" + PORT + PATH;
              HttpsURLConnection connection = null;
              try {
                  URL url = new URL(urlString);
                  connection = (HttpsURLConnection) url.openConnection();
                  connection.setRequestMethod("GET");
                  connection.setConnectTimeout(5000);
                  connection.setReadTimeout(5000);
                  connection.setSSLSocketFactory(new TrustAllX509ExtendedTrustManager().createTrustAllSSLContext().getSocketFactory());
                  int responseCode = connection.getResponseCode();
                  System.out.println("Response Code: " + responseCode);

                  try (BufferedReader reader = new BufferedReader(
                          new InputStreamReader(connection.getInputStream()))) {
                      String responseBody = reader.lines().collect(Collectors.joining("\n"));
                      System.out.println("Response Body (first 200 chars): " + responseBody.substring(0, Math.min(responseBody.length(), 200)));
                  }
              } catch (SSLHandshakeException e) {
                  System.out.println("Connection failed due to exception : " + e);
              } catch (Exception e) {
                  System.out.println("Connection failed due to exception : " + e);
              } finally {
                  if (connection != null) {
                      connection.disconnect();
                  }
              }
          }

          public static class TrustAllX509ExtendedTrustManager extends X509ExtendedTrustManager {

              private final X509Certificate[] EMPTY = new X509Certificate[0];

              @Override
              public void checkClientTrusted(X509Certificate[] chain, String authType) { }

              @Override
              public void checkServerTrusted(X509Certificate[] chain, String authType) { }

              @Override
              public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) { }

              @Override
              public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) { }

              @Override
              public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { }

              @Override
              public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) { }

              @Override
              public X509Certificate[] getAcceptedIssuers() {
                  return EMPTY;
              }

              public SSLContext createTrustAllSSLContext() throws Exception {
                  SSLContext sc = SSLContext.getInstance("TLS");
                  sc.init(null, new TrustManager[]{ new TrustAllX509ExtendedTrustManager() }, new java.security.SecureRandom());
                  return sc;
              }
          }
      }
      ---------- END SOURCE ----------

            djelinski Daniel Jelinski
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: