[PATCH] SSL: added support for TLS Session Tickets (RFC5077).

Piotr Sikora piotr at cloudflare.com
Thu Oct 10 23:22:13 UTC 2013


# HG changeset patch
# User Piotr Sikora <piotr at cloudflare.com>
# Date 1381446441 25200
#      Thu Oct 10 16:07:21 2013 -0700
# Node ID 7132c555e445419008520563da63d3b122772b97
# Parent  5483d9e77b3287b00b1104a07688bda37bc7351e
SSL: added ability to set keys used for Session Tickets (RFC5077).

In order to support key rollover, ssl_session_ticket_key can be defined
multiple times. The first key will be used to issue and resume Session
Tickets, while the rest will be used only to resume them.

    ssl_session_ticket_key   session_ticket_current.key;
    ssl_session_ticket_key   session_ticket_curr-1h.key;
    ssl_session_ticket_key   session_ticket_curr-2h.key;

Please note that nginx supports Session Tickets even without explicit
configuration of the keys and this feature should be only used in setups
where SSL traffic is distributed across multiple nginx servers.

Signed-off-by: Piotr Sikora <piotr at cloudflare.com>

diff -r 5483d9e77b32 -r 7132c555e445 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c Wed Oct 02 15:07:17 2013 +0400
+++ b/src/event/ngx_event_openssl.c Thu Oct 10 16:07:21 2013 -0700
@@ -38,6 +38,18 @@ 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);
+
+#ifdef OPENSSL_NO_SHA256
+#define ngx_ssl_session_ticket_md  EVP_sha1
+#else
+#define ngx_ssl_session_ticket_md  EVP_sha256
+#endif
+#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 +96,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 +170,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 +2267,219 @@ 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_array_t *paths)
+{
+    u_char                         buf[48];
+    ssize_t                        n;
+    ngx_str_t                     *path;
+    ngx_file_t                     file;
+    ngx_uint_t                     i;
+    ngx_array_t                   *keys;
+    ngx_file_info_t                fi;
+    ngx_ssl_session_ticket_key_t  *key;
+
+    if (paths == NULL) {
+        return NGX_OK;
+    }
+
+    keys = ngx_array_create(cf->pool, paths->nelts,
+                            sizeof(ngx_ssl_session_ticket_key_t));
+    if (keys == NULL) {
+        return NGX_ERROR;
+    }
+
+    path = paths->elts;
+    for (i = 0; i < paths->nelts; i++) {
+
+        if (ngx_conf_full_name(cf->cycle, &path[i], 1) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        ngx_memzero(&file, sizeof(ngx_file_t));
+        file.name = path[i];
+        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_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) != 48) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "\"%V\" must be 48 bytes", &file.name);
+            goto failed;
+        }
+
+        n = ngx_read_file(&file, buf, 48, 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 != 48) {
+            ngx_conf_log_error(NGX_LOG_CRIT, cf, 0,
+                               ngx_read_file_n " \"%V\" returned only
%z bytes "
+                               "instead of 48", &file.name, n);
+            goto failed;
+        }
+
+        key = ngx_array_push(keys);
+        if (key == NULL) {
+            goto failed;
+        }
+
+        ngx_memcpy(key->name, buf, 16);
+        ngx_memcpy(key->aes_key, buf + 16, 16);
+        ngx_memcpy(key->hmac_key, buf + 32, 16);
+
+        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);
+        }
+    }
+
+    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;
+    }
+
+    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;
+
+failed:
+
+    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 NGX_ERROR;
+}
+
+
+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)
+{
+    u_char                         buf[32];
+    SSL_CTX                       *ssl_ctx;
+    ngx_uint_t                     i;
+    ngx_array_t                   *keys;
+    ngx_ssl_session_ticket_key_t  *key;
+#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;
+    }
+
+    key = keys->elts;
+
+#if (NGX_DEBUG)
+    c = ngx_ssl_get_connection(ssl_conn);
+#endif
+
+    if (enc == 1) {
+        /* encrypt session ticket */
+
+#if (NGX_DEBUG)
+        ngx_hex_dump(buf, key[0].name, 16);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "ssl session ticket encrypt, key: \"%*s\" (%s session)",
+                       32, buf,
+                       SSL_session_reused(ssl_conn) ? "reused" : "new");
+#endif
+
+        RAND_pseudo_bytes(iv, 16);
+        EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key[0].aes_key, iv);
+        HMAC_Init_ex(hctx, key[0].hmac_key, 16, ngx_ssl_session_ticket_md(),
+                     NULL);
+        memcpy(name, key[0].name, 16);
+
+        return 0;
+
+    } else {
+        /* decrypt session ticket */
+
+        for (i = 0; i < keys->nelts; i++) {
+            if (ngx_strncmp(name, key[i].name, 16) == 0) {
+                break;
+            }
+        }
+
+        if (i == keys->nelts) {
+            ngx_hex_dump(buf, name, 16);
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "ssl session ticket decrypt, key: \"%*s\" "
+                           "not found", 32, buf);
+            return 0;
+        }
+
+#if (NGX_DEBUG)
+        ngx_hex_dump(buf, key[i].name, 16);
+
+        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                       "ssl session ticket decrypt, key: \"%*s\"%s",
+                       32, buf, (i == 0) ? " (default)" : "");
+#endif
+
+        HMAC_Init_ex(hctx, key[i].hmac_key, 16, ngx_ssl_session_ticket_md(),
+                     NULL);
+        EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key[i].aes_key, iv);
+
+        return (i == 0) ? 1 : 2 /* renew */;
+    }
+}
+
+#else
+
+ngx_int_t
+ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
+{
+    if (paths) {
+        ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                      "\"ssl_session_ticket_keys\" ignored, not supported");
+    }
+
+    return NGX_OK;
+}
+
+#endif
+
+
 void
 ngx_ssl_cleanup_ctx(void *data)
 {
diff -r 5483d9e77b32 -r 7132c555e445 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Wed Oct 02 15:07:17 2013 +0400
+++ b/src/event/ngx_event_openssl.h Thu Oct 10 16:07:21 2013 -0700
@@ -83,6 +83,16 @@ typedef struct {
 } ngx_ssl_session_cache_t;


+#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
+
+typedef struct {
+    u_char                      name[16];
+    u_char                      aes_key[16];
+    u_char                      hmac_key[16];
+} ngx_ssl_session_ticket_key_t;
+
+#endif
+

 #define NGX_SSL_SSLv2    0x0002
 #define NGX_SSL_SSLv3    0x0004
@@ -116,6 +126,8 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf
 ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
 ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
     ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout);
+ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_array_t *paths);
 ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data);
 ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
     ngx_uint_t flags);
@@ -175,6 +187,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 5483d9e77b32 -r 7132c555e445 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c Wed Oct 02 15:07:17 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c Thu Oct 10 16:07:21 2013 -0700
@@ -153,6 +153,13 @@ static ngx_command_t  ngx_http_ssl_comma
       0,
       NULL },

+    { ngx_string("ssl_session_ticket_key"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, session_ticket_keys),
+      NULL },
+
     { ngx_string("ssl_session_timeout"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_sec_slot,
@@ -421,6 +428,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t
     sscf->verify_depth = NGX_CONF_UNSET_UINT;
     sscf->builtin_session_cache = NGX_CONF_UNSET;
     sscf->session_timeout = NGX_CONF_UNSET;
+    sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
     sscf->stapling = NGX_CONF_UNSET;
     sscf->stapling_verify = NGX_CONF_UNSET;

@@ -623,6 +631,15 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         return NGX_CONF_ERROR;
     }

+    ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+                         prev->session_ticket_keys, NULL);
+
+    if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
     if (conf->stapling) {

         if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file,
diff -r 5483d9e77b32 -r 7132c555e445 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h Wed Oct 02 15:07:17 2013 +0400
+++ b/src/http/modules/ngx_http_ssl_module.h Thu Oct 10 16:07:21 2013 -0700
@@ -42,6 +42,8 @@ typedef struct {

     ngx_shm_zone_t                 *shm_zone;

+    ngx_array_t                    *session_ticket_keys;
+
     ngx_flag_t                      stapling;
     ngx_flag_t                      stapling_verify;
     ngx_str_t                       stapling_file;
diff -r 5483d9e77b32 -r 7132c555e445 src/mail/ngx_mail_ssl_module.c
--- a/src/mail/ngx_mail_ssl_module.c Wed Oct 02 15:07:17 2013 +0400
+++ b/src/mail/ngx_mail_ssl_module.c Thu Oct 10 16:07:21 2013 -0700
@@ -116,6 +116,13 @@ static ngx_command_t  ngx_mail_ssl_comma
       0,
       NULL },

+    { ngx_string("ssl_session_ticket_key"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_array_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, session_ticket_keys),
+      NULL },
+
     { ngx_string("ssl_session_timeout"),
       NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_sec_slot,
@@ -184,6 +191,7 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf)
     scf->prefer_server_ciphers = NGX_CONF_UNSET;
     scf->builtin_session_cache = NGX_CONF_UNSET;
     scf->session_timeout = NGX_CONF_UNSET;
+    scf->session_ticket_keys = NGX_CONF_UNSET_PTR;

     return scf;
 }
@@ -331,6 +339,15 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf,
         return NGX_CONF_ERROR;
     }

+    ngx_conf_merge_ptr_value(conf->session_ticket_keys,
+                         prev->session_ticket_keys, NULL);
+
+    if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, conf->session_ticket_keys)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
     return NGX_CONF_OK;
 }

diff -r 5483d9e77b32 -r 7132c555e445 src/mail/ngx_mail_ssl_module.h
--- a/src/mail/ngx_mail_ssl_module.h Wed Oct 02 15:07:17 2013 +0400
+++ b/src/mail/ngx_mail_ssl_module.h Thu Oct 10 16:07:21 2013 -0700
@@ -41,6 +41,8 @@ typedef struct {

     ngx_shm_zone_t  *shm_zone;

+    ngx_array_t     *session_ticket_keys;
+
     u_char          *file;
     ngx_uint_t       line;
 } ngx_mail_ssl_conf_t;



More information about the nginx-devel mailing list