Enable / disable SNI in Java for each communication

Task

When performing https communication, SNI (see Wikipedia) should be enabled by server-side settings, and conversely, SNI should be disabled. There is. If the connection fails due to this, the following exception will be thrown.

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

It is enabled by default in Java7 or later, and can be disabled by specifying "-Djsse.enableSNIExtension = false" in the JVM startup option. (Reference article) However, this method sets the entire system to either enable or disable, so it does not work if you want to enable or disable it depending on the connection destination. Note that in versions less than 1.8.0_141, SNI could be disabled secondarily by calling connection.setHostnameVerifier, but in 1.8.0_141 this behavior was fixed to always follow the boot options.

solution

In the startup option, with SNI enabled, by passing an empty list to the setServerNames method when creating a Socket with SocketFactory, it was possible to disable it individually for each communication.

Main.java


import java.io.*;
import java.net.*;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import javax.net.ssl.*;
import sun.security.ssl.SSLSocketImpl;

public class Main {
	public static void connect(String url, boolean disableSni, boolean disableHostnameCheck) throws IOException, GeneralSecurityException {
		HttpsURLConnection connection;
		connection = (HttpsURLConnection)(new URL(url)).openConnection();
		connection.setRequestMethod("GET");

		//Disable host name check
		if (disableHostnameCheck) {
			connection.setHostnameVerifier(new HostnameVerifier() {
				public boolean verify(String hostname, SSLSession session) {
					return true;
				}
			});
		}
		//Certificate check invalidation
		SSLContext sslContext = SSLContext.getInstance("SSL");
		sslContext.init(null, new X509TrustManager[] {new RelaxedX509TrustManager()}, new java.security.SecureRandom());
		SSLSocketFactory socketFactory = sslContext.getSocketFactory();
		//SNI disabled
		if (disableSni) {
			socketFactory = new SniDisabledSSLSocketFactory(socketFactory);
		}
		//Reflect settings in connection
		connection.setSSLSocketFactory(socketFactory);

		//Get and output results
		connection.connect();
		int responseCode = connection.getResponseCode();
		System.out.println("ResponseCode : " + responseCode);
		InputStream input = null;
		try {
			input = connection.getInputStream();
		} catch (IOException e) {
			input = connection.getErrorStream();
		}
		BufferedReader reader = new BufferedReader(new InputStreamReader(input));
		while (true) {
			String line = reader.readLine();
			if (line == null) {
				break;
			}
			System.out.println(line);
		}
	}

	public static void main(String[] args) throws Exception {
		connect("https://www.example.com/", false, true);
		connect("https://www.example.com/", true, true);
		connect("https://www.example.com/", false, false);
		connect("https://www.example.com/", true, false); //Only this pattern has not been tested
	}
}

/**
 * jsse.SSLSocketFactory that disables SNI regardless of the enableSNIExtension setting
 */
class SniDisabledSSLSocketFactory extends SSLSocketFactory {
	private SSLSocketFactory baseSocketFactory;
	public SniDisabledSSLSocketFactory(SSLSocketFactory baseSocketFactory) {
		this.baseSocketFactory = baseSocketFactory;
	}
	private Socket setSni(Socket socket) {
		SSLParameters params = ((SSLSocketImpl)socket).getSSLParameters();
		params.setServerNames(new ArrayList<SNIServerName>()); //Disable SNI by emptying the host name
		((SSLSocketImpl)socket).setSSLParameters(params);
		return socket;
	}
	@Override
	public String[] getDefaultCipherSuites() {
		return baseSocketFactory.getDefaultCipherSuites();
	}
	@Override
	public String[] getSupportedCipherSuites() {
		return baseSocketFactory.getSupportedCipherSuites();
	}
	@Override
	public Socket createSocket(Socket paramSocket, String paramString, int paramInt, boolean paramBoolean) throws IOException {
		return setSni(baseSocketFactory.createSocket(paramSocket, paramString, paramInt, paramBoolean));
	}
	@Override
	public Socket createSocket(String paramString, int paramInt) throws IOException, UnknownHostException {
		return setSni(baseSocketFactory.createSocket(paramString, paramInt));
	}
	@Override
	public Socket createSocket(String paramString, int paramInt1, InetAddress paramInetAddress, int paramInt2) throws IOException, UnknownHostException {
		return setSni(baseSocketFactory.createSocket(paramString, paramInt1, paramInetAddress, paramInt2));
	}
	@Override
	public Socket createSocket(InetAddress paramInetAddress, int paramInt) throws IOException {
		return setSni(baseSocketFactory.createSocket(paramInetAddress, paramInt));
	}
	@Override
	public Socket createSocket(InetAddress paramInetAddress1, int paramInt1, InetAddress paramInetAddress2, int paramInt2) throws IOException {
		return setSni(baseSocketFactory.createSocket(paramInetAddress1, paramInt1, paramInetAddress2, paramInt2));
	}
}

