2 votes

Renégociation SSL avec communication par socket full duplex

J'ai un client-serveur très simple avec un socket bloquant qui communique en duplex intégral. J'ai activé SSL/TLS pour l'application. Le modèle est celui d'un producteur-consommateur typique. Le client produit les données, les envoie au serveur et le serveur les traite. Le seul problème est que, de temps en temps, le serveur renvoie des données au client, qui les traite en conséquence. Vous trouverez ci-dessous un pseudo-code très simple de l'application :

  1 Client:
  2 -------
  3 while (true)
  4 {
  5         if (poll(pollin, timeout=0) || 0 < SSL_pending(ssl))
  6         {
  7                 SSL_read();
  8                 // Handle WANT_READ or WANT_WRITE appropriately.
  9                 // If no error, handle the received control message.
 10         }
 11         // produce data.
 12         while (!poll(pollout))
 13                 ; // Wait until the pipe is ready for a send().
 14         SSL_write();
 15         // Handle WANT_READ or WANT_WRITE appropriately.
 16         if (time to renegotiate)
 17                 SSL_renegotiate(ssl);
 18 }
 19
 20 Server:
 21 -------
 22 while (true)
 23 {
 24         if (poll(pollin, timeout=1s) || 0 < SSL_pending(ssl))
 25         {
 26                 SSL_read();
 27                 // Handle WANT_READ or WANT_WRITE appropriately.
 28                 // If no error, consume data.
 29         }
 30         if (control message needs to be sent)
 31         {
 32                 while (!poll(pollout))
 33                         ; // Wait until the pipe is ready for a send().
 34                 SSL_write();
 35                 // Handle WANT_READ or WANT_WRITE appropriately.
 36         }
 37 }

Le problème survient lorsque, à des fins de test, je force la renégociation SSL (lignes 16-17). La session démarre gentiment, mais après un certain temps, j'obtiens les erreurs suivantes :

Client:
-------
error:140940F5:SSL routines:SSL3_READ_BYTES:unexpected record

Server:
-------
error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message

Il s'avère qu'à peu près au même moment où le client initie une renégociation (ligne 14), le serveur finit par envoyer des données d'application au client (ligne 34). Le client, dans le cadre du processus de renégociation, reçoit ces données d'application et bombarde avec une erreur "unexpected record". De même, lorsque le serveur effectue la réception suivante (ligne 26), il finit par recevoir des données de renégociation alors qu'il attendait des données d'application.

Qu'est-ce que je fais de mal ? Comment dois-je gérer/tester les renégociations SSL avec un canal full-duplex. Notez qu'il n'y a pas de threads impliqués. Il s'agit d'un modèle simple à un seul thread avec des lectures/écritures se produisant à chaque extrémité du socket.

UPDATE : Pour vérifier qu'il n'y a pas de problème avec l'application que j'ai écrite, j'ai même pu reproduire cela assez confortablement avec les implémentations s_client et s_server d'OpenSSL. J'ai démarré un s_server et une fois que le s_client s'est connecté au serveur, j'ai programmé l'envoi d'un paquet de données d'application du serveur au client et un paquet de 'R' (demandes de renégociation) du client au serveur. Finalement, les deux échouent exactement de la même manière que celle décrite ci-dessus.

s_client:

RENEGOTIATING
4840:error:140940F5:SSL routines:SSL3_READ_BYTES:unexpected record:s3_pkt.c:1258:

s_server:

Read BLOCK
ERROR
4838:error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message:s3_pkt.c:1108:SSL alert number 10
4838:error:140940E5:SSL routines:SSL3_READ_BYTES:ssl handshake failure:s3_pkt.c:1185:

UPDATE 2 : Ok. Comme suggéré par David, j'ai retravaillé l'application de test pour utiliser des sockets non bloquants et toujours faire SSL_read et SSL_write en premier et faire la sélection en fonction de ce qu'ils retournent et j'obtiens toujours les mêmes erreurs pendant les renégociations (SSL_write finit par obtenir des données d'application de l'autre côté au milieu de la renégociation). La question est la suivante : à tout moment, si SSL_read renvoie WANT_READ, puis-je supposer que c'est parce qu'il n'y a rien dans le tuyau et continuer avec SSL_write puisque j'ai quelque chose à écrire ? Sinon, c'est probablement la raison pour laquelle je me retrouve avec des erreurs. Soit ça, soit je fais mal la renégociation. Remarque : si SSL_read renvoie WANT_WRITE, je fais toujours une sélection et j'appelle à nouveau SSL_read.

5voto

David Schwartz Points 70129

