Until now, the TLS ticket keys couldn't have been configured and
shared between multiple instances or multiple servers running HAproxy.
The result was that if a request got a TLS ticket from one instance/server
and it hits another one afterwards, it will have to go through the full
SSL handshake and negotation.

This patch enables adding a ticket file to the bind line, which will be
used for all SSL contexts created from that bind line. We can use the
same file on all instances or servers to mitigate this issue and have
consistent TLS tickets assigned. Clients will no longer have to negotiate
every time they change the handling process.

Signed-off-by: Nenad Merdanovic <nmer...@anine.io>
---
 include/common/defaults.h |   5 ++
 include/types/listener.h  |   2 +
 include/types/ssl_sock.h  |   6 ++
 src/cfgparse.c            |   1 +
 src/ssl_sock.c            | 163 +++++++++++++++++++++++++++++++++++++++-------
 5 files changed, 155 insertions(+), 22 deletions(-)

diff --git a/include/common/defaults.h b/include/common/defaults.h
index 0e37dac..3b31849 100644
--- a/include/common/defaults.h
+++ b/include/common/defaults.h
@@ -290,4 +290,9 @@
 #ifndef OCSP_MAX_RESPONSE_TIME_SKEW
 #define OCSP_MAX_RESPONSE_TIME_SKEW 300
 #endif
+
+/* Number of TLS tickets to check, used for rotation */
+#ifndef TLS_TICKETS_NO
+#define TLS_TICKETS_NO 3
+#endif
 #endif /* _COMMON_DEFAULTS_H */
diff --git a/include/types/listener.h b/include/types/listener.h
index 6dcaacc..2f5d566 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -132,6 +132,8 @@ struct bind_conf {
        int strict_sni;            /* refuse negotiation if sni doesn't match a 
certificate */
        struct eb_root sni_ctx;    /* sni_ctx tree of all known certs 
full-names sorted by name */
        struct eb_root sni_w_ctx;  /* sni_ctx tree of all known certs wildcards 
sorted by name */
+       struct tls_sess_key *tls_ticket_keys; /* TLS ticket keys */
+       int tls_ticket_enc_index;  /* array index of the key to use for 
encryption */
 #endif
        int is_ssl;                /* SSL is required for these listeners */
        unsigned long bind_proc;   /* bitmask of processes allowed to use these 
listeners */
diff --git a/include/types/ssl_sock.h b/include/types/ssl_sock.h
index a0b2d79..d769acd 100644
--- a/include/types/ssl_sock.h
+++ b/include/types/ssl_sock.h
@@ -32,4 +32,10 @@ struct sni_ctx {
        struct ebmb_node name;    /* node holding the servername value */
 };
 
+struct tls_sess_key {
+       unsigned char name[16];
+       unsigned char aes_key[16];
+       unsigned char hmac_key[16];
+} __attribute__((packed));
+
 #endif /* _TYPES_SSL_SOCK_H */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index ba07794..f2dc839 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -7740,6 +7740,7 @@ out_uri_auth_compat:
                        free(bind_conf->ciphers);
                        free(bind_conf->ecdhe);
                        free(bind_conf->crl_file);
+                       free(bind_conf->tls_ticket_keys);
 #endif /* USE_OPENSSL */
                }
 
diff --git a/src/ssl_sock.c b/src/ssl_sock.c
index 8739e8b..bcf70c9 100644
--- a/src/ssl_sock.c
+++ b/src/ssl_sock.c
@@ -57,6 +57,7 @@
 #include <common/ticks.h>
 #include <common/time.h>
 #include <common/cfgparse.h>
+#include <common/base64.h>
 
 #include <ebsttree.h>
 
@@ -95,6 +96,13 @@
 #define SSL_SOCK_ST_TO_CAEDEPTH(s) ((s >> (6+16)) & 15)
 #define SSL_SOCK_ST_TO_CRTERROR(s) ((s >> (4+6+16)) & 63)
 
+/* Supported hash function for TLS tickets */
+#ifdef OPENSSL_NO_SHA256
+#define HASH_FUNCT EVP_sha1
+#else
+#define HASH_FUNCT EVP_sha256
+#endif /* OPENSSL_NO_SHA256 */
+
 /* server and bind verify method, it uses a global value as default */
 enum {
        SSL_SOCK_VERIFY_DEFAULT  = 0,
@@ -388,6 +396,47 @@ end:
        return ret;
 }
 
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], 
unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
+{
+       struct tls_sess_key *keys;
+       struct connection *conn;
+       int head;
+       int i;
+
+       conn = (struct connection *)SSL_get_app_data(s);
+       keys = objt_listener(conn->target)->bind_conf->tls_ticket_keys;
+       head = objt_listener(conn->target)->bind_conf->tls_ticket_enc_index;
+
+       if (enc) {
+               memcpy(key_name, keys[head].name, 16);
+
+               if(!RAND_pseudo_bytes(iv, EVP_MAX_IV_LENGTH))
+                       return -1;
+
+               if(!EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, 
keys[head].aes_key, iv))
+                       return -1;
+
+               HMAC_Init_ex(hctx, keys[head].hmac_key, 16, HASH_FUNCT(), NULL);
+
+               return 1;
+       } else {
+               for (i = 0; i < TLS_TICKETS_NO; i++) {
+                       if (!memcmp(key_name, keys[(head + i) % 
TLS_TICKETS_NO].name, 16))
+                               goto found;
+               }
+               return 0;
+
+               found:
+               HMAC_Init_ex(hctx, keys[(head + i) % TLS_TICKETS_NO].hmac_key, 
16, HASH_FUNCT(), NULL);
+               if(!EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, 
keys[(head + i) % TLS_TICKETS_NO].aes_key, iv))
+                       return -1;
+               /* 2 for key renewal, 1 if current key is still valid */
+               return i ? 2 : 1;
+       }
+}
+#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */
+
 /*
  * Callback used to set OCSP status extension content in server hello.
  */
