changeset 8193:4355efde26d8 quic

Added functions to decrypt long packets.
author Vladimir Homutov <vl@nginx.com>
date Thu, 05 Mar 2020 17:18:33 +0300
parents fb0879c65650
children 817c82af127f
files src/event/ngx_event_quic.c
diffstat 1 files changed, 266 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_quic.c
+++ b/src/event/ngx_event_quic.c
@@ -185,9 +185,22 @@ typedef struct {
     ngx_uint_t          type;
     ngx_uint_t          *number;
     ngx_uint_t          flags;
-    ngx_uint_t          version;
-    ngx_str_t          *token;
+    uint32_t            version;
+    ngx_str_t           token;
     ngx_quic_level_t    level;
+
+    /* filled in by parser */
+    ngx_str_t           buf;      /* quic packet from wire */
+    u_char             *pos;      /* current parser position */
+
+    /* cleartext fields */
+    ngx_str_t           dcid;
+    ngx_str_t           scid;
+
+    uint64_t            pn;
+
+    ngx_str_t           payload;  /* decrypted payload */
+
 } ngx_quic_header_t;
 
 
@@ -210,6 +223,15 @@ static int ngx_quic_flush_flight(ngx_ssl
 static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
     enum ssl_encryption_level_t level, uint8_t alert);
 
+static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c,
+    ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c,
+    ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c,
+    ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c);
+static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt);
+
 static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask);
 static uint64_t ngx_quic_parse_int(u_char **pos);
 static void ngx_quic_build_int(u_char **pos, uint64_t value);
@@ -287,7 +309,7 @@ ngx_quic_send_packet(ngx_connection_t *c
         pkt.number = &qc->initial_pn;
         pkt.flags = NGX_QUIC_PKT_INITIAL;
         pkt.secret = &qc->server_in;
-        pkt.token = &initial_token;
+        pkt.token = initial_token;
 
         if (ngx_quic_create_long_packet(c, c->ssl->connection,
                                         &pkt, payload, &res)
@@ -599,8 +621,8 @@ ngx_quic_create_long_packet(ngx_connecti
     *p++ = qc->dcid.len;
     p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len);
 
-    if (pkt->token) { // if pkt->flags & initial ?
-        ngx_quic_build_int(&p, pkt->token->len);
+    if (pkt->level == ssl_encryption_initial) {
+        ngx_quic_build_int(&p, pkt->token.len);
     }
 
     ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
@@ -865,90 +887,114 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_
 
 
 static ngx_int_t
-ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
+ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
 {
-    int                     n, sslerr;
-    ngx_quic_connection_t  *qc;
+    u_char  *p;
+
+    p = pkt->buf.data;
 
-    if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet");
+    ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len);
+
+    if (!(p[0] & NGX_QUIC_PKT_LONG)) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet");
         return NGX_ERROR;
     }
 
-    if (ngx_buf_size(b) < 1200) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
-        return NGX_ERROR;
-    }
+    pkt->flags = *p++;
 
-    ngx_int_t flags = *b->pos++;
-    uint32_t version = ngx_quic_parse_uint32(b->pos);
-    b->pos += sizeof(uint32_t);
+    pkt->version = ngx_quic_parse_uint32(p);
+    p += sizeof(uint32_t);
 
     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic flags:%xi version:%xD", flags, version);
+                   "quic flags:%xi version:%xD", pkt->flags, pkt->version);
 
-    if (version != quic_version) {
+    if (pkt->version != quic_version) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
         return NGX_ERROR;
     }
 
-    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
-    if (qc == NULL) {
-        return NGX_ERROR;
-    }
-
-    c->quic = qc;
+    pkt->dcid.len = *p++;
+    pkt->dcid.data = p;
+    p += pkt->dcid.len;
 
-    qc->dcid.len = *b->pos++;
-    qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
-    if (qc->dcid.data == NULL) {
-        return NGX_ERROR;
-    }
+    pkt->scid.len = *p++;
+    pkt->scid.data = p;
+    p += pkt->scid.len;
 
-    ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len);
-    b->pos += qc->dcid.len;
+    pkt->pos = p;
+
+    return NGX_OK;
+}
+
 