Vous essayez de "regarder à travers" la boîte noire de SSL. C'est une énorme erreur.

     if (poll(pollin, timeout=0) || 0 < SSL_pending(ssl))
     {
             SSL_read();

Vous faites la supposition que pour que SSL_read pour avancer, il doit lire les données de la prise. Il s'agit d'une hypothèse qui peut être fausse. Par exemple, si une renégociation est en cours, le moteur SSL peut avoir besoin d'envoyer des données ensuite, et non de lire des données.

     while (!poll(pollout))
             ; // Wait until the pipe is ready for a send().
     SSL_write();

Comment savez-vous que le moteur SSL veut écrire des données sur le tube ? Vous a-t-il donné un WANT_WRITE indication ? Si non, il doit peut-être lire les données de renégociation pour pouvoir envoyer.

Pour utiliser SSL en mode non bloquant, il suffit de tenter l'opération que vous souhaitez effectuer. Si vous souhaitez lire des données décryptées, appelez SSL_read . Si vous voulez envoyer des données cryptées, appelez SSL_write . Appelez seulement poll si le moteur SSL vous dit de avec un WANT_READ o WANT_WRITE indication.

Mise à jour : : Vous avez un hybride "moitié de chaque" entre les approches bloquantes et non bloquantes. Cela ne peut pas fonctionner. Le problème est simple : Jusqu'à ce que vous appeliez SSL_read vous ne savez pas s'il doit ou non lire le contenu de la prise. Si vous appelez poll d'abord, vous bloquerez même si SSL_read n'a pas besoin de lire depuis le socket. Si vous appelez SSL_read d'abord, il se bloquera s'il a besoin de lire depuis le socket. SSL_pending ne vous aidera pas. Si SSL_read doit écrire à la prise pour avancer, SSL_pending retournera zéro, mais l'appel à poll sera bloqué pour toujours.

Vous avez deux choix sains d'esprit :

  1. Blocage. Laissez l'ensemble des sockets bloqués. Appelez simplement SSL_read quand vous voulez lire et SSL_write quand tu veux écrire. Ils sera bloquer. Les sockets bloquants peuvent bloquer, c'est comme ça qu'ils fonctionnent.

  2. Non-bloquant. Configurer les sockets non-bloquants. Il suffit d'appeler SSL_read quand vous voulez lire et SSL_write quand tu veux écrire. Ils ne bloqueront pas. Si vous obtenez un WANT_READ l'indication, polluer dans le sens de la lecture. Si vous obtenez une WANT_WRITE indication, polluer dans le sens de l'écriture. Notez qu'il est parfaitement normal que SSL_read pour revenir WANT_WRITE et ensuite vous polluez dans le sens de l'écriture. De même, SSL_write peut retourner WANT_READ et ensuite vous polluez dans le sens de la lecture.

Votre code fonctionnerait (en grande partie) si l'implémentation de SSL_read se résumait à "lire des données puis les décrypter" et si SSL_write se résumait à "crypter des données et les envoyer". Le problème est que ces fonctions exécutent en fait une machine d'état sophistiquée qui lit et écrit dans le socket selon les besoins et qui, au final, a pour effet de vous donner des données déchiffrées ou de chiffrer vos données et de les envoyer.

4voto

Karthik Points 730

Après avoir passé du temps à déboguer mon application avec OpenSSL, j'ai trouvé la réponse à la question que j'avais initialement postée. Je la partage ici au cas où elle aiderait d'autres personnes comme moi.

La question que j'avais postée à l'origine concernait une erreur claire d'OpenSSL indiquant qu'il recevait des données d'application au milieu d'une poignée de main. Ce que je n'ai pas compris, c'est qu'OpenSSL s'embrouille lorsqu'il reçoit des données d'application au milieu d'une poignée de main. Il est possible de recevoir des données de poignée de main lors de la réception ou de l'envoi de données d'application, mais pas l'inverse (du moins avec OpenSSL). C'est ce que je n'ai pas compris. C'est aussi la raison pour laquelle la plupart des applications SSL fonctionnent bien, car la plupart d'entre elles sont semi-duplex par nature (HTTPS par exemple), ce qui garantit implicitement qu'aucune donnée d'application n'arrive de manière asynchrone au moment de la prise de contact.

Cela signifie que si vous concevez un protocole client-serveur full-duplex personnalisé (ce qui est mon cas) et que vous voulez y ajouter SSL, il incombe à l'application de lancer une renégociation lorsqu'aucune des deux parties n'envoie de données. Ceci est clairement documenté dans L'API NSS de Mozilla . Sans compter qu'il y a un billet ouvert dans le dépôt de bogues d'OpenSSL concernant ce problème. Dès que j'ai modifié mon application pour qu'elle initie une poignée de main lorsque le client et le serveur n'ont rien à se dire, je n'ai plus rencontré les erreurs ci-dessus.

De plus, je suis d'accord avec les commentaires de David sur le blocage des sockets et j'ai lu beaucoup de ses arguments dans la liste de diffusion OpenSSL également. Mais ce qui est triste, c'est que la plupart des applications existantes sont construites autour de l'interrogation et du blocage des sockets et qu'elles "fonctionnent bien (TM)". Le problème se pose lorsqu'il s'agit de renégociation SSL. Je continue de croire qu'au moins mon application peut gérer la renégociation SSL en présence de sockets bloquants puisqu'il s'agit d'un protocole très limité et personnalisé et que nous (en tant que développeur de l'application) pouvons décider d'effectuer la renégociation lorsque le protocole est au repos. Si cela ne fonctionne pas, je choisirai la voie des sockets non bloquants.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X