MEDIUM: Add support for configurable TLS ticket keys

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>
This commit is contained in:
Nenad Merdanovic 2015-02-27 19:56:49 +01:00 committed by Willy Tarreau
parent eaabd52e29
commit 05552d4b98
5 changed files with 155 additions and 22 deletions

View File

@ -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 */

View 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 */

View 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 */

View 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 */
}

View File

@ -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 },
}};