changeset 8621:9c3be23ddbe7 quic

QUIC: refactored key handling. All key handling functionality is moved into ngx_quic_protection.c. Public structures from ngx_quic_protection.h are now private and new methods are available to manipulate keys. A negotiated cipher is cached in QUIC connection from the set secret callback to avoid calling SSL_get_current_cipher() on each encrypt/decrypt operation. This also reduces the number of unwanted c->ssl->connection occurrences.
author Sergey Kandaurov <pluknet@nginx.com>
date Mon, 02 Nov 2020 18:21:34 +0300
parents d10118e38943
children 183275308d9a
files src/event/ngx_event_quic.c src/event/ngx_event_quic.h src/event/ngx_event_quic_protection.c src/event/ngx_event_quic_protection.h src/event/ngx_event_quic_transport.h
diffstat 5 files changed, 195 insertions(+), 171 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -94,9 +94,6 @@ typedef struct {
  *  are also Initial packets.
 */
 typedef struct {
-    ngx_quic_secret_t                 client_secret;
-    ngx_quic_secret_t                 server_secret;
-
     enum ssl_encryption_level_t       level;
 
     uint64_t                          pnum;        /* to be sent */
@@ -134,10 +131,11 @@ struct ngx_quic_connection_s {
     ngx_quic_tp_t                     ctp;
 
     ngx_quic_send_ctx_t               send_ctx[NGX_QUIC_SEND_CTX_LAST];
-    ngx_quic_secrets_t                keys[NGX_QUIC_ENCRYPTION_LAST];
-    ngx_quic_secrets_t                next_key;
+
     ngx_quic_frames_stream_t          crypto[NGX_QUIC_ENCRYPTION_LAST];
 
+    ngx_quic_keys_t                  *keys;
+
     ngx_quic_conf_t                  *conf;
 
     ngx_event_t                       push;
@@ -643,8 +641,7 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t 
     enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
     const uint8_t *rsecret, size_t secret_len)
 {
-    ngx_connection_t    *c;
-    ngx_quic_secrets_t  *keys;
+    ngx_connection_t  *c;
 
     c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
 
@@ -654,11 +651,8 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t 
     ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len);
 #endif
 
-    keys = &c->quic->keys[level];
-
-    return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
-                                          rsecret, secret_len,
-                                          &keys->client);
+    return ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level,
+                                               cipher, rsecret, secret_len);
 }
 
 
@@ -667,8 +661,7 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t
     enum ssl_encryption_level_t level, const SSL_CIPHER *cipher,
     const uint8_t *wsecret, size_t secret_len)
 {
-    ngx_connection_t    *c;
-    ngx_quic_secrets_t  *keys;
+    ngx_connection_t  *c;
 
     c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
 
@@ -678,11 +671,8 @@ ngx_quic_set_write_secret(ngx_ssl_conn_t
     ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len);
 #endif
 
-    keys = &c->quic->keys[level];
-
-    return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
-                                          wsecret, secret_len,
-                                          &keys->server);
+    return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level,
+                                               cipher, wsecret, secret_len);
 }
 
 #else
@@ -692,25 +682,24 @@ ngx_quic_set_encryption_secrets(ngx_ssl_
     enum ssl_encryption_level_t level, const uint8_t *rsecret,
     const uint8_t *wsecret, size_t secret_len)
 {
-    ngx_int_t            rc;
-    ngx_connection_t    *c;
-    ngx_quic_secrets_t  *keys;
+    ngx_connection_t  *c;
+    const SSL_CIPHER  *cipher;
 
     c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn);
 
     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic ngx_quic_set_encryption_secrets() level:%d", level);
 #ifdef NGX_QUIC_DEBUG_CRYPTO
-    ngx_quic_hexdump(c->log, "quic read", rsecret, secret_len);
+    ngx_quic_hexdump(c->log, "quic read secret", rsecret, secret_len);
 #endif
 