-    qc->scid.len = *b->pos++;
-    qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
-    if (qc->scid.data == NULL) {
-        return NGX_ERROR;
-    }
+static ngx_int_t
+ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+    u_char     *p;
+    ngx_int_t   plen;
 
-    ngx_memcpy(qc->scid.data, b->pos, qc->scid.len);
-    b->pos += qc->scid.len;
+    p = pkt->pos;
 
-    qc->token.len = ngx_quic_parse_int(&b->pos);
-    qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
-    if (qc->token.data == NULL) {
-        return NGX_ERROR;
-    }
+    pkt->token.len = ngx_quic_parse_int(&p);
+    pkt->token.data = p;
+
+    p += pkt->token.len;
 
-    ngx_memcpy(qc->token.data, b->pos, qc->token.len);
-    b->pos += qc->token.len;
+    plen = ngx_quic_parse_int(&p);
 
-    ngx_int_t plen = ngx_quic_parse_int(&b->pos);
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic packet length: %d", plen);
 
-    if (plen > b->last - b->pos) {
+    if (plen > pkt->buf.data + pkt->buf.len - p) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet");
         return NGX_ERROR;
     }
 
-    /* draft-ietf-quic-tls-23#section-5.4.2:
-     * the Packet Number field is assumed to be 4 bytes long
-     * draft-ietf-quic-tls-23#section-5.4.[34]:
-     * AES-Based and ChaCha20-Based header protections sample 16 bytes
-     */
-    u_char *sample = b->pos + 4;
+    pkt->pos = p;
+    pkt->buf.len = plen;
 
-    ngx_quic_hexdump0(c->log, "DCID", qc->dcid.data, qc->dcid.len);
-    ngx_quic_hexdump0(c->log, "SCID", qc->scid.data, qc->scid.len);
-    ngx_quic_hexdump0(c->log, "token", qc->token.data, qc->token.len);
+    ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len);
+    ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len);
+    ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len);
+
     ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic packet length: %d", plen);
-    ngx_quic_hexdump0(c->log, "sample", sample, 16);
+
+    return NGX_OK;
+}
+
+static ngx_int_t
+ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+    u_char     *p;
+    ngx_int_t   plen;
+
+    p = pkt->pos;
+
+    plen = ngx_quic_parse_int(&p);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic packet length: %d", plen);
+
+    if (plen > pkt->buf.data + pkt->buf.len - p) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
+        return NGX_ERROR;
+    }
+
+    pkt->pos = p;
+    pkt->buf.len = plen;
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic packet length: %d", plen);
+
+    return NGX_OK;
+}
 
 
-// initial secret
+static ngx_int_t
+ngx_quic_initial_secret(ngx_connection_t *c)
+{
+    ngx_quic_connection_t *qc = c->quic;
 
     size_t                    is_len;
     uint8_t                   is[SHA256_DIGEST_LENGTH];
@@ -1047,57 +1093,176 @@ ngx_quic_new_connection(ngx_connection_t
         }
     }
 
-// header protection
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+    u_char      clearflags, *p, *sample;
+    uint8_t    *nonce;
+    uint64_t    pn;
+    ngx_int_t   pnl, rc;
+    ngx_str_t   in, ad;
+
+    const EVP_CIPHER       *cipher;
 
-    uint8_t mask[16];
-    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample)
+    uint8_t     mask[16];
+
+    p = pkt->pos;
+
+    /* draft-ietf-quic-tls-23#section-5.4.2:
+     * the Packet Number field is assumed to be 4 bytes long
+     * draft-ietf-quic-tls-23#section-5.4.[34]:
+     * AES-Based and ChaCha20-Based header protections sample 16 bytes
+     */
+
+    sample = p + 4;
+
+    ngx_quic_hexdump0(c->log, "sample", sample, 16);
+
+    /* header protection */
+
+    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample)
         != NGX_OK)
     {
         return NGX_ERROR;
     }
 
