]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: Add support for configurable TLS ticket keys
authorNenad Merdanovic <nmerdan@anine.io>
Fri, 27 Feb 2015 18:56:49 +0000 (19:56 +0100)
committerWilly Tarreau <w@1wt.eu>
Sat, 28 Feb 2015 22:10:22 +0000 (23:10 +0100)
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 <nmerdan@anine.io>
include/common/defaults.h
include/types/listener.h
include/types/ssl_sock.h
src/cfgparse.c
src/ssl_sock.c

index 0e37dac4302517c5e99529001f2053f1543fe8b6..3b31849724d0dc5f5fb33157a8771201a043bd46 100644 (file)
 #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 */
index 6dcaacc14e3239b47c39f95d20f1131e15273b1f..2f5d566ecaf0015517e9f731675ea48c4fc34eb9 100644 (file)
@@ -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 */
index a0b2d79ce7b49800e9ae00dc760256c745dc197c..d769acd025267965e04a39728558cd1f552d21f0 100644 (file)
@@ -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 */
index ba07794c45bb4c53bba775bd0653ff280fe920ae..f2dc83964c58d26cb7d3a35b31dfb1d5f1d722ab 100644 (file)
@@ -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 */
                }
 
index 8739e8bac58a5aaeec7053ba760a1e5f2a9b9af5..bcf70c9fb4dce06a8d1476a051d7e6bff8456065 100644 (file)
@@ -57,6 +57,7 @@
 #include <common/ticks.h>
 #include <common/time.h>
 #include <common/cfgparse.h>
+#include <common/base64.h>
 
 #include <ebsttree.h>
 
 #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 },
 }};