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.
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) {}
}
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.
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