diff src/event/ngx_event_openssl.c @ 9035:3be953161026 quic

Merged with the default branch.
author Sergey Kandaurov <pluknet@nginx.com>
date Thu, 20 Oct 2022 16:41:36 +0400
parents b30bec3d71d6 81b4326daac7
children 91ad1abfb285
line wrap: on
line diff
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -71,10 +71,11 @@ static void ngx_ssl_session_rbtree_inser
     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,
+static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
     unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
     HMAC_CTX *hctx, int enc);
-static void ngx_ssl_session_ticket_keys_cleanup(void *data);
+static ngx_int_t ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log);
+static void ngx_ssl_ticket_keys_cleanup(void *data);
 #endif
 
 #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT
@@ -131,7 +132,7 @@ ngx_module_t  ngx_openssl_module = {
 int  ngx_ssl_connection_index;
 int  ngx_ssl_server_conf_index;
 int  ngx_ssl_session_cache_index;
-int  ngx_ssl_session_ticket_keys_index;
+int  ngx_ssl_ticket_keys_index;
 int  ngx_ssl_ocsp_index;
 int  ngx_ssl_certificate_index;
 int  ngx_ssl_next_certificate_index;
@@ -208,9 +209,9 @@ ngx_ssl_init(ngx_log_t *log)
         return NGX_ERROR;
     }
 
-    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_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_ticket_keys_index == -1) {
         ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                       "SSL_CTX_get_ex_new_index() failed");
         return NGX_ERROR;
@@ -1085,6 +1086,53 @@ ngx_ssl_info_callback(const ngx_ssl_conn
 
 #endif
 
+#ifdef TLS1_3_VERSION
+
+    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP
+        && SSL_version(ssl_conn) == TLS1_3_VERSION)
+    {
+        time_t        now, time, timeout, conf_timeout;
+        SSL_SESSION  *sess;
+
+        /*
+         * OpenSSL with TLSv1.3 updates the session creation time on
+         * session resumption and keeps the session timeout unmodified,
+         * making it possible to maintain the session forever, bypassing
+         * client certificate expiration and revocation.  To make sure
+         * session timeouts are actually used, we now update the session
+         * creation time and reduce the session timeout accordingly.
+         *
+         * BoringSSL with TLSv1.3 ignores configured session timeouts
+         * and uses a hardcoded timeout instead, 7 days.  So we update
+         * session timeout to the configured value as soon as a session
+         * is created.
+         */
+
+        c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
+        sess = SSL_get0_session(ssl_conn);
+
+        if (!c->ssl->session_timeout_set && sess) {
+            c->ssl->session_timeout_set = 1;
+
+            now = ngx_time();
+            time = SSL_SESSION_get_time(sess);
+            timeout = SSL_SESSION_get_timeout(sess);
+            conf_timeout = SSL_CTX_get_timeout(c->ssl->session_ctx);
+
+            timeout = ngx_min(timeout, conf_timeout);
+
+            if (now - time >= timeout) {
+                SSL_SESSION_set1_id_context(sess, (unsigned char *) "", 0);
+
+            } else {
+                SSL_SESSION_set_time(sess, now);
+                SSL_SESSION_set_timeout(sess, timeout - (now - time));
+            }
+        }
+    }
+
+#endif
+
     if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
         c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
 
@@ -1426,9 +1474,9 @@ ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_s
 
     SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
 
-#if SSL_CTRL_SET_ECDH_AUTO
+#ifdef SSL_CTRL_SET_ECDH_AUTO
     /* not needed in OpenSSL 1.1.0+ */
