web-dev-qa-db-fra.com

Erreur JDBC SQL Server sous Java 8: le pilote n'a pas pu établir une connexion sécurisée à SQL Server à l'aide du cryptage SSL (Secure Sockets Layer)

Le message d'erreur suivant s'affiche lorsque je me connecte à une base de données SQL Server à l'aide de la version du pilote Microsoft JDBC:

com.Microsoft.sqlserver.jdbc.SQLServerException: le pilote n'a pas pu établir une connexion sécurisée à SQL Server à l'aide du cryptage SSL (Secure Sockets Layer). Erreur: "SQL Server a renvoyé une réponse incomplète. La connexion a été fermée. ClientConnectionId: 98d0b6f4-f3ca-4683-939e-7c0a0fca5931".

Nous avons récemment mis à niveau nos applications de Java 6 et Java 7 vers Java 8. Tous les systèmes exécutant Java exécutent SUSE Linux Enterprise Server 11 (x86_64), VERSION = 11, PATCHLEVEL = 3.

Voici les faits que j'ai rassemblés avec un programme Java que j'ai écrit et qui ouvre et ferme séquentiellement 1 000 connexions de base de données.

  • Les connexions sont abandonnées avec cette erreur environ 5% à 10% du temps. L'erreur ne se produit pas sur chaque connexion.
  • Le problème se produit UNIQUEMENT avec Java 8. J'ai exécuté le même programme sur Java 7 et le problème n'est pas reproductible. Cela correspond à notre expérience en production avant la mise à niveau. Nous n’avons eu aucun problème d’exécution sous Java 7 en production.
  • Le problème ne se produit pas sur tous nos serveurs Linux exécutant Java 8, il ne se produit que sur certains d'entre eux. Cela me laisse perplexe, mais lorsque je lance le même programme de test sur la même version de la machine virtuelle Java (1.8.0_60, 64 bits) sur différentes instances de Linux, le problème ne se produit pas sur l’une des instances de Linux, mais le problème persiste. se produit sur d'autres. Les instances Linux exécutent la même version de SUSE et ont le même niveau de correctif.
  • Le problème se produit lors de la connexion aux serveurs/bases de données SQL Server 2008 et SQL Server 2014.
  • Le problème se produit que j'utilise la version 4.0 du pilote JDBC de SQL Server ou la version 4.1 plus récente du pilote.

Ce qui rend mes observations uniques par rapport à d’autres sur le Web, c’est que bien que le problème se produise UNIQUEMENT sous Java 8, je ne parviens pas à le faire se produire sur l’un des serveurs Linux apparemment identiques qui exécutent la même machine virtuelle Java 8. D'autres personnes ont également constaté ce problème dans les versions précédentes de Java, mais cela n'a pas été notre cas.

Toute contribution, suggestion ou observation que vous pourriez avoir est appréciée.

22
2Aguy

J'ai activé la journalisation SSL dans la JVM Java 8 sur une instance Linux, ce qui reproduit le problème. La journalisation SSL est activée avec -Djavax.net.debug=ssl:handshake:verbose. Cela a révélé des informations utiles. 

La solution workaround que nous utilisons en production et qui s'est avérée efficace pour nous consiste à définir ce paramètre sur la machine virtuelle Java:

 -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 (encore une fois, 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 sélectionné. Cependant, les connexions n'échouent TOUJOURS pas lorsque TLSv1.2 est utilisé. Ils échouent seulement 5 à 10% du temps.

Maintenant, voici un échange d'un serveur qui n'a pas le problème. Tout le reste est égal. Par exemple, connexion à la même base de données, à la même version de la machine virtuelle Java (Java 1.8.0_60), au 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 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 machine virtuelle Java et le serveur SQL, les connexions sont TOUJOURS réussies. Lorsque TLSv1.2 est négocié, nous rencontrons des échecs de connexion sporadiques. 

(Remarque: Java 7 (1.7.0_51) négocie toujours TLSv1, raison pour laquelle le problème ne s'est jamais posé pour nous avec une JVM Java 7.)

Les questions en suspens sont les suivantes: 

  1. POURQUOI est-ce que la même JVM Java 8 exécutée sur 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. 
  2. Et aussi pourquoi les connexions négociées TLSv1.2 réussissent-elles la plupart du temps, mais pas toutes, sur ce serveur?

Mise à jour le 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, stratégie de force JCE Unlimited et protocole de liaison SSL sur 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

21
2Aguy

Cela semble avoir été corrigé dans la version 4.2 du pilote JDBC MS SQL. J'ai créé un programme où je me suis connecté au serveur 1000 fois, en mettant en pause 100 ms entre chaque tentative. Avec la version 4.1, je pouvais reproduire le problème à chaque fois, bien que cela ne se soit produit que sporadiquement. Avec la version 4.2, je ne pouvais pas reproduire le problème.

5
Tore Refsnes

Avant de mettre à niveau le pilote JDBC SQL, 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 requiert un JRE de 6 et prend en charge l’API JDBC 4.0. 
  • Sqljdbc41.jar requiert un environnement JRE de 7 et prend en charge l’API JDBC 4.1. 
  • Sqljdbc42.jar requiert 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

4
bbhagat

J'ai également abordé ce problème sous Windows Server 2012 R2, avec les pilotes JDBC 4.0 et 4.1 avec Java 7. Cet article de Microsoft attribue la responsabilité aux suites de chiffrement DHE et recommande de les désactiver ou de réduire leur priorité si vous ne pouvez pas effectuer la mise à niveau Pilote JDBC 4.2

1
coolhand

Microsoft a récemment ouvert la source de son pilote. On peut voir l'activité du pilote mssql-jdbc sur GitHub. Je suppose que la dernière version d’aperçu est 6.1.5. 

Vous pouvez également trouver toutes les versions d’aperçu sur maven. Prise en charge de JDK7 et JDK 8.

1
Nikhil

Votre URL devrait être comme ci-dessous et ajouter sql sqljdbc42.jar. Cela résoudra votre problème

url = "jdbc:sqlserver://" +serverName + ":1433;DatabaseName=" + dbName + ";encrypt=true;trustServerCertificate=true;
0
Sunil Kumar