/**
 *TrustManager to allow everything
 */
class RelaxedX509TrustManager implements javax.net.ssl.X509TrustManager {
	public boolean isClientTrusted( java.security.cert.X509Certificate[] chain ){
		return true;
	}
	public boolean isServerTrusted( java.security.cert.X509Certificate[] chain ){
		return true;
	}
	public java.security.cert.X509Certificate[] getAcceptedIssuers() {
		return null;
	}
	public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
	public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {}
}

Operation check

It has been confirmed that it works as intended by connecting to each of the server that actually accepts only SNI enabled and the server that accepts only disabled. However, I couldn't prepare a server that "can connect with host name check enabled" and "accepts only SNI disabled", so I haven't been able to test that pattern. Even with that pattern, we have confirmed that packet capture does not send SNI information.

When I deleted the logic of certificate check invalidation and created SniDisabledSSLSocketFactory based on SocketFactory obtained by connection.getSSLSocketFactory (), it didn't work for some reason (= SNI was always enabled). It is unconfirmed whether SNI can be invalidated while properly checking the certificate using SSLContext generated by SSLContext.getInstance ("SSL").

Digression

I think the only effect of SNI enablement is to send the host name in the SSL / TSL Client Hello packet, but I'm not sure why some "servers will fail when SNI is enabled". Is SNI enabled in the server-side middleware, but the settings are incomplete?

As mentioned at the beginning, SNI is enabled by default in Java 7 and later, but in versions less than 1.8.0_141, SNI is disabled if you call setHostnameVerifier (HostnameVerifier v) and register your own Verifier. It had been. It has been enabled since 1.8.0_141. (See Release Note) This caused me to get hooked when updating Java, which inspired me to write this article.

Recommended Posts

Enable / disable SNI in Java for each communication
[Java] for Each and sorted in Lambda
[Java] Make variables in extended for statement and for Each statement immutable
Processing time measurement for each BCrypt + stretch count in Java
Rock-paper-scissors game for beginners in Java
[For beginners] Run Selenium in Java
TCP communication (socket communication) in Java (ASCII, Binary)
First steps for deep learning in Java
Key points for introducing gRPC in Java
Enable code completion in Eclipse for Mac
[Socket communication (Java)] Impressions of implementing Socket communication in practice for the first time
ChatWork4j for using the ChatWork API in Java
Technology for reading Java source code in Eclipse
Solution for NetBeans 8.2 not working in Java 9 environment
Set pop-up display for Java language in vim.
Compare PDF output in Java for snapshot testing
Things to watch out for in Java equals
For JAVA learning (2018-03-16-01)
Partization in Java
Changes in Java 11
Rock-paper-scissors in Java
2017 IDE for Java
Pi in Java
FizzBuzz in Java
Things to watch out for in future Java development
A note for Initializing Fields in the Java tutorial
[For beginners] Minimum sample to display RecyclerView in Java
Get Locale objects for all locales available in Java
This and that for editing ini in Java. : inieditor-java
[Java] Explains ConcurrentModificationException that occurs in java.util.ArrayList for newcomers
I tried using an extended for statement in Java
How to loop Java Map (for Each / extended for statement)
Links for each version (Japanese version) of Java SE API
[memo] Generate RSA key pair for SSH in Java
Summary of file reading method for each Java file format
A rock-paper-scissors game for two players who play against each other in threads with Java