# HG changeset patch # User Sergey Kandaurov # Date 1586174048 -10800 # Node ID 29354c6fc5f2d7c1ebddfd9a1574ccca5140bdd1 # Parent 1bb5e8538d0c76466b9f8048e125d823fd82cacd TLS Key Update in QUIC. Old keys retention is yet to be implemented. diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c --- 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); diff --git a/src/event/ngx_event_quic_protection.c b/src/event/ngx_event_quic_protection.c --- 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, + ¤t->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, + ¤t->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", diff --git a/src/event/ngx_event_quic_protection.h b/src/event/ngx_event_quic_protection.h --- 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); diff --git a/src/event/ngx_event_quic_transport.h b/src/event/ngx_event_quic_transport.h --- 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;