Lors de la communication avec https, SNI (voir [Wikipedia](voir Wikipedia) doit être activé par les paramètres côté serveur, et inversement, SNI doit être désactivé. Il y a. Si la connexion échoue à cause de cela, l'exception suivante se produira.
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Il est activé par défaut dans Java 7 et versions ultérieures et peut être désactivé en spécifiant "-Djsse.enableSNIExtension = false" dans l'option de démarrage JVM. (Article de référence) Cependant, cette méthode définit l'ensemble du système sur valide ou non valide, de sorte qu'elle ne fonctionne pas si vous souhaitez l'activer ou le désactiver selon la destination de la connexion. Notez que dans les versions inférieures à 1.8.0_141, SNI pouvait être désactivé secondairement en appelant connection.setHostnameVerifier, mais dans 1.8.0_141, ce comportement a été corrigé pour toujours suivre les options de démarrage.
Dans l'option de démarrage, avec SNI activé, en passant une liste vide à la méthode setServerNames lors de la création d'un Socket avec SocketFactory, il était possible de le désactiver individuellement pour chaque 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");
//Désactiver la vérification du nom d'hôte
if (disableHostnameCheck) {
connection.setHostnameVerifier(new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
}
//Invalidation du contrôle du certificat
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new X509TrustManager[] {new RelaxedX509TrustManager()}, new java.security.SecureRandom());
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
//SNI désactivé
if (disableSni) {
socketFactory = new SniDisabledSSLSocketFactory(socketFactory);
}
//Refléter les paramètres en connexion
connection.setSSLSocketFactory(socketFactory);
//Obtenir et afficher les résultats
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); //Seul ce modèle n'a pas été testé
}
}
/**
* jsse.SSLSocketFactory qui désactive SNI quel que soit le paramètre enableSNIExtension
*/
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>()); //Désactivez SNI en vidant le nom d'hôte
((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 pour tout autoriser
*/
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) {}
}
Il a été confirmé que cela fonctionne comme prévu en se connectant à chacun des serveurs qui n'accepte en réalité que SNI activé et le serveur qui accepte uniquement désactivé. Cependant, le modèle n'a pas pu être testé car il n'a pas été possible de préparer un serveur qui «peut se connecter avec la vérification du nom d'hôte activée» et «n'accepte que SNI désactivé». Même avec ce modèle, nous avons confirmé que les informations SNI n'ont pas été envoyées par capture de paquets.
Lorsque j'ai supprimé la logique d'invalidation de vérification de certificat et créé SniDisabledSSLSocketFactory basé sur SocketFactory obtenu par connection.getSSLSocketFactory (), cela n'a pas fonctionné pour une raison quelconque (= SNI était toujours activé). Il n'est pas confirmé si SNI peut être invalidé tout en vérifiant correctement le certificat après avoir utilisé le SSLContext généré par SSLContext.getInstance ("SSL").
Je pense que le seul effet de l'activation de SNI est d'envoyer le nom d'hôte dans le paquet SSL / TSL Client Hello, mais je ne sais pas pourquoi certains "serveurs échoueront lorsque SNI est activé". SNI est-il activé dans le middleware côté serveur, mais les paramètres sont incomplets?
Comme mentionné au début, SNI est activé par défaut dans Java 7 et versions ultérieures, mais dans les versions inférieures à 1.8.0_141, SNI est désactivé si vous appelez setHostnameVerifier (HostnameVerifier v) et enregistrez votre propre Verifier. Il a été. Il est activé depuis le 1.8.0_141. (Voir Note de publication) Cela m'a rendu accro lors de la mise à jour de Java, ce qui m'a inspiré à écrire cet article.
Recommended Posts