-    keys = &c->quic->keys[level];
-
-    rc = ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
-                                        rsecret, secret_len,
-                                        &keys->client);
-    if (rc != 1) {
-        return rc;
+    cipher = SSL_get_current_cipher(ssl_conn);
+
+    if (ngx_quic_keys_set_encryption_secret(c->pool, 0, c->quic->keys, level,
+                                            cipher, rsecret, secret_len)
+        != 1)
+    {
+        return 0;
     }
 
     if (level == ssl_encryption_early_data) {
@@ -718,12 +707,11 @@ ngx_quic_set_encryption_secrets(ngx_ssl_
     }
 
 #ifdef NGX_QUIC_DEBUG_CRYPTO
-    ngx_quic_hexdump(c->log, "quic write", wsecret, secret_len);
+    ngx_quic_hexdump(c->log, "quic write secret", wsecret, secret_len);
 #endif
 
-    return ngx_quic_set_encryption_secret(c->pool, ssl_conn, level,
-                                          wsecret, secret_len,
-                                          &keys->server);
+    return ngx_quic_keys_set_encryption_secret(c->pool, 1, c->quic->keys, level,
+                                               cipher, wsecret, secret_len);
 }
 
 #endif
@@ -965,6 +953,11 @@ ngx_quic_new_connection(ngx_connection_t
         return NULL;
     }
 
+    qc->keys = ngx_quic_keys_new(c->pool);
+    if (qc->keys == NULL) {
+        return NULL;
+    }
+
     ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
                     ngx_quic_rbtree_insert_stream);
 
@@ -1239,7 +1232,7 @@ ngx_quic_send_retry(ngx_connection_t *c)
 
     res.data = buf;
 
