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 ----------
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 ----------