J'ai activé la journalisation SSL dans la JVM Java 8 sur une instance Linux qui reproduit le problème. La journalisation SSL est activée à l'aide de -Djavax.net.debug=ssl:handshake:verbose
. Cela a révélé des informations utiles.
La solution de contournement que nous utilisons en production et qui a fait ses preuves consiste à définir ce paramètre sur la JVM :
-Djdk.tls.client.protocols=TLSv1
Si vous voulez plus de détails, lisez la suite.
Sur un serveur où le problème peut être reproduit (là encore, seulement 5 à 10 % du temps), j'ai observé ce qui suit :
*** ClientHello, TLSv1.2
--- 8<-- SNIP -----
main, WRITE: TLSv1.2 Handshake, length = 195
main, READ: TLSv1.2 Handshake, length = 1130
*** ServerHello, TLSv1.2
--- 8<-- SNIP -----
%% Initialized: [Session-79, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256]
** TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
--- 8<-- SNIP -----
Algorithm: [SHA1withRSA]
--- 8<-- SNIP -----
*** Diffie-Hellman ServerKeyExchange
--- 8<-- SNIP -----
*** ServerHelloDone
*** ClientKeyExchange, DH
--- 8<-- SNIP -----
main, WRITE: TLSv1.2 Handshake, length = 133
--- 8<-- SNIP -----
main, WRITE: TLSv1.2 Change Cipher Spec, length = 1
*** Finished
verify_data: { 108, 116, 29, 115, 13, 26, 154, 198, 17, 125, 114, 166 }
***
main, WRITE: TLSv1.2 Handshake, length = 40
main, called close()
main, called closeInternal(true)
main, SEND TLSv1.2 ALERT: warning, description = close_notify
main, WRITE: TLSv1.2 Alert, length = 26
main, called closeSocket(true)
main, waiting for close_notify or alert: state 5
main, received EOFException: ignored
main, called closeInternal(false)
main, close invoked again; state = 5
main, handling exception: java.io.IOException: SQL Server returned an incomplete response. The connection has been closed. ClientConnectionId:12a722b3-d61d-4ce4-8319-af049a0a4415
Notez que TLSv1.2 est sélectionné par le serveur de base de données et utilisé dans cet échange. J'ai observé que, lorsque les connexions échouent à partir du service linux problématique, TLSv1.2 est TOUJOURS le niveau qui a été sélectionné. Cependant, les connexions n'échouent pas TOUJOURS lorsque TLSv1.2 est utilisé. Ils échouent seulement 5 à 10 % du temps.
Voici maintenant un échange d'un serveur qui n'a PAS le problème. Tout le reste est égal. C'est-à-dire, se connecter à la même base de données, même version de la JVM (Java 1.8.0_60), même pilote JDBC, etc. Notez que, ici, TLSv1 est sélectionné par le serveur de base de données au lieu de TLSv1.2 comme dans le cas du serveur défectueux.
*** ClientHello, TLSv1.2
--- 8<-- SNIP -----
main, WRITE: TLSv1.2 Handshake, length = 207
main, READ: TLSv1 Handshake, length = 604
*** ServerHello, TLSv1
--- 8<-- SNIP -----
Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA
--- 8<-- SNIP -----
%% Initialized: [Session-79, TLS_RSA_WITH_AES_128_CBC_SHA]
** TLS_RSA_WITH_AES_128_CBC_SHA
--- 8<-- SNIP -----
Algorithm: [SHA1withRSA]
--- 8<-- SNIP -----
***
*** ServerHelloDone
*** ClientKeyExchange, RSA PreMasterSecret, TLSv1
--- 8<-- SNIP -----
main, WRITE: TLSv1 Handshake, length = 134
main, WRITE: TLSv1 Change Cipher Spec, length = 1
*** Finished
verify_data: { 26, 155, 166, 89, 229, 193, 126, 39, 103, 206, 126, 21 }
***
main, WRITE: TLSv1 Handshake, length = 48
main, READ: TLSv1 Change Cipher Spec, length = 1
main, READ: TLSv1 Handshake, length = 48
*** Finished
Ainsi, lorsque TLSv1 est négocié entre la JVM Linux et le serveur SQL, les connexions réussissent TOUJOURS. Lorsque TLSv1.2 est négocié, nous obtenons des échecs de connexion sporadiques.
(Remarque :Java 7 (1.7.0_51) négocie toujours TLSv1, c'est pourquoi le problème ne s'est jamais produit pour nous avec une JVM Java 7.)
Les questions ouvertes que nous avons encore sont :
- Pourquoi est-ce que la même JVM Java 8 exécutée à partir de 2 serveurs Linux différents négociera toujours TLSv1, mais lors de la connexion à partir d'un autre serveur Linux, elle négocie toujours TLSv1.2.
- Et pourquoi les connexions négociées TLSv1.2 réussissent-elles la plupart du temps, mais pas toujours, sur ce serveur ?
Mise à jour du 10/06/2017 : Cette publication de Microsoft décrit le problème et la solution proposée.
Ressources :
http://www.infoworld.com/article/2849292/operating-systems/more-patch-problems-reported-with-the-ms14-066-kb-2992611-winshock-mess.html
http://www.infoworld.com/article/2849292/operating-systems/more-patch-problems-reported-with-the-ms14-066-kb-2992611-winshock-mess.html
http://blogs.msdn.com/b/jdbcteam/archive/2008/09/09/the-driver-could-not-establish-a-secure-connection-to-sql-server-by-using-secure- sockets-layer-ssl-encryption.aspx
Java 8 , JCE Unlimited Strength Policy et SSL Handshake over TLS
http://blogs.msdn.com/b/saponsqlserver/archive/2013/05/10/analyzing-jdbc-connection-issues.aspx
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#descPhase2
https://blogs.oracle.com/java-platform-group/entry/java_8_will_use_tls
Cela semble avoir été corrigé dans la version 4.2 du pilote MS SQL JDBC. J'ai créé un programme où je me suis connecté au serveur 1000 fois, en faisant une pause de 100 ms entre chaque tentative. Avec la version 4.1, j'ai pu reproduire le problème à chaque fois, même si cela ne s'est produit que sporadiquement. Avec la version 4.2, je n'ai pas pu reproduire le problème.
Avant de mettre à niveau le pilote SQL JDBC, vérifiez d'abord la compatibilité :
- Sqljdbc.jar nécessite un JRE de 5 et prend en charge l'API JDBC 3.0
- Sqljdbc4.jar nécessite un JRE de 6 et prend en charge l'API JDBC 4.0
- Sqljdbc41.jar nécessite un JRE de 7 et prend en charge l'API JDBC 4.1
- Sqljdbc42.jar nécessite un JRE de 8 et prend en charge l'API JDBC 4.2
Source :https://www.microsoft.com/en-us/download/details.aspx?id=11774