-    u_char  clearflags = flags ^ (mask[0] & 0x0f);
-    ngx_int_t  pnl = (clearflags & 0x03) + 1;
-    uint64_t  pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]);
+    clearflags = pkt->flags ^ (mask[0] & 0x0f);
+    pnl = (clearflags & 0x03) + 1;
+    pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
 
-    ngx_quic_hexdump0(c->log, "sample", sample, 16);
+    pkt->pn = pn;
+
     ngx_quic_hexdump0(c->log, "mask", mask, 5);
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+                   "quic clear flags: %xi", clearflags);
     ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
                    "quic packet number: %uL, len: %xi", pn, pnl);
 
-// packet protection
+    /* packet protection */
 
-    ngx_str_t in;
-    in.data = b->pos;
-    in.len = plen - pnl;
+    in.data = p;
+    in.len = pkt->buf.len - pnl;
 
-    ngx_str_t ad;
-    ad.len = b->pos - b->start;
+    ad.len = p - pkt->buf.data;;
     ad.data = ngx_pnalloc(c->pool, ad.len);
     if (ad.data == NULL) {
         return NGX_ERROR;
     }
 
-    ngx_memcpy(ad.data, b->start, ad.len);
+    ngx_memcpy(ad.data, pkt->buf.data, ad.len);
     ad.data[0] = clearflags;
-    ad.data[ad.len - pnl] = (u_char)pn;
+    ad.data[ad.len - pnl] = (u_char) pn;
 
-    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv);
+    nonce = ngx_pstrdup(c->pool, &pkt->secret->iv);
     nonce[11] ^= pn;
 
     ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
     ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
 
-    ngx_str_t  out;
+    if (c->ssl) {
+        switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) {
+
+        case NGX_AES_128_GCM_SHA256:
+            cipher = EVP_aes_128_gcm();
+            break;
+        case NGX_AES_256_GCM_SHA384:
+            cipher = EVP_aes_256_gcm();
+            break;
+        default:
+            ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
+            return NGX_ERROR;
+        }
+
+    } else {
+        /* initial packets */
+        cipher = EVP_aes_128_gcm();
+    }
+
+    rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload,
+                           nonce, &in, &ad);
 
-    if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce,
-                          &in, &ad)
-        != NGX_OK)
-    {
+    ngx_quic_hexdump0(c->log, "packet payload",
+                      pkt->payload.data, pkt->payload.len);
+
+    return rc;
+}
+
+
+static ngx_int_t
+ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
+{
+    int                     n, sslerr;
+    ngx_str_t               out;
+    ngx_quic_connection_t  *qc;
+
+    ngx_quic_header_t pkt = { 0 };
+
+    pkt.buf.data = b->start;
+    pkt.buf.len = b->last - b->pos;
+
+    if (ngx_buf_size(b) < 1200) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
+        return NGX_ERROR;
+    }
+
+    if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
+    if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "invalid initial packet: 0x%x", pkt.flags);
+        return NGX_ERROR;
+    }
+
+    if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
+    if (qc == NULL) {
+        return NGX_ERROR;
+    }
+
+    c->quic = qc;
+
+    qc->dcid.len = pkt.dcid.len;
+    qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len);
+    if (qc->dcid.data == NULL) {
+        return NGX_ERROR;
+    }
+    ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len);
+
+    qc->scid.len = pkt.scid.len;
+    qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
+    if (qc->scid.data == NULL) {
+        return NGX_ERROR;
+    }
+    ngx_memcpy(qc->scid.data, pkt.scid.data, qc->scid.len);
+
+    qc->token.len = pkt.token.len;
+    qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
+    if (qc->token.data == NULL) {
+        return NGX_ERROR;
+    }
+    ngx_memcpy(qc->token.data, pkt.token.data, qc->token.len);
+
+
+    if (ngx_quic_initial_secret(c) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    pkt.secret = &qc->client_in;
+
+    if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    out = pkt.payload;
 
     if (out.data[0] != 0x06) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
@@ -1183,144 +1348,64 @@ ngx_quic_new_connection(ngx_connection_t
 
 
 static ngx_int_t
-ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
+ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b)
 {
     int                     sslerr;
-    u_char                 *p, *b;
     ssize_t                 n;
     ngx_str_t               out;
     ngx_ssl_conn_t         *ssl_conn;
-    const EVP_CIPHER       *cipher;
     ngx_quic_connection_t  *qc;
 
+    ngx_quic_header_t pkt = { 0 };
+
     qc = c->quic;
     ssl_conn = c->ssl->connection;
 
-    n = bb->last - bb->pos;
-    p = bb->pos;
-    b = bb->start;
+    pkt.buf.data = b->start;
+    pkt.buf.len = b->last - b->pos;
 
-    ngx_quic_hexdump0(c->log, "input", p, n);
-
-    if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type");
+    /* extract cleartext data into pkt */
+    if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    ngx_int_t flags = *p++;
-    uint32_t version = ngx_quic_parse_uint32(p);
-    p += sizeof(uint32_t);
-
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic flags:%xi version:%xD", flags, version);
-
-    if (version != quic_version) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
-        return NGX_ERROR;
-    }
-
-    if (*p++ != qc->dcid.len) {
+    if (pkt.dcid.len != qc->dcid.len) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
         return NGX_ERROR;
     }
 
-    if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) {
+    if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
         return NGX_ERROR;
     }
 
-    p += qc->dcid.len;
-
-    if (*p++ != qc->scid.len) {
+    if (pkt.scid.len != qc->scid.len) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl");
         return NGX_ERROR;
     }
 
-    if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) {
+    if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid");
         return NGX_ERROR;
     }
 
-    p += qc->scid.len;
-
-    ngx_int_t plen = ngx_quic_parse_int(&p);
-
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet length: %d", plen);
-
-    if (plen > b + n - p) {
-        ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
-        return NGX_ERROR;
-    }
-
-    u_char *sample = p + 4;
-
-    ngx_quic_hexdump0(c->log, "sample", sample, 16);
-
-// header protection
-
-    uint8_t mask[16];
-    if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample)
-        != NGX_OK)
-    {
+    if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
+        ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                      "invalid packet type: 0x%x", pkt.flags);
         return NGX_ERROR;
     }
 
-    u_char  clearflags = flags ^ (mask[0] & 0x0f);
-    ngx_int_t  pnl = (clearflags & 0x03) + 1;
-    uint64_t  pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
-
-    ngx_quic_hexdump0(c->log, "mask", mask, 5);
-    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic clear flags: %xi", clearflags);
-    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
-                   "quic packet number: %uL, len: %xi", pn, pnl);
-
-// packet protection
-
-    ngx_str_t in;
-    in.data = p;
-    in.len = plen - pnl;
-
-    ngx_str_t ad;
-    ad.len = p - b;
-    ad.data = ngx_pnalloc(c->pool, ad.len);
-    if (ad.data == NULL) {
+    if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    ngx_memcpy(ad.data, b, ad.len);
-    ad.data[0] = clearflags;
-    ad.data[ad.len - pnl] = (u_char)pn;
-
-    uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv);
-    nonce[11] ^= pn;
-
-    ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
-    ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
+    pkt.secret = &qc->client_hs;
 
-    switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) {
-
-    case NGX_AES_128_GCM_SHA256:
-        cipher = EVP_aes_128_gcm();
-        break;
-
-    case NGX_AES_256_GCM_SHA384:
-        cipher = EVP_aes_256_gcm();
-        break;
-
-    default:
-        ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
+    if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad)
-        != NGX_OK)
-    {
-        return NGX_ERROR;
-    }
-
-    ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
+    out = pkt.payload;
 
     if (out.data[0] != 0x06) {
         ngx_log_error(NGX_LOG_INFO, c->log, 0,
@@ -1388,9 +1473,9 @@ ngx_quic_handshake_input(ngx_connection_
 
     frame->level = ssl_encryption_handshake;
     frame->type = NGX_QUIC_FT_ACK;
-    frame->u.ack.pn = pn;
+    frame->u.ack.pn = pkt.pn;
 
-    ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn);
+    ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pkt.pn);
     ngx_quic_queue_frame(qc, frame);
 
     if (ngx_quic_output(c) != NGX_OK) {