From 6a8d202943d3c4e0c005bc2a0a8bbbf90ca5b5ec Mon Sep 17 00:00:00 2001 From: Ben Kibbey Date: Thu, 25 Aug 2016 23:12:14 -0400 Subject: [PATCH] TLS: Support rehandshake upon SIGHUP. Adds status message REHANDSHAKE which is sent immediately before calling gnutls_rehandshake (). The handshake is done between commands and status messages are queued until complete. --- doc/pwmd.html | 3 ++ doc/pwmd.texi | 6 ++++ src/pwmd.c | 86 +++++++++++++++++++++++++++++++++++++++++---- src/status.c | 5 +++ src/status.h | 5 ++- src/tls.c | 110 ++++++++++++++++++++++++++++++++++++++-------------------- src/tls.h | 3 ++ 7 files changed, 172 insertions(+), 46 deletions(-) diff --git a/doc/pwmd.html b/doc/pwmd.html index 3bd4e436..3f41e7a6 100644 --- a/doc/pwmd.html +++ b/doc/pwmd.html @@ -1673,6 +1673,9 @@ time the next expiry will be. PASSPHRASE_INFO <flags> ...Forwarded from GpgME. Contains information that is useful in a pinentry. Only sent when pinentry is disabled (see OPTION). +REHANDSHAKE +Sent to each TLS client just before performing a cipher renegotiation +after a SIGHUP signal was received.
diff --git a/doc/pwmd.texi b/doc/pwmd.texi index d904b0b1..c1c1ce2c 100644 --- a/doc/pwmd.texi +++ b/doc/pwmd.texi @@ -701,6 +701,12 @@ time the next expiry will be. @tab ... @tab Forwarded from @code{GpgME}. Contains information that is useful in a @command{pinentry}. Only sent when pinentry is disabled (@pxref{OPTION}). + +@item REHANDSHAKE +@cindex REHANDSHAKE +@tab +@tab Sent to each TLS client just before performing a cipher renegotiation +after a SIGHUP signal was received. @end multitable @c Node, Next, Previous, Up diff --git a/src/pwmd.c b/src/pwmd.c index 183e3ecd..e27c0b32 100644 --- a/src/pwmd.c +++ b/src/pwmd.c @@ -203,6 +203,10 @@ reload_rcfile_thread (void *arg) struct slist_s *keep = NULL; struct slist_s *config; int b = disable_list_and_dump; +#ifdef WITH_GNUTLS + char *prio = config_get_string ("global", "tls_cipher_suite"); + char *prio2 = NULL; +#endif pthread_cleanup_push (cleanup_mutex_cb, &rcfile_mutex); pthread_cond_wait (&rcfile_cond, &rcfile_mutex); @@ -230,6 +234,13 @@ reload_rcfile_thread (void *arg) /* Restart listening sockets since they may have changed. */ start_stop_tls (1); start_stop_tls (0); + + prio2 = config_get_string ("global", "tls_cipher_suite"); + if ((prio2 && (!prio || strcmp (prio, prio2))) || (prio && !prio2)) + tls_rehandshake (); + + xfree (prio2); + xfree (prio); #endif crypto_set_keepalive (); pthread_cleanup_pop (0); @@ -1129,18 +1140,74 @@ client_thread (void *data) int n; int eof; - n = poll (fds, 2, -1); + n = poll (fds, 2, 100); if (n == -1) { log_write ("%s", strerror (errno)); break; } +#ifdef WITH_GNUTLS + if (thd->remote && thd->tls && thd->tls->rehandshake) + { + char *prio; + int ret; + const char *e; + + if (thd->tls->rehandshake == 1) + { + prio = config_get_string ("global", "tls_cipher_suite"); + if (!prio) + { + thd->tls->rehandshake = 0; + continue; + } + + ret = gnutls_priority_set_direct (thd->tls->ses, prio, &e); + if (ret == GNUTLS_E_SUCCESS) + { + rc = send_status (cl->ctx, STATUS_REHANDSHAKE, NULL); + if (!rc) + { + rc = assuan_send_data (cl->ctx, NULL, 0); + if (!rc) + { + ret = gnutls_rehandshake (thd->tls->ses); + if (ret) + { + log_write ("%s", gnutls_strerror (ret)); + thd->tls->rehandshake = 0; + } + else + thd->tls->rehandshake = 2; + } + } + + if (rc) + log_write ("%s", pwmd_strerror (rc)); + } + else + log_write ("%s: %s", gnutls_strerror (ret), e); + + xfree (prio); + continue; + } + } +#endif + + if (!n) + continue; + if (fds[1].revents & POLLIN) { - rc = send_msg_queue (thd); - if (rc && gpg_err_code (rc) != GPG_ERR_EPIPE) - break; +#ifdef WITH_GNUTLS + if (!thd->remote || (thd->tls && !thd->tls->rehandshake)) +#endif + { + rc = send_msg_queue (thd); + if (rc && gpg_err_code (rc) != GPG_ERR_EPIPE) + break; + } } #ifdef HAVE_PTHREAD_CANCEL @@ -1175,9 +1242,14 @@ client_thread (void *data) * client has already disconnected and will be converted to * GPG_ERR_EOF during assuan_process_next(). */ - rc = send_msg_queue (thd); - if (rc && gpg_err_code (rc) != GPG_ERR_EPIPE) - break; +#ifdef WITH_GNUTLS + if (!thd->remote || (thd->tls && !thd->tls->rehandshake)) +#endif + { + rc = send_msg_queue (thd); + if (rc && gpg_err_code (rc) != GPG_ERR_EPIPE) + break; + } } } diff --git a/src/status.c b/src/status.c index 2df3af06..c2748f84 100644 --- a/src/status.c +++ b/src/status.c @@ -69,6 +69,11 @@ send_status (assuan_context_t ctx, status_msg_t which, const char *fmt, ...) switch (which) { +#ifdef WITH_GNUTLS + case STATUS_REHANDSHAKE: + status = "REHANDSHAKE"; + break; +#endif case STATUS_EXPIRE: status = "EXPIRE"; break; diff --git a/src/status.h b/src/status.h index f24b5f67..613befcd 100644 --- a/src/status.h +++ b/src/status.h @@ -37,7 +37,10 @@ typedef enum STATUS_GPGME, STATUS_KEEPALIVE, STATUS_STATE, - STATUS_EXPIRE + STATUS_EXPIRE, +#ifdef WITH_GNUTLS + STATUS_REHANDSHAKE, +#endif } status_msg_t; struct status_msg_s diff --git a/src/tls.c b/src/tls.c index 5ca89c90..9158befb 100644 --- a/src/tls.c +++ b/src/tls.c @@ -42,12 +42,14 @@ #define WAIT_INTERVAL 50000 #define TEST_TIMEOUT(timeout, ret, start, rc) \ do { \ + rc = 0; \ if (ret == GNUTLS_E_AGAIN) \ { \ time_t now = time (NULL); \ if (timeout && now - start >= timeout) \ { \ rc = gpg_error (GPG_ERR_ETIMEDOUT); \ + ret = GNUTLS_E_TIMEDOUT; \ break; \ } \ struct timeval tv = { 0, WAIT_INTERVAL }; \ @@ -210,65 +212,82 @@ ssize_t tls_read_hook (assuan_context_t ctx, assuan_fd_t fd, void *data, size_t len) { struct client_s *client = assuan_get_pointer (ctx); + struct tls_s *tls = client->thd->tls; time_t start = time (NULL); ssize_t ret; - rehandshake: do { gpg_error_t rc = 0; + struct timeval tv = { 0, 0 }; + fd_set fds; + int n; + + FD_ZERO (&fds); + FD_SET (fd, &fds); + n = select (client->thd->fd+1, &fds, NULL, NULL, &tv); + if (n == 0 && tls->nl) + { + char c[] = "#\n"; + + memcpy (data, c, sizeof(c)); + ret = strlen (c); + break; + } - ret = gnutls_record_recv (client->thd->tls->ses, data, len); + ret = gnutls_record_recv (tls->ses, data, len); if (ret == GNUTLS_E_REHANDSHAKE) { - ret = gnutls_rehandshake (client->thd->tls->ses); - if (ret == GNUTLS_E_GOT_APPLICATION_DATA) - goto rehandshake; - else if (ret != GNUTLS_E_SUCCESS) - { - log_write ("%s", gnutls_strerror (ret)); - ret = 0; - break; - } + tls->rehandshake = 0; do { - ret = gnutls_handshake (client->thd->tls->ses); - rc = 0; + ret = gnutls_handshake (tls->ses); TEST_TIMEOUT (client->thd->timeout, ret, start, rc); if (rc) - { - log_write ("%s", gnutls_strerror (ret)); - ret = 0; - close (client->thd->fd); - client->thd->fd = -1; - break; - } + break; } - while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); - - if (rc) - break; + while (ret < 0 && gnutls_error_is_fatal (ret) == 0); - continue; + if (!ret) + { + gnutls_kx_algorithm_t kx; + char c[] = "# \n"; + + if (tls->nl) + { + memcpy (data, c, sizeof (c)); + ret = strlen (c); + } + else + ret = GNUTLS_E_AGAIN; + + kx = gnutls_kx_get (tls->ses); + log_write ("PROTO=%s CIPHER=%s MAC=%s KX=%s(%d)", + gnutls_protocol_get_name (gnutls_protocol_get_version + (tls->ses)), + gnutls_cipher_get_name (gnutls_cipher_get (tls->ses)), + gnutls_mac_get_name (gnutls_mac_get (tls->ses)), + gnutls_kx_get_name (kx), gnutls_dh_get_prime_bits (tls->ses)); + if (!tls->nl) + continue; + } + break; } TEST_TIMEOUT (client->thd->timeout, ret, start, rc); if (rc) - { - errno = ETIMEDOUT; - close (client->thd->fd); - client->thd->fd = -1; - return -1; - } + break; } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); if (ret < 0) { - log_write ("%s", gnutls_strerror (ret)); + log_write ("TLS: %s", gnutls_strerror (ret)); ret = 0; } + else if (ret > 0) + tls->nl = ((char *)data)[ret-1] == '\n'; return ret; } @@ -288,15 +307,13 @@ tls_write_hook (assuan_context_t ctx, assuan_fd_t fd, const void *data, ret = gnutls_record_send (client->thd->tls->ses, data, len); TEST_TIMEOUT (client->thd->timeout, ret, start, rc); if (rc) - { - errno = ETIMEDOUT; - close (client->thd->fd); - client->thd->fd = -1; - return -1; - } + break; } while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN); + if (ret < 0) + log_write ("%s", gnutls_strerror (ret)); + return ret; } @@ -482,3 +499,20 @@ tls_validate_access (struct client_s *client, const char *filename) return rc; } + +void +tls_rehandshake () +{ + unsigned t, i; + + MUTEX_LOCK (&cn_mutex); + t = slist_length (cn_thread_list); + for (i = 0; i < t; i++) + { + struct client_thread_s *thd = slist_nth_data (cn_thread_list, i); + + if (thd->remote && thd->tls) + thd->tls->rehandshake = 1; + } + MUTEX_UNLOCK (&cn_mutex); +} diff --git a/src/tls.h b/src/tls.h index 9e048dc9..691101c7 100644 --- a/src/tls.h +++ b/src/tls.h @@ -28,6 +28,8 @@ struct tls_s { gnutls_session_t ses; char *fp; + int nl; + int rehandshake; }; struct client_s; @@ -41,5 +43,6 @@ ssize_t tls_write_hook (assuan_context_t ctx, assuan_fd_t fd, void tls_deinit_params (); gpg_error_t tls_init_params (); gpg_error_t tls_validate_access (struct client_s *client, const char *filename); +void tls_rehandshake (); #endif -- 2.11.4.GIT