diff --git a/include/common/defaults.h b/include/common/defaults.h index 0e37dac43..3b3184972 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 6dcaacc14..2f5d566ec 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 a0b2d79ce..d769acd02 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 ba07794c4..f2dc83964 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 8739e8bac..bcf70c9fb 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -57,6 +57,7 @@ #include #include #include +#include #include @@ -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 }, }};