On Mon, Jan 09, 2023 at 03:13:16PM +0400, Roman Arutyunyan wrote: > # HG changeset patch > # User Roman Arutyunyan <a...@nginx.com> > # Date 1673262402 -14400 > # Mon Jan 09 15:06:42 2023 +0400 > # Branch quic > # Node ID 4e5dfe13c84fe50bec639f1b7dcc81604378a42b > # Parent aaa2a3831eefe4315dfb8a9be7178c79ff67f163 > QUIC: OpenSSL compatibility layer. > > The change allows to compile QUIC with OpenSSL which lacks BoringSSL QUIC API. > > This implementation does not support 0-RTT. > > diff --git a/README b/README > --- a/README > +++ b/README > @@ -53,7 +53,7 @@ 1. Introduction > > 2. Installing > > - A library that provides QUIC support is required to build nginx, there > + A library that provides QUIC support is recommended to build nginx, there > are several of those available on the market: > + BoringSSL [4] > + LibreSSL [5] > @@ -85,6 +85,10 @@ 2. Installing > --with-cc-opt="-I../libressl/build/include" \ > --with-ld-opt="-L../libressl/build/lib" > > + Alternatively, nginx can be configured with OpenSSL compatibility > + layer, which emulates BoringSSL QUIC API for OpenSSL. This mode is > + enabled by default if native QUIC support is not detected. > + > When configuring nginx, it's possible to enable QUIC and HTTP/3 > using the following new configuration options: > > diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf > --- a/auto/lib/openssl/conf > +++ b/auto/lib/openssl/conf > @@ -10,6 +10,7 @@ if [ $OPENSSL != NONE ]; then > > if [ $USE_OPENSSL_QUIC = YES ]; then > have=NGX_QUIC . auto/have > + have=NGX_QUIC_OPENSSL_COMPAT . auto/have
This won't build with QuicTLS sources specified in --with-openssl, due to type/function redefinitions. The patch to address this: diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h b/src/event/quic/ngx_event_quic_openssl_compat.h --- a/src/event/quic/ngx_event_quic_openssl_compat.h +++ b/src/event/quic/ngx_event_quic_openssl_compat.h @@ -7,6 +7,10 @@ #ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ #define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ +#ifdef TLSEXT_TYPE_quic_transport_parameters +#undef NGX_QUIC_OPENSSL_COMPAT +#else + #include <ngx_config.h> #include <ngx_core.h> @@ -48,4 +52,6 @@ void SSL_get_peer_quic_transport_params( const uint8_t **out_params, size_t *out_params_len); +#endif /* TLSEXT_TYPE_quic_transport_parameters */ + #endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ > fi > > case "$CC" in > @@ -124,6 +125,45 @@ else > CORE_INCS="$CORE_INCS $ngx_feature_path" > CORE_LIBS="$CORE_LIBS $ngx_feature_libs" > OPENSSL=YES > + > + if [ $USE_OPENSSL_QUIC = YES ]; then > + > + ngx_feature="OpenSSL QUIC support" > + ngx_feature_name="NGX_OPENSSL_QUIC" > + ngx_feature_run=no > + ngx_feature_incs="#include <openssl/ssl.h>" > + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" > + . auto/feature > + > + if [ $ngx_found = no ]; then bad indent > + > + ngx_feature="OpenSSL QUIC compatibility" > + ngx_feature_name="NGX_QUIC_OPENSSL_COMPAT" > + ngx_feature_run=no > + ngx_feature_incs="#include <openssl/ssl.h>" > + ngx_feature_test=" > + SSL_set_max_early_data(NULL, TLS1_3_VERSION); While practicaly useful to check for TLSv1.3-aware library, this abuses SSL_set_max_early_data() API. > + SSL_CTX_set_msg_callback(NULL, NULL); > + SSL_CTX_set_keylog_callback(NULL, NULL); > + SSL_CTX_add_custom_ext(NULL, 0, 0, NULL, NULL, > + NULL, NULL, NULL)" Just using SSL_CTX_add_custom_ext() seems to be sufficient to ensure this is OpenSSL version 1.1.1+. > + . auto/feature > + fi > + > + if [ $ngx_found = no ]; then > +cat << END > + > +$0: error: certain modules require OpenSSL QUIC support. > +You can either do not enable the modules, or install the OpenSSL library with > +QUIC support into the system, or build the OpenSSL library with QUIC support > +statically from the source with nginx by using --with-openssl=<path> option. > + > +END > + exit 1 > + fi > + > + have=NGX_QUIC . auto/have > + fi > fi > fi > > @@ -139,29 +179,4 @@ with nginx by using --with-openssl=<path > END > exit 1 > fi > - > - if [ $USE_OPENSSL_QUIC = YES ]; then > - > - ngx_feature="OpenSSL QUIC support" > - ngx_feature_name="NGX_QUIC" > - ngx_feature_run=no > - ngx_feature_incs="#include <openssl/ssl.h>" > - ngx_feature_path= > - ngx_feature_libs="-lssl -lcrypto $NGX_LIBDL $NGX_LIBPTHREAD" > - ngx_feature_test="SSL_set_quic_method(NULL, NULL)" > - . auto/feature > - > - if [ $ngx_found = no ]; then > - > -cat << END > - > -$0: error: certain modules require OpenSSL QUIC support. > -You can either do not enable the modules, or install the OpenSSL library with > -QUIC support into the system, or build the OpenSSL library with QUIC support > -statically from the source with nginx by using --with-openssl=<path> option. > - > -END > - exit 1 > - fi > - fi > fi > diff --git a/auto/modules b/auto/modules > --- a/auto/modules > +++ b/auto/modules > @@ -1342,7 +1342,8 @@ if [ $USE_OPENSSL_QUIC = YES ]; then > src/event/quic/ngx_event_quic_tokens.h \ > src/event/quic/ngx_event_quic_ack.h \ > src/event/quic/ngx_event_quic_output.h \ > - src/event/quic/ngx_event_quic_socket.h" > + src/event/quic/ngx_event_quic_socket.h \ > + src/event/quic/ngx_event_quic_openssl_compat.h" > ngx_module_srcs="src/event/quic/ngx_event_quic.c \ > src/event/quic/ngx_event_quic_udp.c \ > src/event/quic/ngx_event_quic_transport.c \ > @@ -1355,7 +1356,8 @@ if [ $USE_OPENSSL_QUIC = YES ]; then > src/event/quic/ngx_event_quic_tokens.c \ > src/event/quic/ngx_event_quic_ack.c \ > src/event/quic/ngx_event_quic_output.c \ > - src/event/quic/ngx_event_quic_socket.c" > + src/event/quic/ngx_event_quic_socket.c \ > + src/event/quic/ngx_event_quic_openssl_compat.c" > > ngx_module_libs= > ngx_module_link=YES > 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 > @@ -9,6 +9,10 @@ > #include <ngx_core.h> > #include <ngx_event.h> > > +#if (NGX_QUIC_OPENSSL_COMPAT) > +#include <ngx_event_quic_openssl_compat.h> > +#endif > + > > #define NGX_SSL_PASSWORD_BUFFER_SIZE 4096 > > @@ -392,6 +396,10 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_ > > SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback); > > +#if (NGX_QUIC_OPENSSL_COMPAT) > + ngx_quic_compat_init(ssl->ctx); > +#endif > + This enables compatibility unconditionally for all TLS versions and consumers, including such modules as the mail module and connections to upstream. While SSL_CTX_add_custom_ext() to enable the transport parameters extension in TLS < 1.3 seems to be harmless, and enabling the keylog callback just leads to redundant function calls, I propose to move this close to consumers, e.g.: 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 @@ -9,6 +9,10 @@ #include <ngx_core.h> #include <ngx_http.h> +#if (NGX_QUIC_OPENSSL_COMPAT) +#include <ngx_event_quic_openssl_compat.h> +#endif + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -1317,16 +1321,19 @@ ngx_http_ssl_init(ngx_conf_t *cf) continue; } + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + if (addr[a].opt.http3) { name = "http3"; +#if (NGX_QUIC_OPENSSL_COMPAT) + ngx_quic_compat_init(sscf->ssl.ctx); +#endif } else { name = "ssl"; } - cscf = addr[a].default_server; - sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (sscf->certificates) { if (addr[a].opt.http3 && !(sscf->protocols & NGX_SSL_TLSv1_3)) { (with similar change in the stream module) So we could remove SOCK_DGRAM checks. > return NGX_OK; > } > > diff --git a/src/event/quic/ngx_event_quic_connection.h > b/src/event/quic/ngx_event_quic_connection.h > --- a/src/event/quic/ngx_event_quic_connection.h > +++ b/src/event/quic/ngx_event_quic_connection.h > @@ -24,6 +24,9 @@ typedef struct ngx_quic_send_ctx_s ng > typedef struct ngx_quic_socket_s ngx_quic_socket_t; > typedef struct ngx_quic_path_s ngx_quic_path_t; > typedef struct ngx_quic_keys_s ngx_quic_keys_t; > +#if (NGX_QUIC_OPENSSL_COMPAT) > +typedef struct ngx_quic_compat_s ngx_quic_compat_t; > +#endif > > #include <ngx_event_quic_transport.h> > #include <ngx_event_quic_protection.h> > @@ -36,6 +39,9 @@ typedef struct ngx_quic_keys_s ng > #include <ngx_event_quic_ack.h> > #include <ngx_event_quic_output.h> > #include <ngx_event_quic_socket.h> > +#if (NGX_QUIC_OPENSSL_COMPAT) > +#include <ngx_event_quic_openssl_compat.h> > +#endif > > > /* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ > @@ -236,6 +242,10 @@ struct ngx_quic_connection_s { > ngx_uint_t nshadowbufs; > #endif > > +#if (NGX_QUIC_OPENSSL_COMPAT) > + ngx_quic_compat_t *compat; > +#endif > + > ngx_quic_streams_t streams; > ngx_quic_congestion_t congestion; > > diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c > b/src/event/quic/ngx_event_quic_openssl_compat.c > new file mode 100644 > --- /dev/null > +++ b/src/event/quic/ngx_event_quic_openssl_compat.c > @@ -0,0 +1,656 @@ > + > +/* > + * Copyright (C) Nginx, Inc. > + */ > + > + > +#include <ngx_config.h> > +#include <ngx_core.h> > +#include <ngx_event.h> > +#include <ngx_event_quic_connection.h> > + > + > +#if (NGX_QUIC_OPENSSL_COMPAT) > + > +#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 > + > +#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 For the record: SSL library sources have the TLSEXT_TYPE_quic_transport_parameters extension value written in the decimal form, so could we. An exception is QuicTLS, which seems to derive hex form from draft days. > + > +#define NGX_QUIC_COMPAT_CLIENT_EARLY "CLIENT_EARLY_TRAFFIC_SECRET" > +#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE > "CLIENT_HANDSHAKE_TRAFFIC_SECRET" > +#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE > "SERVER_HANDSHAKE_TRAFFIC_SECRET" > +#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" > +#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" > + > + > +typedef struct { > + ngx_quic_secret_t secret; > + ngx_uint_t cipher; > +} ngx_quic_compat_keys_t; > + > + > +typedef struct { > + ngx_log_t *log; > + > + u_char type; > + ngx_str_t payload; > + uint64_t number; > + ngx_quic_compat_keys_t *keys; > + > + enum ssl_encryption_level_t level; > +} ngx_quic_compat_record_t; > + > + > +struct ngx_quic_compat_s { > + const SSL_QUIC_METHOD *method; > + > + enum ssl_encryption_level_t write_level; > + enum ssl_encryption_level_t read_level; > + > + uint64_t read_record; > + ngx_quic_compat_keys_t keys; > + > + ngx_str_t tp; > + ngx_str_t ctp; > +}; > + > + > +static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char > *line); > +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_log_t *log, > + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, > + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); > +static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, > + unsigned int ext_type, unsigned int context, const unsigned char **out, > + size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); > +static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, > + unsigned int ext_type, unsigned int context, const unsigned char *in, > + size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); > +static void ngx_quic_compat_message_callback(int write_p, int version, > + int content_type, const void *buf, size_t len, SSL *ssl, void *arg); > +static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, > + u_char *out, ngx_uint_t plain); > +static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, > + ngx_str_t *res); > + > + > +ngx_int_t > +ngx_quic_compat_init(SSL_CTX *ctx) > +{ > + SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); > + > + if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, > + SSL_EXT_CLIENT_HELLO > + |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, > + ngx_quic_compat_add_transport_params_callback, > + NULL, > + NULL, > + > ngx_quic_compat_parse_transport_params_callback, > + NULL) > + == 0) > + { > + return NGX_ERROR; > + } > + > + return NGX_OK; > +} > + > + > +static void > +ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) > +{ > + u_char ch, *p, *start, value; > + size_t n; > + ngx_uint_t write; > + const SSL_CIPHER *cipher; > + ngx_quic_compat_t *com; > + ngx_connection_t *c; > + ngx_quic_connection_t *qc; > + enum ssl_encryption_level_t level; > + u_char secret[EVP_MAX_MD_SIZE]; > + > + c = ngx_ssl_get_connection(ssl); > + if (c->type != SOCK_DGRAM) { > + return; > + } > + > + p = (u_char *) line; > + > + for (start = p; *p && *p != ' '; p++); > + > + n = p - start; > + > + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat secret %*s", n, start); > + > + if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_EARLY) - 1 > + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_EARLY, n) == 0) For the record, with the current approach, this condition will never match: > + { > + level = ssl_encryption_early_data; > + write = 0; > + > + } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 > + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == > 0) > + { > + level = ssl_encryption_handshake; > + write = 0; > + > + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 > + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == > 0) > + { > + level = ssl_encryption_handshake; > + write = 1; > + > + } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 > + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) > + == 0) > + { > + level = ssl_encryption_application; > + write = 0; > + > + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 > + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) > + == 0) > + { > + level = ssl_encryption_application; > + write = 1; > + > + } else { > + return; > + } > + > + if (*p++ == '\0') { > + return; > + } > + > + for ( /* void */ ; *p && *p != ' '; p++); > + > + if (*p++ == '\0') { > + return; > + } > + > + for (n = 0, start = p; *p; p++) { > + ch = *p; > + > + if (ch >= '0' && ch <= '9') { > + value = ch - '0'; > + goto next; > + } > + > + ch = (u_char) (ch | 0x20); > + > + if (ch >= 'a' && ch <= 'f') { > + value = ch - 'a' + 10; > + goto next; > + } > + > + ngx_log_error(NGX_LOG_EMERG, c->log, 0, > + "invalid OpenSSL QUIC secret format"); > + > + return; > + > + next: > + > + if ((p - start) % 2) { > + secret[n++] += value; > + > + } else { > + if (n >= EVP_MAX_MD_SIZE) { > + ngx_log_error(NGX_LOG_EMERG, c->log, 0, > + "too big OpenSSL QUIC secret"); > + return; > + } > + > + secret[n] = (value << 4); > + } > + } > + > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + cipher = SSL_get_current_cipher(ssl); > + > + if (write) { > + com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); > + com->write_level = level; > + > + } else { > + com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); > + com->read_level = level; > + com->read_record = 0; > + > + (void) ngx_quic_compat_set_encryption_secret(c->log, &com->keys, > level, > + cipher, secret, n); > + } > +} > + > + > +static ngx_int_t > +ngx_quic_compat_set_encryption_secret(ngx_log_t *log, > + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, > + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) > +{ > + ngx_int_t key_len; > + ngx_str_t secret_str; > + ngx_uint_t i; > + ngx_quic_hkdf_t seq[2]; > + ngx_quic_secret_t *peer_secret; > + ngx_quic_ciphers_t ciphers; > + > + peer_secret = &keys->secret; > + > + keys->cipher = SSL_CIPHER_get_id(cipher); > + > + key_len = ngx_quic_ciphers(keys->cipher, &ciphers, level); > + > + if (key_len == NGX_ERROR) { > + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); > + return NGX_ERROR; > + } > + > + if (sizeof(peer_secret->secret.data) < secret_len) { > + ngx_log_error(NGX_LOG_ALERT, log, 0, > + "unexpected secret len: %uz", secret_len); > + 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; > + > + secret_str.len = secret_len; > + secret_str.data = (u_char *) secret; > + > + ngx_quic_hkdf_set(&seq[0], "tls13 key", &peer_secret->key, &secret_str); > + ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); > + > + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { > + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { > + return NGX_ERROR; > + } > + } > + > + return NGX_OK; > +} > + > + > +static int > +ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int > ext_type, > + unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, > + size_t chainidx, int *al, void *add_arg) > +{ > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + if (c->type != SOCK_DGRAM) { > + return 0; > + } > + > + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat add transport params"); > + > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + > + *out = com->tp.data; > + *outlen = com->tp.len; > + > + return 1; > +} > + > + > +static int > +ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int > ext_type, > + unsigned int context, const unsigned char *in, size_t inlen, X509 *x, > + size_t chainidx, int *al, void *parse_arg) > +{ > + u_char *p; > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + if (c->type != SOCK_DGRAM) { > + return 0; > + } > + > + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat parse transport params"); > + > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + > + p = ngx_pnalloc(c->pool, inlen); > + if (p == NULL) { > + return 0; > + } > + > + ngx_memcpy(p, in, inlen); > + > + com->ctp.data = p; > + com->ctp.len = inlen; > + > + return 1; > +} > + > + > +int > +SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) > +{ > + BIO *rbio, *wbio; > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + > + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); > + > + qc = ngx_quic_get_connection(c); > + > + qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); > + if (qc->compat == NULL) { > + return 0; > + } > + > + com = qc->compat; > + com->method = quic_method; > + > + rbio = BIO_new(BIO_s_mem()); > + if (rbio == NULL) { > + return 0; > + } > + > + wbio = BIO_new(BIO_s_null()); > + if (wbio == NULL) { > + return 0; > + } > + > + SSL_set_bio(ssl, rbio, wbio); > + > + SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); > + > + /* early data is not supported */ > + SSL_set_max_early_data(ssl, 0); > + > + return 1; > +} > + > + > +static void > +ngx_quic_compat_message_callback(int write_p, int version, int content_type, > + const void *buf, size_t len, SSL *ssl, void *arg) > +{ > + ngx_uint_t alert; > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + enum ssl_encryption_level_t level; > + > + if (!write_p) { > + return; > + } > + > + c = ngx_ssl_get_connection(ssl); > + qc = ngx_quic_get_connection(c); > + > + if (qc == NULL) { > + /* closing */ > + return; > + } > + > + com = qc->compat; > + level = com->write_level; > + > + switch (content_type) { > + > + case SSL3_RT_HANDSHAKE: > + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat tx %s len:%uz ", > + ngx_quic_level_name(level), len); > + > + (void) com->method->add_handshake_data(ssl, level, buf, len); > + > + break; > + > + case SSL3_RT_ALERT: > + if (len >= 2) { > + alert = ((u_char *) buf)[1]; > + > + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat %s alert:%ui len:%uz ", > + ngx_quic_level_name(level), alert, len); > + > + (void) com->method->send_alert(ssl, level, alert); > + > + break; > + } > + > + /* fall through */ > + > + default: > + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat %s ignore msg:%d len:%uz ", > + ngx_quic_level_name(level), content_type, len); Other content_type values are typically SSL3_RT_HEADER with its short header, and SSL3_RT_INNER_CONTENT_TYPE pseudo content type used to preface the inner TLSv1.3 content type. As such, I think such log should be removed for brevity as typically useless. > + > + break; > + } > +} > + > + > +int > +SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, > + const uint8_t *data, size_t len) > +{ > + BIO *rbio; > + size_t n; > + u_char *p; > + ngx_str_t res; > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + ngx_quic_compat_record_t rec; > + u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; > + u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 > + + SSL3_RT_HEADER_LENGTH > + + EVP_GCM_TLS_TAG_LEN]; > + > + c = ngx_ssl_get_connection(ssl); > + > + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s > len:%uz", > + ngx_quic_level_name(level), len); > + > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + rbio = SSL_get_rbio(ssl); > + > + while (len) { > + ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); > + > + rec.type = SSL3_RT_HANDSHAKE; > + rec.log = c->log; > + rec.number = com->read_record++; > + rec.keys = &com->keys; > + > + if (level == ssl_encryption_initial) { > + n = ngx_min(len, 65535); > + > + rec.payload.len = n; > + rec.payload.data = (u_char *) data; > + > + ngx_quic_compat_create_header(&rec, out, 1); > + > + BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); > + BIO_write(rbio, data, n); > + > +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) > + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat record len:%uz %*xs%*xs", > + n + SSL3_RT_HEADER_LENGTH, > + (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); > +#endif > + > + } else { > + n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); > + > + p = ngx_cpymem(in, data, n); > + *p++ = SSL3_RT_HANDSHAKE; > + > + rec.payload.len = p - in; > + rec.payload.data = in; > + > + res.data = out; > + > + if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { > + return 0; > + } > + > +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) > + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, > + "quic compat record len:%uz %xV", res.len, &res); > +#endif > + > + BIO_write(rbio, res.data, res.len); > + } > + > + data += n; > + len -= n; > + } > + > + return 1; > +} > + > + > +static size_t > +ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, > + ngx_uint_t plain) > +{ > + u_char type; > + size_t len; > + > + len = rec->payload.len; > + > + if (plain) { > + type = rec->type; > + > + } else { > + type = SSL3_RT_APPLICATION_DATA; > + len += EVP_GCM_TLS_TAG_LEN; > + } > + > + out[0] = type; > + out[1] = 0x03; > + out[2] = 0x03; > + out[3] = (len >> 8); > + out[4] = len; > + > + return 5; > +} > + > + > +static ngx_int_t > +ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) > +{ > + ngx_str_t ad, out; > + ngx_quic_secret_t *secret; > + ngx_quic_ciphers_t ciphers; > + u_char nonce[NGX_QUIC_IV_LEN]; > + > + ad.data = res->data; > + ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); > + > + out.len = rec->payload.len + EVP_GCM_TLS_TAG_LEN; > + out.data = res->data + ad.len; > + > +#ifdef NGX_QUIC_DEBUG_CRYPTO > + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, > + "quic compat ad len:%uz %xV", ad.len, &ad); > +#endif > + > + if (ngx_quic_ciphers(rec->keys->cipher, &ciphers, rec->level) == > NGX_ERROR) > + { > + return NGX_ERROR; > + } > + > + secret = &rec->keys->secret; > + > + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); > + ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); > + > + if (ngx_quic_tls_seal(ciphers.c, secret, &out, > + nonce, &rec->payload, &ad, rec->log) > + != NGX_OK) > + { > + return NGX_ERROR; > + } > + > + res->len = ad.len + out.len; > + > + return NGX_OK; > +} > + > + > +enum ssl_encryption_level_t > +SSL_quic_read_level(const SSL *ssl) > +{ > + ngx_connection_t *c; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + qc = ngx_quic_get_connection(c); > + > + return qc->compat->read_level; > +} > + > + > +enum ssl_encryption_level_t > +SSL_quic_write_level(const SSL *ssl) > +{ > + ngx_connection_t *c; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + qc = ngx_quic_get_connection(c); > + > + return qc->compat->write_level; > +} > + > + > +int > +SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, > + size_t params_len) > +{ > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + > + com->tp.len = params_len; > + com->tp.data = (u_char *) params; > + > + return 1; > +} > + > + > +void > +SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t > **out_params, > + size_t *out_params_len) > +{ > + ngx_connection_t *c; > + ngx_quic_compat_t *com; > + ngx_quic_connection_t *qc; > + > + c = ngx_ssl_get_connection(ssl); > + qc = ngx_quic_get_connection(c); > + com = qc->compat; > + > + *out_params = com->ctp.data; > + *out_params_len = com->ctp.len; > +} > + > +#endif /* NGX_QUIC_OPENSSL_COMPAT */ > diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h > b/src/event/quic/ngx_event_quic_openssl_compat.h > new file mode 100644 > --- /dev/null > +++ b/src/event/quic/ngx_event_quic_openssl_compat.h > @@ -0,0 +1,51 @@ > + > +/* > + * Copyright (C) Nginx, Inc. > + */ > + > + > +#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ > +#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ > + > + > +#include <ngx_config.h> > +#include <ngx_core.h> > + > + > +enum ssl_encryption_level_t { > + ssl_encryption_initial = 0, > + ssl_encryption_early_data, > + ssl_encryption_handshake, > + ssl_encryption_application > +}; > + > + > +typedef struct ssl_quic_method_st { > + int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, > + const SSL_CIPHER *cipher, > + const uint8_t *rsecret, size_t secret_len); > + int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, > + const SSL_CIPHER *cipher, > + const uint8_t *wsecret, size_t secret_len); bad indent > + int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, > + const uint8_t *data, size_t len); > + int (*flush_flight)(SSL *ssl); > + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, > + uint8_t alert); > +} SSL_QUIC_METHOD; > + > + > +ngx_int_t ngx_quic_compat_init(SSL_CTX *ctx); > + > +int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); > +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, > + const uint8_t *data, size_t len); > +enum ssl_encryption_level_t SSL_quic_read_level(const SSL *ssl); > +enum ssl_encryption_level_t SSL_quic_write_level(const SSL *ssl); > +int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, > + size_t params_len); > +void SSL_get_peer_quic_transport_params(const SSL *ssl, > + const uint8_t **out_params, size_t *out_params_len); > + > + > +#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ > diff --git a/src/event/quic/ngx_event_quic_protection.c > b/src/event/quic/ngx_event_quic_protection.c > --- a/src/event/quic/ngx_event_quic_protection.c > +++ b/src/event/quic/ngx_event_quic_protection.c > @@ -23,37 +23,6 @@ > #endif > > > -#ifdef OPENSSL_IS_BORINGSSL > -#define ngx_quic_cipher_t EVP_AEAD > -#else > -#define ngx_quic_cipher_t EVP_CIPHER > -#endif > - > - > -typedef struct { > - const ngx_quic_cipher_t *c; > - const EVP_CIPHER *hp; > - const EVP_MD *d; > -} ngx_quic_ciphers_t; > - > - > -typedef struct { > - size_t out_len; > - u_char *out; > - > - size_t prk_len; > - const uint8_t *prk; > - > - size_t label_len; > - const u_char *label; > -} ngx_quic_hkdf_t; > - > -#define ngx_quic_hkdf_set(seq, _label, _out, _prk) > \ > - (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; > \ > - (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, > \ > - (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char > *)(_label); > - > - > static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, > const EVP_MD *digest, const u_char *prk, size_t prk_len, > const u_char *info, size_t info_len); > @@ -63,20 +32,12 @@ static ngx_int_t ngx_hkdf_extract(u_char > > static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, > uint64_t *largest_pn); > -static void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); > -static ngx_int_t ngx_quic_ciphers(ngx_uint_t id, > - ngx_quic_ciphers_t *ciphers, enum ssl_encryption_level_t level); > > static ngx_int_t ngx_quic_tls_open(const ngx_quic_cipher_t *cipher, > ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, > ngx_str_t *ad, ngx_log_t *log); > -static ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, > - ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, > - ngx_str_t *ad, ngx_log_t *log); > static ngx_int_t ngx_quic_tls_hp(ngx_log_t *log, const EVP_CIPHER *cipher, > ngx_quic_secret_t *s, u_char *out, u_char *in); > -static ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, > - const EVP_MD *digest, ngx_log_t *log); > > static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, > ngx_str_t *res); > @@ -84,7 +45,7 @@ static ngx_int_t ngx_quic_create_retry_p > ngx_str_t *res); > > > -static ngx_int_t > +ngx_int_t > ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, > enum ssl_encryption_level_t level) > { > @@ -221,7 +182,7 @@ ngx_quic_keys_set_initial_secret(ngx_qui > } > > > -static ngx_int_t > +ngx_int_t > ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t > *log) > { > size_t info_len; > @@ -480,7 +441,7 @@ ngx_quic_tls_open(const ngx_quic_cipher_ > } > > > -static ngx_int_t > +ngx_int_t > ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, > ngx_str_t *out, u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t > *log) > { > @@ -961,7 +922,7 @@ ngx_quic_parse_pn(u_char **pos, ngx_int_ > } > > > -static void > +void > ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) > { > nonce[len - 8] ^= (pn >> 56) & 0x3f; > diff --git a/src/event/quic/ngx_event_quic_protection.h > b/src/event/quic/ngx_event_quic_protection.h > --- a/src/event/quic/ngx_event_quic_protection.h > +++ b/src/event/quic/ngx_event_quic_protection.h > @@ -23,6 +23,13 @@ > #define NGX_QUIC_MAX_MD_SIZE 48 > > > +#ifdef OPENSSL_IS_BORINGSSL > +#define ngx_quic_cipher_t EVP_AEAD > +#else > +#define ngx_quic_cipher_t EVP_CIPHER > +#endif > + > + > typedef struct { > size_t len; > u_char data[NGX_QUIC_MAX_MD_SIZE]; > @@ -56,6 +63,30 @@ struct ngx_quic_keys_s { > }; > > > +typedef struct { > + const ngx_quic_cipher_t *c; > + const EVP_CIPHER *hp; > + const EVP_MD *d; > +} ngx_quic_ciphers_t; > + > + > +typedef struct { > + size_t out_len; > + u_char *out; > + > + size_t prk_len; > + const uint8_t *prk; > + > + size_t label_len; > + const u_char *label; > +} ngx_quic_hkdf_t; > + > +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) > \ > + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; > \ > + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, > \ > + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char > *)(_label); > + > + > ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, > ngx_str_t *secret, ngx_log_t *log); > ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, > @@ -70,6 +101,14 @@ void ngx_quic_keys_switch(ngx_connection > ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); > ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); > ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); > +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); > +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers, > + enum ssl_encryption_level_t level); > +ngx_int_t ngx_quic_tls_seal(const ngx_quic_cipher_t *cipher, > + ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, ngx_str_t *in, > + ngx_str_t *ad, ngx_log_t *log); > +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, > + ngx_log_t *log); > > > #endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ > diff --git a/src/event/quic/ngx_event_quic_ssl.c > b/src/event/quic/ngx_event_quic_ssl.c > --- a/src/event/quic/ngx_event_quic_ssl.c > +++ b/src/event/quic/ngx_event_quic_ssl.c > @@ -10,6 +10,13 @@ > #include <ngx_event_quic_connection.h> > > > +#if defined OPENSSL_IS_BORINGSSL > \ > + || defined LIBRESSL_VERSION_NUMBER > \ > + || NGX_QUIC_OPENSSL_COMPAT > +#define NGX_QUIC_BORINGSSL_API 1 > +#endif > + > + > /* > * RFC 9000, 7.5. Cryptographic Message Buffering > * > @@ -18,7 +25,7 @@ > #define NGX_QUIC_MAX_BUFFERED 65535 > > > -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER > +#if (NGX_QUIC_BORINGSSL_API) > static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, > enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, > const uint8_t *secret, size_t secret_len); > @@ -39,7 +46,7 @@ static int ngx_quic_send_alert(ngx_ssl_c > static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t > *data); > > > -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER > +#if (NGX_QUIC_BORINGSSL_API) > > static int > ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, > @@ -67,12 +74,6 @@ ngx_quic_set_read_secret(ngx_ssl_conn_t > return 0; > } > > - if (level == ssl_encryption_early_data) { > - if (ngx_quic_init_streams(c) != NGX_OK) { > - return 0; > - } > - } > - > return 1; > } > > @@ -138,10 +139,6 @@ ngx_quic_set_encryption_secrets(ngx_ssl_ > } > > if (level == ssl_encryption_early_data) { > - if (ngx_quic_init_streams(c) != NGX_OK) { > - return 0; > - } > - > return 1; > } > > @@ -456,6 +453,12 @@ ngx_quic_crypto_input(ngx_connection_t * > return NGX_ERROR; > } > > + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data)) { > + if (ngx_quic_init_streams(c) != NGX_OK) { > + return NGX_ERROR; > + } > + } > + > return NGX_OK; > } > For the record, these chunks were merged. > @@ -540,7 +543,7 @@ ngx_quic_init_connection(ngx_connection_ > ssl_conn = c->ssl->connection; > > if (!quic_method.send_alert) { > -#if defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER > +#if (NGX_QUIC_BORINGSSL_API) > quic_method.set_read_secret = ngx_quic_set_read_secret; > quic_method.set_write_secret = ngx_quic_set_write_secret; > #else > diff --git a/src/event/quic/ngx_event_quic_transport.h > b/src/event/quic/ngx_event_quic_transport.h > --- a/src/event/quic/ngx_event_quic_transport.h > +++ b/src/event/quic/ngx_event_quic_transport.h > @@ -11,6 +11,10 @@ > #include <ngx_config.h> > #include <ngx_core.h> > > +#if (NGX_QUIC_OPENSSL_COMPAT) > +#include <ngx_event_quic_openssl_compat.h> > +#endif > + > > /* > * RFC 9000, 17.2. Long Header Packets I think it make sense to adjust the compat header inclusion in ngx_event_quic_connection.h, in order to make this part unnecessary, and for more consistency: diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_eve nt_quic_connection.h --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -28,6 +28,9 @@ typedef struct ngx_quic_keys_s ng typedef struct ngx_quic_compat_s ngx_quic_compat_t; #endif +#if (NGX_QUIC_OPENSSL_COMPAT) +#include <ngx_event_quic_openssl_compat.h> +#endif #include <ngx_event_quic_transport.h> #include <ngx_event_quic_protection.h> #include <ngx_event_quic_frames.h> @@ -39,9 +42,6 @@ typedef struct ngx_quic_compat_s ng #include <ngx_event_quic_ack.h> #include <ngx_event_quic_output.h> #include <ngx_event_quic_socket.h> -#if (NGX_QUIC_OPENSSL_COMPAT) -#include <ngx_event_quic_openssl_compat.h> -#endif /* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h --- a/src/event/quic/ngx_event_quic_transport.h +++ b/src/event/quic/ngx_event_quic_transport.h @@ -11,10 +11,6 @@ #include <ngx_config.h> #include <ngx_core.h> -#if (NGX_QUIC_OPENSSL_COMPAT) -#include <ngx_event_quic_openssl_compat.h> -#endif - /* * RFC 9000, 17.2. Long Header Packets In other news, I looked how to obtain 0-RTT keys without interacting with SSL_read_early_data(), which enters the OpenSSL handshake state machine into a special mode and causes TLSv1.3/QUIC handshake message mismatches. This approach uses the SSL_SESSION_get_master_key() API. In TLSv1.3, it returns PSK, which accoring to TLSv1.3 key schedule is used as the input secret to extract Early Secret, then to derive client_early_traffic_secret with ClientHello message transcript digest, which we also control, so obtaining 0-RTT keys is relatively easy. Still, OpenSSL knows nothing about we accept 0-RTT, and as such it won't set the required early_data extension in ServerHello, which means we rejected it, even if 0-RTT was read and acknowledged. So the only way to switch the state seems to call SSL_read_early_data() with all its hard-ways. _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel