changeset 8185:6a76d9657772 quic

QUIC handshake final bits. Added handling of client Finished, both feeding and acknowledgement. This includes sending NST in 1-RTT triggered by a handshake process.
author Sergey Kandaurov <pluknet@nginx.com>
date Tue, 03 Mar 2020 17:25:02 +0300
parents ec1f84996990
children 0a2683df5f11
files src/event/ngx_event_quic.c
diffstat 1 files changed, 207 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -174,6 +174,9 @@ static int ngx_quic_add_handshake_data(n
 static ngx_int_t ngx_quic_create_long_packet(ngx_connection_t *c,
     ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in,
     ngx_str_t *res);
+static ngx_int_t ngx_quic_create_short_packet(ngx_connection_t *c,
+    ngx_ssl_conn_t *ssl_conn, ngx_quic_header_t *pkt, ngx_str_t *in,
+    ngx_str_t *res);
 static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
 static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
     enum ssl_encryption_level_t level, uint8_t alert);
@@ -441,14 +444,110 @@ ngx_quic_create_long_packet(ngx_connecti
 }
 
 
+static ngx_int_t
+ngx_quic_create_short_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn,
+    ngx_quic_header_t *pkt, ngx_str_t *payload, ngx_str_t *res)
+{
+    u_char                 *p, *pnp, *name, *nonce, *sample, *packet;
+    ngx_str_t               ad, out;
+    const EVP_CIPHER       *cipher;
+    ngx_quic_connection_t  *qc;
+
+    u_char                  mask[16];
+
+    qc = c->quic;
+
+    out.len = payload->len + EVP_GCM_TLS_TAG_LEN;
+
+    ad.data = ngx_alloc(25 /*max header*/, c->log);
+    if (ad.data == 0) {
+        return NGX_ERROR;
+    }
+
+    p = ad.data;
+
+    *p++ = 0x40;
+
+    p = ngx_cpymem(p, qc->scid.data, qc->scid.len);
+
+    pnp = p;
+
+    *p++ = (*pkt->number)++;
+
+    ad.len = p - ad.data;
+
+    ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
+
+    name = (u_char *) SSL_get_cipher(ssl_conn);
+
+    if (ngx_strcasecmp(name, (u_char *) "TLS_AES_128_GCM_SHA256") == 0
+        || ngx_strcasecmp(name, (u_char *) "(NONE)") == 0)
+    {
+        cipher = EVP_aes_128_gcm();
+
+    } else if (ngx_strcasecmp(name, (u_char *) "TLS_AES_256_GCM_SHA384") == 0) {
+        cipher = EVP_aes_256_gcm();
+
+    } else {
+        return NGX_ERROR;
+    }
+
+    nonce = ngx_pstrdup(c->pool, &pkt->secret->iv);
+    if (pkt->level == ssl_encryption_handshake) {
+        nonce[11] ^= (*pkt->number - 1);
+    }
+
+    ngx_quic_hexdump0(c->log, "server_iv", pkt->secret->iv.data, 12);
+    ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
+
+    if (ngx_quic_tls_seal(c, cipher, pkt->secret, &out, nonce, payload, &ad)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_quic_hexdump0(c->log, "out", out.data, out.len);
+
+    sample = &out.data[3]; // pnl=0
+    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    ngx_quic_hexdump0(c->log, "sample", sample, 16);
+    ngx_quic_hexdump0(c->log, "mask", mask, 16);
+    ngx_quic_hexdump0(c->log, "hp_key", pkt->secret->hp.data, 16);
+
+    // header protection, pnl = 0
+    ad.data[0] ^= mask[0] & 0x1f;
+    *pnp ^= mask[1];
+
+    packet = ngx_alloc(ad.len + out.len, c->log);
+    if (packet == 0) {
+        return NGX_ERROR;
+    }
+
+    p = ngx_cpymem(packet, ad.data, ad.len);
+    p = ngx_cpymem(p, out.data, out.len);
+
+    ngx_quic_hexdump0(c->log, "packet", packet, p - packet);
+
+    res->data = packet;
+    res->len = p - packet;
+
+    return NGX_OK;
+}
+
+
 static void
-ngx_quic_create_ack(u_char **p)
+ngx_quic_create_ack(u_char **p, uint64_t num)
 {
     ngx_quic_build_int(p, NGX_QUIC_FT_ACK);
+    ngx_quic_build_int(p, num);
     ngx_quic_build_int(p, 0);
     ngx_quic_build_int(p, 0);
-    ngx_quic_build_int(p, 0);
-    ngx_quic_build_int(p, 0);
+    ngx_quic_build_int(p, num);
 }
 
 
@@ -493,7 +592,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn
     ngx_quic_create_crypto(&p, (u_char *) data, len);
 
     if (level == ssl_encryption_initial) {
-        ngx_quic_create_ack(&p);
+        ngx_quic_create_ack(&p, 0);
 
         pkt.number = &qc->initial_pn;
         pkt.flags = NGX_QUIC_PKT_INITIAL;
@@ -510,6 +609,7 @@ ngx_quic_add_handshake_data(ngx_ssl_conn
 
     } else {
         pkt.number = &qc->appdata_pn;
+        pkt.secret = &qc->server_ad;
     }
 
     payload.len = p - payload.data;
@@ -518,10 +618,21 @@ ngx_quic_add_handshake_data(ngx_ssl_conn
                    "ngx_quic_add_handshake_data: clear_len:%uz",
                    payload.len);
 
-    if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res)
-        != NGX_OK)
-    {
-        return 0;
+    if (level == ssl_encryption_application) {
+
+        if (ngx_quic_create_short_packet(c, ssl_conn, &pkt, &payload, &res)
+            != NGX_OK)
+        {
+            return 0;
+        }
+
+    } else {
+
+        if (ngx_quic_create_long_packet(c, ssl_conn, &pkt, &payload, &res)
+            != NGX_OK)
+        {
+            return 0;
+        }
     }
 
     // TODO: save state of data to send into qc (push into queue)
@@ -884,11 +995,12 @@ ngx_quic_new_connection(ngx_connection_t
 static ngx_int_t
 ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
 {
+    int                     sslerr;
     ssize_t                 n;
     ngx_str_t               out;
     const EVP_CIPHER       *cipher;
     ngx_quic_connection_t  *qc;
-    u_char                  *p, *b;
+    u_char                 *p, *b;
 
     qc = c->quic;
 
@@ -1020,6 +1132,92 @@ ngx_quic_handshake_input(ngx_connection_
 
     ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
 
+    if (out.data[0] != 0x06) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "non-CRYPTO frame in HS packet, skipping");
+        return NGX_OK;
+    }
+
+    if (out.data[1] != 0x00) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "not yet supported CRYPTO offset in initial packet");
+        return NGX_ERROR;
+    }
+
+    uint8_t *crypto = &out.data[2];
+    uint64_t crypto_len = ngx_quic_parse_int(&crypto);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic Handshake packet CRYPTO length: %uL pp:%p:%p",
+                   crypto_len, out.data, crypto);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+                   (int) SSL_quic_read_level(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    if (!SSL_provide_quic_data(c->ssl->connection,
+                               SSL_quic_read_level(c->ssl->connection),
+                               crypto, crypto_len))
+    {
+        ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+                      "SSL_provide_quic_data() failed");
+        return NGX_ERROR;
+    }
+
+    n = SSL_do_handshake(c->ssl->connection);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
+
+    if (n == -1) {
+        sslerr = SSL_get_error(c->ssl->connection, n);
+
+        ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d",
+                       sslerr);
+
+        if (sslerr == SSL_ERROR_SSL) {
+            ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed");
+        }
+    }
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "SSL_quic_read_level: %d, SSL_quic_write_level: %d",
+                   (int) SSL_quic_read_level(c->ssl->connection),
+                   (int) SSL_quic_write_level(c->ssl->connection));
+
+    // ACK Client Finished
+
+    ngx_str_t                 payload, res;
+    ngx_quic_header_t         pkt;
+    ngx_memzero(&pkt, sizeof(ngx_quic_header_t));
+
+    pkt.level = ssl_encryption_handshake;
+    pkt.number = &qc->handshake_pn;
+    pkt.flags = NGX_QUIC_PKT_HANDSHAKE;
+    pkt.secret = &qc->server_hs;
+
+    payload.data = ngx_alloc(5 /*minimal ACK*/, c->log);
+    if (payload.data == 0) {
+        return 0;
+    }
+
+    p = payload.data;
+    ngx_quic_create_ack(&p, pn);
+
+    payload.len = p - payload.data;
+
+    if (ngx_quic_create_long_packet(c, c->ssl->connection, &pkt, &payload, &res)
+        != NGX_OK)
+    {
+        return 0;
+    }
+
+    qc->out = res;
+
+    if (ngx_quic_output(c) != NGX_OK) {
+        return 0;
+    }
+
     return NGX_OK;
 }