[PATCH 5 of 8] QUIC: reusing crypto contexts for packet protection

Sergey Kandaurov pluknet at nginx.com
Tue Oct 17 10:38:23 UTC 2023


> On 13 Oct 2023, at 19:13, Sergey Kandaurov <pluknet at nginx.com> wrote:
> 
> 
>> On 19 Sep 2023, at 17:53, Roman Arutyunyan <arut at nginx.com> wrote:
>> 
>> Hi,
>> 
>> On Thu, Sep 07, 2023 at 07:13:57PM +0400, Sergey Kandaurov wrote:
>>> # HG changeset patch
>>> # User Sergey Kandaurov <pluknet at nginx.com>
>>> # Date 1694099424 -14400
>>> #      Thu Sep 07 19:10:24 2023 +0400
>>> # Node ID 28f7491bc79771f9cfa882b1b5584fa48ea42e6b
>>> # Parent  24e5d652ecc861f0c68607d20941abbf3726fdf1
>>> QUIC: reusing crypto contexts for packet protection.
>>> 
>>> diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
>>> --- a/src/event/quic/ngx_event_quic.c
>>> +++ b/src/event/quic/ngx_event_quic.c
>>> @@ -225,6 +225,7 @@ ngx_quic_new_connection(ngx_connection_t
>>> {
>>>    ngx_uint_t              i;
>>>    ngx_quic_tp_t          *ctp;
>>> +    ngx_pool_cleanup_t     *cln;
>>>    ngx_quic_connection_t  *qc;
>>> 
>>>    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
>>> @@ -237,6 +238,14 @@ ngx_quic_new_connection(ngx_connection_t
>>>        return NULL;
>>>    }
>>> 
>>> +    cln = ngx_pool_cleanup_add(c->pool, 0);
>>> +    if (cln == NULL) {
>>> +        return NULL;
>>> +    }
>>> +
>>> +    cln->handler = ngx_quic_keys_cleanup;
>>> +    cln->data = qc->keys;
>> 
>> I think it's better to cleanup keys in ngx_quic_close_connection().
>> We do the same with sockets by calling ngx_quic_close_sockets().
>> We just have to carefully handle the errors later in this function and cleanup
>> keys when ngx_quic_open_sockets() fails.
> 
> While this may look error prone compared with the cleanup handler,
> you convinced me to remove it because of ngx_quic_send_early_cc().
> To be merged:
> 
> diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
> --- a/src/event/quic/ngx_event_quic.c
> +++ b/src/event/quic/ngx_event_quic.c
> @@ -227,7 +227,6 @@ ngx_quic_new_connection(ngx_connection_t
> {
>     ngx_uint_t              i;
>     ngx_quic_tp_t          *ctp;
> -    ngx_pool_cleanup_t     *cln;
>     ngx_quic_connection_t  *qc;
> 
>     qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
> @@ -240,14 +239,6 @@ ngx_quic_new_connection(ngx_connection_t
>         return NULL;
>     }
> 
> -    cln = ngx_pool_cleanup_add(c->pool, 0);
> -    if (cln == NULL) {
> -        return NULL;
> -    }
> -
> -    cln->handler = ngx_quic_keys_cleanup;
> -    cln->data = qc->keys;
> -
>     qc->version = pkt->version;
> 
>     ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
> @@ -344,6 +335,7 @@ ngx_quic_new_connection(ngx_connection_t
>     qc->validated = pkt->validated;
> 
>     if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) {
> +        ngx_quic_keys_cleanup(qc->keys);
>         return NULL;
>     }
> 
> @@ -594,6 +586,8 @@ ngx_quic_close_connection(ngx_connection
> 
>     ngx_quic_close_sockets(c);
> 
> +    ngx_quic_keys_cleanup(qc->keys);
> +
>     ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed");
> 
>     /* may be tested from SSL callback during SSL shutdown */
> diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c
> --- a/src/event/quic/ngx_event_quic_output.c
> +++ b/src/event/quic/ngx_event_quic_output.c
> @@ -941,13 +941,17 @@ ngx_quic_send_early_cc(ngx_connection_t 
>     res.data = dst;
> 
>     if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) {
> +        ngx_quic_keys_cleanup(pkt.keys);
>         return NGX_ERROR;
>     }
> 
>     if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) {
> +        ngx_quic_keys_cleanup(pkt.keys);
>         return NGX_ERROR;
>     }
> 
> +    ngx_quic_keys_cleanup(pkt.keys);
> +
>     return NGX_DONE;
> }
> 
> diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c
> --- a/src/event/quic/ngx_event_quic_protection.c
> +++ b/src/event/quic/ngx_event_quic_protection.c
> @@ -189,10 +189,16 @@ ngx_quic_keys_set_initial_secret(ngx_qui
>     }
> 
>     if (ngx_quic_crypto_init(ciphers.c, server, 1, log) == NGX_ERROR) {
> -        return NGX_ERROR;
> +        goto failed;
>     }
> 
>     return NGX_OK;
> +
> +failed:
> +
> +    ngx_quic_keys_cleanup(keys);
> +
> +    return NGX_ERROR;
> }
> 
> 
> @@ -793,10 +799,8 @@ failed:
> 
> 
> void
> -ngx_quic_keys_cleanup(void *data)
> +ngx_quic_keys_cleanup(ngx_quic_keys_t *keys)
> {
> -    ngx_quic_keys_t *keys = data;
> -
>     size_t               i;
>     ngx_quic_secrets_t  *secrets;
> 
> diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h
> --- a/src/event/quic/ngx_event_quic_protection.h
> +++ b/src/event/quic/ngx_event_quic_protection.h
> @@ -103,7 +103,7 @@ void ngx_quic_keys_discard(ngx_quic_keys
>     enum ssl_encryption_level_t level);
> void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys);
> void ngx_quic_keys_update(ngx_event_t *ev);
> -void ngx_quic_keys_cleanup(void *data);
> +void ngx_quic_keys_cleanup(ngx_quic_keys_t *keys);
> 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);
> void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn);
> 
> 
>> 
>>>    qc->version = pkt->version;
>>> 
>>>    ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel,
>>> diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c
>>> --- a/src/event/quic/ngx_event_quic_openssl_compat.c
>>> +++ b/src/event/quic/ngx_event_quic_openssl_compat.c
>>> @@ -54,9 +54,10 @@ struct ngx_quic_compat_s {
>>> 
>>> 
>>> static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line);
>>> -static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
>>> +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_connection_t *c,
>>>    ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
>>>    const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len);
>>> +static void ngx_quic_compat_cleanup_encryption_secret(void *data);
>>> static int ngx_quic_compat_add_transport_params_callback(SSL *ssl,
>>>    unsigned int ext_type, unsigned int context, const unsigned char **out,
>>>    size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg);
>>> @@ -214,14 +215,14 @@ ngx_quic_compat_keylog_callback(const SS
>>>        com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n);
>>>        com->read_record = 0;
>>> 
>>> -        (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, level,
>>> +        (void) ngx_quic_compat_set_encryption_secret(c, &com->keys, level,
>>>                                                     cipher, secret, n);
>>>    }
>>> }
>>> 
>>> 
>>> static ngx_int_t
>>> -ngx_quic_compat_set_encryption_secret(ngx_log_t *log,
>>> +ngx_quic_compat_set_encryption_secret(ngx_connection_t *c,
>>>    ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level,
>>>    const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
>>> {
>>> @@ -231,6 +232,7 @@ ngx_quic_compat_set_encryption_secret(ng
>>>    ngx_quic_hkdf_t      seq[2];
>>>    ngx_quic_secret_t   *peer_secret;
>>>    ngx_quic_ciphers_t   ciphers;
>>> +    ngx_pool_cleanup_t  *cln;
>>> 
>>>    peer_secret = &keys->secret;
>>> 
>>> @@ -239,12 +241,12 @@ ngx_quic_compat_set_encryption_secret(ng
>>>    key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level);
>>> 
>>>    if (key_len == NGX_ERROR) {
>>> -        ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher");
>>> +        ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
>>>        return NGX_ERROR;
>>>    }
>>> 
>>>    if (sizeof(peer_secret->secret.data) < secret_len) {
>>> -        ngx_log_error(NGX_LOG_ALERT, log, 0,
>>> +        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
>>>                      "unexpected secret len: %uz", secret_len);
>>>        return NGX_ERROR;
>>>    }
>>> @@ -262,15 +264,42 @@ ngx_quic_compat_set_encryption_secret(ng
>>>    ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str);
>>> 
>>>    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
>>> -        if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) {
>>> +        if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) {
>>>            return NGX_ERROR;
>>>        }
>>>    }
>>> 
>>> +    ngx_quic_crypto_cleanup(peer_secret);
>>> +
>>> +    if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) {
>>> +        return NGX_ERROR;
>>> +    }
>>> +
>>> +    /* register cleanup handler once */
>>> +
>>> +    if (level == ssl_encryption_handshake) {
>> 
>> Does not look perfect, but I don't see a simpler and better solution.
> 
> I don't see either, without introducing some state (see below).
> 
>> 
>>> +        cln = ngx_pool_cleanup_add(c->pool, 0);
>>> +        if (cln == NULL) {
>> 
>> Cleanup peer_secret here?
>> 
>> Alternatively, move this block up.
> 
> Fixed, thanks.
> 
> With introducing keys->cleanup:
> 
> diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c
> --- a/src/event/quic/ngx_event_quic_openssl_compat.c
> +++ b/src/event/quic/ngx_event_quic_openssl_compat.c
> @@ -25,6 +25,7 @@
> typedef struct {
>     ngx_quic_secret_t             secret;
>     ngx_uint_t                    cipher;
> +    ngx_uint_t                    cleanup;  /* unsigned cleanup:1 */
> } ngx_quic_compat_keys_t;
> 
> 
> @@ -269,15 +270,11 @@ ngx_quic_compat_set_encryption_secret(ng
>         }
>     }
> 
> -    ngx_quic_crypto_cleanup(peer_secret);
> -
> -    if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) {
> -        return NGX_ERROR;
> -    }
> -
>     /* register cleanup handler once */
> 
> -    if (level == ssl_encryption_handshake) {
> +    if (!keys->cleanup) {
> +        keys->cleanup = 1;
> +
>         cln = ngx_pool_cleanup_add(c->pool, 0);
>         if (cln == NULL) {
>             return NGX_ERROR;
> @@ -287,6 +284,12 @@ ngx_quic_compat_set_encryption_secret(ng
>         cln->data = peer_secret;
>     }
> 
> +    ngx_quic_crypto_cleanup(peer_secret);
> +
> +    if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) {
> +        return NGX_ERROR;
> +    }
> +
>     return NGX_OK;
> }
> 

Version without a cleanup field (to be merged).
While it may look less readable (and I like it less),
it allows to save ngx_uint_t per connection.

diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c
--- a/src/event/quic/ngx_event_quic_openssl_compat.c
+++ b/src/event/quic/ngx_event_quic_openssl_compat.c
@@ -269,15 +269,12 @@ ngx_quic_compat_set_encryption_secret(ng
         }
     }
 
-    ngx_quic_crypto_cleanup(peer_secret);
-
-    if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) {
-        return NGX_ERROR;
-    }
-
     /* register cleanup handler once */
 
-    if (level == ssl_encryption_handshake) {
+    if (peer_secret->ctx) {
+        ngx_quic_crypto_cleanup(peer_secret);
+
+    } else {
         cln = ngx_pool_cleanup_add(c->pool, 0);
         if (cln == NULL) {
             return NGX_ERROR;
@@ -287,6 +284,10 @@ ngx_quic_compat_set_encryption_secret(ng
         cln->data = peer_secret;
     }
 
+    if (ngx_quic_crypto_init(ciphers.c, peer_secret, 1, c->log) == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }
 

> 
>> 
>>> +            return NGX_ERROR;
>>> +        }
>>> +
>>> +        cln->handler = ngx_quic_compat_cleanup_encryption_secret;
>>> +        cln->data = peer_secret;
>>> +    }
>>> +
>>>    return NGX_OK;
>>> }
>>> 
>>> 
>>> +static void
>>> +ngx_quic_compat_cleanup_encryption_secret(void *data)
>>> +{
>>> +    ngx_quic_secret_t *secret = data;
>>> +
>>> +    ngx_quic_crypto_cleanup(secret);
>>> +}
>>> +
>>> +

[..]

-- 
Sergey Kandaurov


More information about the nginx-devel mailing list