changeset 8319:29354c6fc5f2 quic

TLS Key Update in QUIC. Old keys retention is yet to be implemented.
author Sergey Kandaurov <pluknet@nginx.com>
date Mon, 06 Apr 2020 14:54:08 +0300
parents 1bb5e8538d0c
children 6e1213ef469a
files src/event/ngx_event_quic.c src/event/ngx_event_quic_protection.c src/event/ngx_event_quic_protection.h src/event/ngx_event_quic_transport.h
diffstat 4 files changed, 156 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -68,6 +68,7 @@ struct ngx_quic_connection_s {
 
     ngx_quic_namespace_t              ns[NGX_QUIC_NAMESPACE_LAST];
     ngx_quic_secrets_t                keys[NGX_QUIC_ENCRYPTION_LAST];
+    ngx_quic_secrets_t                next_key;
     uint64_t                          crypto_offset[NGX_QUIC_ENCRYPTION_LAST];
 
     ngx_ssl_t                        *ssl;
@@ -88,6 +89,7 @@ struct ngx_quic_connection_s {
 
     unsigned                          send_timer_set:1;
     unsigned                          closing:1;
+    unsigned                          key_phase:1;
 };
 
 
@@ -979,7 +981,8 @@ ngx_quic_early_input(ngx_connection_t *c
 static ngx_int_t
 ngx_quic_app_input(ngx_connection_t *c, ngx_quic_header_t *pkt)
 {
-    ngx_quic_secrets_t     *keys;
+    ngx_int_t               rc;
+    ngx_quic_secrets_t     *keys, *next, tmp;
     ngx_quic_connection_t  *qc;
     static u_char           buf[NGX_QUIC_DEFAULT_MAX_PACKET_SIZE];
 
@@ -988,6 +991,7 @@ ngx_quic_app_input(ngx_connection_t *c, 
     qc = c->quic;
 
     keys = &c->quic->keys[ssl_encryption_application];
+    next = &c->quic->next_key;
 
     if (keys->client.key.len == 0) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
@@ -1000,6 +1004,8 @@ ngx_quic_app_input(ngx_connection_t *c, 
     }
 
     pkt->secret = &keys->client;
+    pkt->next = &next->client;
+    pkt->key_phase = c->quic->key_phase;
     pkt->level = ssl_encryption_application;
     pkt->plaintext = buf;
 
@@ -1007,7 +1013,31 @@ ngx_quic_app_input(ngx_connection_t *c, 
         return NGX_ERROR;
     }
 
-    return ngx_quic_payload_handler(c, pkt);
+    /* switch keys on Key Phase change */
+
+    if (pkt->key_update) {
+        c->quic->key_phase ^= 1;
+
+        tmp = *keys;
+        *keys = *next;
+        *next = tmp;
+    }
+
+    rc = ngx_quic_payload_handler(c, pkt);
+
+    if (rc == NGX_ERROR) {
+        return NGX_ERROR;
+    }
+
+    /* generate next keys */
+
+    if (pkt->key_update) {
+        if (ngx_quic_key_update(c, keys, next) != NGX_OK) {
+            return NGX_ERROR;
+        }
+    }
+
+    return rc;
 }
 
 
@@ -1330,6 +1360,18 @@ ngx_quic_handle_crypto_frame(ngx_connect
         ngx_quic_queue_frame(c->quic, frame);
         }
 #endif
+
+        /*
+         * Generating next keys before a key update is received.
+         * 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)
+        {
+            return NGX_ERROR;
+        }
     }
 
     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
@@ -1756,7 +1798,8 @@ ngx_quic_send_frames(ngx_connection_t *c
         pkt.flags = NGX_QUIC_PKT_HANDSHAKE;
 
     } else {
-        pkt.flags = 0x40; // TODO: macro, set FIXED bit
+        // TODO: macro, set FIXED bit
+        pkt.flags = 0x40 | (c->quic->key_phase ? NGX_QUIC_PKT_KPHASE : 0);
     }
 
     ngx_quic_set_packet_number(&pkt, ns);
--- a/src/event/ngx_event_quic_protection.c
+++ b/src/event/ngx_event_quic_protection.c
@@ -232,9 +232,11 @@ ngx_quic_hkdf_expand(ngx_pool_t *pool, c
     uint8_t  *p;
     uint8_t   info[20];
 
-    out->data = ngx_pnalloc(pool, out->len);
     if (out->data == NULL) {
-        return NGX_ERROR;
+        out->data = ngx_pnalloc(pool, out->len);
+        if (out->data == NULL) {
+            return NGX_ERROR;
+        }
     }
 
     info_len = 2 + 1 + label->len + 1;
@@ -622,6 +624,14 @@ ngx_quic_set_encryption_secret(ngx_pool_
         return 0;
     }
 
+    peer_secret->secret.data = ngx_pnalloc(pool, secret_len);
+    if (peer_secret->secret.data == NULL) {
+        return NGX_ERROR;
+    }
+
+    peer_secret->secret.len = secret_len;
+    ngx_memcpy(peer_secret->secret.data, secret, secret_len);
+
     peer_secret->key.len = key_len;
     peer_secret->iv.len = NGX_QUIC_IV_LEN;
     peer_secret->hp.len = key_len;
@@ -650,6 +660,83 @@ ngx_quic_set_encryption_secret(ngx_pool_
 }
 
 
+ngx_int_t
+ngx_quic_key_update(ngx_connection_t *c, ngx_quic_secrets_t *current,
+    ngx_quic_secrets_t *next)
+{
+    ngx_uint_t          i;
+    ngx_quic_ciphers_t  ciphers;
+
+    ngx_log_debug(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update");
+
+    if (ngx_quic_ciphers(c->ssl->connection, &ciphers,
+                         ssl_encryption_application)
+        == NGX_ERROR)
+    {
+        return NGX_ERROR;
+    }
+
+    next->client.secret.len = current->client.secret.len;
+    next->client.key.len = current->client.key.len;
+    next->client.iv.len = current->client.iv.len;
+    next->client.hp = current->client.hp;
+
+    next->server.secret.len = current->server.secret.len;
+    next->server.key.len = current->server.key.len;
+    next->server.iv.len = current->server.iv.len;
+    next->server.hp = current->server.hp;
+
+    struct {
+        ngx_str_t   label;
+        ngx_str_t  *key;
+        ngx_str_t  *secret;
+    } seq[] = {
+        {
+            ngx_string("tls13 quic ku"),
+            &next->client.secret,
+            &current->client.secret,
+        },
+        {
+            ngx_string("tls13 quic key"),
+            &next->client.key,
+            &next->client.secret,
+        },
+        {
+            ngx_string("tls13 quic iv"),
+            &next->client.iv,
+            &next->client.secret,
+        },
+        {
+            ngx_string("tls13 quic ku"),
+            &next->server.secret,
+            &current->server.secret,
+        },
+        {
+            ngx_string("tls13 quic key"),
+            &next->server.key,
+            &next->server.secret,
+        },
+        {
+            ngx_string("tls13 quic iv"),
+            &next->server.iv,
+            &next->server.secret,
+        },
+    };
+
+    for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) {
+
+        if (ngx_quic_hkdf_expand(c->pool, ciphers.d, seq[i].key, &seq[i].label,
+                                 seq[i].secret->data, seq[i].secret->len)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
+
+
 static ssize_t
 ngx_quic_create_long_packet(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
     ngx_str_t *res)
@@ -821,8 +908,9 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
 {
     u_char               clearflags, *p, *sample;
     uint64_t             pn;
-    ngx_int_t            pnl, rc;
+    ngx_int_t            pnl, rc, key_phase;
     ngx_str_t            in, ad;
+    ngx_quic_secret_t   *secret;
     ngx_quic_ciphers_t   ciphers;
     uint8_t              mask[16], nonce[12];
 
@@ -830,6 +918,8 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
         return NGX_ERROR;
     }
 
+    secret = pkt->secret;
+
     p = pkt->raw->pos;
 
     /* draft-ietf-quic-tls-23#section-5.4.2:
@@ -844,7 +934,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
 
     /* header protection */
 
-    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;
@@ -855,6 +945,12 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
 
     } else {
         clearflags = pkt->flags ^ (mask[0] & 0x1f);
+        key_phase = (clearflags & NGX_QUIC_PKT_KPHASE) != 0;
+
+        if (key_phase != pkt->key_phase) {
+            secret = pkt->next;
+            pkt->key_update = 1;
+        }
     }
 
     pnl = (clearflags & 0x03) + 1;
@@ -889,7 +985,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
         ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256;
     } while (--pnl);
 
-    ngx_memcpy(nonce, pkt->secret->iv.data, pkt->secret->iv.len);
+    ngx_memcpy(nonce, secret->iv.data, secret->iv.len);
     ngx_quic_compute_nonce(nonce, sizeof(nonce), pn);
 
     ngx_quic_hexdump0(pkt->log, "nonce", nonce, 12);
@@ -903,7 +999,7 @@ ngx_quic_decrypt(ngx_quic_header_t *pkt,
 
     pkt->payload.data = pkt->plaintext + ad.len;
 
-    rc = ngx_quic_tls_open(ciphers.c, pkt->secret, &pkt->payload,
+    rc = ngx_quic_tls_open(ciphers.c, secret, &pkt->payload,
                            nonce, &in, &ad, pkt->log);
 
     ngx_quic_hexdump0(pkt->log, "packet payload",
--- a/src/event/ngx_event_quic_protection.h
+++ b/src/event/ngx_event_quic_protection.h
@@ -33,6 +33,9 @@ int ngx_quic_set_encryption_secret(ngx_p
     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);
+
 ssize_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_ssl_conn_t *ssl_conn,
      ngx_str_t *res);
 
--- a/src/event/ngx_event_quic_transport.h
+++ b/src/event/ngx_event_quic_transport.h
@@ -19,6 +19,7 @@
 #define NGX_QUIC_PKT_ZRTT                                0xD0 /* 17.2.3 */
 #define NGX_QUIC_PKT_HANDSHAKE                           0xE0 /* 17.2.4 */
 #define NGX_QUIC_PKT_RETRY                               0xF0 /* 17.2.5 */
+#define NGX_QUIC_PKT_KPHASE                              0x04 /* 17.3   */
 
 #define ngx_quic_pkt_in(flags)     (((flags) & 0xF0) == NGX_QUIC_PKT_INITIAL)
 #define ngx_quic_pkt_zrtt(flags)   (((flags) & 0xF0) == NGX_QUIC_PKT_ZRTT)
@@ -237,6 +238,7 @@ typedef struct {
     ngx_log_t                                  *log;
 
     struct ngx_quic_secret_s                   *secret;
+    struct ngx_quic_secret_s                   *next;
     uint64_t                                    number;
     uint8_t                                     num_len;
     uint32_t                                    trunc;
@@ -258,8 +260,9 @@ typedef struct {
     u_char                                     *plaintext;
     ngx_str_t                                   payload; /* decrypted data */
 
-    ngx_uint_t                                  need_ack;
-                                                   /* unsigned need_ack:1; */
+    unsigned                                    need_ack:1;
+    unsigned                                    key_phase:1;
+    unsigned                                    key_update:1;
 } ngx_quic_header_t;