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