Activer / désactiver SNI en Java pour chaque communication

Tâche

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.

Solution

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

Contrôle de fonctionnement

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

De côté

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

Activer / désactiver SNI en Java pour chaque communication
[Java] Rendre les variables de l'instruction for étendue et de chaque instruction immuables
BCrypt + Mesure du temps de traitement pour chaque nombre d'étirements en Java
Les débutants jouent à des jeux Janken en Java
[Pour les débutants] Exécutez Selenium sur Java
Communication TCP (communication socket) en Java (ASCII, Binaire)
Premiers pas pour l'apprentissage profond en Java
Points clés pour l'introduction de gRPC en Java
Autoriser l'achèvement du code dans Eclipse pour Mac
[Communication Socket (Java)] Impressions de la mise en œuvre de la communication Socket dans la pratique pour la première fois
ChatWork4j pour l'utilisation de l'API ChatWork en Java
Techniques de lecture du code source Java dans Eclipse
La solution pour NetBeans 8.2 ne fonctionne pas dans l'environnement Java 9
Définissez un affichage contextuel pour le langage Java avec vim.
Comparez la sortie PDF en Java pour les tests d'instantanés
Points à connaître avec Java Equals
Pour l'apprentissage JAVA (2018-03-16-01)
Partition en Java
Changements dans Java 11
Janken à Java
IDE 2017 pour Java
Taux circonférentiel à Java
FizzBuzz en Java
Points à surveiller dans le développement futur de Java
Remarque sur l'initialisation des champs dans le didacticiel Java
[Pour les débutants] Exemple minimum pour afficher RecyclerView en Java
Obtention des objets Locale pour tous les paramètres régionaux disponibles en Java
Ceci et cela pour éditer ini en Java. : inieditor-java
[Java] explique ConcurrentModificationException qui se produit dans java.util.ArrayList pour les nouveaux arrivants
J'ai essayé d'utiliser l'instruction Extended for en Java
Comment faire une boucle Java Map (for Each / extended for statement)
Liens pour chaque version (version japonaise) de l'API Java SE
[mémo] Générer une paire de clés RSA pour SSH en Java
Résumé des méthodes de lecture de fichiers pour chaque format de fichier Java
Un jeu Janken à deux joueurs avec Java où les threads se jouent les uns contre les autres