From 32b2728ebb15c793f98beca09e21bb81386d5ac5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Thu, 5 Mar 2020 17:18:33 +0300 Subject: [PATCH] Added functions to decrypt long packets. --- src/event/ngx_event_quic.c | 445 ++++++++++++++++++++++--------------- 1 file changed, 265 insertions(+), 180 deletions(-) diff --git a/src/event/ngx_event_quic.c b/src/event/ngx_event_quic.c index 5bd423f45..cc515f054 100644 --- 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_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); +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, ngx_quic_connection_t *qc, 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_connection_t *c, ngx_ssl_conn_t *ssl_conn, *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_conn, enum ssl_encryption_level_t level, 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; - if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet"); + p = pkt->buf.data; + + 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; - } + pkt->dcid.len = *p++; + pkt->dcid.data = p; + p += pkt->dcid.len; - c->quic = qc; + pkt->scid.len = *p++; + pkt->scid.data = p; + p += pkt->scid.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->pos = p; - ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); - b->pos += qc->dcid.len; + 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; - } - ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); - b->pos += qc->scid.len; +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; - 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; - } + p = pkt->pos; - ngx_memcpy(qc->token.data, b->pos, qc->token.len); - b->pos += qc->token.len; + pkt->token.len = ngx_quic_parse_int(&p); + pkt->token.data = p; - ngx_int_t plen = ngx_quic_parse_int(&b->pos); + p += pkt->token.len; - if (plen > b->last - b->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 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", 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_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_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 *c, ngx_ssl_t *ssl, ngx_buf_t *b) } } -// header protection + return NGX_OK; +} - uint8_t mask[16]; - if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample) + +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]; + + 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]); + + pkt->pn = pn; - ngx_quic_hexdump0(c->log, "sample", sample, 16); 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) { - if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce, - &in, &ad) - != NGX_OK) - { + 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); + + 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; } - ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len); + if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) { + return NGX_ERROR; + } + + 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 *c, ngx_ssl_t *ssl, ngx_buf_t *b) 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"); + 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 *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 (ngx_quic_process_handshake_header(c, &pkt) != 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(&p, pnl, &mask[1]); + pkt.secret = &qc->client_hs; - 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_decrypt(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); - - 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"); - 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_t *c, ngx_buf_t *bb) 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) {