-    if (ngx_quic_encrypt(&pkt, NULL, &res) != NGX_OK) {
+    if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -1990,8 +1983,6 @@ ngx_quic_process_packet(ngx_connection_t
     ngx_quic_header_t *pkt)
 {
     ngx_int_t               rc;
-    ngx_ssl_conn_t         *ssl_conn;
-    ngx_quic_secrets_t     *keys, *next, tmp;
     ngx_quic_send_ctx_t    *ctx;
     ngx_quic_connection_t  *qc;
 
@@ -2135,26 +2126,19 @@ ngx_quic_process_packet(ngx_connection_t
 
     c->log->action = "decrypting packet";
 
-    keys = &qc->keys[pkt->level];
-
-    if (keys->client.key.len == 0) {
+    if (!ngx_quic_keys_available(qc->keys, pkt->level)) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
                       "quic no level %d keys yet, ignoring packet", pkt->level);
         return NGX_DECLINED;
     }
 
-    next = &qc->next_key;
-
-    pkt->secret = &keys->client;
-    pkt->next = &next->client;
+    pkt->keys = qc->keys;
     pkt->key_phase = qc->key_phase;
     pkt->plaintext = buf;
 
     ctx = ngx_quic_get_send_ctx(qc, pkt->level);
 
-    ssl_conn = c->ssl ? c->ssl->connection : NULL;
-
-    rc = ngx_quic_decrypt(pkt, ssl_conn, &ctx->largest_pn);
+    rc = ngx_quic_decrypt(pkt, &ctx->largest_pn);
     if (rc != NGX_OK) {
         qc->error = pkt->error;
         qc->error_reason = "failed to decrypt packet";
@@ -2190,44 +2174,32 @@ ngx_quic_process_packet(ngx_connection_t
         return ngx_quic_payload_handler(c, pkt);
     }
 
-    /* switch keys on Key Phase change */
-
-    if (pkt->key_update) {
-        qc->key_phase ^= 1;
-
-        tmp = *keys;
-        *keys = *next;
-        *next = tmp;
-    }
+    if (!pkt->key_update) {
+        return ngx_quic_payload_handler(c, pkt);
+    }
+
+    /* switch keys and generate next on Key Phase change */
+
+    qc->key_phase ^= 1;
+    ngx_quic_keys_switch(c, qc->keys);
 
     rc = ngx_quic_payload_handler(c, pkt);
     if (rc != NGX_OK) {
         return rc;
     }
 
-    /* generate next keys */
-
-    if (pkt->key_update) {
-        if (ngx_quic_key_update(c, keys, next) != NGX_OK) {
-            return NGX_ERROR;
-        }
-    }
-
-    return NGX_OK;
+    return ngx_quic_keys_update(c, qc->keys);
 }
 
 
 static ngx_int_t
 ngx_quic_init_secrets(ngx_connection_t *c)
 {
-    ngx_quic_secrets_t     *keys;
     ngx_quic_connection_t  *qc;
 
-    qc =c->quic;
-    keys = &qc->keys[ssl_encryption_initial];
-
-    if (ngx_quic_set_initial_secret(c->pool, &keys->client, &keys->server,
-                                    &qc->odcid)
+    qc = c->quic;
+
+    if (ngx_quic_keys_set_initial_secret(c->pool, qc->keys, &qc->odcid)
         != NGX_OK)
     {
         return NGX_ERROR;
@@ -2249,11 +2221,12 @@ ngx_quic_discard_ctx(ngx_connection_t *c
 
     qc = c->quic;
 
-    if (qc->keys[level].client.key.len == 0) {
+    if (!ngx_quic_keys_available(qc->keys, level)) {
         return;
     }
 
-    qc->keys[level].client.key.len = 0;
+    ngx_quic_keys_discard(qc->keys, level);
+
     qc->pto_count = 0;
 
     ctx = ngx_quic_get_send_ctx(qc, level);
@@ -3634,10 +3607,7 @@ ngx_quic_crypto_input(ngx_connection_t *
      * See quic-tls 9.4 Header Protection Timing Side-Channels.
      */
 
-    if (ngx_quic_key_update(c, &c->quic->keys[ssl_encryption_application],
-                            &c->quic->next_key)
-        != NGX_OK)
-    {
+    if (ngx_quic_keys_update(c, c->quic->keys) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -4503,10 +4473,8 @@ ngx_quic_send_frames(ngx_connection_t *c
     ngx_str_t               out, res;
     ngx_msec_t              now;
     ngx_queue_t            *q;
-    ngx_ssl_conn_t         *ssl_conn;
     ngx_quic_frame_t       *f, *start;
     ngx_quic_header_t       pkt;
-    ngx_quic_secrets_t     *keys;
     ngx_quic_connection_t  *qc;
     static u_char           src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
     static u_char           dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE];
@@ -4514,8 +4482,6 @@ ngx_quic_send_frames(ngx_connection_t *c
     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic ngx_quic_send_frames");
 
-    ssl_conn = c->ssl ? c->ssl->connection : NULL;
-
     q = ngx_queue_head(frames);
     start = ngx_queue_data(q, ngx_quic_frame_t, queue);
 
@@ -4554,9 +4520,7 @@ ngx_quic_send_frames(ngx_connection_t *c
 
     qc = c->quic;
 
-    keys = &c->quic->keys[start->level];
-
-    pkt.secret = &keys->server;
+    pkt.keys = qc->keys;
 
     pkt.flags = NGX_QUIC_PKT_FIXED_BIT;
 
@@ -4603,7 +4567,7 @@ ngx_quic_send_frames(ngx_connection_t *c
                    ngx_quic_level_name(start->level), out.len, pkt.need_ack,
                    pkt.number, pkt.num_len, pkt.trunc);
 
-    if (ngx_quic_encrypt(&pkt, ssl_conn, &res) != NGX_OK) {
+    if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
         ngx_quic_free_frames(c, frames);
         return NGX_ERROR;
     }
--- a/src/event/ngx_event_quic.h
+++ b/src/event/ngx_event_quic.h
@@ -116,6 +116,9 @@ struct ngx_quic_stream_s {
 };
 
 
+typedef struct ngx_quic_keys_s  ngx_quic_keys_t;
+
+
 void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf);
 ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi);
 void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err,
--- a/src/event/ngx_event_quic_protection.c
+++ b/src/event/ngx_event_quic_protection.c
@@ -24,6 +24,7 @@
 #define ngx_quic_cipher_t             EVP_CIPHER
 #endif
 
+
 typedef struct {
     const ngx_quic_cipher_t  *c;
     const EVP_CIPHER         *hp;
@@ -31,6 +32,27 @@ typedef struct {
 } ngx_quic_ciphers_t;
 
 
+typedef struct ngx_quic_secret_s {
+    ngx_str_t                 secret;
+    ngx_str_t                 key;
+    ngx_str_t                 iv;
+    ngx_str_t                 hp;
+} ngx_quic_secret_t;
+
+
+typedef struct {
+    ngx_quic_secret_t         client;
+    ngx_quic_secret_t         server;
+} ngx_quic_secrets_t;
+
+
+struct ngx_quic_keys_s {
+    ngx_quic_secrets_t        secrets[NGX_QUIC_ENCRYPTION_LAST];
+    ngx_quic_secrets_t        next_key;
+    ngx_uint_t                cipher;
+};
+
+
 static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len,
     const EVP_MD *digest, const u_char *prk, size_t prk_len,
     const u_char *info, size_t info_len);
@@ -41,7 +63,7 @@ static ngx_int_t ngx_hkdf_extract(u_char
 static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
     uint64_t *largest_pn);
 static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
-static ngx_int_t ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn,
+static ngx_int_t ngx_quic_ciphers(ngx_uint_t id,
     ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level);
 
 static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher,
@@ -56,30 +78,21 @@ static ngx_int_t ngx_quic_hkdf_expand(ng
     ngx_str_t *out, ngx_str_t *label, const uint8_t *prk, size_t prk_len);
 
 static ngx_int_t ngx_quic_create_long_packet(ngx_quic_header_t *pkt,
-    ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+    ngx_str_t *res);
 static ngx_int_t ngx_quic_create_short_packet(ngx_quic_header_t *pkt,
-    ngx_ssl_conn_t *ssl_conn, ngx_str_t *res);
+    ngx_str_t *res);
 static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt,
     ngx_str_t *res);
 
 
 static ngx_int_t
-ngx_quic_ciphers(ngx_ssl_conn_t *ssl_conn, ngx_quic_ciphers_t *ciphers,
+ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers,
     enum ssl_encryption_level_t level)
 {
-    ngx_int_t          id, len;
-    const SSL_CIPHER  *cipher;
+    ngx_int_t  len;
 
     if (level == ssl_encryption_initial) {
         id = NGX_AES_128_GCM_SHA256;
-
-    } else {
-        cipher = SSL_get_current_cipher(ssl_conn);
-        if (cipher == NULL) {
-            return NGX_ERROR;
-        }
-
-        id = SSL_CIPHER_get_id(cipher) & 0xffff;
     }
 
     switch (id) {
@@ -130,14 +143,15 @@ ngx_quic_ciphers(ngx_ssl_conn_t *ssl_con
 
 
 ngx_int_t
-ngx_quic_set_initial_secret(ngx_pool_t *pool, ngx_quic_secret_t *client,
-    ngx_quic_secret_t *server, ngx_str_t *secret)
+ngx_quic_keys_set_initial_secret(ngx_pool_t *pool, ngx_quic_keys_t *keys,
+    ngx_str_t *secret)
 {
-    size_t             is_len;
-    uint8_t            is[SHA256_DIGEST_LENGTH];
-    ngx_uint_t         i;
-    const EVP_MD      *digest;
-    const EVP_CIPHER  *cipher;
+    size_t              is_len;
+    uint8_t             is[SHA256_DIGEST_LENGTH];
+    ngx_uint_t          i;
+    const EVP_MD       *digest;
+    const EVP_CIPHER   *cipher;
+    ngx_quic_secret_t  *client, *server;
 
     static const uint8_t salt[20] =
 #if (NGX_QUIC_DRAFT_VERSION >= 29)
@@ -148,6 +162,9 @@ ngx_quic_set_initial_secret(ngx_pool_t *
         "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02";
 #endif
 
+    client = &keys->secrets[ssl_encryption_initial].client;
+    server = &keys->secrets[ssl_encryption_initial].server;
+
     /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */
 
     cipher = EVP_aes_128_gcm();
@@ -626,16 +643,25 @@ failed:
 }
 
 
-int
-ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn,
-    enum ssl_encryption_level_t level, const uint8_t *secret,
-    size_t secret_len, ngx_quic_secret_t *peer_secret)
+int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write,
+    ngx_quic_keys_t *keys, enum ssl_encryption_level_t level,
+    const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
 {
-    ngx_int_t           key_len;
-    ngx_uint_t          i;
-    ngx_quic_ciphers_t  ciphers;
+    ngx_int_t            key_len;
+    ngx_uint_t           i;
+    ngx_quic_secret_t   *peer_secret;
+    ngx_quic_ciphers_t   ciphers;
+
+    peer_secret = is_write ? &keys->secrets[level].server
+                           : &keys->secrets[level].client;
 
-    key_len = ngx_quic_ciphers(ssl_conn, &ciphers, level);
+    /*
+     * SSL_CIPHER_get_protocol_id() is not universally available,
+     * casting to uint16_t works for both OpenSSL and BoringSSL
+     */
+    keys->cipher = (uint16_t) SSL_CIPHER_get_id(cipher);
+
+    key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
 
     if (key_len == NGX_ERROR) {
         ngx_ssl_error(NGX_LOG_INFO, pool->log, 0, "unexpected cipher");
@@ -682,17 +708,56 @@ ngx_quic_set_encryption_secret(ngx_pool_
 }
 
 
+ngx_quic_keys_t *
+ngx_quic_keys_new(ngx_pool_t *pool)
+{
+    return ngx_pcalloc(pool, sizeof(ngx_quic_keys_t));
+}
+
+
+ngx_uint_t
+ngx_quic_keys_available(ngx_quic_keys_t *keys,
+    enum ssl_encryption_level_t level)
+{
+    return keys->secrets[level].client.key.len != 0;
+}
+
+
+void
+ngx_quic_keys_discard(ngx_quic_keys_t *keys,
+     enum ssl_encryption_level_t level)
+{
+    keys->secrets[level].client.key.len = 0;
+}
+
+
+void
+ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys)
+{
+    ngx_quic_secrets_t  *current, *next, tmp;
+
+    current = &keys->secrets[ssl_encryption_application];
+    next = &keys->next_key;
+
+    tmp = *current;
+    *current = *next;
+    *next = tmp;
+}
+
+
 ngx_int_t
-ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current,
-    ngx_quic_secrets_t *next)
+ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys)
 {
-    ngx_uint_t          i;
-    ngx_quic_ciphers_t  ciphers;
+    ngx_uint_t           i;
+    ngx_quic_ciphers_t   ciphers;
+    ngx_quic_secrets_t  *current, *next;
+
+    current = &keys->secrets[ssl_encryption_application];
+    next = &keys->next_key;
 
     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
 
-    if (ngx_quic_ciphers(c->ssl->connection, &ciphers,
-                         ssl_encryption_application)
+    if (ngx_quic_ciphers(keys->cipher, &ciphers, ssl_encryption_application)
         == NGX_ERROR)
     {
         return NGX_ERROR;
@@ -760,12 +825,12 @@ ngx_quic_key_update(ngx_connection_t *c,
 
 
 static ngx_int_t
-ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-    ngx_str_t *res)
+ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
 {
     u_char              *pnp, *sample;
     ngx_str_t            ad, out;
     ngx_uint_t           i;
+    ngx_quic_secret_t   *secret;
     ngx_quic_ciphers_t   ciphers;
     u_char               nonce[12], mask[16];
 
@@ -780,14 +845,17 @@ ngx_quic_create_long_packet(ngx_quic_hea
     ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len);
 #endif
 
-    if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+    if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
+    {
         return NGX_ERROR;
     }
 
-    ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len);
+    secret = &pkt->keys->secrets[pkt->level].server;
+
+    ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
     ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
 
-    if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out,
+    if (ngx_quic_tls_seal(ciphers.c, secret, &out,
                           nonce, &pkt->payload, &ad, pkt->log)
         != NGX_OK)
     {
@@ -795,7 +863,7 @@ ngx_quic_create_long_packet(ngx_quic_hea
     }
 
     sample = &out.data[4 - pkt->num_len];
-    if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample)
+    if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
         != NGX_OK)
     {
         return NGX_ERROR;
@@ -815,12 +883,12 @@ ngx_quic_create_long_packet(ngx_quic_hea
 
 
 static ngx_int_t
-ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-    ngx_str_t *res)
+ngx_quic_create_short_packet(ngx_quic_header_t *pkt, ngx_str_t *res)
 {
     u_char              *pnp, *sample;
     ngx_str_t            ad, out;
     ngx_uint_t           i;
+    ngx_quic_secret_t   *secret;
     ngx_quic_ciphers_t   ciphers;
     u_char               nonce[12], mask[16];
 
@@ -835,14 +903,17 @@ ngx_quic_create_short_packet(ngx_quic_he
     ngx_quic_hexdump(pkt->log, "quic ad", ad.data, ad.len);
 #endif
 
-    if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+    if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
+    {
         return NGX_ERROR;
     }
 
-    ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len);
+    secret = &pkt->keys->secrets[pkt->level].server;
+
+    ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
     ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number);
 
-    if (ngx_quic_tls_seal(ciphers.c, pkt->secret, &out,
+    if (ngx_quic_tls_seal(ciphers.c, secret, &out,
                           nonce, &pkt->payload, &ad, pkt->log)
         != NGX_OK)
     {
@@ -850,7 +921,7 @@ ngx_quic_create_short_packet(ngx_quic_he
     }
 
     sample = &out.data[4 - pkt->num_len];
-    if (ngx_quic_tls_hp(pkt->log, ciphers.hp, pkt->secret, mask, sample)
+    if (ngx_quic_tls_hp(pkt->log, ciphers.hp, secret, mask, sample)
         != NGX_OK)
     {
         return NGX_ERROR;
@@ -902,7 +973,7 @@ ngx_quic_create_retry_packet(ngx_quic_he
     ngx_quic_hexdump(pkt->log, "quic retry itag", ad.data, ad.len);
 #endif
 
-    if (ngx_quic_ciphers(NULL, &ciphers, pkt->level) == NGX_ERROR) {
+    if (ngx_quic_ciphers(0, &ciphers, pkt->level) == NGX_ERROR) {
         return NGX_ERROR;
     }
 
@@ -1033,24 +1104,22 @@ ngx_quic_compute_nonce(u_char *nonce, si
 
 
 ngx_int_t
-ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-    ngx_str_t *res)
+ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res)
 {
     if (ngx_quic_short_pkt(pkt->flags)) {
-        return ngx_quic_create_short_packet(pkt, ssl_conn, res);
+        return ngx_quic_create_short_packet(pkt, res);
     }
 
     if (ngx_quic_pkt_retry(pkt->flags)) {
         return ngx_quic_create_retry_packet(pkt, res);
     }
 
-    return ngx_quic_create_long_packet(pkt, ssl_conn, res);
+    return ngx_quic_create_long_packet(pkt, res);
 }
 
 
 ngx_int_t
-ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-    uint64_t *largest_pn)
+ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn)
 {
     u_char               clearflags, *p, *sample;
     size_t               len;
@@ -1062,11 +1131,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
     ngx_quic_ciphers_t   ciphers;
     uint8_t              mask[16], nonce[12];
 
-    if (ngx_quic_ciphers(ssl_conn, &ciphers, pkt->level) == NGX_ERROR) {
+    if (ngx_quic_ciphers(pkt->keys->cipher, &ciphers, pkt->level) == NGX_ERROR)
+    {
         return NGX_ERROR;
     }
 
-    secret = pkt->secret;
+    secret = &pkt->keys->secrets[pkt->level].client;
 
     p = pkt->raw->pos;
     len = pkt->data + pkt->len - p;
@@ -1099,7 +1169,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
         key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0;
 
         if (key_phase != pkt->key_phase) {
-            secret = pkt->next;
+            secret = &pkt->keys->next_key.client;
             pkt->key_update = 1;
         }
     }
--- a/src/event/ngx_event_quic_protection.h
+++ b/src/event/ngx_event_quic_protection.h
@@ -15,37 +15,24 @@
 #define NGX_QUIC_ENCRYPTION_LAST  ((ssl_encryption_application) + 1)
 
 
-typedef struct ngx_quic_secret_s {
-    ngx_str_t                 secret;
-    ngx_str_t                 key;
-    ngx_str_t                 iv;
-    ngx_str_t                 hp;
-} ngx_quic_secret_t;
-
+ngx_quic_keys_t *ngx_quic_keys_new(ngx_pool_t *pool);
+ngx_int_t ngx_quic_keys_set_initial_secret(ngx_pool_t *pool,
+    ngx_quic_keys_t *keys, ngx_str_t *secret);
+int ngx_quic_keys_set_encryption_secret(ngx_pool_t *pool, ngx_uint_t is_write,
+    ngx_quic_keys_t *keys, enum ssl_encryption_level_t level,
+    const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
+ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys,
+     enum ssl_encryption_level_t level);
+void ngx_quic_keys_discard(ngx_quic_keys_t *keys,
+     enum ssl_encryption_level_t level);
+void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
+ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys);
 
-typedef struct {
-    ngx_quic_secret_t         client;
-    ngx_quic_secret_t         server;
-} ngx_quic_secrets_t;
-
-
-ngx_int_t ngx_quic_set_initial_secret(ngx_pool_t *pool,
-    ngx_quic_secret_t *client, ngx_quic_secret_t *server,
-    ngx_str_t *secret);
-
-int ngx_quic_set_encryption_secret(ngx_pool_t *pool, ngx_ssl_conn_t *ssl_conn,
-    enum ssl_encryption_level_t level, const uint8_t *secret, size_t secret_len,
-    ngx_quic_secret_t *peer_secret);
-
-ngx_int_t ngx_quic_key_update(ngx_connection_t *c,
-    ngx_quic_secrets_t *current, ngx_quic_secrets_t *next);
 ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid,
     ngx_str_t *key, u_char *token);
 
-ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-     ngx_str_t *res);
-ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
-     uint64_t *largest_pn);
+ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res);
+ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
 
 
 #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */
--- a/src/event/ngx_event_quic_transport.h
+++ b/src/event/ngx_event_quic_transport.h
@@ -290,8 +290,8 @@ struct ngx_quic_frame_s {
 typedef struct {
     ngx_log_t                                  *log;
 
-    struct ngx_quic_secret_s                   *secret;
-    struct ngx_quic_secret_s                   *next;
+    ngx_quic_keys_t                            *keys;
+
     ngx_msec_t                                  received;
     uint64_t                                    number;
     uint8_t                                     num_len;