# HG changeset patch # User Sergey Kandaurov # Date 1582884591 -10800 # Node ID 5d91389e0fd30f3da38974c313e84314ccf8281e # Parent 7999d3fbb76529f8c17e9a27cd03384064204e8e Initial QUIC support in http. diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -1242,7 +1242,8 @@ if [ $USE_OPENSSL = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_openssl_module ngx_module_incs= - ngx_module_deps=src/event/ngx_event_openssl.h + ngx_module_deps="src/event/ngx_event_openssl.h \ + src/event/ngx_event_quic.h" ngx_module_srcs="src/event/ngx_event_openssl.c src/event/ngx_event_openssl_stapling.c" ngx_module_libs= diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -147,13 +147,14 @@ struct ngx_connection_s { socklen_t socklen; ngx_str_t addr_text; - ngx_proxy_protocol_t *proxy_protocol; + ngx_proxy_protocol_t *proxy_protocol; #if (NGX_SSL || NGX_COMPAT) - ngx_ssl_connection_t *ssl; + ngx_quic_connection_t *quic; + ngx_ssl_connection_t *ssl; #endif - ngx_udp_connection_t *udp; + ngx_udp_connection_t *udp; struct sockaddr *local_sockaddr; socklen_t local_socklen; diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -12,23 +12,24 @@ #include -typedef struct ngx_module_s ngx_module_t; -typedef struct ngx_conf_s ngx_conf_t; -typedef struct ngx_cycle_s ngx_cycle_t; -typedef struct ngx_pool_s ngx_pool_t; -typedef struct ngx_chain_s ngx_chain_t; -typedef struct ngx_log_s ngx_log_t; -typedef struct ngx_open_file_s ngx_open_file_t; -typedef struct ngx_command_s ngx_command_t; -typedef struct ngx_file_s ngx_file_t; -typedef struct ngx_event_s ngx_event_t; -typedef struct ngx_event_aio_s ngx_event_aio_t; -typedef struct ngx_connection_s ngx_connection_t; -typedef struct ngx_thread_task_s ngx_thread_task_t; -typedef struct ngx_ssl_s ngx_ssl_t; -typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; -typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; -typedef struct ngx_udp_connection_s ngx_udp_connection_t; +typedef struct ngx_module_s ngx_module_t; +typedef struct ngx_conf_s ngx_conf_t; +typedef struct ngx_cycle_s ngx_cycle_t; +typedef struct ngx_pool_s ngx_pool_t; +typedef struct ngx_chain_s ngx_chain_t; +typedef struct ngx_log_s ngx_log_t; +typedef struct ngx_open_file_s ngx_open_file_t; +typedef struct ngx_command_s ngx_command_t; +typedef struct ngx_file_s ngx_file_t; +typedef struct ngx_event_s ngx_event_t; +typedef struct ngx_event_aio_s ngx_event_aio_t; +typedef struct ngx_connection_s ngx_connection_t; +typedef struct ngx_thread_task_s ngx_thread_task_t; +typedef struct ngx_ssl_s ngx_ssl_t; +typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; +typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; +typedef struct ngx_udp_connection_s ngx_udp_connection_t; typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); @@ -82,6 +83,7 @@ typedef void (*ngx_connection_handler_pt #include #if (NGX_OPENSSL) #include +#include #endif #include #include diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -89,6 +89,126 @@ static void *ngx_openssl_create_conf(ngx static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void ngx_openssl_exit(ngx_cycle_t *cycle); +#if NGX_OPENSSL_QUIC + +static int +quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len) +{ + size_t *len; + uint8_t **rsec, **wsec; + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_ssl_handshake_log(c); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + u_char buf[64]; + size_t m; + + m = ngx_hex_dump(buf, (u_char *) read_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + + m = ngx_hex_dump(buf, (u_char *) write_secret, secret_len) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "set_encryption_secrets: %*s, len: %uz, level:%d", + m, buf, secret_len, (int) level); + } +#endif + + switch (level) { + + case ssl_encryption_handshake: + len = &c->quic->handshake_secret_len; + rsec = &c->quic->handshake_read_secret; + wsec = &c->quic->handshake_write_secret; + break; + + case ssl_encryption_application: + len = &c->quic->application_secret_len; + rsec = &c->quic->application_read_secret; + wsec = &c->quic->application_write_secret; + break; + + default: + return 0; + } + + *len = secret_len; + + *rsec = ngx_pnalloc(c->pool, secret_len); + if (*rsec == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(*rsec, read_secret, secret_len); + + *wsec = ngx_pnalloc(c->pool, secret_len); + if (*wsec == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(*wsec, write_secret, secret_len); + + return 1; +} + + +static int +quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char buf[512]; + ngx_int_t m; + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + m = ngx_hex_dump(buf, (u_char *) data, ngx_min(len, 256)) - buf; + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic_add_handshake_data: %*s%s, len: %uz, level:%d", + m, buf, len < 512 ? "" : "...", len, (int) level); + + if (!(SSL_provide_quic_data(ssl_conn, level, data, len))) { + ERR_print_errors_fp(stderr); + return 0; + } + + return 1; +} + + +static int +quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ + printf("quic_flush_flight()\n"); + return 1; +} + + +static int +quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + printf("quic_send_alert(), lvl=%d, alert=%d\n", level, alert); + return 1; +} + + +static SSL_QUIC_METHOD quic_method = { + quic_set_encryption_secrets, + quic_add_handshake_data, + quic_flush_flight, + quic_send_alert, +}; + +#endif + static ngx_command_t ngx_openssl_commands[] = { @@ -1460,6 +1580,29 @@ ngx_ssl_early_data(ngx_conf_t *cf, ngx_s ngx_int_t +ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) +{ + if (!enable) { + return NGX_OK; + } + +#if NGX_OPENSSL_QUIC + + SSL_CTX_set_quic_method(ssl->ctx, &quic_method); +printf("%s\n", __func__); + return NGX_OK; + +#else + + ngx_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_quic\" is not supported on this platform"); + return NGX_ERROR; + +#endif +} + + +ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable) { if (!enable) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #endif #include +#include #include #ifndef OPENSSL_NO_OCSP #include @@ -189,6 +191,7 @@ ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); +ngx_int_t ngx_ssl_quic(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_client_session_cache(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_uint_t enable); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, diff --git a/src/event/ngx_event_quic.h b/src/event/ngx_event_quic.h new file mode 100644 --- /dev/null +++ b/src/event/ngx_event_quic.h @@ -0,0 +1,31 @@ + +/* + * + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +struct ngx_quic_connection_s { + ngx_str_t scid; + ngx_str_t dcid; + ngx_str_t token; + + ngx_str_t client_in; + ngx_str_t client_in_key; + ngx_str_t client_in_iv; + ngx_str_t client_in_hp; + + size_t handshake_secret_len; + uint8_t *handshake_read_secret; + uint8_t *handshake_write_secret; + + size_t application_secret_len; + uint8_t *application_read_secret; + uint8_t *application_write_secret; +}; + + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -249,6 +249,13 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, early_data), NULL }, + { ngx_string("ssl_quic"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, quic), + NULL }, + ngx_null_command }; @@ -568,6 +575,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t sscf->enable = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; + sscf->quic = NGX_CONF_UNSET; sscf->buffer_size = NGX_CONF_UNSET_SIZE; sscf->verify = NGX_CONF_UNSET_UINT; sscf->verify_depth = NGX_CONF_UNSET_UINT; @@ -612,6 +620,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_value(conf->early_data, prev->early_data, 0); + ngx_conf_merge_value(conf->quic, prev->quic, 0); + ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); @@ -696,6 +706,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * } } +printf("ngx_ssl_create\n"); if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) { return NGX_CONF_ERROR; } @@ -857,6 +868,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * return NGX_CONF_ERROR; } + if (ngx_ssl_quic(cf, &conf->ssl, conf->quic) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -1141,13 +1156,15 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { +printf("ssl %d http3 %d\n", addr[a].opt.ssl, addr[a].opt.http3); - if (!addr[a].opt.ssl) { + if (!addr[a].opt.ssl && !addr[a].opt.http3) { continue; } cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; +printf("sscf->protocols %lx\n", sscf->protocols); if (sscf->certificates == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, @@ -1156,6 +1173,14 @@ ngx_http_ssl_init(ngx_conf_t *cf) cscf->file_name, cscf->line); return NGX_ERROR; } + + if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" did not enable TLSv1.3 for " + "the \"listen ... http3\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } } } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -21,6 +21,7 @@ typedef struct { ngx_flag_t prefer_server_ciphers; ngx_flag_t early_data; + ngx_flag_t quic; ngx_uint_t protocols; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1203,6 +1203,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_SSL) + ngx_uint_t http3; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1238,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; #endif +#if (NGX_HTTP_SSL) + http3 = lsopt->http3 || addr[i].opt.http3; +#endif if (lsopt->set) { @@ -1274,6 +1280,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, n #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_SSL) + addr[i].opt.http3 = http3; +#endif return NGX_OK; } @@ -1317,6 +1326,17 @@ ngx_http_add_address(ngx_conf_t *cf, ngx #endif +#if (NGX_HTTP_SSL && !defined NGX_OPENSSL_QUIC) + + if (lsopt->http3) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "nginx was built with OpenSSL that lacks QUIC " + "support, HTTP/3 is not enabled for %V", + &lsopt->addr_text); + } + +#endif + addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; @@ -1807,6 +1827,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_h #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SSL) + addrs[i].conf.http3 = addr[i].opt.http3; +#endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL @@ -1872,6 +1895,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_ #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; #endif +#if (NGX_HTTP_SSL) + addrs6[i].conf.http3 = addr[i].opt.http3; +#endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; if (addr[i].hash.buckets == NULL diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -3822,11 +3822,6 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx continue; } - if (ngx_strcmp(value[n].data, "quic") == 0) { - lsopt.type = SOCK_DGRAM; - continue; - } - if (ngx_strcmp(value[n].data, "bind") == 0) { lsopt.set = 1; lsopt.bind = 1; @@ -4004,6 +3999,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx #endif } + if (ngx_strcmp(value[n].data, "http3") == 0) { +#if (NGX_HTTP_SSL) + lsopt.http3 = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"http3\" parameter requires " + "ngx_http_ssl_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strcmp(value[n].data, "spdy") == 0) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "invalid parameter \"spdy\": " diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -75,6 +75,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned http3:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -238,6 +239,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned http3:1; unsigned proxy_protocol:1; }; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -62,6 +62,8 @@ static u_char *ngx_http_log_error_handle #if (NGX_HTTP_SSL) static void ngx_http_ssl_handshake(ngx_event_t *rev); static void ngx_http_ssl_handshake_handler(ngx_connection_t *c); + +static void ngx_http_quic_handshake(ngx_event_t *rev); #endif @@ -328,6 +330,14 @@ ngx_http_init_connection(ngx_connection_ rev->ready = 1; } +#if (NGX_HTTP_SSL) + if (hc->addr_conf->http3) { + hc->quic = 1; + c->log->action = "QUIC handshaking"; + rev->handler = ngx_http_quic_handshake; + } +#endif + #if (NGX_HTTP_V2) if (hc->addr_conf->http2) { rev->handler = ngx_http_v2_init; @@ -647,6 +657,491 @@ ngx_http_alloc_request(ngx_connection_t #if (NGX_HTTP_SSL) +static uint64_t +ngx_quic_parse_int(u_char **pos) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + p = *pos; + len = 1 << ((*p & 0xc0) >> 6); + value = *p++ & 0x3f; + + while (--len) { + value = (value << 8) + *p++; + } + + *pos = p; + return value; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask) +{ + u_char *p; + uint64_t value; + + p = *pos; + value = *p++ ^ *mask++; + + while (--len) { + value = (value << 8) + (*p++ ^ *mask++); + } + + *pos = p; + return value; +} + + +static void +ngx_http_quic_handshake(ngx_event_t *rev) +{ + int n, sslerr; +#if (NGX_DEBUG) + u_char buf[512]; + size_t m; +#endif + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_connection_t *hc; + ngx_quic_connection_t *qc; + ngx_http_ssl_srv_conf_t *sscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "quic handshake"); + + c = rev->data; + hc = c->data; + b = c->buffer; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + ngx_http_close_connection(c); + return; + } + + c->quic = qc; + + printf("buffer %p %p:%p:%p:%p \n", b, b->start, b->pos, b->last, b->end); + + if ((b->pos[0] & 0xf0) != 0xc0) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "invalid initial packet"); + ngx_http_close_connection(c); + return; + } + + if (ngx_buf_size(b) < 1200) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "too small UDP datagram"); + ngx_http_close_connection(c); + return; + } + + ngx_int_t flags = *b->pos++; + uint32_t version = ngx_http_v2_parse_uint32(b->pos); + b->pos += 4; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic flags:%xi version:%xD", flags, version); + + if (version != 0xff000017) { + ngx_log_error(NGX_LOG_INFO, rev->log, 0, "unsupported quic version"); + ngx_http_close_connection(c); + return; + } + + qc->dcid.len = *b->pos++; + qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len); + if (qc->dcid.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len); + b->pos += qc->dcid.len; + + qc->scid.len = *b->pos++; + qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len); + if (qc->scid.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->scid.data, b->pos, qc->scid.len); + b->pos += qc->scid.len; + + qc->token.len = ngx_quic_parse_int(&b->pos); + qc->token.data = ngx_pnalloc(c->pool, qc->token.len); + if (qc->token.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(qc->token.data, b->pos, qc->token.len); + b->pos += qc->token.len; + + uint64_t plen = ngx_quic_parse_int(&b->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.3: + * AES-Based header protection samples 16 bytes + */ + u_char *sample = b->pos + 4; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->dcid.data, qc->dcid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic DCID: %*s, len: %uz", m, buf, qc->dcid.len); + + m = ngx_hex_dump(buf, qc->scid.data, qc->scid.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic SCID: %*s, len: %uz", m, buf, qc->scid.len); + + m = ngx_hex_dump(buf, qc->token.data, qc->token.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic token: %*s, len: %uz", m, buf, qc->token.len); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet length: %d", plen); + + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic sample: %*s", m, buf); + } +#endif + +// initial secret + + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + const EVP_MD *digest; + static const uint8_t salt[20] = + "\xc3\xee\xf7\x12\xc7\x2e\xbb\x5a\x11\xa7" + "\xd2\x43\x2b\xb4\x63\x65\xbe\xf9\xf5\x02"; + + digest = EVP_sha256(); + HKDF_extract(is, &is_len, digest, qc->dcid.data, qc->dcid.len, salt, + sizeof(salt)); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, (uint8_t *) salt, sizeof(salt)) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic salt: %*s, len: %uz", m, buf, sizeof(salt)); + + m = ngx_hex_dump(buf, is, is_len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic initial secret: %*s, len: %uz", m, buf, is_len); + } +#endif + + size_t hkdfl_len; + uint8_t hkdfl[20]; + uint8_t *p; + + /* draft-ietf-quic-tls-23#section-5.2 */ + + qc->client_in.len = SHA256_DIGEST_LENGTH; + qc->client_in.data = ngx_pnalloc(c->pool, qc->client_in.len); + if (qc->client_in.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 client in") - 1 + 1; + hkdfl[0] = 0; + hkdfl[1] = qc->client_in.len; + hkdfl[2] = sizeof("tls13 client in") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 client in", + sizeof("tls13 client in") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in.data, qc->client_in.len, + digest, is, is_len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in) failed"); + ngx_http_close_connection(c); + return; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic EVP key:%d tag:%d nonce:%d", + EVP_AEAD_key_length(EVP_aead_aes_128_gcm()), + EVP_AEAD_max_tag_len(EVP_aead_aes_128_gcm()), + EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm())); + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.3 */ + + qc->client_in_key.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->client_in_key.data = ngx_pnalloc(c->pool, qc->client_in_key.len); + if (qc->client_in_key.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic key") - 1 + 1; + hkdfl[1] = qc->client_in_key.len; + hkdfl[2] = sizeof("tls13 quic key") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic key", + sizeof("tls13 quic key") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_key.data, qc->client_in_key.len, + digest, qc->client_in.data, qc->client_in.len, + hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_key) failed"); + ngx_http_close_connection(c); + return; + } + + qc->client_in_iv.len = EVP_AEAD_nonce_length(EVP_aead_aes_128_gcm()); + qc->client_in_iv.data = ngx_pnalloc(c->pool, qc->client_in_iv.len); + if (qc->client_in_iv.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic iv") - 1 + 1; + hkdfl[1] = qc->client_in_iv.len; + hkdfl[2] = sizeof("tls13 quic iv") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic iv", sizeof("tls13 quic iv") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_iv.data, qc->client_in_iv.len, digest, + qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_iv) failed"); + ngx_http_close_connection(c); + return; + } + + /* AEAD_AES_128_GCM prior to handshake, quic-tls-23#section-5.4.1 */ + + qc->client_in_hp.len = EVP_AEAD_key_length(EVP_aead_aes_128_gcm()); + qc->client_in_hp.data = ngx_pnalloc(c->pool, qc->client_in_hp.len); + if (qc->client_in_hp.data == NULL) { + ngx_http_close_connection(c); + return; + } + + hkdfl_len = 2 + 1 + sizeof("tls13 quic hp") - 1 + 1; + hkdfl[1] = qc->client_in_hp.len; + hkdfl[2] = sizeof("tls13 quic hp") - 1; + p = ngx_cpymem(&hkdfl[3], "tls13 quic hp", sizeof("tls13 quic hp") - 1); + *p = '\0'; + + if (HKDF_expand(qc->client_in_hp.data, qc->client_in_hp.len, digest, + qc->client_in.data, qc->client_in.len, hkdfl, hkdfl_len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "HKDF_expand(client_in_hp) failed"); + ngx_http_close_connection(c); + return; + } + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, qc->client_in.data, qc->client_in.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic client initial secret: %*s, len: %uz", + m, buf, qc->client_in.len); + + m = ngx_hex_dump(buf, qc->client_in_key.data, qc->client_in_key.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic key: %*s, len: %uz", + m, buf, qc->client_in_key.len); + + m = ngx_hex_dump(buf, qc->client_in_iv.data, qc->client_in_iv.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic iv: %*s, len: %uz", m, buf, qc->client_in_iv.len); + + m = ngx_hex_dump(buf, qc->client_in_hp.data, qc->client_in_hp.len) + - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic hp: %*s, len: %uz", m, buf, qc->client_in_hp.len); + } +#endif + +// header protection + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + uint8_t mask[16]; + int outlen; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, + qc->client_in_hp.data, NULL) + != 1) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptInit_ex() failed"); + ngx_http_close_connection(c); + return; + } + + if (!EVP_EncryptUpdate(ctx, mask, &outlen, sample, 16)) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_EncryptUpdate() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_CIPHER_CTX_free(ctx); + + 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]); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, sample, 16) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic sample: %*s", m, buf); + + m = ngx_hex_dump(buf, mask, 5) - buf; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic mask: %*s", m, buf); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet number: %uL, len: %xi", pn, pnl); + } +#endif + +// packet protection + + ngx_str_t ciphertext; + ciphertext.data = b->pos; + ciphertext.len = plen - pnl; + + ngx_str_t ad; + ad.len = b->pos - b->start; + ad.data = ngx_pnalloc(c->pool, ad.len); + if (ad.data == NULL) { + ngx_http_close_connection(c); + return; + } + + ngx_memcpy(ad.data, b->start, ad.len); + ad.data[0] = clearflags; + ad.data[ad.len - pnl] = (u_char)pn; + + uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in_iv); + nonce[11] ^= pn; + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, nonce, 12) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic nonce: %*s, len: %uz", m, buf, 12); + + m = ngx_hex_dump(buf, ad.data, ad.len) - buf; + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic ad: %*s, len: %uz", m, buf, ad.len); + } +#endif + + EVP_AEAD_CTX *aead = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), + qc->client_in_key.data, + qc->client_in_key.len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + uint8_t cleartext[1600]; + size_t cleartext_len = sizeof(cleartext); + + if (EVP_AEAD_CTX_open(aead, cleartext, &cleartext_len, sizeof(cleartext), + nonce, qc->client_in_iv.len, ciphertext.data, + ciphertext.len, ad.data, ad.len) + != 1) + { + EVP_AEAD_CTX_free(aead); + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "EVP_AEAD_CTX_open() failed"); + ngx_http_close_connection(c); + return; + } + + EVP_AEAD_CTX_free(aead); + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + m = ngx_hex_dump(buf, cleartext, ngx_min(cleartext_len, 256)) - buf; + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, rev->log, 0, + "quic packet: %*s%s, len: %uz", + m, buf, m < 512 ? "" : "...", cleartext_len); + } +#endif + + sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); + + if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) + != NGX_OK) + { + ngx_http_close_connection(c); + return; + } + + 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); + } + + 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), + &cleartext[4], cleartext_len - 4)) + { + ngx_ssl_error(NGX_LOG_INFO, rev->log, 0, + "SSL_provide_quic_data() failed"); + ngx_http_close_connection(c); + return; + } + + 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)); + + ngx_http_close_connection(c); + return; +} + + static void ngx_http_ssl_handshake(ngx_event_t *rev) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -323,6 +323,7 @@ typedef struct { ngx_chain_t *free; unsigned ssl:1; + unsigned quic:1; unsigned proxy_protocol:1; } ngx_http_connection_t;