@@ -1585,6 +1634,16 @@ int ssl_sock_prepare_ctx(struct bind_conf *bind_conf, 
SSL_CTX *ctx, struct proxy
                ERR_clear_error();
        }
 
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+       if(bind_conf->tls_ticket_keys) {
+               if (!SSL_CTX_set_tlsext_ticket_key_cb(ctx, 
ssl_tlsext_ticket_key_cb)) {
+                       Alert("Proxy '%s': unable to set callback for TLS 
ticket validation for bind '%s' at [%s:%d].\n",
+                               curproxy->id, bind_conf->arg, bind_conf->file, 
bind_conf->line);
+                       cfgerr++;
+               }
+       }
+#endif
+
        if (global.tune.ssllifetime)
                SSL_CTX_set_timeout(ctx, global.tune.ssllifetime);
 
@@ -4220,6 +4279,65 @@ static int bind_parse_strict_sni(char **args, int 
cur_arg, struct proxy *px, str
        return 0;
 }
 
+/* parse the "tls-ticket-keys" bind keyword */
+static int bind_parse_tls_ticket_keys(char **args, int cur_arg, struct proxy 
*px, struct bind_conf *conf, char **err)
+{
+#if (defined SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB && TLS_TICKETS_NO > 0)
+       FILE *f;
+       int i = 0;
+       char thisline[LINESIZE];
+
+       if (!*args[cur_arg + 1]) {
+               if (err)
+                       memprintf(err, "'%s' : missing TLS ticket keys file 
path", args[cur_arg]);
+               return ERR_ALERT | ERR_FATAL;
+       }
+
+       conf->tls_ticket_keys = malloc(TLS_TICKETS_NO * sizeof(struct 
tls_sess_key));
+
+       if ((f = fopen(args[cur_arg + 1], "r")) == NULL) {
+               if (err)
+                       memprintf(err, "'%s' : unable to load ssl tickets keys 
file", args[cur_arg+1]);
+               return ERR_ALERT | ERR_FATAL;
+       }
+
+       while (fgets(thisline, sizeof(thisline), f) != NULL) {
+               int len = strlen(thisline);
+               /* Strip newline characters from the end */
+               if(thisline[len - 1] == '\n')
+                       thisline[--len] = 0;
+
+               if(thisline[len - 1] == '\r')
+                       thisline[--len] = 0;
+
+               if (base64dec(thisline, len, (char *) (conf->tls_ticket_keys + 
i % TLS_TICKETS_NO), sizeof(struct tls_sess_key)) != sizeof(struct 
tls_sess_key)) {
+                       if (err)
+                               memprintf(err, "'%s' : unable to decode base64 
key on line %d", args[cur_arg+1], i + 1);
+                       return ERR_ALERT | ERR_FATAL;
+               }
+               i++;
+       }
+
+       if (i < TLS_TICKETS_NO) {
+               if (err)
+                       memprintf(err, "'%s' : please supply at least %d keys 
in the tls-tickets-file", args[cur_arg+1], TLS_TICKETS_NO);
+               return ERR_ALERT | ERR_FATAL;
+       }
+
+       fclose(f);
+
+       /* Use penultimate key for encryption, handle when TLS_TICKETS_NO = 1 */
+       i-=2;
+       conf->tls_ticket_enc_index = i < 0 ? 0 : i;
+
+       return 0;
+#else
+       if (err)
+               memprintf(err, "'%s' : TLS ticket callback extension not 
supported", args[cur_arg]);
+       return ERR_ALERT | ERR_FATAL;
+#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */
+}
+
 /* parse the "verify" bind keyword */
 static int bind_parse_verify(char **args, int cur_arg, struct proxy *px, 
struct bind_conf *conf, char **err)
 {
@@ -4643,28 +4761,29 @@ static struct acl_kw_list acl_kws = {ILH, {
  * not enabled.
  */
 static struct bind_kw_list bind_kws = { "SSL", { }, {
-       { "alpn",                  bind_parse_alpn,           1 }, /* set ALPN 
supported protocols */
-       { "ca-file",               bind_parse_ca_file,        1 }, /* set 
CAfile to process verify on client cert */
-       { "ca-ignore-err",         bind_parse_ignore_err,     1 }, /* set error 
IDs to ignore on verify depth > 0 */
-       { "ciphers",               bind_parse_ciphers,        1 }, /* set SSL 
cipher suite */
-       { "crl-file",              bind_parse_crl_file,       1 }, /* set 
certificat revocation list file use on client cert verify */
-       { "crt",                   bind_parse_crt,            1 }, /* load SSL 
certificates from this location */
-       { "crt-ignore-err",        bind_parse_ignore_err,     1 }, /* set error 
IDs to ingore on verify depth == 0 */
-       { "crt-list",              bind_parse_crt_list,       1 }, /* load a 
list of crt from this location */
-       { "ecdhe",                 bind_parse_ecdhe,          1 }, /* defines 
named curve for elliptic curve Diffie-Hellman */
-       { "force-sslv3",           bind_parse_force_sslv3,    0 }, /* force 
SSLv3 */
-       { "force-tlsv10",          bind_parse_force_tlsv10,   0 }, /* force 
TLSv10 */
-       { "force-tlsv11",          bind_parse_force_tlsv11,   0 }, /* force 
TLSv11 */
-       { "force-tlsv12",          bind_parse_force_tlsv12,   0 }, /* force 
TLSv12 */
-       { "no-sslv3",              bind_parse_no_sslv3,       0 }, /* disable 
SSLv3 */
-       { "no-tlsv10",             bind_parse_no_tlsv10,      0 }, /* disable 
TLSv10 */
-       { "no-tlsv11",             bind_parse_no_tlsv11,      0 }, /* disable 
TLSv11 */
-       { "no-tlsv12",             bind_parse_no_tlsv12,      0 }, /* disable 
TLSv12 */
-       { "no-tls-tickets",        bind_parse_no_tls_tickets, 0 }, /* disable 
session resumption tickets */
-       { "ssl",                   bind_parse_ssl,            0 }, /* enable 
SSL processing */
-       { "strict-sni",            bind_parse_strict_sni,     0 }, /* refuse 
negotiation if sni doesn't match a certificate */
-       { "verify",                bind_parse_verify,         1 }, /* set SSL 
verify method */
-       { "npn",                   bind_parse_npn,            1 }, /* set NPN 
supported protocols */
+       { "alpn",                  bind_parse_alpn,            1 }, /* set ALPN 
supported protocols */
+       { "ca-file",               bind_parse_ca_file,         1 }, /* set 
CAfile to process verify on client cert */
+       { "ca-ignore-err",         bind_parse_ignore_err,      1 }, /* set 
error IDs to ignore on verify depth > 0 */
+       { "ciphers",               bind_parse_ciphers,         1 }, /* set SSL 
cipher suite */
+       { "crl-file",              bind_parse_crl_file,        1 }, /* set 
certificat revocation list file use on client cert verify */
+       { "crt",                   bind_parse_crt,             1 }, /* load SSL 
certificates from this location */
+       { "crt-ignore-err",        bind_parse_ignore_err,      1 }, /* set 
error IDs to ingore on verify depth == 0 */
+       { "crt-list",              bind_parse_crt_list,        1 }, /* load a 
list of crt from this location */
+       { "ecdhe",                 bind_parse_ecdhe,           1 }, /* defines 
named curve for elliptic curve Diffie-Hellman */
+       { "force-sslv3",           bind_parse_force_sslv3,     0 }, /* force 
SSLv3 */
+       { "force-tlsv10",          bind_parse_force_tlsv10,    0 }, /* force 
TLSv10 */
+       { "force-tlsv11",          bind_parse_force_tlsv11,    0 }, /* force 
TLSv11 */
+       { "force-tlsv12",          bind_parse_force_tlsv12,    0 }, /* force 
TLSv12 */
+       { "no-sslv3",              bind_parse_no_sslv3,        0 }, /* disable 
SSLv3 */
+       { "no-tlsv10",             bind_parse_no_tlsv10,       0 }, /* disable 
TLSv10 */
+       { "no-tlsv11",             bind_parse_no_tlsv11,       0 }, /* disable 
TLSv11 */
+       { "no-tlsv12",             bind_parse_no_tlsv12,       0 }, /* disable 
TLSv12 */
+       { "no-tls-tickets",        bind_parse_no_tls_tickets,  0 }, /* disable 
session resumption tickets */
+       { "ssl",                   bind_parse_ssl,             0 }, /* enable 
SSL processing */
+       { "strict-sni",            bind_parse_strict_sni,      0 }, /* refuse 
negotiation if sni doesn't match a certificate */
+       { "tls-ticket-keys",       bind_parse_tls_ticket_keys, 1 }, /* set file 
to load TLS ticket keys from */
+       { "verify",                bind_parse_verify,          1 }, /* set SSL 
verify method */
+       { "npn",                   bind_parse_npn,             1 }, /* set NPN 
supported protocols */
        { NULL, NULL, 0 },
 }};
 
-- 
2.1.4


Reply via email to