-    SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
+    (void) SSL_CTX_set_ecdh_auto(ssl->ctx, 1);
 #endif
 
     if (ngx_strcmp(name->data, "auto") == 0) {
@@ -1769,7 +1817,7 @@ ngx_ssl_handshake(ngx_connection_t *c)
 #endif
 #endif
 
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
         if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -1914,7 +1962,7 @@ ngx_ssl_try_early_data(ngx_connection_t 
         c->read->ready = 1;
         c->write->ready = 1;
 
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
         if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) {
             ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -2943,7 +2991,7 @@ ngx_ssl_write_early(ngx_connection_t *c,
 static ssize_t
 ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size)
 {
-#ifdef BIO_get_ktls_send
+#if (defined BIO_get_ktls_send && !NGX_WIN32)
 
     int        sslerr, flags;
     ssize_t    n;
@@ -3430,6 +3478,9 @@ ngx_ssl_connection_error(ngx_connection_
 #ifdef SSL_R_VERSION_TOO_LOW
             || n == SSL_R_VERSION_TOO_LOW                            /*  396 */
 #endif
+#ifdef SSL_R_BAD_RECORD_TYPE
+            || n == SSL_R_BAD_RECORD_TYPE                            /*  443 */
+#endif
             || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */
 #ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE
             || n == SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE             /* 1010 */
@@ -3774,6 +3825,12 @@ ngx_ssl_session_cache_init(ngx_shm_zone_
 
     ngx_queue_init(&cache->expire_queue);
 
+    cache->ticket_keys[0].expire = 0;
+    cache->ticket_keys[1].expire = 0;
+    cache->ticket_keys[2].expire = 0;
+
+    cache->fail_time = 0;
+
     len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len;
 
     shpool->log_ctx = ngx_slab_alloc(shpool, len);
@@ -3792,16 +3849,16 @@ ngx_ssl_session_cache_init(ngx_shm_zone_
 
 /*
  * The length of the session id is 16 bytes for SSLv2 sessions and
- * between 1 and 32 bytes for SSLv3/TLSv1, typically 32 bytes.
- * It seems that the typical length of the external ASN1 representation
- * of a session is 118 or 119 bytes for SSLv3/TSLv1.
+ * between 1 and 32 bytes for SSLv3 and TLS, typically 32 bytes.
+ * Typical length of the external ASN1 representation of a session
+ * is about 150 bytes plus SNI server name.
  *
- * Thus on 32-bit platforms we allocate separately an rbtree node,
- * a session id, and an ASN1 representation, they take accordingly
- * 64, 32, and 128 bytes.
+ * On 32-bit platforms we allocate an rbtree node, a session id, and
+ * an ASN1 representation in a single allocation, it typically takes
+ * 256 bytes.
  *
  * On 64-bit platforms we allocate separately an rbtree node + session_id,
- * and an ASN1 representation, they take accordingly 128 and 128 bytes.
+ * and an ASN1 representation, they take accordingly 128 and 256 bytes.
  *
  * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow,
  * so they are outside the code locked by shared pool mutex
@@ -3811,7 +3868,8 @@ static int
 ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess)
 {
     int                       len;
-    u_char                   *p, *id, *cached_sess, *session_id;
+    u_char                   *p, *session_id;
+    size_t                    n;
     uint32_t                  hash;
     SSL_CTX                  *ssl_ctx;
     unsigned int              session_id_length;
@@ -3822,17 +3880,42 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
     ngx_ssl_session_cache_t  *cache;
     u_char                    buf[NGX_SSL_MAX_SESSION_SIZE];
 
+#ifdef TLS1_3_VERSION
+
+    /*
+     * OpenSSL tries to save TLSv1.3 sessions into session cache
+     * even when using tickets for stateless session resumption,
+     * "because some applications just want to know about the creation
+     * of a session"; do not cache such sessions
+     */
+
+    if (SSL_version(ssl_conn) == TLS1_3_VERSION
+        && (SSL_get_options(ssl_conn) & SSL_OP_NO_TICKET) == 0)
+    {
+        return 0;
+    }
+
+#endif
+
     len = i2d_SSL_SESSION(sess, NULL);
 
     /* do not cache too big session */
 
-    if (len > (int) NGX_SSL_MAX_SESSION_SIZE) {
+    if (len > NGX_SSL_MAX_SESSION_SIZE) {
         return 0;
     }
 
     p = buf;
     i2d_SSL_SESSION(sess, &p);
 
+    session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
+
+    /* do not cache sessions with too long session id */
+
+    if (session_id_length > 32) {
+        return 0;
+    }
+
     c = ngx_ssl_get_connection(ssl_conn);
 
     ssl_ctx = c->ssl->session_ctx;
@@ -3846,23 +3929,13 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
     /* drop one or two expired sessions */
     ngx_ssl_expire_sessions(cache, shpool, 1);
 
-    cached_sess = ngx_slab_alloc_locked(shpool, len);
-
-    if (cached_sess == NULL) {
-
-        /* drop the oldest non-expired session and try once more */
-
-        ngx_ssl_expire_sessions(cache, shpool, 0);
-
-        cached_sess = ngx_slab_alloc_locked(shpool, len);
-
-        if (cached_sess == NULL) {
-            sess_id = NULL;
-            goto failed;
-        }
-    }
-
-    sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+#if (NGX_PTR_SIZE == 8)
+    n = sizeof(ngx_ssl_sess_id_t);
+#else
+    n = offsetof(ngx_ssl_sess_id_t, session) + len;
+#endif
+
+    sess_id = ngx_slab_alloc_locked(shpool, n);
 
     if (sess_id == NULL) {
 
@@ -3870,41 +3943,34 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
         ngx_ssl_expire_sessions(cache, shpool, 0);
 
-        sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t));
+        sess_id = ngx_slab_alloc_locked(shpool, n);
 
         if (sess_id == NULL) {
             goto failed;
         }
     }
 
-    session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length);
-
 #if (NGX_PTR_SIZE == 8)
 
-    id = sess_id->sess_id;
-
-#else
-
-    id = ngx_slab_alloc_locked(shpool, session_id_length);
-
-    if (id == NULL) {
+    sess_id->session = ngx_slab_alloc_locked(shpool, len);
+
+    if (sess_id->session == NULL) {
 
         /* drop the oldest non-expired session and try once more */
 
         ngx_ssl_expire_sessions(cache, shpool, 0);
 
-        id = ngx_slab_alloc_locked(shpool, session_id_length);
-
-        if (id == NULL) {
+        sess_id->session = ngx_slab_alloc_locked(shpool, len);
+
+        if (sess_id->session == NULL) {
             goto failed;
         }
     }
 
 #endif
 
-    ngx_memcpy(cached_sess, buf, len);
-
-    ngx_memcpy(id, session_id, session_id_length);
+    ngx_memcpy(sess_id->session, buf, len);
+    ngx_memcpy(sess_id->id, session_id, session_id_length);
 
     hash = ngx_crc32_short(session_id, session_id_length);
 
@@ -3914,9 +3980,7 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
     sess_id->node.key = hash;
     sess_id->node.data = (u_char) session_id_length;
-    sess_id->id = id;
     sess_id->len = len;
-    sess_id->session = cached_sess;
 
     sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx);
 
@@ -3930,18 +3994,17 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_
 
 failed:
 
-    if (cached_sess) {
-        ngx_slab_free_locked(shpool, cached_sess);
-    }
-
     if (sess_id) {
         ngx_slab_free_locked(shpool, sess_id);
     }
 
     ngx_shmtx_unlock(&shpool->mutex);
 
-    ngx_log_error(NGX_LOG_ALERT, c->log, 0,
-                  "could not allocate new session%s", shpool->log_ctx);
+    if (cache->fail_time != ngx_time()) {
+        cache->fail_time = ngx_time();
+        ngx_log_error(NGX_LOG_WARN, c->log, 0,
+                      "could not allocate new session%s", shpool->log_ctx);
+    }
 
     return 0;
 }
@@ -4027,9 +4090,10 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_
 
             ngx_rbtree_delete(&cache->session_rbtree, node);
 
+            ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
             ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-            ngx_slab_free_locked(shpool, sess_id->id);
 #endif
             ngx_slab_free_locked(shpool, sess_id);
 
@@ -4117,9 +4181,10 @@ ngx_ssl_remove_session(SSL_CTX *ssl, ngx
 
             ngx_rbtree_delete(&cache->session_rbtree, node);
 
+            ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
             ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-            ngx_slab_free_locked(shpool, sess_id->id);
 #endif
             ngx_slab_free_locked(shpool, sess_id);
 
@@ -4166,9 +4231,10 @@ ngx_ssl_expire_sessions(ngx_ssl_session_
 
         ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node);
 
+        ngx_explicit_memzero(sess_id->session, sess_id->len);
+
+#if (NGX_PTR_SIZE == 8)
         ngx_slab_free_locked(shpool, sess_id->session);
-#if (NGX_PTR_SIZE == 4)
-        ngx_slab_free_locked(shpool, sess_id->id);
 #endif
         ngx_slab_free_locked(shpool, sess_id);
     }
@@ -4222,23 +4288,25 @@ ngx_ssl_session_rbtree_insert_value(ngx_
 ngx_int_t
 ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths)
 {
-    u_char                         buf[80];
-    size_t                         size;
-    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_pool_cleanup_t            *cln;
-    ngx_ssl_session_ticket_key_t  *key;
-
-    if (paths == NULL) {
+    u_char                 buf[80];
+    size_t                 size;
+    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_pool_cleanup_t    *cln;
+    ngx_ssl_ticket_key_t  *key;
+
+    if (paths == NULL
+        && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL)
+    {
         return NGX_OK;
     }
 
-    keys = ngx_array_create(cf->pool, paths->nelts,
-                            sizeof(ngx_ssl_session_ticket_key_t));
+    keys = ngx_array_create(cf->pool, paths ? paths->nelts : 3,
+                            sizeof(ngx_ssl_ticket_key_t));
     if (keys == NULL) {
         return NGX_ERROR;
     }
@@ -4248,9 +4316,41 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
         return NGX_ERROR;
     }
 
-    cln->handler = ngx_ssl_session_ticket_keys_cleanup;
+    cln->handler = ngx_ssl_ticket_keys_cleanup;
     cln->data = keys;
 
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_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_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;
+    }
+
+    if (paths == NULL) {
+
+        /* placeholder for keys in shared memory */
+
+        key = ngx_array_push_n(keys, 3);
+        key[0].shared = 1;
+        key[0].expire = 0;
+        key[1].shared = 1;
+        key[1].expire = 0;
+        key[2].shared = 1;
+        key[2].expire = 0;
+
+        return NGX_OK;
+    }
+
     path = paths->elts;
     for (i = 0; i < paths->nelts; i++) {
 
@@ -4305,6 +4405,9 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
             goto failed;
         }
 
+        key->shared = 0;
+        key->expire = 1;
+
         if (size == 48) {
             key->size = 48;
             ngx_memcpy(key->name, buf, 16);
@@ -4326,25 +4429,6 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *
         ngx_explicit_memzero(&buf, 80);
     }
 
-    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:
@@ -4361,29 +4445,33 @@ failed:
 
 
 static int
-ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
+ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn,
     unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx,
     HMAC_CTX *hctx, int enc)
 {
-    size_t                         size;
-    SSL_CTX                       *ssl_ctx;
-    ngx_uint_t                     i;
-    ngx_array_t                   *keys;
-    ngx_connection_t              *c;
-    ngx_ssl_session_ticket_key_t  *key;
-    const EVP_MD                  *digest;
-    const EVP_CIPHER              *cipher;
+    size_t                 size;
+    SSL_CTX               *ssl_ctx;
+    ngx_uint_t             i;
+    ngx_array_t           *keys;
+    ngx_connection_t      *c;
+    ngx_ssl_ticket_key_t  *key;
+    const EVP_MD          *digest;
+    const EVP_CIPHER      *cipher;
 
     c = ngx_ssl_get_connection(ssl_conn);
     ssl_ctx = c->ssl->session_ctx;
 
+    if (ngx_ssl_rotate_ticket_keys(ssl_ctx, c->log) != NGX_OK) {
+        return -1;
+    }
+
 #ifdef OPENSSL_NO_SHA256
     digest = EVP_sha1();
 #else
     digest = EVP_sha256();
 #endif
 
-    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index);
+    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
     if (keys == NULL) {
         return -1;
     }
@@ -4394,7 +4482,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
         /* encrypt session ticket */
 
         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket encrypt, key: \"%*xs\" (%s session)",
+                       "ssl ticket encrypt, key: \"%*xs\" (%s session)",
                        (size_t) 16, key[0].name,
                        SSL_session_reused(ssl_conn) ? "reused" : "new");
 
@@ -4441,7 +4529,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
         }
 
         ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket decrypt, key: \"%*xs\" not found",
+                       "ssl ticket decrypt, key: \"%*xs\" not found",
                        (size_t) 16, name);
 
         return 0;
