[PATCH] SSL: added support for TLS Session Tickets (RFC5077).
Piotr Sikora
piotr at cloudflare.com
Sat Sep 28 09:55:36 UTC 2013
# HG changeset patch
# User Piotr Sikora <piotr at cloudflare.com>
# Date 1380361691 25200
# Sat Sep 28 02:48:11 2013 -0700
# Node ID 6d3710969a18e2d0d817e297c2e17f941a58cd40
# Parent a720f0b0e08345ebb01353250f4031bb6e141385
SSL: added support for TLS Session Tickets (RFC5077).
Signed-off-by: Piotr Sikora <piotr at cloudflare.com>
diff -r a720f0b0e083 -r 6d3710969a18 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c Fri Sep 27 19:39:33 2013 +0400
+++ b/src/event/ngx_event_openssl.c Sat Sep 28 02:48:11 2013 -0700
@@ -38,6 +38,12 @@ static void ngx_ssl_expire_sessions(ngx_
static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp,
ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+static int ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+ unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
+ HMAC_CTX *hctx, int enc);
+#endif
+
static void *ngx_openssl_create_conf(ngx_cycle_t *cycle);
static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
static void ngx_openssl_exit(ngx_cycle_t *cycle);
@@ -84,6 +90,9 @@ int ngx_ssl_server_conf_index;
int ngx_ssl_session_cache_index;
int ngx_ssl_certificate_index;
int ngx_ssl_stapling_index;
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+int ngx_ssl_session_ticket_keys_index;
+#endif
ngx_int_t
@@ -155,6 +164,18 @@ ngx_ssl_init(ngx_log_t *log)
return NGX_ERROR;
}
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+ ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL,
+ NULL, NULL);
+ if (ngx_ssl_session_ticket_keys_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+ "SSL_CTX_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+
+#endif
+
return NGX_OK;
}
@@ -2240,6 +2261,122 @@ ngx_ssl_session_rbtree_insert_value(ngx_
}
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+ngx_int_t
+ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl,
+ ngx_ssl_session_ticket_keys_t *keys, time_t timeout)
+{
+ if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_ticket_keys_index, keys)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+
+ SSL_CTX_set_timeout(ssl->ctx, (long) timeout);
+
+ if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx,
+ ngx_ssl_session_ticket_key_callback)
+ == 0)
+ {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "nginx was built with Session Tickets support, however, "
+ "now it is linked dynamically to an OpenSSL library "
+ "which has no tlsext support, therefore Session Tickets "
+ "are not available");
+ }
+
+ return NGX_OK;
+}
+
+
+static int
+ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+ unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
+ HMAC_CTX *hctx, int enc)
+{
+ int rc;
+ SSL_CTX *ssl_ctx;
+ ngx_uint_t i;
+ ngx_ssl_session_ticket_key_t *key;
+ ngx_ssl_session_ticket_keys_t *keys;
+#if (NGX_DEBUG)
+ ngx_connection_t *c;
+#endif
+
+ ssl_ctx = SSL_get_SSL_CTX(ssl_conn);
+
+ keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index);
+ if (keys == NULL) {
+ return -1;
+ }
+
+#if (NGX_DEBUG)
+ c = ngx_ssl_get_connection(ssl_conn);
+#endif
+
+ if (enc == 1) {
+ /* encrypt session ticket */
+
+ key = keys->default_key;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "ssl session ticket encrypt, key: \"%*s\" (%s session)",
+ key->name_len, key->name,
+ SSL_session_reused(ssl_conn) ? "reused" : "new");
+
+ RAND_pseudo_bytes(iv, 16);
+ EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key->aes_key, iv);
+ HMAC_Init_ex(hctx, key->hmac_key, 16, ngx_ssl_session_ticket_md(),
+ NULL);
+ memcpy(name, key->name, 16);
+
+ return 0;
+
+ } else {
+ /* decrypt session ticket */
+
+ if (ngx_strncmp(name, keys->default_key->name, 16)) {
+
+ key = keys->keys.elts;
+ for (i = 0; i < keys->keys.nelts; i++) {
+ if (ngx_strncmp(name, key[i].name, 16) == 0) {
+ break;
+ }
+ }
+
+ if (i == keys->keys.nelts) {
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "ssl session ticket decrypt, key: \"%*s\" "
+ "not found", 16, name);
+ return 0;
+ }
+
+ key = &key[i];
+ rc = 2; /* success, renew */
+
+ } else {
+ key = keys->default_key;
+ rc = 1; /* success */
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "ssl session ticket decrypt, key: \"%*s\"",
+ key->name_len, key->name);
+
+ HMAC_Init_ex(hctx, key->hmac_key, 16, ngx_ssl_session_ticket_md(),
+ NULL);
+ EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key->aes_key, iv);
+
+ return rc;
+ }
+}
+
+#endif
+
+
void
ngx_ssl_cleanup_ctx(void *data)
{
diff -r a720f0b0e083 -r 6d3710969a18 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Fri Sep 27 19:39:33 2013 +0400
+++ b/src/event/ngx_event_openssl.h Sat Sep 28 02:48:11 2013 -0700
@@ -83,6 +83,24 @@ typedef struct {
} ngx_ssl_session_cache_t;
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+typedef struct {
+ size_t name_len;
+ u_char name[16];
+
+ u_char aes_key[16];
+ u_char hmac_key[16];
+} ngx_ssl_session_ticket_key_t;
+
+
+typedef struct {
+ ngx_ssl_session_ticket_key_t *default_key;
+ ngx_array_t keys;
+} ngx_ssl_session_ticket_keys_t;
+
+#endif
+
#define NGX_SSL_SSLv2 0x0002
#define NGX_SSL_SSLv3 0x0004
@@ -136,6 +154,16 @@ ngx_int_t ngx_ssl_set_session(ngx_connec
|| n == X509_V_ERR_CERT_UNTRUSTED \
|| n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl,
+ ngx_ssl_session_ticket_keys_t *keys, time_t timeout);
+
+#ifdef OPENSSL_NO_SHA256
+#define ngx_ssl_session_ticket_md EVP_sha1
+#else
+#define ngx_ssl_session_ticket_md EVP_sha256
+#endif
+#endif
ngx_int_t ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);
@@ -175,6 +203,9 @@ extern int ngx_ssl_server_conf_index;
extern int ngx_ssl_session_cache_index;
extern int ngx_ssl_certificate_index;
extern int ngx_ssl_stapling_index;
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+extern int ngx_ssl_session_ticket_keys_index;
+#endif
#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff -r a720f0b0e083 -r 6d3710969a18 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c Fri Sep 27 19:39:33 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c Sat Sep 28 02:48:11 2013 -0700
@@ -38,6 +38,11 @@ static char *ngx_http_ssl_enable(ngx_con
static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+static char *ngx_http_ssl_session_ticket_key(ngx_conf_t *cf,
ngx_command_t *cmd,
+ void *conf);
+#endif
+
static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
@@ -153,6 +158,17 @@ static ngx_command_t ngx_http_ssl_comma
0,
NULL },
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+ { ngx_string("ssl_session_ticket_key"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE23,
+ ngx_http_ssl_session_ticket_key,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+#endif
+
{ ngx_string("ssl_session_timeout"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_sec_slot,
@@ -413,6 +429,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t
* sscf->shm_zone = NULL;
* sscf->stapling_file = { 0, NULL };
* sscf->stapling_responder = { 0, NULL };
+ * sscf->session_ticket_keys = NULL;
*/
sscf->enable = NGX_CONF_UNSET;
@@ -634,6 +651,30 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
}
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+ if (conf->session_ticket_keys == NULL) {
+ conf->session_ticket_keys = prev->session_ticket_keys;
+ }
+
+ if (conf->session_ticket_keys) {
+ if (conf->session_ticket_keys->default_key == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "default \"ssl_session_ticket_key\" is not defined");
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_session_ticket_keys(cf, &conf->ssl,
+ conf->session_ticket_keys,
+ conf->session_timeout)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+#endif
+
return NGX_CONF_OK;
}
@@ -769,6 +810,146 @@ invalid:
return NGX_CONF_ERROR;
}
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+static char *
+ngx_http_ssl_session_ticket_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ char *rc;
+ u_char buf[32];
+ ssize_t n;
+ ngx_str_t *value;
+ ngx_file_t file;
+ ngx_uint_t i;
+ ngx_file_info_t fi;
+ ngx_ssl_session_ticket_key_t *key, *k;
+
+ if (sscf->session_ticket_keys == NULL) {
+ sscf->session_ticket_keys = ngx_pcalloc(cf->pool,
+ sizeof(ngx_ssl_session_ticket_keys_t));
+ if (sscf->session_ticket_keys == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_array_init(&sscf->session_ticket_keys->keys, cf->pool, 4,
+ sizeof(ngx_ssl_session_ticket_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ key = ngx_array_push(&sscf->session_ticket_keys->keys);
+ if (key == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ if (value[1].len > 16) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"ssl_session_ticket_key\" name \"%V\" too long, "
+ "it cannot exceed 16 characters", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 4) {
+
+ if (ngx_strcmp(value[3].data, "default")) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[3]);
+ return NGX_CONF_ERROR;
+ }
+
+ if (sscf->session_ticket_keys->default_key) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "default \"ssl_session_ticket_key\" is already "
+ "defined");
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->session_ticket_keys->default_key = key;
+ }
+
+ ngx_memzero(key->name, 16);
+ key->name_len = ngx_cpymem(key->name, value[1].data, value[1].len)
+ - key->name;
+
+ k = sscf->session_ticket_keys->keys.elts;
+ for (i = 0; i < sscf->session_ticket_keys->keys.nelts - 1; i++) {
+ if (ngx_strncmp(key->name, k[i].name, 16) == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"ssl_session_ticket_key\" named \"%V\" "
+ "is already defined", &value[1]);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_conf_full_name(cf->cycle, &value[2], 1) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&file, sizeof(ngx_file_t));
+ file.name = value[2];
+ file.log = cf->log;
+
+ file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, 0, 0);
+ if (file.fd == NGX_INVALID_FILE) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno,
+ ngx_open_file_n " \"%V\" failed", &file.name);
+
+ return NGX_CONF_ERROR;
+ }
+
+ rc = NGX_CONF_ERROR;
+
+ if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_fd_info_n " \"%V\" failed", &file.name);
+ goto failed;
+ }
+
+ if (ngx_file_size(&fi) != 32) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "\"%V\" must be 32 bytes", &file.name);
+ goto failed;
+ }
+
+ n = ngx_read_file(&file, buf, 32, 0);
+
+ if (n == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno,
+ ngx_read_file_n " \"%V\" failed", &file.name);
+ goto failed;
+ }
+
+ if (n != 32) {
+ ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+ ngx_read_file_n " \"%V\" returned only %z bytes "
+ "instead of 32", &file.name, n);
+ goto failed;
+ }
+
+ ngx_memcpy(key->aes_key, buf, 16);
+ ngx_memcpy(key->hmac_key, buf + 16, 16);
+
+ rc = NGX_CONF_OK;
+
+failed:
+
+ if (file.fd != NGX_INVALID_FILE) {
+ if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno,
+ ngx_close_file_n " \"%V\" failed", &file.name);
+ }
+ }
+
+ return rc;
+}
+
+#endif
static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
diff -r a720f0b0e083 -r 6d3710969a18 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h Fri Sep 27 19:39:33 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.h Sat Sep 28 02:48:11 2013 -0700
@@ -49,6 +49,10 @@ typedef struct {
u_char *file;
ngx_uint_t line;
+
+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+ ngx_ssl_session_ticket_keys_t *session_ticket_keys;
+#endif
} ngx_http_ssl_srv_conf_t;
More information about the nginx-devel
mailing list