Until now, HAproxy needed to be restarted to change the TLS ticket
keys. With this patch, the TLS keys can be updated on a per-file
basis using the admin socket. Two new socket commands have been
introduced: "show tls-keys" and "set ssl tls-keys".

Signed-off-by: Nenad Merdanovic <nmer...@anine.io>
---
 include/proto/ssl_sock.h |   6 ++
 include/types/applet.h   |   5 ++
 include/types/ssl_sock.h |   2 +
 src/dumpstats.c          | 161 +++++++++++++++++++++++++++++++++++++++++++++++
 src/haproxy.c            |   3 +
 src/ssl_sock.c           |  95 ++++++++++++++++++++++++++++
 6 files changed, 272 insertions(+)

diff --git a/include/proto/ssl_sock.h b/include/proto/ssl_sock.h
index 6eb97eb..fa5eef5 100644
--- a/include/proto/ssl_sock.h
+++ b/include/proto/ssl_sock.h
@@ -58,6 +58,12 @@ unsigned int ssl_sock_get_verify_result(struct connection 
*conn);
 #if (defined SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB && !defined OPENSSL_NO_OCSP)
 int ssl_sock_update_ocsp_response(struct chunk *ocsp_response, char **err);
 #endif
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+int ssl_sock_update_tlskey(char *filename, struct chunk *tlskey, char **err);
+struct tls_keys_ref *tlskeys_ref_lookup(const char *filename);
+struct tls_keys_ref *tlskeys_ref_lookupid(int unique_id);
+void tlskeys_finalize_config(void);
+#endif
 
 #endif /* _PROTO_SSL_SOCK_H */
 