@@ -4449,7 +4537,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
     found:
 
         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                       "ssl session ticket decrypt, key: \"%*xs\"%s",
+                       "ssl ticket decrypt, key: \"%*xs\"%s",
                        (size_t) 16, key[i].name, (i == 0) ? " (default)" : "");
 
         if (key[i].size == 48) {
@@ -4486,7 +4574,7 @@ ngx_ssl_session_ticket_key_callback(ngx_
 
         /* renew if non-default key */
 
-        if (i != 0) {
+        if (i != 0 && key[i].expire) {
             return 2;
         }
 
@@ -4495,13 +4583,142 @@ ngx_ssl_session_ticket_key_callback(ngx_
 }
 
 
+static ngx_int_t
+ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log)
+{
+    time_t                    now, expire;
+    ngx_array_t              *keys;
+    ngx_shm_zone_t           *shm_zone;
+    ngx_slab_pool_t          *shpool;
+    ngx_ssl_ticket_key_t     *key;
+    ngx_ssl_session_cache_t  *cache;
+    u_char                    buf[80];
+
+    keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index);
+    if (keys == NULL) {
+        return NGX_OK;
+    }
+
+    key = keys->elts;
+
+    if (!key[0].shared) {
+        return NGX_OK;
+    }
+
+    /*
+     * if we don't need to update expiration of the current key
+     * and the previous key is still needed, don't sync with shared
+     * memory to save some work; in the worst case other worker process
+     * will switch to the next key, but this process will still be able
+     * to decrypt tickets encrypted with it
+     */
+
+    now = ngx_time();
+    expire = now + SSL_CTX_get_timeout(ssl_ctx);
+
+    if (key[0].expire >= expire && key[1].expire >= now) {
+        return NGX_OK;
+    }
+
+    shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index);
+
+    cache = shm_zone->data;
+    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+    ngx_shmtx_lock(&shpool->mutex);
+
+    key = cache->ticket_keys;
+
+    if (key[0].expire == 0) {
+
+        /* initialize the current key */
+
+        if (RAND_bytes(buf, 80) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
+            ngx_shmtx_unlock(&shpool->mutex);
+            return NGX_ERROR;
+        }
+
+        key[0].shared = 1;
+        key[0].expire = expire;
+        key[0].size = 80;
+        ngx_memcpy(key[0].name, buf, 16);
+        ngx_memcpy(key[0].hmac_key, buf + 16, 32);
+        ngx_memcpy(key[0].aes_key, buf + 48, 32);
+
+        ngx_explicit_memzero(&buf, 80);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "ssl ticket key: \"%*xs\"",
+                       (size_t) 16, key[0].name);
+
+        /*
+         * copy the current key to the next key, as initialization of
+         * the previous key will replace the current key with the next
+         * key
+         */
+
+        key[2] = key[0];
+    }
+
+    if (key[1].expire < now) {
+
+        /*
+         * if the previous key is no longer needed (or not initialized),
+         * replace it with the current key, replace the current key with
+         * the next key, and generate new next key
+         */
+
+        key[1] = key[0];
+        key[0] = key[2];
+
+        if (RAND_bytes(buf, 80) != 1) {
+            ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed");
+            ngx_shmtx_unlock(&shpool->mutex);
+            return NGX_ERROR;
+        }
+
+        key[2].shared = 1;
+        key[2].expire = 0;
+        key[2].size = 80;
+        ngx_memcpy(key[2].name, buf, 16);
+        ngx_memcpy(key[2].hmac_key, buf + 16, 32);
+        ngx_memcpy(key[2].aes_key, buf + 48, 32);
+
+        ngx_explicit_memzero(&buf, 80);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0,
+                       "ssl ticket key: \"%*xs\"",
+                       (size_t) 16, key[2].name);
+    }
+
+    /*
+     * update expiration of the current key: it is going to be needed
+     * at least till the session being created expires
+     */
+
+    if (expire > key[0].expire) {
+        key[0].expire = expire;
+    }
+
+    /* sync keys to the worker process memory */
+
+    ngx_memcpy(keys->elts, cache->ticket_keys,
+               2 * sizeof(ngx_ssl_ticket_key_t));
+
+    ngx_shmtx_unlock(&shpool->mutex);
+
+    return NGX_OK;
+}
+
+
 static void
-ngx_ssl_session_ticket_keys_cleanup(void *data)
+ngx_ssl_ticket_keys_cleanup(void *data)
 {
     ngx_array_t  *keys = data;
 
     ngx_explicit_memzero(keys->elts,
-                         keys->nelts * sizeof(ngx_ssl_session_ticket_key_t));
+                         keys->nelts * sizeof(ngx_ssl_ticket_key_t));
 }
 
 #else