Aktivieren / Deaktivieren von SNI in Java für jede Kommunikation

Aufgabe

Bei der Kommunikation mit https sollte SNI (siehe [Wikipedia](siehe Wikipedia) über die serverseitigen Einstellungen aktiviert und umgekehrt sollte SNI deaktiviert werden. Es gibt. Wenn die Verbindung aufgrund dessen fehlschlägt, tritt die folgende Ausnahme auf.

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

Es ist standardmäßig in Java 7 und höher aktiviert und kann durch Angabe von "-Djsse.enableSNIExtension = false" in der JVM-Startoption deaktiviert werden. (Referenzartikel) Diese Methode setzt jedoch das gesamte System auf entweder gültig oder ungültig, sodass es nicht funktioniert, wenn Sie es je nach Verbindungsziel aktivieren oder deaktivieren möchten. Beachten Sie, dass in Versionen unter 1.8.0_141 SNI durch Aufrufen von connection.setHostnameVerifier sekundär deaktiviert werden kann. In 1.8.0_141 wurde dieses Verhalten jedoch behoben, um immer den Startoptionen zu folgen.

Lösung

Wenn SNI in der Startoption aktiviert war, konnte es für jede Kommunikation einzeln deaktiviert werden, indem beim Erstellen eines Sockets mit SocketFactory eine leere Liste an die Methode setServerNames übergeben wurde.

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");

		//Deaktivieren Sie die Überprüfung des Hostnamens
		if (disableHostnameCheck) {
			connection.setHostnameVerifier(new HostnameVerifier() {
				public boolean verify(String hostname, SSLSession session) {
					return true;
				}
			});
		}
		//Ungültigmachung der Zertifikatsprüfung
		SSLContext sslContext = SSLContext.getInstance("SSL");
		sslContext.init(null, new X509TrustManager[] {new RelaxedX509TrustManager()}, new java.security.SecureRandom());
		SSLSocketFactory socketFactory = sslContext.getSocketFactory();
		//SNI deaktiviert
		if (disableSni) {
			socketFactory = new SniDisabledSSLSocketFactory(socketFactory);
		}
		//Einstellungen in Verbindung widerspiegeln
		connection.setSSLSocketFactory(socketFactory);

		//Ergebnisse abrufen und ausgeben
		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); //Nur dieses Muster wurde nicht getestet
	}
}

/**
 * jsse.SSLSocketFactory, die SNI unabhängig von der Einstellung enableSNIExtension deaktiviert
 */
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>()); //Deaktivieren Sie SNI, indem Sie den Hostnamen leeren
		((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, um alles zuzulassen
 */
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) {}
}

Funktionsprüfung

Es wurde bestätigt, dass es wie beabsichtigt funktioniert, indem eine Verbindung zu jedem Server hergestellt wird, der nur SNI aktiviert und der Server nur deaktiviert akzeptiert. Das Muster konnte jedoch nicht getestet werden, da es nicht möglich war, einen Server vorzubereiten, der "eine Verbindung mit aktivierter Hostnamenprüfung herstellen kann" und "nur deaktiviertes SNI akzeptiert". Selbst mit diesem Muster haben wir bestätigt, dass SNI-Informationen nicht durch Paketerfassung gesendet wurden.

Als ich die Ungültigkeitslogik für die Zertifikatsprüfung löschte und SniDisabledSSLSocketFactory basierend auf der von connection.getSSLSocketFactory () erhaltenen SocketFactory erstellte, funktionierte dies aus irgendeinem Grund nicht (= SNI war immer aktiviert). Es ist nicht bestätigt, ob SNI ungültig gemacht werden kann, während das Zertifikat ordnungsgemäß überprüft wird, nachdem der von SSLContext.getInstance ("SSL") generierte SSLContext verwendet wurde.

Beiseite

Ich denke, der einzige Effekt der SNI-Aktivierung besteht darin, den Hostnamen im Hello-Paket des SSL / TSL-Clients zu senden, aber ich bin nicht sicher, warum einige "Server ausfallen, wenn SNI aktiviert ist". Ist SNI in der serverseitigen Middleware aktiviert, aber die Einstellungen sind unvollständig?

Wie eingangs erwähnt, ist SNI in Java 7 und höher standardmäßig aktiviert. In Versionen unter 1.8.0_141 ist SNI jedoch deaktiviert, wenn Sie setHostnameVerifier (HostnameVerifier v) aufrufen und Ihren eigenen Verifier registrieren. Es war gewesen. Es ist seit 1.8.0_141 aktiviert. (Siehe Versionshinweis) Dies hat mich beim Aktualisieren von Java begeistert, was mich dazu inspiriert hat, diesen Artikel zu schreiben.

Recommended Posts

Aktivieren / Deaktivieren von SNI in Java für jede Kommunikation
[Java] Machen Sie die Variablen der erweiterten for-Anweisung und für jede Anweisung unveränderlich
BCrypt + Verarbeitungszeitmessung für jede Streckenanzahl in Java
Anfänger spielen Janken-Spiele in Java
[Für Anfänger] Führen Sie Selenium auf Java aus
TCP-Kommunikation (Socket-Kommunikation) in Java (ASCII, Binär)
Erste Schritte für tiefes Lernen in Java
Wichtige Punkte für die Einführung von gRPC in Java
Ermöglichen Sie die Code-Vervollständigung in Eclipse für Mac
[Socket-Kommunikation (Java)] Eindrücke von der erstmaligen Implementierung der Socket-Kommunikation in der Praxis
ChatWork4j für die Verwendung der ChatWork-API in Java
Techniken zum Lesen von Java-Quellcode in Eclipse
Lösung für NetBeans 8.2 funktioniert nicht in Java 9-Umgebung
Stellen Sie mit vim die Popup-Anzeige für die Java-Sprache ein.
Vergleichen Sie die PDF-Ausgabe in Java für Snapshot-Tests
Punkte, die bei Java beachtet werden müssen, sind gleich
Für JAVA-Lernen (2018-03-16-01)
Partisierung in Java
Änderungen in Java 11
Janken in Java
2017 IDE für Java
Umfangsrate in Java
FizzBuzz in Java
Dinge, auf die Sie bei der zukünftigen Java-Entwicklung achten sollten
Ein Hinweis zum Initialisieren von Feldern im Java-Lernprogramm
[Für Anfänger] Mindestbeispiel für die Anzeige von RecyclerView in Java
Abrufen von Gebietsschemaobjekten für alle in Java verfügbaren Gebietsschemas
Dies und das zum Bearbeiten von ini in Java. : inieditor-java
[Java] Erläutert die ConcurrentModificationException, die in java.util.ArrayList für Neulinge auftritt
Ich habe versucht, die erweiterte for-Anweisung in Java zu verwenden
So schleifen Sie Java Map (für jede / erweiterte for-Anweisung)
Links für jede Version (japanische Version) der Java SE-API
[memo] RSA-Schlüsselpaar für SSH in Java generieren
Zusammenfassung der Methoden zum Lesen von Dateien für jedes Java-Dateiformat
Ein Janken-Spiel für zwei Spieler mit Java, das zwischen Threads konkurriert