diff --git a/include/types/applet.h b/include/types/applet.h
index c2db0ec..5efeea5 100644
--- a/include/types/applet.h
+++ b/include/types/applet.h
@@ -99,6 +99,11 @@ struct appctx {
                        struct pattern_expr *expr;
                        struct chunk chunk;
                } map;
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+               struct {
+                       struct tls_keys_ref *ref;
+               } tlskeys;
+#endif
                struct {
                        int connected;
                        struct hlua_socket *socket;
diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h
index 4642124..e71ba79 100644
--- a/include/types/ssl_sock.h
+++ b/include/types/ssl_sock.h
@@ -32,6 +32,8 @@ struct sni_ctx {
        struct ebmb_node name;    /* node holding the servername value */
 };
 
+extern struct list tlskeys_reference;
+
 struct tls_sess_key {
        unsigned char name[16];
        unsigned char aes_key[16];
diff --git a/src/dumpstats.c b/src/dumpstats.c
index b8e822f..a572e6a 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -66,6 +66,7 @@
 
 #ifdef USE_OPENSSL
 #include <proto/ssl_sock.h>
+#include <types/ssl_sock.h>
 #endif
 
 /* stats socket states */
@@ -88,6 +89,7 @@ enum {
        STAT_CLI_O_PAT,      /* list all entries of a pattern */
        STAT_CLI_O_MLOOK,    /* lookup a map entry */
        STAT_CLI_O_POOLS,    /* dump memory pools */
+       STAT_CLI_O_TLSK,     /* list all TLS ticket keys references */
 };
 
 /* Actions available for the stats admin forms */
@@ -134,6 +136,9 @@ static int stats_dump_stat_to_buffer(struct 
stream_interface *si, struct uri_aut
 static int stats_pats_list(struct stream_interface *si);
 static int stats_pat_list(struct stream_interface *si);
 static int stats_map_lookup(struct stream_interface *si);
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+static int stats_tlskeys_list(struct stream_interface *si);
+#endif
 static void cli_release_handler(struct appctx *appctx);
 
 /*
@@ -974,6 +979,51 @@ static struct server *expect_server_admin(struct stream 
*s, struct stream_interf
        return sv;
 }
 
+/* This function is used with TLS ticket keys management. It permits to browse
+ * each reference. The variable <getnext> must contain the current node,
+ * <end> point to the root node.
+ */
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+static inline
+struct tls_keys_ref *tlskeys_list_get_next(struct tls_keys_ref *getnext, 
struct list *end)
+{
+       struct tls_keys_ref *ref = getnext;
+
+       while (1) {
+
+               /* Get next list entry. */
+               ref = LIST_NEXT(&ref->list, struct tls_keys_ref *, list);
+
+               /* If the entry is the last of the list, return NULL. */
+               if (&ref->list == end)
+                       return NULL;
+
+               return ref;
+       }
+}
+
+static inline
+struct tls_keys_ref *tlskeys_ref_lookup_ref(const char *reference)
+{
+       int id;
+       char *error;
+
+       /* If the reference starts by a '#', this is numeric id. */
+       if (reference[0] == '#') {
+               /* Try to convert the numeric id. If the conversion fails, the 
lookup fails. */
+               id = strtol(reference + 1, &error, 10);
+               if (*error != '\0')
+                       return NULL;
+
+               /* Perform the unique id lookup. */
+               return tlskeys_ref_lookupid(id);
+       }
+
+       /* Perform the string lookup. */
+       return tlskeys_ref_lookup(reference);
+}
+#endif
+
 /* This function is used with map and acl management. It permits to browse
  * each reference. The variable <getnext> must contain the current node,
  * <end> point to the root node and the <flags> permit to filter required
@@ -1145,6 +1195,17 @@ static int stats_sock_parse_request(struct 
stream_interface *si, char *line)
                else if (strcmp(args[1], "table") == 0) {
                        stats_sock_table_request(si, args, STAT_CLI_O_TAB);
                }
+               else if (strcmp(args[1], "tls-keys") == 0) {
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+                       appctx->st2 = STAT_ST_INIT;
+                       appctx->st0 = STAT_CLI_O_TLSK;
+#else
+                       appctx->ctx.cli.msg = "HAProxy was compiled against a 
version of OpenSSL "
+                                               "that doesn't support 
specifying TLS ticket keys\n";
+                       appctx->st0 = STAT_CLI_PRINT;
+#endif
+                       return 1;
+               }
                else if (strcmp(args[1], "map") == 0 ||
                         strcmp(args[1], "acl") == 0) {
 
@@ -1810,6 +1871,42 @@ static int stats_sock_parse_request(struct 
stream_interface *si, char *line)
                                return 1;
 #endif
                        }
+                       else if (strcmp(args[2], "tls-key") == 0) {
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+                               /* Expect two parameters: the filename and the 
new new TLS key in encoding */
+                               if (!*args[3] || !*args[4]) {
+                                       appctx->ctx.cli.msg = "'set ssl 
tls-key' expects a filename and the new TLS key in base64 encoding.\n";
+                                       appctx->st0 = STAT_CLI_PRINT;
+                                       return 1;
+                               }
+
+                               appctx->ctx.tlskeys.ref = 
tlskeys_ref_lookup_ref(args[3]);
+                               if(!appctx->ctx.tlskeys.ref) {
+                                       appctx->ctx.cli.msg = "'set ssl 
tls-key' unable to locate referenced filename\n";
+                                       appctx->st0 = STAT_CLI_PRINT;
+                                       return 1;
+                               }
+
+                               trash.len = base64dec(args[4], strlen(args[4]), 
trash.str, trash.size);
+                               if (trash.len != sizeof(struct tls_sess_key)) {
+                                       appctx->ctx.cli.msg = "'set ssl 
tls-key' received invalid base64 encoded TLS key.\n";
+                                       appctx->st0 = STAT_CLI_PRINT;
+                                       return 1;
+                               }
+
+                               memcpy(appctx->ctx.tlskeys.ref->tlskeys + 2 % 
TLS_TICKETS_NO, trash.str, trash.len);
+                               appctx->ctx.tlskeys.ref->tls_ticket_enc_index = 
appctx->ctx.tlskeys.ref->tls_ticket_enc_index + 1 % TLS_TICKETS_NO;
+
+                               appctx->ctx.cli.msg = "TLS ticket key updated!";
+                               appctx->st0 = STAT_CLI_PRINT;
+                               return 1;
+#else
+                               appctx->ctx.cli.msg = "HAProxy was compiled 
against a version of OpenSSL "
+                                                       "that doesn't support 
specifying TLS ticket keys\n";
+                               appctx->st0 = STAT_CLI_PRINT;
+                               return 1;
+#endif
+                       }
                        else {
                                appctx->ctx.cli.msg = "'set ssl' only supports 
'ocsp-response'.\n";
                                appctx->st0 = STAT_CLI_PRINT;
@@ -2373,6 +2470,12 @@ static void cli_io_handler(struct appctx *appctx)
                                if (stats_dump_pools_to_buffer(si))
                                        appctx->st0 = STAT_CLI_PROMPT;
                                break;
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+                       case STAT_CLI_O_TLSK:
+                               if (stats_tlskeys_list(si))
+                                       appctx->st0 = STAT_CLI_PROMPT;
+                               break;
+#endif
                        default: /* abnormal state */
                                cli_release_handler(appctx);
                                appctx->st0 = STAT_CLI_PROMPT;
@@ -5319,6 +5422,64 @@ static int stats_dump_full_sess_to_buffer(struct 
stream_interface *si, struct st
        return 1;
 }
 
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+static int stats_tlskeys_list(struct stream_interface *si) {
+       struct appctx *appctx = __objt_appctx(si->end);
+
+       switch (appctx->st2) {
+       case STAT_ST_INIT:
+               /* Display the column headers. If the message cannot be sent,
+                * quit the fucntion with returning 0. The function is called
+                * later and restart at the state "STAT_ST_INIT".
+                */
+               chunk_reset(&trash);
+               chunk_appendf(&trash, "# id (file)\n");
+               if (bi_putchk(si_ic(si), &trash) == -1) {
+                       si_applet_cant_put(si);
+                       return 0;
+               }
+
+               /* Now, we start the browsing of the references lists.
+                * Note that the following call to LIST_ELEM return bad 
pointer. The only
+                * avalaible field of this pointer is <list>. It is used with 
the function
+                * tlskeys_list_get_next() for retruning the first avalaible 
entry
+                */
+               appctx->ctx.tlskeys.ref = LIST_ELEM(&tlskeys_reference, struct 
tls_keys_ref *, list);
+               appctx->ctx.tlskeys.ref = 
tlskeys_list_get_next(appctx->ctx.tlskeys.ref, &tlskeys_reference);
+
+               appctx->st2 = STAT_ST_LIST;
+               /* fall through */
+
+       case STAT_ST_LIST:
+               while (appctx->ctx.tlskeys.ref) {
+                       chunk_reset(&trash);
+
+                       chunk_appendf(&trash, "%d (%s)\n", 
appctx->ctx.tlskeys.ref->unique_id,
+                                     appctx->ctx.tlskeys.ref->filename);
+
+                       if (bi_putchk(si_ic(si), &trash) == -1) {
+                               /* let's try again later from this stream. We 
add ourselves into
+                                * this stream's users so that it can remove us 
upon termination.
+                                */
+                               si_applet_cant_put(si);
+                               return 0;
+                       }
+
+                       /* get next list entry and check the end of the list */
+                       appctx->ctx.tlskeys.ref = 
tlskeys_list_get_next(appctx->ctx.tlskeys.ref, &tlskeys_reference);
+               }
+
+               appctx->st2 = STAT_ST_FIN;
+               /* fall through */
+
+       default:
+               appctx->st2 = STAT_ST_FIN;
+               return 1;
+       }
+       return 0;
+}
+#endif
+
 static int stats_pats_list(struct stream_interface *si)
 {
        struct appctx *appctx = __objt_appctx(si->end);
diff --git a/src/haproxy.c b/src/haproxy.c
index 233c434..353ff8a 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -734,6 +734,9 @@ void init(int argc, char **argv)
        }
 
        pattern_finalize_config();
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+       tlskeys_finalize_config();
+#endif
 
        err_code |= check_config_validity();
        if (err_code & (ERR_ABORT|ERR_FATAL)) {
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 2029298..1016394 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -115,6 +115,10 @@ enum {
 int sslconns = 0;
 int totalsslconns = 0;
 
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+struct list tlskeys_reference = LIST_HEAD_INIT(tlskeys_reference);
+#endif
+
 #ifndef OPENSSL_NO_DH
 static DH *local_dh_1024 = NULL;
 static DH *local_dh_2048 = NULL;
@@ -436,6 +440,88 @@ static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char 
key_name[16], unsigned
                return i ? 2 : 1;
        }
 }
+
+struct tls_keys_ref *tlskeys_ref_lookup(const char *filename)
+{
+        struct tls_keys_ref *ref;
+
+        list_for_each_entry(ref, &tlskeys_reference, list)
+                if (ref->filename && strcmp(filename, ref->filename) == 0)
+                        return ref;
+        return NULL;
+}
+
+struct tls_keys_ref *tlskeys_ref_lookupid(int unique_id)
+{
+        struct tls_keys_ref *ref;
+
+        list_for_each_entry(ref, &tlskeys_reference, list)
+                if (ref->unique_id == unique_id)
+                        return ref;
+        return NULL;
+}
+
+int ssl_sock_update_tlskey(char *filename, struct chunk *tlskey, char **err) {
+       struct tls_keys_ref *ref = tlskeys_ref_lookup(filename);
+
+       if(!ref) {
+               memprintf(err, "Unable to locate the referenced filename: %s", 
filename);
+               return 1;
+       }
+
+       memcpy((char *) (ref->tlskeys + 2 % TLS_TICKETS_NO), tlskey->str, 
tlskey->len);
+       ref->tls_ticket_enc_index = ref->tls_ticket_enc_index + 1 % 
TLS_TICKETS_NO;
+
+       return 0;
+}
+
+/* This function finalize the configuration parsing. Its set all the
+ * automatic ids
+ */
+void tlskeys_finalize_config(void)
+{
+       int i = 0;
+       struct tls_keys_ref *ref, *ref2, *ref3;
+       struct list tkr = LIST_HEAD_INIT(tkr);
+
+       list_for_each_entry(ref, &tlskeys_reference, list) {
+               if (ref->unique_id == -1) {
+                       /* Look for the first free id. */
+                       while (1) {
+                               list_for_each_entry(ref2, &tlskeys_reference, 
list) {
+                                       if (ref2->unique_id == i) {
+                                               i++;
+                                               break;
+                                       }
+                               }
+                               if (&ref2->list == &tlskeys_reference)
+                                       break;
+                       }
+
+                       /* Uses the unique id and increment it for the next 
entry. */
+                       ref->unique_id = i;
+                       i++;
+               }
+       }
+
+       /* This sort the reference list by id. */
+       list_for_each_entry_safe(ref, ref2, &tlskeys_reference, list) {
+               LIST_DEL(&ref->list);
+               list_for_each_entry(ref3, &tkr, list) {
+                       if (ref->unique_id < ref3->unique_id) {
+                               LIST_ADDQ(&ref3->list, &ref->list);
+                               break;
+                       }
+               }
+               if (&ref3->list == &tkr)
+                       LIST_ADDQ(&tkr, &ref->list);
+       }
+
+       /* swap root */
+       LIST_ADD(&tkr, &tlskeys_reference);
+       LIST_DEL(&tkr);
+}
+
 #endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */
 
 /*
@@ -4367,6 +4453,12 @@ static int bind_parse_tls_ticket_keys(char **args, int 
cur_arg, struct proxy *px
                return ERR_ALERT | ERR_FATAL;
        }
 
+       keys_ref = tlskeys_ref_lookup(args[cur_arg + 1]);
+       if(keys_ref) {
+               conf->keys_ref = keys_ref;
+               return 0;
+       }
+
        keys_ref = malloc(sizeof(struct tls_keys_ref));
        keys_ref->tlskeys = malloc(TLS_TICKETS_NO * sizeof(struct 
tls_sess_key));
 
@@ -4406,8 +4498,11 @@ static int bind_parse_tls_ticket_keys(char **args, int 
cur_arg, struct proxy *px
        /* Use penultimate key for encryption, handle when TLS_TICKETS_NO = 1 */
        i-=2;
        keys_ref->tls_ticket_enc_index = i < 0 ? 0 : i;
+       keys_ref->unique_id = -1;
        conf->keys_ref = keys_ref;
 
+       LIST_ADD(&tlskeys_reference, &keys_ref->list);
+
        return 0;
 #else
        if (err)
-- 
2.1.4


Reply via email to