Subject: bug#47321: 27.1; gnutls.c does not handle TLS rehandshake from server

From: Robert Pluim <[email-masked]>
Date: Thu, 07 Jul 2022 16:44:35 +0200

>>>>> On Thu, 07 Jul 2022 10:48:15 +0200, Jan Ypma <[email-masked]> said:

    Jan> Unfortunately, I don't have access to the related Azure infrastructure
    Jan> directly. And, with this being a closed-source 3rd party cloud
    Jan> service, they could have upgraded/changed their server behavior and
    Jan> nobody would know :).

    Jan> I suggest we leave the discussion open, perhaps others on Azure that
    Jan> /are/ able to use GnuTLS could chime in.

You can actually test this with ʼopenssl s_serverʼ

The following patch works for me, but only with TLS1.2. For some
reason GnuTLS and OpenSSL donʼt want to enable secure renegotiation
when talking TLS1.3 to each other. Also, youʼre supposed to loop checking
for GNUTLS_E_AGAIN when calling gnutls_record_recv, but if I do that
Emacs ends up spinning at 100% CPU sometimes.

If you want to force TLS1.2, youʼll have to set
gnutls-algorithm-priority to something like
"NORMAL:%DUMBFW:!VERS-ALL:+VERS-TLS1.2"

TL;DR Probably not ready for primetime

You can run the server like so:

    cd emacs/test/lisp/net/network-stream-resources
    openssl s_server -port 6666 -cert cert.pem -key key.pem -tls1_2

For the client, something like this:

(defun rtest ()
  (require 'gnutls)
  (require 'network-stream)
  (let ((proc (open-gnutls-stream "rtest" "rtest" "localhost" 6666)))
    (process-send-string proc "Host:\n")
    (switch-to-buffer "rtest")))

then in the terminal where the openssl server is running, you can use
the 'r' key to request renegotiation (or just type random stuff to
have it be echoed back to Emacs).

diff --git a/src/gnutls.c b/src/gnutls.c
index a0de0238c4..846721ba86 100644
--- a/src/gnutls.c
+++ b/src/gnutls.c
@@ -74,6 +74,14 @@
 #  define HAVE_GNUTLS_AEAD
 # endif
 
+# if GNUTLS_VERSION_NUMBER >= 0x030605
+#  define HAVE_GNUTLS_AUTO_REAUTH
+# endif
+
+# if GNUTLS_VERSION_NUMBER >= 0x030610
+#  define HAVE_GNUTLS_POST_HANDSHAKE_AUTH
+# endif
+
 # ifdef WINDOWSNT
 #  include <windows.h>
 #  include "w32common.h"
@@ -771,11 +779,16 @@ emacs_gnutls_read (struct Lisp_Process *proc, char *buf, ptrdiff_t nbyte)
       return -1;
     }
 
+  /* We have to handle GNUTLS_E_AGAIN here for the case where Secure
+     Renegotiation happens.  The renegotiation itself is handled
+     inside GnuTLS, but we need to retry the gnutls_record_recv for
+     things to actually work.  */
   ssize_t rtnval;
   do
     rtnval = gnutls_record_recv (state, buf, nbyte);
   while (rtnval == GNUTLS_E_INTERRUPTED);
-
+  if (rtnval == GNUTLS_E_AGAIN)
+    rtnval = gnutls_record_recv (state, buf, nbyte);
   if (rtnval >= 0)
     return rtnval;
   else if (rtnval == GNUTLS_E_UNEXPECTED_PACKET_LENGTH)
@@ -2066,6 +2079,12 @@ DEFUN ("gnutls-boot", Fgnutls_boot, Sgnutls_boot, 3, 3, 0,
   if (XPROCESS (proc)->is_non_blocking_client)
     gnutls_flags |= GNUTLS_NONBLOCK;
 # endif
+# ifdef HAVE_GNUTLS_AUTO_REAUTH
+  gnutls_flags |= GNUTLS_AUTO_REAUTH;
+# endif
+# ifdef HAVE_GNUTLS_POST_HANDSHAKE_AUTH
+  gnutls_flags |= GNUTLS_POST_HANDSHAKE_AUTH;
+#endif
   ret = gnutls_init (&state, gnutls_flags);
   XPROCESS (proc)->gnutls_state = state;
   if (ret < GNUTLS_E_SUCCESS)

Robert
--