From tpxp at live.fr Mon Aug 5 07:51:36 2024 From: tpxp at live.fr (Thomas P.) Date: Mon, 5 Aug 2024 07:51:36 +0000 Subject: [auth_request] Add support for complex values in the auth_request directive value Message-ID: Hello, It looks like my previous message did not make it to this list, so I'm resending it. I'm building an app which makes use of auth_request to trigger some NJS code during the processing of a request and its error handlers if any. I wanted to use some variables like $upstream_code to tweak the JS script behaviour depending on whether I'm in a error handler or not. Some variables (like upstream_code) may not be available in the subrequest context, and passing them through the URI is sufficient for my use case. So here's a changeset switching nginx's interpretation of the auth_request value to a complex value, enabling use of any variable in the value. Another use case is to pass a location captured variable to the auth_request handler. Thanks in advance for your review, I'll gladly answer any question and I hope this first contribution isn't too bad (it's my first time with Mercurial so please bear with me). Regards, Thomas P. # HG changeset patch # User Thomas P. # Date 1722625005 -7200 # Fri Aug 02 20:56:45 2024 +0200 # Node ID 53d18e2171b9bdf4ca746c04d1503630fbaff9e1 # Parent d1b8568f3042f6019a2302dda4afbadd051fe54b feat(auth_request): add support for complex values in auth_request This changeset enables using variables in the value passed to the auth_request directive. Since auth_request runs after the rewrite phase, this gives more flexibility to select the location used for the authentication request, and enables passing additional values that are not available in a subrequest context like $upstream_code (through the request URI). diff -r d1b8568f3042 -r 53d18e2171b9 src/http/modules/ngx_http_auth_request_module.c --- a/src/http/modules/ngx_http_auth_request_module.c Thu Jul 18 17:43:25 2024 +0400 +++ b/src/http/modules/ngx_http_auth_request_module.c Fri Aug 02 20:56:45 2024 +0200 @@ -11,7 +11,7 @@ typedef struct { - ngx_str_t uri; + ngx_http_complex_value_t *uri; ngx_array_t *vars; } ngx_http_auth_request_conf_t; @@ -101,6 +101,7 @@ static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r) { + ngx_str_t res; ngx_table_elt_t *h, *ho, **ph; ngx_http_request_t *sr; ngx_http_post_subrequest_t *ps; @@ -109,7 +110,7 @@ arcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_request_module); - if (arcf->uri.len == 0) { + if (arcf->uri == NULL) { return NGX_DECLINED; } @@ -192,7 +193,14 @@ ps->handler = ngx_http_auth_request_done; ps->data = ctx; - if (ngx_http_subrequest(r, &arcf->uri, NULL, &sr, ps, + if (ngx_http_complex_value(r, arcf->uri, &res) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "auth request uri evaluated to: %V", &res); + + if (ngx_http_subrequest(r, &res, NULL, &sr, ps, NGX_HTTP_SUBREQUEST_WAITED) != NGX_OK) { @@ -322,6 +330,7 @@ */ conf->vars = NGX_CONF_UNSET_PTR; + conf->uri = NGX_CONF_UNSET_PTR; return conf; } @@ -333,7 +342,7 @@ ngx_http_auth_request_conf_t *prev = parent; ngx_http_auth_request_conf_t *conf = child; - ngx_conf_merge_str_value(conf->uri, prev->uri, ""); + ngx_conf_merge_ptr_value(conf->uri, prev->uri, NULL); ngx_conf_merge_ptr_value(conf->vars, prev->vars, NULL); return NGX_CONF_OK; @@ -362,24 +371,40 @@ static char * ngx_http_auth_request(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_http_auth_request_conf_t *arcf = conf; + ngx_http_auth_request_conf_t *arcf = conf; + ngx_http_complex_value_t *cv; + ngx_http_compile_complex_value_t ccv; ngx_str_t *value; - if (arcf->uri.data != NULL) { + if (arcf->uri != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; if (ngx_strcmp(value[1].data, "off") == 0) { - arcf->uri.len = 0; - arcf->uri.data = (u_char *) ""; + arcf->uri = NGX_CONF_UNSET_PTR; return NGX_CONF_OK; } - arcf->uri = value[1]; + cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t)); + if (cv == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = &value[1]; + ccv.complex_value = cv; + + if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + arcf->uri = cv; return NGX_CONF_OK; } From tpxp at live.fr Wed Aug 7 11:54:17 2024 From: tpxp at live.fr (Thomas P.) Date: Wed, 7 Aug 2024 11:54:17 +0000 Subject: [http_rewrite] Avoid duplicate non-cacheable variable calls for complex expressions Message-ID: Hello, I'm experimenting with non-cacheable variables to compute some values used in http_rewrite. I've noticed that, for directives like "set $var $computed_var", I'm getting 2 calls to $computed_var's get_handler. After reading the code behind this directive, I've noticed it goes through ngx_http_get_flushed_variable twice for every external variable: once to determine its length, then another time to copy contents (after allocating the total length of the result). While I expect the get_handler function to be called every time the variable is referred to, I think calling it once shall suffice. Switching to ngx_http_get_indexed_variable fixes the issue, and the cache is still cleared after evaluating the set directive it seems, as calling the non cacheable variable in another expression calls get_handler another time. All tests still pass with this change. Here's the only behaviour change I could observe: > Consider a non-cached variable $weird_var keeping track of its call numbers, and returning "0" then N times the number of calls so far: 01, 022, 0333... > Use it twice in the same directive: "set $stuff $weird_var$weird_var" > The variable's get_handler will be called twice (which sort of makes sense since the variable is referred to twice) but the resulting value is the result of the last call, truncated since it doesn't fit in the allocated buffer: stuff is 02202 (intuitively, it would be 01022) > Without this patch, the result would be 03330 which is also not the expected result anyway, so I doubt this would break anyone's workflow. On the other side, this speeds up complex value processing as we don't make unnecessary calls. The changeset is pretty basic, happy reviewing! Thomas P. # HG changeset patch # User Thomas P. # Date 1722959175 -7200 # Tue Aug 06 17:46:15 2024 +0200 # Node ID d6283a655f35a03f49427a2d88ebbc27f6946e3d # Parent d1b8568f3042f6019a2302dda4afbadd051fe54b [http_rewrite] Avoid duplicate non-cacheable variable calls for complex expressions The http_rewrite module's rewrite handler called twice variables' get_handler for non-cacheable variables referenced in expressions like "set $a $upstream_code;". It goes through ngx_http_get_flushed_variable once to determine variable length, and one more time to get the variable contents. Setting the flushed attribute on the script engine fixes the issue. This flag is already set in other parts that rely on http_script and it did not break any test, so it can be safely set. Besides, this only impacts non-cacheable variables processing. diff -r d1b8568f3042 -r d6283a655f35 src/http/modules/ngx_http_rewrite_module.c --- a/src/http/modules/ngx_http_rewrite_module.c Thu Jul 18 17:43:25 2024 +0400 +++ b/src/http/modules/ngx_http_rewrite_module.c Tue Aug 06 17:46:15 2024 +0200 @@ -174,6 +174,7 @@ e->quote = 1; e->log = rlcf->log; e->status = NGX_DECLINED; + e->flushed = 1; while (*(uintptr_t *) e->ip) { code = *(ngx_http_script_code_pt *) e->ip; From jiri.setnicka at cdn77.com Fri Aug 9 15:21:50 2024 From: jiri.setnicka at cdn77.com (=?UTF-8?B?SmnFmcOtIFNldG5pxI1rYQ==?=) Date: Fri, 9 Aug 2024 17:21:50 +0200 Subject: HTTP3 speed tuning Message-ID: Hello, I have been experimenting with the HTTP/3 performance (using nginx build from the current master branch) for the past several days and I am not satisfied with the performance. I was trying to fiddle firstly with the nginx settings, systems settings, and lately also with the algorithm parameters in the nginx code itself, but without success. There are several benchmarks to present the problem. I used the nginx build from the current master branch (git commit 145b228530, hg revision 9270:d1b8568f3042) with the curl from Debian testing (with http3 support) or with a custom build of the curl using OpenSSL (same numbers were achieved with both builds of the curl, so I combined both measurements into one). The nginx.conf is attached. The same server was used for all tests (Intel E5-2683 with 40Gbit NIC). Multiple clients were tried with similar results (Hetzner VPS, home server connected to 500Mbps network, laptop over WiFi). Results: * VPS: o iperf3: 1.68 Gbps o iperf3 --udp:1.66 Gbps o ping: 13 ms o avg download speed of 1GiB file served by nginx over HTTP/2:   214.4 MiB/s (1.7 Gbps) o avg download speed of 1GiB file served by nginx over HTTP/3:   4.82 MiB/s (38.5 Mbps) * Home server with 500 Mbit connection o iperf3: 498 Mbps o iperf3 --udp: 508 Mbps o ping: 6 ms o avg download speed of 1GiB file served by nginx over HTTP/2:   58.8 MiB/s (470 Mbps) o avg download speed of 1GiB file served by nginx over HTTP/3:   6.1 MiB/s (48.8 Mbps) * My laptop over WiFi on work network: o iperf3: 351 Mbps o iperf3 --udp: 395 Mbps o ping: 4 ms o avg download speed of 1GiB file served by nginx over HTTP/2:   40.8 MiB/s (326 Mbps) o avg download speed of 1GiB file served by nginx over HTTP/3:   13.3 MiB/s (106.4 Mbps) Similar download speeds were achieved using browsers (Firefox 115, Chromium 126). I was trying to to experiment with some directives firstly: * quic_bpf - no change, even using the eBPF worker sockets patch from Roman Arutyunyan * quic_gso - effect of this could be observed in tcpdumps or debug nginx log, but no effect on the speed Also, I captured tcpdumps of this traffic and it seems like there are very few lost packets, so it shouldn't be caused by waiting for lost packets and retransmission of them. My thoughts were that there could be something wrong with the congestion control or the ACK processing, so I went roughly through the ngx_event_quic_ack.c code, trying to modify some of the constants, but also with no success in increasing the throughput of the HTTP/3 nginx implementation. The strange thing I noticed in the debug log (also attached), is that there are multiple cycles over the same data ranges when the ACK frame is received, which is probably triggered by overlapping ACK ranges from the curl, but it yields into many times cycling over the same data. Besides that, I am not aware of any other performance penalty in the nginx code, which may yield in slow HTTP/3 speed. I would like to solve this mystery and help with optimizing the HTTP/3 performance, but I think I am at a dead end with my ideas. Any idea how to improve this would be appreciated. Thanks. Jiří Setnička -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- daemon off; worker_processes auto; worker_cpu_affinity auto; error_log /tmp/nginx-quic/error.log info; pid /tmp/nginx-quic/nginx.pid; quic_bpf on; events { worker_connections 768; } http { error_log /tmp/nginx-quic/error.log info; access_log /tmp/nginx-quic/access.log; sendfile on; quic_gso on; client_body_temp_path /tmp/nginx-quic/client_body_temp; proxy_temp_path /tmp/nginx-quic/proxy_temp; fastcgi_temp_path /tmp/nginx-quic/fastcgi_temp; uwsgi_temp_path /tmp/nginx-quic/uwsgi_temp; scgi_temp_path /tmp/nginx-quic/scgi_temp; server { # for better compatibility it's recommended # to use the same port for quic and https listen 443 quic reuseport; listen 443 ssl; http2 on; http3 on; ssl_certificate /opt/mc/nginx_ssl/test_cert.pem; ssl_certificate_key /opt/mc/nginx_ssl/test_cert.key; root /tmp/nginx-quic/www; location / { # required for browsers to direct them to quic port add_header Alt-Svc 'h3=":443"; ma=86400'; try_files $uri $uri/ =404; } } } -------------- next part -------------- A non-text attachment was scrubbed... Name: debug_log.log Type: text/x-log Size: 183414 bytes Desc: not available URL: From pluknet at nginx.com Fri Aug 9 16:56:28 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 09 Aug 2024 16:56:28 +0000 Subject: [nginx] Version bump. Message-ID: details: https://hg.nginx.org/nginx/rev/6b4faeec4630 branches: changeset: 9271:6b4faeec4630 user: Sergey Kandaurov date: Fri Aug 09 18:01:42 2024 +0400 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r d1b8568f3042 -r 6b4faeec4630 src/core/nginx.h --- a/src/core/nginx.h Thu Jul 18 17:43:25 2024 +0400 +++ b/src/core/nginx.h Fri Aug 09 18:01:42 2024 +0400 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1027000 -#define NGINX_VERSION "1.27.0" +#define nginx_version 1027001 +#define NGINX_VERSION "1.27.1" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From pluknet at nginx.com Fri Aug 9 16:56:31 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 09 Aug 2024 16:56:31 +0000 Subject: [nginx] Typo fixed. Message-ID: details: https://hg.nginx.org/nginx/rev/6392cb0d83e8 branches: changeset: 9272:6392cb0d83e8 user: Sergey Kandaurov date: Fri Aug 09 19:12:23 2024 +0400 description: Typo fixed. diffstat: docs/xml/nginx/changes.xml | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (18 lines): diff -r 6b4faeec4630 -r 6392cb0d83e8 docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml Fri Aug 09 18:01:42 2024 +0400 +++ b/docs/xml/nginx/changes.xml Fri Aug 09 19:12:23 2024 +0400 @@ -51,12 +51,12 @@ if "gzip", "gunzip", "ssi", "sub_filter" nginx не собирался gcc 14, -если использовался параметр --with-atomic.
+если использовался параметр --with-libatomic.
Спасибо Edgar Bonet.
nginx could not be built by gcc 14 -if the --with-atomic option was used.
+if the --with-libatomic option was used.
Thanks to Edgar Bonet.
From pluknet at nginx.com Fri Aug 9 16:56:34 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 09 Aug 2024 16:56:34 +0000 Subject: [nginx] QUIC: discarding 0-RTT keys. Message-ID: details: https://hg.nginx.org/nginx/rev/906a42885ce2 branches: changeset: 9273:906a42885ce2 user: Sergey Kandaurov date: Fri Aug 09 19:12:25 2024 +0400 description: QUIC: discarding 0-RTT keys. For simplicity, this is done on successful decryption of a 1-RTT packet. diffstat: src/event/quic/ngx_event_quic.c | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-) diffs (20 lines): diff -r 6392cb0d83e8 -r 906a42885ce2 src/event/quic/ngx_event_quic.c --- a/src/event/quic/ngx_event_quic.c Fri Aug 09 19:12:23 2024 +0400 +++ b/src/event/quic/ngx_event_quic.c Fri Aug 09 19:12:25 2024 +0400 @@ -1022,6 +1022,16 @@ ngx_quic_handle_payload(ngx_connection_t } } + if (pkt->level == ssl_encryption_application) { + /* + * RFC 9001, 4.9.3. Discarding 0-RTT Keys + * + * After receiving a 1-RTT packet, servers MUST discard + * 0-RTT keys within a short time + */ + ngx_quic_discard_ctx(c, ssl_encryption_early_data); + } + if (qc->closing) { /* * RFC 9000, 10.2. Immediate Close From pluknet at nginx.com Fri Aug 9 16:56:37 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 09 Aug 2024 16:56:37 +0000 Subject: [nginx] QUIC: zero out existing keying material only. Message-ID: details: https://hg.nginx.org/nginx/rev/ee7eb005b3b4 branches: changeset: 9274:ee7eb005b3b4 user: Sergey Kandaurov date: Fri Aug 09 19:12:26 2024 +0400 description: QUIC: zero out existing keying material only. Previously, this used to have extra ngx_explicit_memzero() calls from within ngx_quic_keys_cleanup(), which might be suboptimal. diffstat: src/event/quic/ngx_event_quic_protection.c | 29 +++++++++++++++++++++++------ 1 files changed, 23 insertions(+), 6 deletions(-) diffs (53 lines): diff -r 906a42885ce2 -r ee7eb005b3b4 src/event/quic/ngx_event_quic_protection.c --- a/src/event/quic/ngx_event_quic_protection.c Fri Aug 09 19:12:25 2024 +0400 +++ b/src/event/quic/ngx_event_quic_protection.c Fri Aug 09 19:12:26 2024 +0400 @@ -743,8 +743,15 @@ ngx_quic_keys_discard(ngx_quic_keys_t *k ngx_quic_crypto_hp_cleanup(client); ngx_quic_crypto_hp_cleanup(server); - ngx_explicit_memzero(client->secret.data, client->secret.len); - ngx_explicit_memzero(server->secret.data, server->secret.len); + if (client->secret.len) { + ngx_explicit_memzero(client->secret.data, client->secret.len); + client->secret.len = 0; + } + + if (server->secret.len) { + ngx_explicit_memzero(server->secret.data, server->secret.len); + server->secret.len = 0; + } } @@ -844,6 +851,9 @@ ngx_quic_keys_update(ngx_event_t *ev) ngx_explicit_memzero(current->server.secret.data, current->server.secret.len); + current->client.secret.len = 0; + current->server.secret.len = 0; + ngx_explicit_memzero(client_key.data, client_key.len); ngx_explicit_memzero(server_key.data, server_key.len); @@ -870,10 +880,17 @@ ngx_quic_keys_cleanup(ngx_quic_keys_t *k ngx_quic_crypto_cleanup(&next->client); ngx_quic_crypto_cleanup(&next->server); - ngx_explicit_memzero(next->client.secret.data, - next->client.secret.len); - ngx_explicit_memzero(next->server.secret.data, - next->server.secret.len); + if (next->client.secret.len) { + ngx_explicit_memzero(next->client.secret.data, + next->client.secret.len); + next->client.secret.len = 0; + } + + if (next->server.secret.len) { + ngx_explicit_memzero(next->server.secret.data, + next->server.secret.len); + next->server.secret.len = 0; + } } From pluknet at nginx.com Fri Aug 9 16:56:40 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Fri, 09 Aug 2024 16:56:40 +0000 Subject: [nginx] Stream ssl_preread: do not reallocate a parsed SNI host. Message-ID: details: https://hg.nginx.org/nginx/rev/b5550a7f16c7 branches: changeset: 9275:b5550a7f16c7 user: Sergey Kandaurov date: Fri Aug 09 19:12:26 2024 +0400 description: Stream ssl_preread: do not reallocate a parsed SNI host. We own this memory from the session pool. diffstat: src/stream/ngx_stream_ssl_preread_module.c | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r ee7eb005b3b4 -r b5550a7f16c7 src/stream/ngx_stream_ssl_preread_module.c --- a/src/stream/ngx_stream_ssl_preread_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/stream/ngx_stream_ssl_preread_module.c Fri Aug 09 19:12:26 2024 +0400 @@ -519,7 +519,7 @@ ngx_stream_ssl_preread_servername(ngx_st host = *servername; - rc = ngx_stream_validate_host(&host, c->pool, 1); + rc = ngx_stream_validate_host(&host, c->pool, 0); if (rc == NGX_ERROR) { return NGX_ERROR; From chipitsine at gmail.com Sun Aug 11 18:17:41 2024 From: chipitsine at gmail.com (=?UTF-8?B?0JjQu9GM0Y8g0KjQuNC/0LjRhtC40L0=?=) Date: Sun, 11 Aug 2024 20:17:41 +0200 Subject: [nginx] QUIC: discarding 0-RTT keys. In-Reply-To: References: Message-ID: Hello, how was that found ? is there some compliance (automated) test ? пт, 9 авг. 2024 г. в 18:57, Sergey Kandaurov : > details: https://hg.nginx.org/nginx/rev/906a42885ce2 > branches: > changeset: 9273:906a42885ce2 > user: Sergey Kandaurov > date: Fri Aug 09 19:12:25 2024 +0400 > description: > QUIC: discarding 0-RTT keys. > > For simplicity, this is done on successful decryption of a 1-RTT packet. > > diffstat: > > src/event/quic/ngx_event_quic.c | 10 ++++++++++ > 1 files changed, 10 insertions(+), 0 deletions(-) > > diffs (20 lines): > > diff -r 6392cb0d83e8 -r 906a42885ce2 src/event/quic/ngx_event_quic.c > --- a/src/event/quic/ngx_event_quic.c Fri Aug 09 19:12:23 2024 +0400 > +++ b/src/event/quic/ngx_event_quic.c Fri Aug 09 19:12:25 2024 +0400 > @@ -1022,6 +1022,16 @@ ngx_quic_handle_payload(ngx_connection_t > } > } > > + if (pkt->level == ssl_encryption_application) { > + /* > + * RFC 9001, 4.9.3. Discarding 0-RTT Keys > + * > + * After receiving a 1-RTT packet, servers MUST discard > + * 0-RTT keys within a short time > + */ > + ngx_quic_discard_ctx(c, ssl_encryption_early_data); > + } > + > if (qc->closing) { > /* > * RFC 9000, 10.2. Immediate Close > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From leo.izen at gmail.com Mon Aug 12 12:47:34 2024 From: leo.izen at gmail.com (=?iso-8859-1?q?Leo_Izen?=) Date: Mon, 12 Aug 2024 08:47:34 -0400 Subject: [PATCH] conf/mime.types: add image/jxl internet media type Message-ID: <351f249fca852fd70128.1723466854@gauss.local> # HG changeset patch # User Leo Izen # Date 1723466276 14400 # Mon Aug 12 08:37:56 2024 -0400 # Node ID 351f249fca852fd701285299b2d5d43c7e911fd2 # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 conf/mime.types: add image/jxl internet media type image/jxl for JPEG XL images is an officially reocgnized internet media type[1] by the IANA, and browser support is starting to exist (e.g. safari on macOS and iOS, plus Firefox behind a nightly flag, etc.) so servers should be able to serve .jxl images out of the box. [1]: https://www.iana.org/assignments/media-types/image/jxl [2]: https://web.archive.org/web/20240306204210/ https://www.iana.org/assignments/media-types/image/jxl diff -r b5550a7f16c7 -r 351f249fca85 conf/mime.types --- a/conf/mime.types Fri Aug 09 19:12:26 2024 +0400 +++ b/conf/mime.types Mon Aug 12 08:37:56 2024 -0400 @@ -16,6 +16,7 @@ text/x-component htc; image/avif avif; + image/jxl jxl; image/png png; image/svg+xml svg svgz; image/tiff tif tiff; From a.bavshin at nginx.com Mon Aug 12 20:52:31 2024 From: a.bavshin at nginx.com (Aleksei Bavshin) Date: Mon, 12 Aug 2024 13:52:31 -0700 Subject: [PATCH 2 of 6] SSL: object caching In-Reply-To: <8eee61e223bb7cb7475e.1721763025@linux> References: <59ac183dfee8e9641563.1721763024@linux> <8eee61e223bb7cb7475e.1721763025@linux> Message-ID: On 7/23/2024 12:30 PM, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762842 0 > # Tue Jul 23 19:27:22 2024 +0000 > # Node ID 8eee61e223bb7cb7475e50b866fd6b9a83fa5fa0 > # Parent 59ac183dfee8e9641563e043eb19480d91dd7cc0 > SSL: object caching. > > Added ngx_openssl_cache_module, which indexes a type-aware object cache. > It maps an id to a unique instance, and provides references to it, which > are dropped when the cycle's pool is destroyed. Also, for those objects > that can be cached, valid references may be pulled from cycle->old_cycle. > > The cache will be used in subsequent patches. > > diff --git a/auto/modules b/auto/modules > --- a/auto/modules > +++ b/auto/modules > @@ -1307,10 +1307,11 @@ fi > > if [ $USE_OPENSSL = YES ]; then > ngx_module_type=CORE > - ngx_module_name=ngx_openssl_module > + ngx_module_name="ngx_openssl_module ngx_openssl_cache_module" > ngx_module_incs= > ngx_module_deps=src/event/ngx_event_openssl.h > ngx_module_srcs="src/event/ngx_event_openssl.c > + src/event/ngx_event_openssl_cache.c > src/event/ngx_event_openssl_stapling.c" > ngx_module_libs= > ngx_module_link=YES > 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 > @@ -83,7 +83,8 @@ > #endif > > > -typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; > +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; > +typedef struct ngx_ssl_cache_type_s ngx_ssl_cache_type_t; > > > typedef struct { > @@ -233,6 +234,9 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_co > void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); > ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); > > +void *ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, > + ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data); > + > ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); > ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, > ngx_array_t *passwords); > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > new file mode 100644 > --- /dev/null > +++ b/src/event/ngx_event_openssl_cache.c > @@ -0,0 +1,277 @@ > + > +/* > + * Copyright (C) Nginx, Inc. > + */ > + > + > +#include > +#include > +#include > + > + > +typedef struct { > + ngx_rbtree_node_t node; > + ngx_str_t id; > + > + ngx_ssl_cache_type_t *type; > + void *value; > +} ngx_ssl_cache_node_t; > + > + > +typedef void *(*ngx_ssl_cache_create_pt)(ngx_str_t *id, char **err, void *data); > +typedef void (*ngx_ssl_cache_free_pt)(void *data); > +typedef void *(*ngx_ssl_cache_ref_pt)(char **err, void *data); > + > + > +struct ngx_ssl_cache_type_s { > + const char *name; > + > + ngx_ssl_cache_create_pt create; > + ngx_ssl_cache_free_pt free; > + ngx_ssl_cache_ref_pt ref; > +}; > + > + > +typedef struct { > + ngx_rbtree_t rbtree; > + ngx_rbtree_node_t sentinel; > +} ngx_ssl_cache_t; > + > + > +static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); > +static void ngx_ssl_cache_cleanup(void *data); > +static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, > + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); > + > +static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, > + ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); > + > + > +static ngx_core_module_t ngx_openssl_cache_module_ctx = { > + ngx_string("openssl_cache"), > + ngx_openssl_cache_create_conf, > + NULL > +}; > + > + > +ngx_module_t ngx_openssl_cache_module = { > + NGX_MODULE_V1, > + &ngx_openssl_cache_module_ctx, /* module context */ > + NULL, /* module directives */ > + NGX_CORE_MODULE, /* module type */ > + NULL, /* init master */ > + NULL, /* init module */ > + NULL, /* init process */ > + NULL, /* init thread */ > + NULL, /* exit thread */ > + NULL, /* exit process */ > + NULL, /* exit master */ > + NGX_MODULE_V1_PADDING > +}; > + > + > +static void * > +ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) > +{ > + ngx_ssl_cache_t *cache; > + ngx_pool_cleanup_t *cln; > + > + cache = ngx_pcalloc(cycle->pool, sizeof(ngx_ssl_cache_t)); > + if (cache == NULL) { > + return NULL; > + } > + > + cln = ngx_pool_cleanup_add(cycle->pool, 0); > + if (cln == NULL) { > + return NULL; > + } > + > + cln->handler = ngx_ssl_cache_cleanup; > + cln->data = cache; > + > + ngx_rbtree_init(&cache->rbtree, &cache->sentinel, > + ngx_ssl_cache_node_insert); > + > + return cache; > +} > + > + > +static void > +ngx_ssl_cache_cleanup(void *data) > +{ > + ngx_ssl_cache_t *cache = data; > + > + ngx_rbtree_t *tree; > + ngx_rbtree_node_t *node; > + ngx_ssl_cache_node_t *cn; > + > + tree = &cache->rbtree; > + > + if (tree->root == tree->sentinel) { > + return; > + } > + > + for (node = ngx_rbtree_min(tree->root, tree->sentinel); > + node; > + node = ngx_rbtree_next(tree, node)) > + { > + cn = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); > + > + if (cn->type != NULL && cn->value != NULL) { > + cn->type->free(cn->value); > + } > + } > +} > + > + > +static void > +ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, > + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) > +{ > + ngx_rbtree_node_t **p; > + ngx_ssl_cache_node_t *n, *t; > + > + for ( ;; ) { > + > + n = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); > + t = ngx_rbtree_data(temp, ngx_ssl_cache_node_t, node); > + > + if (node->key != temp->key) { > + > + p = (node->key < temp->key) ? &temp->left : &temp->right; > + > + } else if (n->type != t->type) { > + > + p = (n->type < t->type) ? &temp->left : &temp->right; > + > + } else { > + > + p = (ngx_memn2cmp(n->id.data, t->id.data, n->id.len, t->id.len) > + < 0) ? &temp->left : &temp->right; > + } > + > + if (*p == sentinel) { > + break; > + } > + > + temp = *p; > + } > + > + *p = node; > + node->parent = temp; > + node->left = sentinel; > + node->right = sentinel; > + ngx_rbt_red(node); > +} > + > + > +void * > +ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, > + ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data) > +{ > + void *value; > + uint32_t hash; > + ngx_ssl_cache_t *cache; > + ngx_ssl_cache_node_t *cn; > + > + value = NULL; > + > + hash = ngx_murmur_hash2(id->data, id->len); > + > + cache = (ngx_ssl_cache_t *) ngx_get_conf(cycle->conf_ctx, > + ngx_openssl_cache_module); > + > + if (ngx_process == NGX_PROCESS_WORKER > + || ngx_process == NGX_PROCESS_SINGLE) > + { > + return type->create(id, err, data); > + } This check kept bothering me, so I figured we can try to improve it. The logic for configuration time and per-connection fetches is quite different and we're always aware of the context we're working in. It would be cleaner to have separate fetch implementations, like that: void * ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data) { ... } void * ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data) { return type->create(id, err, data); } Full diff is attached, but it needs to be properly split between patches 2-6. As a bonus, the change makes it easier to isolate any future logic for dynamic certificate caching. It was noticeable on our review of early prototypes how combining both code paths in the `ngx_ssl_cache_fetch` code made the function unnecessarily complicated. > + > + cn = ngx_ssl_cache_lookup(cache, type, id, hash); > + > + if (cn == NULL) { > + cn = ngx_palloc(pool, sizeof(ngx_ssl_cache_node_t) + id->len + 1); > + if (cn == NULL) { > + return NULL; > + } > + > + cn->node.key = hash; > + cn->id.data = (u_char *)(cn + 1); > + cn->id.len = id->len; > + cn->type = type; > + cn->value = NULL; > + > + ngx_cpystrn(cn->id.data, id->data, id->len + 1); > + > + ngx_rbtree_insert(&cache->rbtree, &cn->node); > + } > + > + /* try to use a reference from the cache */ > + > + if (cn->value != NULL) { > + value = type->ref(err, cn->value); > + } > + > + if (value == NULL) { > + value = type->create(id, err, data); > + } > + > + if (value != NULL && cn->value == NULL) { > + /* we have a value and the node needs one; try to reference it */ > + cn->value = type->ref(err, value); > + } > + > + return value; > +} > + > + > +static ngx_ssl_cache_node_t * > +ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, > + ngx_str_t *id, uint32_t hash) > +{ > + ngx_int_t rc; > + ngx_rbtree_node_t *node, *sentinel; > + ngx_ssl_cache_node_t *cn; > + > + node = cache->rbtree.root; > + sentinel = cache->rbtree.sentinel; > + > + while (node != sentinel) { > + > + if (hash < node->key) { > + node = node->left; > + continue; > + } > + > + if (hash > node->key) { > + node = node->right; > + continue; > + } > + > + /* hash == node->key */ > + > + cn = (ngx_ssl_cache_node_t *) node; > + > + if ((ngx_uint_t) type < (ngx_uint_t) cn->type) { > + node = node->left; > + continue; > + } > + > + if ((ngx_uint_t) type > (ngx_uint_t) cn->type) { > + node = node->right; > + continue; > + } > + > + /* type == cn->type */ > + > + rc = ngx_memn2cmp(id->data, cn->id.data, id->len, cn->id.len); > + > + if (rc == 0) { > + return cn; > + } > + > + node = (rc < 0) ? node->left : node->right; > + } > + > + return NULL; > +} > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel -------------- next part -------------- diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index e3f9c7bcc..ed8c8feff 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -439,8 +439,7 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, STACK_OF(X509) *chain; ngx_ssl_name_t *name; - chain = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_cert, - &err, cert, NULL); + chain = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_cert, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -534,8 +533,7 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, } #endif - pkey = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_key, &err, - key, passwords); + pkey = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_key, &err, key, passwords); if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -568,8 +566,8 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, EVP_PKEY *pkey; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_fetch((ngx_cycle_t *) ngx_cycle, c->pool, - &ngx_ssl_cache_cert, &err, cert, NULL); + chain = ngx_ssl_cache_connection_fetch(&ngx_ssl_cache_cert, &err, cert, + NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, @@ -609,8 +607,8 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, #endif - pkey = ngx_ssl_cache_fetch((ngx_cycle_t *) ngx_cycle, c->pool, - &ngx_ssl_cache_key, &err, key, passwords); + pkey = ngx_ssl_cache_connection_fetch(&ngx_ssl_cache_key, &err, key, + passwords); if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, c->log, 0, @@ -686,8 +684,7 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, } xs = SSL_CTX_get_cert_store(ssl->ctx); - xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, - cert, NULL); + xsk = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_ca, &err, cert, NULL); if (xsk == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -792,8 +789,7 @@ ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, } xs = SSL_CTX_get_cert_store(ssl->ctx); - xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, - cert, NULL); + xsk = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_ca, &err, cert, NULL); if (xsk == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -860,8 +856,7 @@ ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl) return NGX_ERROR; } - xcsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_crl, - &err, crl, NULL); + xcsk = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_crl, &err, crl, NULL); if (xcsk == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 490f995b9..7bf79a017 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -234,8 +234,10 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s); void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); -void *ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, - ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data); +void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, + char **err, ngx_str_t *id, void *data); +void *ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, + ngx_str_t *id, void *data); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 7e207c916..64eae9c9c 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -219,8 +219,8 @@ ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, void * -ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, - ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data) +ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, char **err, + ngx_str_t *id, void *data) { void *value; uint32_t hash; @@ -231,19 +231,13 @@ ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, hash = ngx_murmur_hash2(id->data, id->len); - cache = (ngx_ssl_cache_t *) ngx_get_conf(cycle->conf_ctx, + cache = (ngx_ssl_cache_t *) ngx_get_conf(cf->cycle->conf_ctx, ngx_openssl_cache_module); - if (ngx_process == NGX_PROCESS_WORKER - || ngx_process == NGX_PROCESS_SINGLE) - { - return type->create(id, err, data); - } - cn = ngx_ssl_cache_lookup(cache, type, id, hash); if (cn == NULL) { - cn = ngx_palloc(pool, sizeof(ngx_ssl_cache_node_t) + id->len + 1); + cn = ngx_palloc(cf->pool, sizeof(ngx_ssl_cache_node_t) + id->len + 1); if (cn == NULL) { return NULL; } @@ -278,6 +272,14 @@ ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, } +void * +ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, + ngx_str_t *id, void *data) +{ + return type->create(id, err, data); +} + + static ngx_ssl_cache_node_t * ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash) From pclicoder at gmail.com Tue Aug 13 06:11:25 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Mon, 12 Aug 2024 23:11:25 -0700 Subject: [PATCH 1 of 1] CONF: make ssl_client_certificate optional with only tlsv1_3 Message-ID: Hi Nginx Devs This is my first Nginx patch. So guide me, if mistakes are made while following the process. *Testing done:* Ran nginx-tests. Got the same results with or without patch. *PATCH description* is in commit logs. *Code doubts:* Currently I used [~NGX_SSL_TLSv1_3] to find if configured protocols have any other value than TLSv1.3. Another way to do the same is: [NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2]. Kindly let me know which way is more prefered. -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: ssl_client_certificate_optional_tls1_3.patch Type: application/octet-stream Size: 4082 bytes Desc: not available URL: From pclicoder at gmail.com Tue Aug 13 06:34:56 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Mon, 12 Aug 2024 23:34:56 -0700 Subject: [PATCH 1 of 1] CONF: make ssl_client_certificate optional with only tlsv1_3 Message-ID: Hi Nginx Devs *Testing done:* Ran nginx-tests. Got the same results with or without patch. *PATCH description:* As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) send Certificate Authorities (CAs) in the Certificate Request packet. Today, Nginx makes the ssl_client_certificate directive mandatory with ssl_verify_client. Issuers from this CA certificate file are sent to client in the Certificate Request packet. If only TLSv1.3 protocol is configured, and considering, it is not mandatory to send CAs to clients. Nginx should make ssl_client_certificate optional. This patch makes ssl_client_certificate optional. *Code doubts:* Currently I used [~NGX_SSL_TLSv1_3] to find if configured protocols have any other value than TLSv1.3. Another way to do the same is: [NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2]. Kindly let me know which way is more prefered. *PATCH:* # HG changeset patch # User Praveen Chaudhary # Date 1723406727 25200 # Sun Aug 11 13:05:27 2024 -0700 # Node ID 9006e478c2f2a2e023fba104aff9c175c3e17e49 # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 Make ssl_client_certificate directive optional with TLSv1.3. - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) send Certificate Authorities (CAs) in the Certificate Request packet. This makes ssl_client_certificate directive optional when only TLS 1.3 is used for mutual TLS configurations. - Today, Nginx requires ssl_client_certificate directive to be set to CA Certificates file, if ssl_verify_client is enabled, even when using only TLS 1.3. Else Nginx does not reload or restart. diff -r b5550a7f16c7 -r 9006e478c2f2 src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -787,10 +787,16 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & ~NGX_SSL_TLSv1_3) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r b5550a7f16c7 -r 9006e478c2f2 src/mail/ngx_mail_ssl_module.c --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -450,12 +450,19 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & ~NGX_SSL_TLSv1_3) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } + if (ngx_ssl_client_certificate(cf, &conf->ssl, &conf->client_certificate, conf->verify_depth) diff -r b5550a7f16c7 -r 9006e478c2f2 src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -932,10 +932,16 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & ~NGX_SSL_TLSv1_3) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, -------------- next part -------------- An HTML attachment was scrubbed... URL: -------------- next part -------------- A non-text attachment was scrubbed... Name: ssl_client_certificate_optional_tls1_3.patch Type: application/octet-stream Size: 4082 bytes Desc: not available URL: From pluknet at nginx.com Wed Aug 14 14:06:26 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:26 +0000 Subject: [nginx] Mp4: fixed buffer underread while updating stsz atom. Message-ID: details: https://hg.nginx.org/nginx/rev/f88c253ac8b3 branches: changeset: 9276:f88c253ac8b3 user: Roman Arutyunyan date: Mon Aug 12 18:20:43 2024 +0400 description: Mp4: fixed buffer underread while updating stsz atom. While cropping an stsc atom in ngx_http_mp4_crop_stsc_data(), a 32-bit integer overflow could happen, which could result in incorrect seeking and a very large value stored in "samples". This resulted in a large invalid value of trak->end_chunk_samples. This value is further used to calculate the value of trak->end_chunk_samples_size in ngx_http_mp4_update_stsz_atom(). While doing this, a large invalid value of trak->end_chunk_samples could result in reading memory before stsz atom start. This could potentially result in a segfault. diffstat: src/http/modules/ngx_http_mp4_module.c | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diffs (31 lines): diff -r b5550a7f16c7 -r f88c253ac8b3 src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:43 2024 +0400 @@ -3099,7 +3099,8 @@ static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, ngx_uint_t start) { - uint32_t start_sample, chunk, samples, id, next_chunk, n, + uint64_t n; + uint32_t start_sample, chunk, samples, id, next_chunk, prev_samples; ngx_buf_t *data, *buf; ngx_uint_t entries, target_chunk, chunk_samples; @@ -3160,7 +3161,7 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 "samples:%uD, id:%uD", start_sample, chunk, next_chunk - chunk, samples, id); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample < n) { goto found; @@ -3182,7 +3183,7 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD", start_sample, chunk, next_chunk - chunk, samples); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample > n) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, From pluknet at nginx.com Wed Aug 14 14:06:29 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:29 +0000 Subject: [nginx] Mp4: rejecting unordered chunks in stsc atom. Message-ID: details: https://hg.nginx.org/nginx/rev/f32027d744ba branches: changeset: 9277:f32027d744ba user: Roman Arutyunyan date: Mon Aug 12 18:20:45 2024 +0400 description: Mp4: rejecting unordered chunks in stsc atom. Unordered chunks could result in trak->end_chunk smaller than trak->start_chunk in ngx_http_mp4_crop_stsc_data(). Later in ngx_http_mp4_update_stco_atom() this caused buffer overread while trying to calculate trak->end_offset. diffstat: src/http/modules/ngx_http_mp4_module.c | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-) diffs (17 lines): diff -r f88c253ac8b3 -r f32027d744ba src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:43 2024 +0400 +++ b/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:45 2024 +0400 @@ -3156,6 +3156,13 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 next_chunk = ngx_mp4_get_32value(entry->chunk); + if (next_chunk < chunk) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "unordered mp4 stsc chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample:%uD, chunk:%uD, chunks:%uD, " "samples:%uD, id:%uD", From pluknet at nginx.com Wed Aug 14 14:06:31 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:31 +0000 Subject: [nginx] Updated OpenSSL used for win32 builds. Message-ID: details: https://hg.nginx.org/nginx/rev/d0cba9811899 branches: changeset: 9278:d0cba9811899 user: Sergey Kandaurov date: Mon Aug 12 18:20:49 2024 +0400 description: Updated OpenSSL used for win32 builds. diffstat: misc/GNUmakefile | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r f32027d744ba -r d0cba9811899 misc/GNUmakefile --- a/misc/GNUmakefile Mon Aug 12 18:20:45 2024 +0400 +++ b/misc/GNUmakefile Mon Aug 12 18:20:49 2024 +0400 @@ -6,7 +6,7 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-3.0.13 +OPENSSL = openssl-3.0.14 ZLIB = zlib-1.3.1 PCRE = pcre2-10.39 From pluknet at nginx.com Wed Aug 14 14:06:34 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:34 +0000 Subject: [nginx] nginx-1.27.1-RELEASE Message-ID: details: https://hg.nginx.org/nginx/rev/417b1045b18e branches: changeset: 9279:417b1045b18e user: Sergey Kandaurov date: Mon Aug 12 18:20:52 2024 +0400 description: nginx-1.27.1-RELEASE diffstat: docs/xml/nginx/changes.xml | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 49 insertions(+), 0 deletions(-) diffs (59 lines): diff -r d0cba9811899 -r 417b1045b18e docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml Mon Aug 12 18:20:49 2024 +0400 +++ b/docs/xml/nginx/changes.xml Mon Aug 12 18:20:52 2024 +0400 @@ -5,6 +5,55 @@ + + + + +обработка специально созданного mp4-файла модулем ngx_http_mp4_module +могла приводить к падению рабочего процесса (CVE-2024-7347).
+Спасибо Nils Bars. +
+ +processing of a specially crafted mp4 file by the ngx_http_mp4_module +might cause a worker process crash (CVE-2024-7347).
+Thanks to Nils Bars. +
+
+ + + +теперь обработчик в модуле stream не является обязательным. + + +now the stream module handler is not mandatory. + + + + + +новые HTTP/2-соединения могли игнорировать +плавное завершение старых рабочих процессов.
+Спасибо Kasei Wang. +
+ +new HTTP/2 connections might ignore +graceful shutdown of old worker processes.
+Thanks to Kasei Wang. +
+
+ + + +Исправления в HTTP/3. + + +Bugfixes in HTTP/3. + + + +
+ + From pluknet at nginx.com Wed Aug 14 14:06:37 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:37 +0000 Subject: [nginx] release-1.27.1 tag Message-ID: details: https://hg.nginx.org/nginx/rev/8796dfbe7177 branches: changeset: 9280:8796dfbe7177 user: Sergey Kandaurov date: Mon Aug 12 18:21:01 2024 +0400 description: release-1.27.1 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff -r 417b1045b18e -r 8796dfbe7177 .hgtags --- a/.hgtags Mon Aug 12 18:20:52 2024 +0400 +++ b/.hgtags Mon Aug 12 18:21:01 2024 +0400 @@ -479,3 +479,4 @@ 294a3d07234f8f65d7b0e0b0e2c5b05c12c5da0a 173a0a7dbce569adbb70257c6ec4f0f6bc585009 release-1.25.4 8618e4d900cc71082fbe7dc72af087937d64faf5 release-1.25.5 2166e329fb4ed7d6da7c823ee6499f7d06d7bc00 release-1.27.0 +417b1045b18ecbca0ca583073a221ffbfbaee5d9 release-1.27.1 From pluknet at nginx.com Wed Aug 14 14:06:40 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:40 +0000 Subject: [nginx] Version bump. Message-ID: details: https://hg.nginx.org/nginx/rev/2b404783068b branches: stable-1.26 changeset: 9281:2b404783068b user: Sergey Kandaurov date: Mon Aug 12 18:21:52 2024 +0400 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r dd5bc1844be3 -r 2b404783068b src/core/nginx.h --- a/src/core/nginx.h Tue May 28 17:28:07 2024 +0400 +++ b/src/core/nginx.h Mon Aug 12 18:21:52 2024 +0400 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1026001 -#define NGINX_VERSION "1.26.1" +#define nginx_version 1026002 +#define NGINX_VERSION "1.26.2" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From pluknet at nginx.com Wed Aug 14 14:06:43 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:43 +0000 Subject: [nginx] Typo fixed. Message-ID: details: https://hg.nginx.org/nginx/rev/c49f86822244 branches: stable-1.26 changeset: 9282:c49f86822244 user: Sergey Kandaurov date: Fri Aug 09 19:12:23 2024 +0400 description: Typo fixed. diffstat: docs/xml/nginx/changes.xml | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (18 lines): diff -r 2b404783068b -r c49f86822244 docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml Mon Aug 12 18:21:52 2024 +0400 +++ b/docs/xml/nginx/changes.xml Fri Aug 09 19:12:23 2024 +0400 @@ -39,12 +39,12 @@ if "gzip", "gunzip", "ssi", "sub_filter" nginx не собирался gcc 14, -если использовался параметр --with-atomic.
+если использовался параметр --with-libatomic.
Спасибо Edgar Bonet.
nginx could not be built by gcc 14 -if the --with-atomic option was used.
+if the --with-libatomic option was used.
Thanks to Edgar Bonet.
From pluknet at nginx.com Wed Aug 14 14:06:46 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:46 +0000 Subject: [nginx] Mp4: fixed buffer underread while updating stsz atom. Message-ID: details: https://hg.nginx.org/nginx/rev/8800d5a26a2a branches: stable-1.26 changeset: 9283:8800d5a26a2a user: Roman Arutyunyan date: Mon Aug 12 18:20:43 2024 +0400 description: Mp4: fixed buffer underread while updating stsz atom. While cropping an stsc atom in ngx_http_mp4_crop_stsc_data(), a 32-bit integer overflow could happen, which could result in incorrect seeking and a very large value stored in "samples". This resulted in a large invalid value of trak->end_chunk_samples. This value is further used to calculate the value of trak->end_chunk_samples_size in ngx_http_mp4_update_stsz_atom(). While doing this, a large invalid value of trak->end_chunk_samples could result in reading memory before stsz atom start. This could potentially result in a segfault. diffstat: src/http/modules/ngx_http_mp4_module.c | 7 ++++--- 1 files changed, 4 insertions(+), 3 deletions(-) diffs (31 lines): diff -r c49f86822244 -r 8800d5a26a2a src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c Fri Aug 09 19:12:23 2024 +0400 +++ b/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:43 2024 +0400 @@ -3099,7 +3099,8 @@ static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, ngx_uint_t start) { - uint32_t start_sample, chunk, samples, id, next_chunk, n, + uint64_t n; + uint32_t start_sample, chunk, samples, id, next_chunk, prev_samples; ngx_buf_t *data, *buf; ngx_uint_t entries, target_chunk, chunk_samples; @@ -3160,7 +3161,7 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 "samples:%uD, id:%uD", start_sample, chunk, next_chunk - chunk, samples, id); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample < n) { goto found; @@ -3182,7 +3183,7 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD", start_sample, chunk, next_chunk - chunk, samples); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample > n) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, From pluknet at nginx.com Wed Aug 14 14:06:49 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:49 +0000 Subject: [nginx] Mp4: rejecting unordered chunks in stsc atom. Message-ID: details: https://hg.nginx.org/nginx/rev/925684c60b86 branches: stable-1.26 changeset: 9284:925684c60b86 user: Roman Arutyunyan date: Mon Aug 12 18:20:45 2024 +0400 description: Mp4: rejecting unordered chunks in stsc atom. Unordered chunks could result in trak->end_chunk smaller than trak->start_chunk in ngx_http_mp4_crop_stsc_data(). Later in ngx_http_mp4_update_stco_atom() this caused buffer overread while trying to calculate trak->end_offset. diffstat: src/http/modules/ngx_http_mp4_module.c | 7 +++++++ 1 files changed, 7 insertions(+), 0 deletions(-) diffs (17 lines): diff -r 8800d5a26a2a -r 925684c60b86 src/http/modules/ngx_http_mp4_module.c --- a/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:43 2024 +0400 +++ b/src/http/modules/ngx_http_mp4_module.c Mon Aug 12 18:20:45 2024 +0400 @@ -3156,6 +3156,13 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4 next_chunk = ngx_mp4_get_32value(entry->chunk); + if (next_chunk < chunk) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "unordered mp4 stsc chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample:%uD, chunk:%uD, chunks:%uD, " "samples:%uD, id:%uD", From pluknet at nginx.com Wed Aug 14 14:06:52 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:52 +0000 Subject: [nginx] Updated OpenSSL used for win32 builds. Message-ID: details: https://hg.nginx.org/nginx/rev/d1edb09453c6 branches: stable-1.26 changeset: 9285:d1edb09453c6 user: Sergey Kandaurov date: Mon Aug 12 18:20:49 2024 +0400 description: Updated OpenSSL used for win32 builds. diffstat: misc/GNUmakefile | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diffs (12 lines): diff -r 925684c60b86 -r d1edb09453c6 misc/GNUmakefile --- a/misc/GNUmakefile Mon Aug 12 18:20:45 2024 +0400 +++ b/misc/GNUmakefile Mon Aug 12 18:20:49 2024 +0400 @@ -6,7 +6,7 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-3.0.13 +OPENSSL = openssl-3.0.14 ZLIB = zlib-1.3.1 PCRE = pcre2-10.39 From pluknet at nginx.com Wed Aug 14 14:06:55 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:55 +0000 Subject: [nginx] nginx-1.26.2-RELEASE Message-ID: details: https://hg.nginx.org/nginx/rev/e4c5da06073c branches: stable-1.26 changeset: 9286:e4c5da06073c user: Sergey Kandaurov date: Mon Aug 12 18:25:45 2024 +0400 description: nginx-1.26.2-RELEASE diffstat: docs/xml/nginx/changes.xml | 18 ++++++++++++++++++ 1 files changed, 18 insertions(+), 0 deletions(-) diffs (28 lines): diff -r d1edb09453c6 -r e4c5da06073c docs/xml/nginx/changes.xml --- a/docs/xml/nginx/changes.xml Mon Aug 12 18:20:49 2024 +0400 +++ b/docs/xml/nginx/changes.xml Mon Aug 12 18:25:45 2024 +0400 @@ -5,6 +5,24 @@ + + + + +обработка специально созданного mp4-файла модулем ngx_http_mp4_module +могла приводить к падению рабочего процесса (CVE-2024-7347).
+Спасибо Nils Bars. +
+ +processing of a specially crafted mp4 file by the ngx_http_mp4_module +might cause a worker process crash (CVE-2024-7347).
+Thanks to Nils Bars. +
+
+ +
+ + From pluknet at nginx.com Wed Aug 14 14:06:58 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 14 Aug 2024 14:06:58 +0000 Subject: [nginx] release-1.26.2 tag Message-ID: details: https://hg.nginx.org/nginx/rev/9273d346e6fe branches: stable-1.26 changeset: 9287:9273d346e6fe user: Sergey Kandaurov date: Mon Aug 12 18:28:31 2024 +0400 description: release-1.26.2 tag diffstat: .hgtags | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diffs (8 lines): diff -r e4c5da06073c -r 9273d346e6fe .hgtags --- a/.hgtags Mon Aug 12 18:25:45 2024 +0400 +++ b/.hgtags Mon Aug 12 18:28:31 2024 +0400 @@ -480,3 +480,4 @@ 173a0a7dbce569adbb70257c6ec4f0f6bc585009 8618e4d900cc71082fbe7dc72af087937d64faf5 release-1.25.5 a58202a8c41bf0bd97eef1b946e13105a105520d release-1.26.0 a63c124e34bcf2d1d1feb8d40ff075103b967c4c release-1.26.1 +e4c5da06073ca24e2ffc5c8f8b8d7833a926356f release-1.26.2 From pluknet at nginx.com Wed Aug 14 14:42:37 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 14 Aug 2024 18:42:37 +0400 Subject: [nginx] QUIC: discarding 0-RTT keys. In-Reply-To: References: Message-ID: <19ABFC9C-D979-4F6D-BBE2-B782649AE4D2@nginx.com> > On 11 Aug 2024, at 22:17, Илья Шипицин wrote: > > Hello, > > how was that found ? is there some compliance (automated) test ? That's one of items in the TODO list. The change can be covered by adding an appropriate test to nginx-tests, though that will likely require some polishing in HTTP3.pm. -- Sergey Kandaurov From noreply at nginx.com Wed Aug 14 22:20:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 14 Aug 2024 22:20:02 +0000 (UTC) Subject: [njs] Types: fixed NgxKeyValuePair definition. Message-ID: <20240814222002.3FAAD4878A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4630230c3d53a28c777d9c5d07efbb1a4ebc3446 branches: master commit: 4630230c3d53a28c777d9c5d07efbb1a4ebc3446 user: Thomas P. date: Tue, 30 Jul 2024 14:20:19 +0200 description: Types: fixed NgxKeyValuePair definition. NgxSharedDict.items() returns an array of `[key, value]` pairs, as `Object.entries()`. --- ts/ngx_core.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/ngx_core.d.ts b/ts/ngx_core.d.ts index af8fc4f3..b459c929 100644 --- a/ts/ngx_core.d.ts +++ b/ts/ngx_core.d.ts @@ -250,7 +250,7 @@ interface NgxFetchOptions { declare class SharedMemoryError extends Error {} type NgxSharedDictValue = string | number; -type NgxKeyValuePair = { key: string, value: V }; +type NgxKeyValuePair = [string, V]; /** * Interface of a dictionary shared among the working processes. From chipitsine at gmail.com Thu Aug 15 08:07:33 2024 From: chipitsine at gmail.com (=?UTF-8?B?0JjQu9GM0Y8g0KjQuNC/0LjRhtC40L0=?=) Date: Thu, 15 Aug 2024 10:07:33 +0200 Subject: [nginx] QUIC: discarding 0-RTT keys. In-Reply-To: <19ABFC9C-D979-4F6D-BBE2-B782649AE4D2@nginx.com> References: <19ABFC9C-D979-4F6D-BBE2-B782649AE4D2@nginx.com> Message-ID: I meant something like thishttps://github.com/summerwind/h2spec What comes to mind, same key restriction must be applied for h2 as well On Wed, Aug 14, 2024, 16:42 Sergey Kandaurov wrote: > > > On 11 Aug 2024, at 22:17, Илья Шипицин wrote: > > > > Hello, > > > > how was that found ? is there some compliance (automated) test ? > > That's one of items in the TODO list. > > The change can be covered by adding an appropriate test to nginx-tests, > though that will likely require some polishing in HTTP3.pm. > > -- > Sergey Kandaurov > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From chipitsine at gmail.com Thu Aug 15 08:20:36 2024 From: chipitsine at gmail.com (=?UTF-8?B?0JjQu9GM0Y8g0KjQuNC/0LjRhtC40L0=?=) Date: Thu, 15 Aug 2024 10:20:36 +0200 Subject: [nginx] QUIC: discarding 0-RTT keys. In-Reply-To: References: <19ABFC9C-D979-4F6D-BBE2-B782649AE4D2@nginx.com> Message-ID: QUIC Interop suite looks like a candidate for such test as it already includes 0-RTT test On Thu, Aug 15, 2024, 10:07 Илья Шипицин wrote: > I meant something like thishttps://github.com/summerwind/h2spec > > > What comes to mind, same key restriction must be applied for h2 as well > > On Wed, Aug 14, 2024, 16:42 Sergey Kandaurov wrote: > >> >> > On 11 Aug 2024, at 22:17, Илья Шипицин wrote: >> > >> > Hello, >> > >> > how was that found ? is there some compliance (automated) test ? >> >> That's one of items in the TODO list. >> >> The change can be covered by adding an appropriate test to nginx-tests, >> though that will likely require some polishing in HTTP3.pm. >> >> -- >> Sergey Kandaurov >> _______________________________________________ >> nginx-devel mailing list >> nginx-devel at nginx.org >> https://mailman.nginx.org/mailman/listinfo/nginx-devel >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Thu Aug 15 15:56:23 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 15 Aug 2024 19:56:23 +0400 Subject: [PATCH 1 of 2] Stream: client certificate validation with OCSP Message-ID: <7d94e3fcad21b90fb137.1723737383@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1723737051 -14400 # Thu Aug 15 19:50:51 2024 +0400 # Node ID 7d94e3fcad21b90fb13734ed0f9a2f019e23f882 # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 Stream: client certificate validation with OCSP. diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -51,6 +51,8 @@ static char *ngx_stream_ssl_password_fil void *conf); static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -80,6 +82,14 @@ static ngx_conf_enum_t ngx_stream_ssl_v }; +static ngx_conf_enum_t ngx_stream_ssl_ocsp[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("leaf"), 2 }, + { ngx_null_string, 0 } +}; + + static ngx_conf_post_t ngx_stream_ssl_conf_command_post = { ngx_stream_ssl_conf_command_check }; @@ -212,6 +222,27 @@ static ngx_command_t ngx_stream_ssl_com offsetof(ngx_stream_ssl_srv_conf_t, crl), NULL }, + { ngx_string("ssl_ocsp"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_enum_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, ocsp), + &ngx_stream_ssl_ocsp }, + + { ngx_string("ssl_ocsp_responder"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, ocsp_responder), + NULL }, + + { ngx_string("ssl_ocsp_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_ssl_ocsp_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, @@ -777,6 +808,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ * sscf->alpn = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; + * sscf->ocsp_responder = { 0, NULL }; */ sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; @@ -792,6 +824,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ sscf->session_timeout = NGX_CONF_UNSET; sscf->session_tickets = NGX_CONF_UNSET; sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; + sscf->ocsp = NGX_CONF_UNSET_UINT; + sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; return sscf; } @@ -846,6 +880,10 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0); + ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, ""); + ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, + prev->ocsp_cache_zone, NULL); conf->ssl.log = cf->log; @@ -959,6 +997,23 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t } } + if (conf->ocsp) { + + if (conf->verify == 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_ocsp\" is incompatible with " + "\"ssl_verify_client optional_no_ca\""); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp, + conf->ocsp_cache_zone) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1232,6 +1287,85 @@ invalid: static char * +ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_srv_conf_t *sscf = conf; + + size_t len; + ngx_int_t n; + ngx_str_t *value, name, size; + ngx_uint_t j; + + if (sscf->ocsp_cache_zone != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + sscf->ocsp_cache_zone = NULL; + return NGX_CONF_OK; + } + + if (value[1].len <= sizeof("shared:") - 1 + || ngx_strncmp(value[1].data, "shared:", sizeof("shared:") - 1) != 0) + { + goto invalid; + } + + len = 0; + + for (j = sizeof("shared:") - 1; j < value[1].len; j++) { + if (value[1].data[j] == ':') { + break; + } + + len++; + } + + if (len == 0 || j == value[1].len) { + goto invalid; + } + + name.len = len; + name.data = value[1].data + sizeof("shared:") - 1; + + size.len = value[1].len - j - 1; + size.data = name.data + len + 1; + + n = ngx_parse_size(&size); + + if (n == NGX_ERROR) { + goto invalid; + } + + if (n < (ngx_int_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "OCSP cache \"%V\" is too small", &value[1]); + + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone = ngx_shared_memory_add(cf, &name, n, + &ngx_stream_ssl_module_ctx); + if (sscf->ocsp_cache_zone == NULL) { + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone->init = ngx_ssl_ocsp_cache_init; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid OCSP cache \"%V\"", &value[1]); + + return NGX_CONF_ERROR; +} + + +static char * ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation @@ -1308,6 +1442,27 @@ ngx_stream_ssl_init(ngx_conf_t *cf) ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + cscfp = cmcf->servers.elts; + + for (s = 0; s < cmcf->servers.nelts; s++) { + + sscf = cscfp[s]->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->ssl.ctx == NULL) { + continue; + } + + cscf = cscfp[s]->ctx->srv_conf[ngx_stream_core_module.ctx_index]; + + if (sscf->ocsp) { + if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, cscf->resolver, + cscf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + } h = ngx_array_push(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers); if (h == NULL) { diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -53,6 +53,10 @@ typedef struct { ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; + + ngx_uint_t ocsp; + ngx_str_t ocsp_responder; + ngx_shm_zone_t *ocsp_cache_zone; } ngx_stream_ssl_srv_conf_t; From pluknet at nginx.com Thu Aug 15 15:56:24 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 15 Aug 2024 19:56:24 +0400 Subject: [PATCH 2 of 2] Stream: OCSP stapling In-Reply-To: <7d94e3fcad21b90fb137.1723737383@enoparse.local> References: <7d94e3fcad21b90fb137.1723737383@enoparse.local> Message-ID: <0be1cc94cb87c8e5fa8a.1723737384@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1723737247 -14400 # Thu Aug 15 19:54:07 2024 +0400 # Node ID 0be1cc94cb87c8e5fa8a50d798838403b7326a33 # Parent 7d94e3fcad21b90fb13734ed0f9a2f019e23f882 Stream: OCSP stapling. diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -243,6 +243,34 @@ static ngx_command_t ngx_stream_ssl_com 0, NULL }, + { ngx_string("ssl_stapling"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling), + NULL }, + + { ngx_string("ssl_stapling_file"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_file), + NULL }, + + { ngx_string("ssl_stapling_responder"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_responder), + NULL }, + + { ngx_string("ssl_stapling_verify"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_verify), + NULL }, + { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, @@ -809,6 +837,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; * sscf->ocsp_responder = { 0, NULL }; + * sscf->stapling_file = { 0, NULL }; + * sscf->stapling_responder = { 0, NULL }; */ sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; @@ -826,6 +856,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; sscf->ocsp = NGX_CONF_UNSET_UINT; sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; + sscf->stapling = NGX_CONF_UNSET; + sscf->stapling_verify = NGX_CONF_UNSET; return sscf; } @@ -885,6 +917,12 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, prev->ocsp_cache_zone, NULL); + ngx_conf_merge_value(conf->stapling, prev->stapling, 0); + ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0); + ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); + ngx_conf_merge_str_value(conf->stapling_responder, + prev->stapling_responder, ""); + conf->ssl.log = cf->log; if (conf->certificates) { @@ -983,18 +1021,18 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t { return NGX_CONF_ERROR; } + } - if (ngx_ssl_trusted_certificate(cf, &conf->ssl, - &conf->trusted_certificate, - conf->verify_depth) - != NGX_OK) - { - return NGX_CONF_ERROR; - } + if (ngx_ssl_trusted_certificate(cf, &conf->ssl, + &conf->trusted_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } - if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { - return NGX_CONF_ERROR; - } + if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { + return NGX_CONF_ERROR; } if (conf->ocsp) { @@ -1055,6 +1093,17 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t return NGX_CONF_ERROR; } + if (conf->stapling) { + + if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file, + &conf->stapling_responder, conf->stapling_verify) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + } + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1454,6 +1503,15 @@ ngx_stream_ssl_init(ngx_conf_t *cf) cscf = cscfp[s]->ctx->srv_conf[ngx_stream_core_module.ctx_index]; + if (sscf->stapling) { + if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, cscf->resolver, + cscf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + if (sscf->ocsp) { if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, cscf->resolver, cscf->resolver_timeout) diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -57,6 +57,11 @@ typedef struct { ngx_uint_t ocsp; ngx_str_t ocsp_responder; ngx_shm_zone_t *ocsp_cache_zone; + + ngx_flag_t stapling; + ngx_flag_t stapling_verify; + ngx_str_t stapling_file; + ngx_str_t stapling_responder; } ngx_stream_ssl_srv_conf_t; From pluknet at nginx.com Thu Aug 15 15:59:17 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 15 Aug 2024 19:59:17 +0400 Subject: [PATCH 1 of 2] Stream: client certificate validation with OCSP In-Reply-To: <7d94e3fcad21b90fb137.1723737383@enoparse.local> References: <7d94e3fcad21b90fb137.1723737383@enoparse.local> Message-ID: On Thu, Aug 15, 2024 at 07:56:23PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1723737051 -14400 > # Thu Aug 15 19:50:51 2024 +0400 > # Node ID 7d94e3fcad21b90fb13734ed0f9a2f019e23f882 > # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 > Stream: client certificate validation with OCSP. > > [..] Tests: # HG changeset patch # User Sergey Kandaurov # Date 1723737182 -14400 # Thu Aug 15 19:53:02 2024 +0400 # Node ID 88b145af017198e739ab593d906fefa812368523 # Parent f5ef37b2e2604afb0dc155e1ae92c6807f0645b9 Tests: client certificate validation tests with OCSP in stream. diff --git a/stream_ssl_ocsp.t b/stream_ssl_ocsp.t new file mode 100644 --- /dev/null +++ b/stream_ssl_ocsp.t @@ -0,0 +1,535 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for OCSP with client certificates. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64 qw/ decode_base64 /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx qw/ :DEFAULT http_end /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new() + ->has(qw/stream stream_ssl stream_return sni socket_ssl_sni/) + ->has_daemon('openssl'); + +plan(skip_all => 'no OCSP support in BoringSSL') + if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + ssl_ocsp leaf; + ssl_verify_client on; + ssl_verify_depth 2; + ssl_client_certificate trusted.crt; + + ssl_certificate_key rsa.key; + ssl_certificate rsa.crt; + + ssl_session_cache shared:SSL:1m; + ssl_session_tickets on; + + server { + listen 127.0.0.1:8443 ssl; + server_name localhost; + return "$ssl_client_verify:$ssl_session_reused"; + } + + server { + listen 127.0.0.1:8443 ssl; + server_name sni; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp_responder http://127.0.0.1:8082; + } + + server { + listen 127.0.0.1:8443 ssl; + server_name resolver; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp on; + } + + server { + listen 127.0.0.1:8444 ssl; + server_name localhost; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp_responder http://127.0.0.1:8081; + ssl_ocsp on; + } + + server { + listen 127.0.0.1:8445 ssl; + server_name localhost; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp_responder http://127.0.0.1:8083/test; + } + + server { + listen 127.0.0.1:8446 ssl; + server_name localhost; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp_cache shared:OCSP:1m; + } + + server { + listen 127.0.0.1:8447 ssl; + server_name localhost; + return "$ssl_client_verify:$ssl_session_reused"; + + ssl_ocsp_responder http://127.0.0.1:8082; + ssl_client_certificate root.crt; + } +} + +EOF + +my $d = $t->testdir(); +my $p = port(8081); + +$t->write_file('openssl.conf', <write_file('ca.conf', <write_file('ca2.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('int', 'end') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('ec-end') { + system("openssl ecparam -genkey -out $d/$name.key -name prime256v1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create EC param: $!\n"; + system("openssl req -new -key $d/$name.key " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/ca2.conf " + . "-keyfile $d/root.key -cert $d/root.crt " + . "-subj /CN=int/ -in $d/int.csr -out $d/int.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for int: $!\n"; + +system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/int.key -cert $d/int.crt " + . "-subj /CN=ec-end/ -in $d/ec-end.csr -out $d/ec-end.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for ec-end: $!\n"; + +system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/int.key -cert $d/int.crt " + . "-subj /CN=end/ -in $d/end.csr -out $d/end.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for end: $!\n"; + +# RFC 6960, serialNumber + +system("openssl x509 -in $d/int.crt -serial -noout " + . ">>$d/serial_int 2>>$d/openssl.out") == 0 + or die "Can't obtain serial for end: $!\n"; + +my $serial_int = pack("n2", 0x0202, hex $1) + if $t->read_file('serial_int') =~ /(\d+)/; + +system("openssl x509 -in $d/end.crt -serial -noout " + . ">>$d/serial 2>>$d/openssl.out") == 0 + or die "Can't obtain serial for end: $!\n"; + +my $serial = pack("n2", 0x0202, hex $1) if $t->read_file('serial') =~ /(\d+)/; + +# ocsp end + +system("openssl ocsp -issuer $d/int.crt -cert $d/end.crt " + . "-reqout $d/req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/int.crt -rkey $d/int.key " + . "-reqin $d/req.der -respout $d/resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP response: $!\n"; + +system("openssl ocsp -issuer $d/int.crt -cert $d/ec-end.crt " + . "-reqout $d/ec-req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create EC OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/root.crt -rkey $d/root.key " + . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create EC OCSP response: $!\n"; + +$t->write_file('trusted.crt', + $t->read_file('int.crt') . $t->read_file('root.crt')); + +# server cert/key + +foreach my $name ('rsa') { + system('openssl req -x509 -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.crt -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->run_daemon(\&http_daemon, $t, port(8081)); +$t->run_daemon(\&http_daemon, $t, port(8082)); +$t->run_daemon(\&http_daemon, $t, port(8083)); +$t->try_run('no ssl_ocsp')->plan(15); + +$t->waitforsocket("127.0.0.1:" . port(8081)); +$t->waitforsocket("127.0.0.1:" . port(8082)); +$t->waitforsocket("127.0.0.1:" . port(8083)); + +############################################################################### + +like(get('end'), qr/SUCCESS/, 'ocsp leaf'); + +# demonstrate that ocsp int request is failed due to missing resolver + +like(get('end', sni => 'resolver'), + qr/FAILED:certificate status request failed/, + 'ocsp many failed request'); + +# demonstrate that ocsp int request is actually made by failing ocsp response + +like(get('end', port => 8444), + qr/FAILED:certificate status request failed/, + 'ocsp many failed'); + +# now prepare valid ocsp int response + +system("openssl ocsp -issuer $d/root.crt -cert $d/int.crt " + . "-reqout $d/int-req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/root.crt " + . "-rsigner $d/root.crt -rkey $d/root.key " + . "-reqin $d/int-req.der -respout $d/int-resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP response: $!\n"; + +like(get('end', port => 8444), qr/SUCCESS/, 'ocsp many'); + +# store into ssl_ocsp_cache + +like(get('end', port => 8446), qr/SUCCESS/, 'cache store'); + +# revoke + +system("openssl ca -config $d/ca.conf -revoke $d/end.crt " + . "-keyfile $d/root.key -cert $d/root.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't revoke end.crt: $!\n"; + +system("openssl ocsp -issuer $d/int.crt -cert $d/end.crt " + . "-reqout $d/req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/int.crt -rkey $d/int.key " + . "-reqin $d/req.der -respout $d/revoked.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP response: $!\n"; + +like(get('end'), qr/FAILED:certificate revoked/, 'revoked'); + +# with different responder where it's still valid + +like(get('end', port => 8445), qr/SUCCESS/, 'ocsp responder'); + +# with different context to responder where it's still valid + +like(get('end', sni => 'sni'), qr/SUCCESS/, 'ocsp context'); + +# with cached ocsp response it's still valid + +like(get('end', port => 8446), qr/SUCCESS/, 'cache lookup'); + +# ocsp end response signed with invalid (root) cert, expect HTTP 400 + +like(get('ec-end'), + qr/FAILED:certificate status request failed/, + 'root ca not trusted'); + +# now sign ocsp end response with valid int cert + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/int.crt -rkey $d/int.key " + . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create EC OCSP response: $!\n"; + +like(get('ec-end'), qr/SUCCESS/, 'ocsp ecdsa'); + +my $s = session('ec-end'); + +TODO: { +local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' + if $Net::SSLeay::VERSION < 1.88 && test_tls13(); +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); +local $TODO = 'no TLSv1.3 sessions in LibreSSL' + if $t->has_module('LibreSSL') && test_tls13(); + +like(get('ec-end', ses => $s), + qr/SUCCESS:r/, 'session reused'); + +} + +# revoke with saved session + +system("openssl ca -config $d/ca.conf -revoke $d/ec-end.crt " + . "-keyfile $d/root.key -cert $d/root.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't revoke end.crt: $!\n"; + +system("openssl ocsp -issuer $d/int.crt -cert $d/ec-end.crt " + . "-reqout $d/ec-req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/int.crt -rkey $d/int.key " + . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP response: $!\n"; + +# reusing session with revoked certificate + +TODO: { +local $TODO = 'no TLSv1.3 sessions, old Net::SSLeay' + if $Net::SSLeay::VERSION < 1.88 && test_tls13(); +local $TODO = 'no TLSv1.3 sessions, old IO::Socket::SSL' + if $IO::Socket::SSL::VERSION < 2.061 && test_tls13(); +local $TODO = 'no TLSv1.3 sessions in LibreSSL' + if $t->has_module('LibreSSL') && test_tls13(); + +like(get('ec-end', ses => $s), + qr/FAILED:certificate revoked:r/, 'session reused - revoked'); + +} + +# regression test for self-signed + +like(get('root', port => 8447), qr/SUCCESS/, 'ocsp one'); + +# check for errors + +like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); + +############################################################################### + +sub get { + my $s = get_socket(@_) || return; + return http_end($s); +} + +sub session { + my $s = get_socket(@_) || return; + http_end($s); + return $s; +} + +sub get_socket { + my ($cert, %extra) = @_; + my $ses = $extra{ses}; + my $sni = $extra{sni} || 'localhost'; + my $port = $extra{port} || 8443; + + return http( + "GET /serial HTTP/1.0\nHost: $sni\n\n", + start => 1, PeerAddr => '127.0.0.1:' . port($port), + SSL => 1, + SSL_hostname => $sni, + SSL_session_cache_size => 100, + SSL_reuse_ctx => $ses, + $cert ? ( + SSL_cert_file => "$d/$cert.crt", + SSL_key_file => "$d/$cert.key" + ) : () + ); +} + +sub test_tls13 { + my $s = stream( + PeerAddr => '127.0.0.1:' . port(8443), + SSL => 1 + ); + return ($s->socket()->get_sslversion_int() > 0x303); +} + +############################################################################### + +sub http_daemon { + my ($t, $port) = @_; + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => "127.0.0.1:$port", + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + my $resp; + + while (<$client>) { + Test::Nginx::log_core('||', $_); + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+\/([^ ]+)\s+HTTP/i; + next unless $uri; + + if ($port == port(8083)) { + next unless $uri =~ s/^test\///; + } + + $uri =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; + my $req = decode_base64($uri); + + if (index($req, $serial_int) > 0) { + $resp = 'int-resp'; + + } elsif (index($req, $serial) > 0) { + $resp = 'resp'; + + # used to differentiate ssl_ocsp_responder + + if ($port == port(8081) && -e "$d/revoked.der") { + $resp = 'revoked'; + } + + } else { + $resp = 'ec-resp'; + } + + next unless -s "$d/$resp.der"; + + # ocsp dummy handler + + select undef, undef, undef, 0.02; + + $headers = <<"EOF"; +HTTP/1.1 200 OK +Connection: close +Content-Type: application/ocsp-response + +EOF + + local $/; + open my $fh, '<', "$d/$resp.der" + or die "Can't open $resp.der: $!"; + binmode $fh; + my $content = <$fh>; + close $fh; + + print $client $headers . $content; + } +} + +############################################################################### From pluknet at nginx.com Thu Aug 15 16:01:29 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 15 Aug 2024 20:01:29 +0400 Subject: [PATCH 2 of 2] Stream: OCSP stapling In-Reply-To: <0be1cc94cb87c8e5fa8a.1723737384@enoparse.local> References: <7d94e3fcad21b90fb137.1723737383@enoparse.local> <0be1cc94cb87c8e5fa8a.1723737384@enoparse.local> Message-ID: On Thu, Aug 15, 2024 at 07:56:24PM +0400, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1723737247 -14400 > # Thu Aug 15 19:54:07 2024 +0400 > # Node ID 0be1cc94cb87c8e5fa8a50d798838403b7326a33 > # Parent 7d94e3fcad21b90fb13734ed0f9a2f019e23f882 > Stream: OCSP stapling. > > [..] Tests: # HG changeset patch # User Sergey Kandaurov # Date 1723737188 -14400 # Thu Aug 15 19:53:08 2024 +0400 # Node ID 529b417ca5ed7a43d46ead5995c99b9ce9a8da79 # Parent 88b145af017198e739ab593d906fefa812368523 Tests: OCSP stapling tests in stream. diff --git a/stream_ssl_stapling.t b/stream_ssl_stapling.t new file mode 100644 --- /dev/null +++ b/stream_ssl_stapling.t @@ -0,0 +1,410 @@ +#!/usr/bin/perl + +# (C) Sergey Kandaurov +# (C) Nginx, Inc. + +# Tests for OCSP stapling. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use MIME::Base64 qw/ decode_base64 /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_ssl socket_ssl/) + ->has_daemon('openssl'); + +eval { defined &Net::SSLeay::set_tlsext_status_type or die; }; +plan(skip_all => 'Net::SSLeay too old') if $@; +eval { defined &IO::Socket::SSL::SSL_OCSP_TRY_STAPLE or die; }; +plan(skip_all => 'IO::Socket::SSL too old') if $@; + +plan(skip_all => 'no OCSP stapling') if $t->has_module('BoringSSL'); + +$t->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + ssl_stapling on; + ssl_trusted_certificate trusted.crt; + + ssl_certificate ec-end-int.crt; + ssl_certificate_key ec-end.key; + + ssl_certificate end-int.crt; + ssl_certificate_key end.key; + + ssl_ciphers DEFAULT:ECCdraft; + + server { + listen 127.0.0.1:8443 ssl; + listen 127.0.0.1:8080; + server_name localhost; + } + + server { + listen 127.0.0.1:8444 ssl; + server_name localhost; + + ssl_stapling_responder http://127.0.0.1:8081/; + } + + server { + listen 127.0.0.1:8445 ssl; + server_name localhost; + + ssl_stapling_verify on; + } + + server { + listen 127.0.0.1:8446 ssl; + server_name localhost; + + ssl_certificate ec-end.crt; + ssl_certificate_key ec-end.key; + } + + server { + listen 127.0.0.1:8447 ssl; + server_name localhost; + + ssl_certificate end-int.crt; + ssl_certificate_key end.key; + + ssl_stapling_file %%TESTDIR%%/resp.der; + } + + server { + listen 127.0.0.1:8448 ssl; + server_name localhost; + + ssl_certificate ec-end-int.crt; + ssl_certificate_key ec-end.key; + + ssl_stapling_file %%TESTDIR%%/ec-resp.der; + } + + server { + listen 127.0.0.1:8449 ssl; + server_name localhost; + + ssl_stapling_responder http://127.0.0.1:8080/; + } +} + +EOF + +my $d = $t->testdir(); +my $p = port(8081); + +$t->write_file('openssl.conf', <write_file('ca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('int', 'end') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +foreach my $name ('ec-end') { + system("openssl ecparam -genkey -out $d/$name.key -name prime256v1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create EC param: $!\n"; + system("openssl req -new -key $d/$name.key " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/root.key -cert $d/root.crt " + . "-subj /CN=int/ -in $d/int.csr -out $d/int.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for int: $!\n"; + +system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/int.key -cert $d/int.crt " + . "-subj /CN=ec-end/ -in $d/ec-end.csr -out $d/ec-end.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for ec-end: $!\n"; + +system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/int.key -cert $d/int.crt " + . "-subj /CN=end/ -in $d/end.csr -out $d/end.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for end: $!\n"; + +# RFC 6960, serialNumber + +system("openssl x509 -in $d/end.crt -serial -noout " + . ">>$d/serial 2>>$d/openssl.out") == 0 + or die "Can't obtain serial for end: $!\n"; + +my $serial = pack("n2", 0x0202, hex $1) if $t->read_file('serial') =~ /(\d+)/; + +system("openssl ca -config $d/ca.conf -revoke $d/end.crt " + . "-keyfile $d/root.key -cert $d/root.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't revoke end.crt: $!\n"; + +system("openssl ocsp -issuer $d/int.crt -cert $d/end.crt " + . "-reqout $d/req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/root.crt -rkey $d/root.key " + . "-reqin $d/req.der -respout $d/resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create OCSP response: $!\n"; + +system("openssl ocsp -issuer $d/int.crt -cert $d/ec-end.crt " + . "-reqout $d/ec-req.der >>$d/openssl.out 2>&1") == 0 + or die "Can't create EC OCSP request: $!\n"; + +system("openssl ocsp -index $d/certindex -CA $d/int.crt " + . "-rsigner $d/root.crt -rkey $d/root.key " + . "-reqin $d/ec-req.der -respout $d/ec-resp.der -ndays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create EC OCSP response: $!\n"; + +$t->write_file('trusted.crt', + $t->read_file('int.crt') . $t->read_file('root.crt')); +$t->write_file('end-int.crt', + $t->read_file('end.crt') . $t->read_file('int.crt')); +$t->write_file('ec-end-int.crt', + $t->read_file('ec-end.crt') . $t->read_file('int.crt')); + +$t->run_daemon(\&http_daemon, $t); +$t->try_run('no ssl_stapling')->plan(10); + +$t->waitforsocket("127.0.0.1:" . port(8081)); + +############################################################################### + +staple(8443, 'RSA'); +staple(8443, 'ECDSA'); +staple(8444, 'RSA'); +staple(8444, 'ECDSA'); +staple(8445, 'ECDSA'); +staple(8446, 'ECDSA'); +staple(8449, 'ECDSA'); + +sleep 1; + +ok(!staple(8443, 'RSA'), 'staple revoked'); + +TODO: { +local $TODO = 'broken TLSv1.3 sigalgs in LibreSSL' + if $t->has_module('LibreSSL') && test_tls13(); + +ok(staple(8443, 'ECDSA'), 'staple success'); + +} + +ok(!staple(8444, 'RSA'), 'responder revoked'); + +TODO: { +local $TODO = 'broken TLSv1.3 sigalgs in LibreSSL' + if $t->has_module('LibreSSL') && test_tls13(); + +ok(staple(8444, 'ECDSA'), 'responder success'); + +} + +ok(!staple(8445, 'ECDSA'), 'verify - root not trusted'); + +ok(staple(8446, 'ECDSA', "$d/int.crt"), 'cert store'); + +is(staple(8447, 'RSA'), '1 1', 'file revoked'); +is(staple(8448, 'ECDSA'), '1 0', 'file success'); + +ok(!staple(8449, 'ECDSA'), 'ocsp error'); + +TODO: { +local $TODO = 'broken TLSv1.3 sigalgs in LibreSSL' + if $t->has_module('LibreSSL') && test_tls13(); + +like(`grep -F '[crit]' ${\($t->testdir())}/error.log`, qr/^$/s, 'no crit'); + +} + +############################################################################### + +sub staple { + my ($port, $ciphers, $ca) = @_; + my (@resp); + + my $staple_cb = sub { + my ($s, $resp) = @_; + push @resp, !!$resp; + return 1 unless $resp; + + # Contrary to the documentation, IO::Socket::SSL calls the + # SSL_ocsp_staple_callback with the socket, and not the + # Net::SSLeay object. + + my $ssl = $s->_get_ssl_object(); + + my $cert = Net::SSLeay::get_peer_certificate($ssl); + my $certid = eval { Net::SSLeay::OCSP_cert2ids($ssl, $cert) } + or do { die "no OCSP_CERTID for certificate: $@"; }; + + my @res = Net::SSLeay::OCSP_response_results($resp, $certid); + push @resp, $res[0][2]->{'statusType'}; + }; + + my $ctx_cb = sub { + my $ctx = shift; + return unless defined $ciphers; + my $ssleay = Net::SSLeay::SSLeay(); + return if ($ssleay < 0x1000200f || $ssleay == 0x20000000); + my @sigalgs = ('RSA+SHA256:PSS+SHA256', 'RSA+SHA256'); + @sigalgs = ($ciphers . '+SHA256') unless $ciphers eq 'RSA'; + # SSL_CTRL_SET_SIGALGS_LIST + Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[0]) + or Net::SSLeay::CTX_ctrl($ctx, 98, 0, $sigalgs[1]) + or die("Failed to set sigalgs"); + }; + + my $s = http_get( + '/', start => 1, PeerAddr => '127.0.0.1:' . port($port), + SSL => 1, + SSL_cipher_list => $ciphers, + SSL_create_ctx_callback => $ctx_cb, + SSL_ocsp_staple_callback => $staple_cb, + SSL_ocsp_mode => IO::Socket::SSL::SSL_OCSP_TRY_STAPLE(), + SSL_ca_file => $ca + ); + + return $s unless $s; + return join ' ', @resp; +} + +sub test_tls13 { + my $s = stream( + PeerAddr => '127.0.0.1:' . port(8443), + SSL => 1 + ); + return ($s->socket()->get_sslversion_int() > 0x303); +} + +############################################################################### + +sub http_daemon { + my ($t) = shift; + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => "127.0.0.1:" . port(8081), + Listen => 5, + Reuse => 1 + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+\/([^ ]+)\s+HTTP/i; + next unless $uri; + + $uri =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; + my $req = decode_base64($uri); + my $resp = index($req, $serial) > 0 ? 'resp' : 'ec-resp'; + + # ocsp dummy handler + + select undef, undef, undef, 0.02; + + $headers = <<"EOF"; +HTTP/1.1 200 OK +Connection: close +Content-Type: application/ocsp-response + +EOF + + local $/; + open my $fh, '<', "$d/$resp.der" + or die "Can't open $resp.der: $!"; + binmode $fh; + my $content = <$fh>; + close $fh; + + print $client $headers . $content; + } +} + +############################################################################### From noreply at nginx.com Thu Aug 15 16:09:01 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 15 Aug 2024 16:09:01 +0000 (UTC) Subject: [njs] HTTP: expose capture group variables. Message-ID: <20240815160901.EAB09486FD@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8553c2a6a2cb0c7bdd111f5b1799aaba8004eba6 branches: master commit: 8553c2a6a2cb0c7bdd111f5b1799aaba8004eba6 user: Thomas P. date: Tue, 30 Jul 2024 18:03:34 +0200 description: HTTP: expose capture group variables. --- nginx/ngx_http_js_module.c | 29 +++++++++++- nginx/t/js_capture_variables.t | 101 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 86d907df..824b647c 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -2954,10 +2954,10 @@ static njs_int_t ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, ngx_http_request_t *r, njs_value_t *setval, njs_value_t *retval) { - njs_int_t rc; + njs_int_t rc, is_capture, start, length; njs_str_t val, s; ngx_str_t name; - ngx_uint_t key; + ngx_uint_t i, key; ngx_http_variable_t *v; ngx_http_core_main_conf_t *cmcf; ngx_http_variable_value_t *vv; @@ -2972,6 +2972,31 @@ ngx_http_js_request_variables(njs_vm_t *vm, njs_object_prop_t *prop, name.len = val.length; if (setval == NULL) { + is_capture = 1; + for (i = 0; i < name.len; i++) { + if (name.data[i] < '0' || name.data[i] > '9') { + is_capture = 0; + break; + } + } + + if (is_capture) { + key = ngx_atoi(name.data, name.len) * 2; + if (r->captures == NULL || r->captures_data == NULL + || r->ncaptures <= key) + { + njs_value_undefined_set(retval); + return NJS_DECLINED; + } + + start = r->captures[key]; + length = r->captures[key + 1] - start; + + return ngx_js_prop(vm, njs_vm_prop_magic32(prop), retval, + &r->captures_data[start], length); + } + + /* Lookup the variable in nginx variables */ key = ngx_hash_strlow(name.data, name.data, name.len); vv = ngx_http_get_variable(r, &name, key); diff --git a/nginx/t/js_capture_variables.t b/nginx/t/js_capture_variables.t new file mode 100644 index 00000000..ccc977ee --- /dev/null +++ b/nginx/t/js_capture_variables.t @@ -0,0 +1,101 @@ +#!/usr/bin/perl + +# (C) Thomas P. + +# Tests for http njs module, reading location capture variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location ~ /(.+)/(.+) { + js_content test.variables; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs capture variables')->plan(4); + +############################################################################### + +TODO: { +local $TODO = 'not yet' unless has_version('0.8.6'); + +like(http_get('/test/hello?index=0'), qr/"\/test\/hello"/, 'global capture'); +like(http_get('/test/hello?index=1'), qr/"test"/, 'local capture 1'); +like(http_get('/test/hello?index=2'), qr/"hello"/, 'local capture 2'); +like(http_get('/test/hello?index=3'), qr/"undefined"/, 'undefined capture'); + +} + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### From xeioex at nginx.com Fri Aug 16 01:04:47 2024 From: xeioex at nginx.com (Dmitry Volyntsev) Date: Thu, 15 Aug 2024 18:04:47 -0700 Subject: [http_rewrite] Avoid duplicate non-cacheable variable calls for complex expressions In-Reply-To: References: Message-ID: On 8/7/24 4:54 AM, Thomas P. wrote: > Hello, > > I'm experimenting with non-cacheable variables to compute some values used in http_rewrite. I've noticed that, for directives like "set $var $computed_var", I'm getting 2 calls to $computed_var's get_handler. After reading the code behind this directive, I've noticed it goes through ngx_http_get_flushed_variable twice for every external variable: once to determine its length, then another time to copy contents (after allocating the total length of the result). While I expect the get_handler function to be called every time the variable is referred to, I think calling it once shall suffice. Switching to ngx_http_get_indexed_variable fixes the issue, and the cache is still cleared after evaluating the set directive it seems, as calling the non cacheable variable in another expression calls get_handler another time. All tests still pass with this change. > > Here's the only behaviour change I could observe: >> Consider a non-cached variable $weird_var keeping track of its call numbers, and returning "0" then N times the number of calls so far: 01, 022, 0333... >> Use it twice in the same directive: "set $stuff $weird_var$weird_var" >> The variable's get_handler will be called twice (which sort of makes sense since the variable is referred to twice) but the resulting value is the result of the last call, truncated since it doesn't fit in the allocated buffer: stuff is 02202 (intuitively, it would be 01022) >> Without this patch, the result would be 03330 which is also not the expected result anyway, so I doubt this would break anyone's workflow. On the other side, this speeds up complex value processing as we don't make unnecessary calls. > The changeset is pretty basic, happy reviewing! > > Thomas P. > > # HG changeset patch > # User Thomas P. > # Date 1722959175 -7200 > # Tue Aug 06 17:46:15 2024 +0200 > # Node ID d6283a655f35a03f49427a2d88ebbc27f6946e3d > # Parent d1b8568f3042f6019a2302dda4afbadd051fe54b > [http_rewrite] Avoid duplicate non-cacheable variable calls for complex expressions > > The http_rewrite module's rewrite handler called twice variables' > get_handler for non-cacheable variables referenced in expressions like > "set $a $upstream_code;". It goes through ngx_http_get_flushed_variable > once to determine variable length, and one more time to get the variable > contents. > > Setting the flushed attribute on the script engine fixes the > issue. This flag is already set in other parts that rely on http_script and > it did not break any test, so it can be safely set. Besides, this only impacts > non-cacheable variables processing. > > diff -r d1b8568f3042 -r d6283a655f35 src/http/modules/ngx_http_rewrite_module.c > --- a/src/http/modules/ngx_http_rewrite_module.c Thu Jul 18 17:43:25 2024 +0400 > +++ b/src/http/modules/ngx_http_rewrite_module.c Tue Aug 06 17:46:15 2024 +0200 > @@ -174,6 +174,7 @@ > e->quote = 1; > e->log = rlcf->log; > e->status = NGX_DECLINED; > + e->flushed = 1; > > while (*(uintptr_t *) e->ip) { > code = *(ngx_http_script_code_pt *) e->ip; Additional context: NJS patch https://github.com/nginx/njs/pull/771 which introduced no_cacheable variables triggers a heap-buffer-overflow when the length of a returned no_cacheable variable varies. Setting e->flushed to 1 fixes the problem. Some observation of nginx code: We do set flush to 1 in every place in http and stream except rewrite handler. This looks like the only place like this. If e.flush is always 1 we can simplify script code, by removing ngx_http_get_flushed_variable() calls which cause buffer-overflow when a variable is no_cacheable and its length varies. > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] Fixed Fixed Buffer.prototype.write(). Message-ID: <20240816013602.CDDC5487D5@pubserv1.nginx> details: https://github.com/nginx/njs/commit/5cf4fd025664ea7b100757865ca9c59e44830cba branches: master commit: 5cf4fd025664ea7b100757865ca9c59e44830cba user: Dmitry Volyntsev date: Wed, 7 Aug 2024 22:48:04 -0700 description: Fixed Fixed Buffer.prototype.write(). --- src/njs_buffer.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index fa087a19..1ce98ef2 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -1577,7 +1577,7 @@ encoding: return NJS_ERROR; } - if (offset >= array->byte_length) { + if (offset > array->byte_length) { njs_range_error(vm, "\"offset\" is out of range"); return NJS_ERROR; } @@ -1596,6 +1596,7 @@ njs_buffer_write_string(njs_vm_t *vm, njs_value_t *value, njs_int_t ret; njs_str_t str; njs_value_t dst; + const u_char *p, *end, *prev; njs_array_buffer_t *buffer; buffer = njs_typed_array_buffer(array); @@ -1618,6 +1619,23 @@ njs_buffer_write_string(njs_vm_t *vm, njs_value_t *value, goto done; } + length = njs_min(str.length, (size_t) length); + + if (encoding->decode == njs_string_decode_utf8) { + /* Avoid writing incomplete UTF-8 characters. */ + p = prev = str.start; + end = p + length; + + while (p < end) { + p = njs_utf8_next(p, str.start + str.length); + if (p <= end) { + prev = p; + } + } + + length = prev - str.start; + } + memcpy(start, str.start, length); done: From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] Fixed Buffer.prototype.lastIndexOf(). Message-ID: <20240816013602.C50284878A@pubserv1.nginx> details: https://github.com/nginx/njs/commit/5d15a8d6dabb44afefd5308e0ebde0cf73597b88 branches: master commit: 5d15a8d6dabb44afefd5308e0ebde0cf73597b88 user: Dmitry Volyntsev date: Tue, 6 Aug 2024 23:05:33 -0700 description: Fixed Buffer.prototype.lastIndexOf(). --- src/njs_buffer.c | 23 ++++++++++------------- src/test/njs_unit_test.c | 3 +++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 1d42cd06..fc95aebd 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -1981,7 +1981,7 @@ njs_buffer_prototype_index_of(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t last, njs_value_t *retval) { uint8_t byte; - int64_t from, to, increment, length, offset, index, i; + int64_t from, to, increment, length, index, i; njs_int_t ret; njs_str_t str; njs_value_t *this, *value, *value_from, *enc, dst; @@ -2070,8 +2070,7 @@ encoding: return NJS_ERROR; } - u8 = &buffer->u.u8[0]; - offset = array->offset; + u8 = &buffer->u.u8[array->offset]; switch (value->type) { case NJS_STRING: @@ -2104,23 +2103,21 @@ encoding: goto done; } - if (last) { - if (from - to < (int64_t) str.length) { - goto done; - } + if (str.length > (size_t) length) { + goto done; + } + if (last) { from -= str.length - 1; + from = njs_max(from, 0); } else { - if (to - from < (int64_t) str.length) { - goto done; - } - to -= str.length - 1; + to = njs_min(to, length); } for (i = from; i != to; i += increment) { - if (memcmp(&u8[offset + i], str.start, str.length) == 0) { + if (memcmp(&u8[i], str.start, str.length) == 0) { index = i; goto done; } @@ -2132,7 +2129,7 @@ encoding: byte = njs_number_to_uint32(njs_number(value)); for (i = from; i != to; i += increment) { - if (u8[offset + i] == byte) { + if (u8[i] == byte) { index = i; goto done; } diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 75933665..c6979c13 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -21550,6 +21550,9 @@ static njs_unit_test_t njs_buffer_module_test[] = "buf.lastIndexOf('C')"), njs_str("2") }, + { njs_str("Buffer.from('abcdef').lastIndexOf('abc', 1)"), + njs_str("0") }, + { njs_str("['swap16', 'swap32', 'swap64'].every(method => {" " var buf = Buffer.from([]);" " buf[method]();" From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] QuickJS: fixed exception handling in shell output. Message-ID: <20240816013602.D8BF6487D8@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4b8188857b4385b5d2aff44cb548e92521c6c91f branches: master commit: 4b8188857b4385b5d2aff44cb548e92521c6c91f user: Dmitry Volyntsev date: Tue, 13 Aug 2024 17:14:14 -0700 description: QuickJS: fixed exception handling in shell output. --- external/njs_shell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index 672215c1..ec2a3875 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2108,6 +2108,7 @@ njs_qjs_njs_getter(JSContext *ctx, JSValueConst this_val) static njs_int_t njs_qjs_global_init(JSContext *ctx, JSValue global_obj); +static void njs_qjs_dump_error(JSContext *ctx); static void @@ -2126,7 +2127,7 @@ njs_qjs_dump_obj(JSContext *ctx, FILE *f, JSValueConst val, const char *prefix, JS_FreeCString(ctx, str); } else { - fprintf(f, "%s[exception]\n", prefix); + njs_qjs_dump_error(ctx); } } From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] Fixed Buffer.prototype.writeInt8() and friends. Message-ID: <20240816013602.D3418487D7@pubserv1.nginx> details: https://github.com/nginx/njs/commit/43dcb8ddaf30bc7f7ffd172de669e288b230073b branches: master commit: 43dcb8ddaf30bc7f7ffd172de669e288b230073b user: Dmitry Volyntsev date: Thu, 8 Aug 2024 00:12:29 -0700 description: Fixed Buffer.prototype.writeInt8() and friends. --- src/njs_buffer.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 1ce98ef2..07054bf0 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -10,8 +10,14 @@ #define INT24_MAX 0x7FFFFF -#define INT40_MAX 0x7FFFFFFFFFULL -#define INT48_MAX 0x7FFFFFFFFFFFULL +#define INT24_MIN (-0x800000) +#define INT40_MAX 0x7FFFFFFFFFLL +#define INT40_MIN (-0x8000000000LL) +#define INT48_MAX 0x7FFFFFFFFFFFLL +#define INT48_MIN (-0x800000000000LL) +#define UINT24_MAX 0xFFFFFFLL +#define UINT40_MAX 0xFFFFFFFFFFLL +#define UINT48_MAX 0xFFFFFFFFFFFFLL #define njs_buffer_magic(size, sign, little) \ ((size << 2) | (sign << 1) | little) @@ -1273,7 +1279,7 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, uint32_t u32; uint64_t index, size; njs_int_t ret; - njs_bool_t little, swap; + njs_bool_t little, swap, sign; njs_value_t *this, *value; njs_typed_array_t *array; njs_array_buffer_t *buffer; @@ -1321,6 +1327,7 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, } little = magic & 1; + sign = (magic >> 1) & 1; swap = little; #if NJS_HAVE_LITTLE_ENDIAN @@ -1336,12 +1343,42 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, switch (size) { case 1: + if (sign) { + if (i64 < INT8_MIN || i64 > INT8_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int8"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT8_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint8"); + return NJS_ERROR; + } + } + *u8 = i64; break; case 2: u32 = (uint16_t) i64; + if (sign) { + if (i64 < INT16_MIN || i64 > INT16_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int16"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT16_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint16"); + return NJS_ERROR; + } + } + if (swap) { u32 = njs_bswap_u16(u32); } @@ -1350,6 +1387,21 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, break; case 3: + if (sign) { + if (i64 < INT24_MIN || i64 > INT24_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int24"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT24_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint24"); + return NJS_ERROR; + } + } + if (little) { *u8++ = i64; i64 >>= 8; *u8++ = i64; i64 >>= 8; @@ -1366,6 +1418,21 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, break; case 4: + if (sign) { + if (i64 < INT32_MIN || i64 > INT32_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int32"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT32_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint32"); + return NJS_ERROR; + } + } + u32 = i64; if (swap) { @@ -1376,6 +1443,21 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, break; case 5: + if (sign) { + if (i64 < INT40_MIN || i64 > INT40_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int40"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT40_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint40"); + return NJS_ERROR; + } + } + if (little) { *u8++ = i64; i64 >>= 8; *u8++ = i64; i64 >>= 8; @@ -1397,6 +1479,21 @@ njs_buffer_prototype_write_int(njs_vm_t *vm, njs_value_t *args, case 6: default: + if (sign) { + if (i64 < INT48_MIN || i64 > INT48_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable int48"); + return NJS_ERROR; + } + + } else { + if (i64 < 0 || i64 > UINT48_MAX) { + njs_range_error(vm, "value is outside the range of " + "a representable uint48"); + return NJS_ERROR; + } + } + if (little) { *u8++ = i64; i64 >>= 8; *u8++ = i64; i64 >>= 8; From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] QuickJS: added buffer module. Message-ID: <20240816013602.E9806487DA@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d1c615eaa208a4e7c92b661f8f7674e92f19aedf branches: master commit: d1c615eaa208a4e7c92b661f8f7674e92f19aedf user: Dmitry Volyntsev date: Wed, 7 Aug 2024 23:11:24 -0700 description: QuickJS: added buffer module. --- src/qjs.h | 2 + src/qjs_buffer.c | 1845 ++++++++++++++++++++++++++++++++++++++++----- src/test/njs_unit_test.c | 817 -------------------- test/buffer.t.js | 815 +++++++++++++++++++- test/harness/runTsuite.js | 3 + 5 files changed, 2465 insertions(+), 1017 deletions(-) diff --git a/src/qjs.h b/src/qjs.h index 71e23d78..00e9296a 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -16,6 +16,7 @@ #include #include #include +#include #if defined(__GNUC__) && (__GNUC__ >= 8) #pragma GCC diagnostic push @@ -43,6 +44,7 @@ JSContext *qjs_new_context(JSRuntime *rt, _Bool eval); JSValue qjs_buffer_alloc(JSContext *ctx, size_t size); +JSValue qjs_buffer_create(JSContext *ctx, u_char *start, size_t size); JSValue qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain); typedef int (*qjs_buffer_encode_t)(JSContext *ctx, const njs_str_t *src, diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 166eb970..83764e02 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -6,23 +6,75 @@ #include +#define INT24_MAX 0x7FFFFF +#define INT24_MIN (-0x800000) +#define INT40_MAX 0x7FFFFFFFFFLL +#define INT40_MIN (-0x8000000000LL) +#define INT48_MAX 0x7FFFFFFFFFFFLL +#define INT48_MIN (-0x800000000000LL) +#define UINT24_MAX 0xFFFFFFLL +#define UINT40_MAX 0xFFFFFFFFFFLL +#define UINT48_MAX 0xFFFFFFFFFFFFLL + +#define qjs_buffer_magic(size, sign, little) \ + ((size << 2) | (sign << 1) | little) + static JSValue qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv); +static JSValue qjs_bufferobj_alloc(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int ignored); +static JSValue qjs_buffer_byte_length(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_buffer_compare(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_buffer_fill(JSContext *ctx, JSValueConst buffer, + JSValueConst fill, JSValueConst encode, uint64_t offset, uint64_t end); static JSValue qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_is_encoding(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_compare(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_copy(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_equals(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_fill(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_includes(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_index_of(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv, int last); +static JSValue qjs_buffer_prototype_read_float(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv, int magic); +static JSValue qjs_buffer_prototype_read_int(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv, int magic); +static JSValue qjs_buffer_prototype_swap(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int size); static JSValue qjs_buffer_prototype_to_json(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); static JSValue qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv); +static JSValue qjs_buffer_prototype_write_int(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv, int magic); +static JSValue qjs_buffer_prototype_write_float(JSContext *ctx, + JSValueConst this_val, int argc, JSValueConst *argv, int magic); static JSValue qjs_buffer_from_string(JSContext *ctx, JSValueConst str, JSValueConst encoding); static JSValue qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst obj, size_t offset, size_t size, size_t bytes, int float32); -static JSValue qjs_buffer_from_array_buffer(JSContext *ctx, u_char *buf, - size_t size, JSValueConst offset, JSValueConst length); static JSValue qjs_buffer_from_object(JSContext *ctx, JSValueConst obj); +static JSValue qjs_buffer_compare_array(JSContext *ctx, JSValue val1, + JSValue val2, JSValueConst target_start, JSValueConst target_end, + JSValueConst source_start, JSValueConst source_end); static int qjs_base64_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst); static size_t qjs_base64_encode_length(JSContext *ctx, const njs_str_t *src); @@ -38,7 +90,8 @@ static int qjs_hex_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst); static size_t qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src); static int qjs_hex_decode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst); static size_t qjs_hex_decode_length(JSContext *ctx, const njs_str_t *src); -static JSValue qjs_new_uint8_array(JSContext *ctx, size_t size); +static JSValue qjs_new_uint8_array(JSContext *ctx, int argc, + JSValueConst *argv); static JSModuleDef *qjs_buffer_init(JSContext *ctx, const char *name); @@ -103,14 +156,103 @@ static const JSCFunctionListEntry qjs_buffer_export[] = { static const JSCFunctionListEntry qjs_buffer_props[] = { + JS_CFUNC_MAGIC_DEF("alloc", 3, qjs_bufferobj_alloc, 0), + JS_CFUNC_MAGIC_DEF("allocUnsafe", 3, qjs_bufferobj_alloc, 1), + JS_CFUNC_DEF("byteLength", 2, qjs_buffer_byte_length), + JS_CFUNC_DEF("compare", 6, qjs_buffer_compare), + JS_CFUNC_DEF("concat", 1, qjs_buffer_concat), JS_CFUNC_DEF("from", 3, qjs_buffer_from), JS_CFUNC_DEF("isBuffer", 1, qjs_buffer_is_buffer), + JS_CFUNC_DEF("isEncoding", 1, qjs_buffer_is_encoding), }; static const JSCFunctionListEntry qjs_buffer_proto[] = { + JS_CFUNC_DEF("compare", 5, qjs_buffer_prototype_compare), + JS_CFUNC_DEF("copy", 5, qjs_buffer_prototype_copy), + JS_CFUNC_DEF("equals", 1, qjs_buffer_prototype_equals), + JS_CFUNC_DEF("fill", 4, qjs_buffer_prototype_fill), + JS_CFUNC_DEF("includes", 3, qjs_buffer_prototype_includes), + JS_CFUNC_MAGIC_DEF("indexOf", 3, qjs_buffer_prototype_index_of, 0), + JS_CFUNC_MAGIC_DEF("lastIndexOf", 3, qjs_buffer_prototype_index_of, 1), + JS_CFUNC_MAGIC_DEF("readFloatLE", 1, qjs_buffer_prototype_read_float, + qjs_buffer_magic(4, 1, 1)), + JS_CFUNC_MAGIC_DEF("readFloatBE", 1, qjs_buffer_prototype_read_float, + qjs_buffer_magic(4, 1, 0)), + JS_CFUNC_MAGIC_DEF("readDoubleLE", 1, qjs_buffer_prototype_read_float, + qjs_buffer_magic(8, 1, 1)), + JS_CFUNC_MAGIC_DEF("readDoubleBE", 1, qjs_buffer_prototype_read_float, + qjs_buffer_magic(8, 1, 0)), + JS_CFUNC_MAGIC_DEF("readInt8", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(1, 1, 1)), + JS_CFUNC_MAGIC_DEF("readUInt8", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(1, 0, 1)), + JS_CFUNC_MAGIC_DEF("readInt16LE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(2, 1, 1)), + JS_CFUNC_MAGIC_DEF("readUInt16LE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(2, 0, 1)), + JS_CFUNC_MAGIC_DEF("readInt16BE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(2, 1, 0)), + JS_CFUNC_MAGIC_DEF("readUInt16BE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(2, 0, 0)), + JS_CFUNC_MAGIC_DEF("readInt32LE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(4, 1, 1)), + JS_CFUNC_MAGIC_DEF("readUInt32LE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(4, 0, 1)), + JS_CFUNC_MAGIC_DEF("readInt32BE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(4, 1, 0)), + JS_CFUNC_MAGIC_DEF("readUInt32BE", 1, qjs_buffer_prototype_read_int, + qjs_buffer_magic(4, 0, 0)), + JS_CFUNC_MAGIC_DEF("readIntLE", 2, qjs_buffer_prototype_read_int, + qjs_buffer_magic(0, 1, 1)), + JS_CFUNC_MAGIC_DEF("readUIntLE", 2, qjs_buffer_prototype_read_int, + qjs_buffer_magic(0, 0, 1)), + JS_CFUNC_MAGIC_DEF("readIntBE", 2, qjs_buffer_prototype_read_int, + qjs_buffer_magic(0, 1, 0)), + JS_CFUNC_MAGIC_DEF("readUIntBE", 2, qjs_buffer_prototype_read_int, + qjs_buffer_magic(0, 0, 0)), + JS_CFUNC_MAGIC_DEF("swap16", 0, qjs_buffer_prototype_swap, 2), + JS_CFUNC_MAGIC_DEF("swap32", 0, qjs_buffer_prototype_swap, 4), + JS_CFUNC_MAGIC_DEF("swap64", 0, qjs_buffer_prototype_swap, 8), JS_CFUNC_DEF("toJSON", 0, qjs_buffer_prototype_to_json), JS_CFUNC_DEF("toString", 1, qjs_buffer_prototype_to_string), + JS_CFUNC_DEF("write", 4, qjs_buffer_prototype_write), + JS_CFUNC_MAGIC_DEF("writeInt8", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(1, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeUInt8", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(1, 0, 1)), + JS_CFUNC_MAGIC_DEF("writeInt16LE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(2, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeUInt16LE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(2, 0, 1)), + JS_CFUNC_MAGIC_DEF("writeInt16BE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(2, 1, 0)), + JS_CFUNC_MAGIC_DEF("writeUInt16BE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(2, 0, 0)), + JS_CFUNC_MAGIC_DEF("writeInt32LE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(4, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeUInt32LE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(4, 0, 1)), + JS_CFUNC_MAGIC_DEF("writeInt32BE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(4, 1, 0)), + JS_CFUNC_MAGIC_DEF("writeUInt32BE", 1, qjs_buffer_prototype_write_int, + qjs_buffer_magic(4, 0, 0)), + JS_CFUNC_MAGIC_DEF("writeIntLE", 2, qjs_buffer_prototype_write_int, + qjs_buffer_magic(0, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeUIntLE", 2, qjs_buffer_prototype_write_int, + qjs_buffer_magic(0, 0, 1)), + JS_CFUNC_MAGIC_DEF("writeIntBE", 2, qjs_buffer_prototype_write_int, + qjs_buffer_magic(0, 1, 0)), + JS_CFUNC_MAGIC_DEF("writeUIntBE", 2, qjs_buffer_prototype_write_int, + qjs_buffer_magic(0, 0, 0)), + JS_CFUNC_MAGIC_DEF("writeFloatLE", 2, qjs_buffer_prototype_write_float, + qjs_buffer_magic(4, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeFloatBE", 2, qjs_buffer_prototype_write_float, + qjs_buffer_magic(4, 1, 0)), + JS_CFUNC_MAGIC_DEF("writeDoubleLE", 2, qjs_buffer_prototype_write_float, + qjs_buffer_magic(8, 1, 1)), + JS_CFUNC_MAGIC_DEF("writeDoubleBE", 2, qjs_buffer_prototype_write_float, + qjs_buffer_magic(8, 1, 0)), }; @@ -201,6 +343,288 @@ qjs_buffer(JSContext *ctx, JSValueConst this_val, int argc, } +static JSValue +qjs_buffer_ctor(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret, proto; + + ret = qjs_new_uint8_array(ctx, argc, argv); + if (JS_IsException(ret)) { + return ret; + } + + proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + JS_SetPrototype(ctx, ret, proto); + JS_FreeValue(ctx, proto); + + return ret; +} + + +static JSValue +qjs_bufferobj_alloc(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv, int ignored) +{ + JSValue buffer, ret; + uint32_t size; + + if (!JS_IsNumber(argv[0])) { + return JS_ThrowTypeError(ctx, "The \"size\" argument must be of type" + " number"); + } + + if (JS_ToUint32(ctx, &size, argv[0])) { + return JS_EXCEPTION; + } + + buffer = qjs_buffer_alloc(ctx, size); + if (JS_IsException(buffer)) { + return buffer; + } + + if (!JS_IsUndefined(argv[1])) { + ret = qjs_buffer_fill(ctx, buffer, argv[1], argv[2], 0, size); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, buffer); + return ret; + } + } + + return buffer; +} + + +static JSValue +qjs_buffer_byte_length(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + size_t size; + JSValue ret; + njs_str_t src; + const qjs_buffer_encoding_t *encoding; + + if (JS_GetArrayBuffer(ctx, &size, argv[0]) != NULL) { + return JS_NewInt32(ctx, size); + } + + ret = JS_GetTypedArrayBuffer(ctx, argv[0], NULL, &size, NULL); + if (!JS_IsException(ret)) { + JS_FreeValue(ctx, ret); + return JS_NewInt32(ctx, size); + } + + if (!JS_IsString(argv[0])) { + return JS_ThrowTypeError(ctx, "first argument is not a string " + "or Buffer-like object"); + } + + encoding = qjs_buffer_encoding(ctx, argv[1], 1); + if (encoding == NULL) { + return JS_EXCEPTION; + } + + src.start = (u_char *) JS_ToCStringLen(ctx, &src.length, argv[0]); + + if (encoding->decode_length != NULL) { + size = encoding->decode_length(ctx, &src); + + } else { + size = src.length; + } + + JS_FreeCString(ctx, (char *) src.start); + + return JS_NewInt32(ctx, size); +} + + +static JSValue +qjs_buffer_compare(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + return qjs_buffer_compare_array(ctx, argv[0], argv[1], argv[2], argv[3], + argv[4], argv[5]); +} + + +static JSValue +qjs_buffer_concat(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + u_char *p; + size_t n; + JSValue list, length, val, ret, buffer; + uint32_t i, len, list_len; + njs_str_t buf, dst; + + list = argv[0]; + + if (!JS_IsArray(ctx, list)) { + return JS_ThrowTypeError(ctx, + "\"list\" argument must be an instance of Array"); + } + + length = JS_GetPropertyStr(ctx, list, "length"); + if (JS_IsException(length)) { + return JS_EXCEPTION; + } + + len = 0; + if (JS_ToUint32(ctx, &list_len, length)) { + JS_FreeValue(ctx, length); + return JS_EXCEPTION; + } + + JS_FreeValue(ctx, length); + + if (JS_IsUndefined(argv[1])) { + for (i = 0; i < list_len; i++) { + val = JS_GetPropertyUint32(ctx, list, i); + if (JS_IsException(val)) { + return JS_EXCEPTION; + } + + ret = qjs_typed_array_data(ctx, val, &buf); + JS_FreeValue(ctx, val); + if (JS_IsException(ret)) { + return JS_ThrowTypeError(ctx, "\"list[%d]\" argument must be an" + " instance of Buffer or Uint8Array", i); + } + + if ((SIZE_MAX - len) < buf.length) { + return JS_ThrowTypeError(ctx, + "Total size of buffers is too large"); + } + + len += buf.length; + } + + } else { + if (JS_ToUint32(ctx, &len, argv[1])) { + return JS_EXCEPTION; + } + } + + buffer = qjs_buffer_alloc(ctx, len); + if (JS_IsException(buffer)) { + return JS_EXCEPTION; + } + + ret = qjs_typed_array_data(ctx, buffer, &dst); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + + p = dst.start; + + for (i = 0; len != 0 && i < list_len; i++) { + val = JS_GetPropertyUint32(ctx, list, i); + if (JS_IsException(val)) { + JS_FreeValue(ctx, buffer); + return JS_EXCEPTION; + } + + ret = qjs_typed_array_data(ctx, val, &buf); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, buffer); + JS_FreeValue(ctx, val); + return JS_EXCEPTION; + } + + JS_FreeValue(ctx, val); + + n = njs_min((size_t) len, buf.length); + p = njs_cpymem(p, buf.start, n); + + len -= n; + } + + if (len != 0) { + njs_memzero(p, len); + } + + return buffer; +} + + +static JSValue +qjs_buffer_fill(JSContext *ctx, JSValueConst buffer, JSValueConst fill, + JSValueConst encode, uint64_t offset, uint64_t end) +{ + JSValue ret, fill_buf; + uint32_t n; + njs_str_t dst, src; + + ret = qjs_typed_array_data(ctx, buffer, &dst); + if (JS_IsException(ret)) { + return ret; + } + + if (end > dst.length) { + return JS_ThrowRangeError(ctx, "\"end\" is out of range"); + } + + if (offset >= end) { + return buffer; + } + + if (JS_IsNumber(fill)) { + if (JS_ToUint32(ctx, &n, fill)) { + return JS_EXCEPTION; + } + + memset(dst.start + offset, n & 0xff, end - offset); + return buffer; + } + + fill_buf = JS_UNDEFINED; + + if (JS_IsString(fill)) { + fill_buf = qjs_buffer_from_string(ctx, fill, encode); + if (JS_IsException(fill_buf)) { + return fill_buf; + } + + fill = fill_buf; + } + + ret = qjs_typed_array_data(ctx, fill, &src); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, fill_buf); + return ret; + } + + if (src.length == 0) { + memset(dst.start + offset, 0, end - offset); + JS_FreeValue(ctx, fill_buf); + return buffer; + } + + if (src.start >= (dst.start + dst.length) + || dst.start >= (dst.start + dst.length)) + { + while (offset < end) { + n = njs_min(src.length, end - offset); + memcpy(dst.start + offset, src.start, n); + offset += n; + } + + } else { + while (offset < end) { + n = njs_min(src.length, end - offset); + memmove(dst.start + offset, src.start, n); + offset += n; + } + } + + JS_FreeValue(ctx, fill_buf); + + return buffer; +} + + static JSValue qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) @@ -230,188 +654,1223 @@ qjs_buffer_from(JSContext *ctx, JSValueConst this_val, int argc, return ctor; } - name = JS_GetPropertyStr(ctx, ctor, "name"); - if (JS_IsException(name)) { - JS_FreeValue(ctx, ret); - return name; - } + name = JS_GetPropertyStr(ctx, ctor, "name"); + if (JS_IsException(name)) { + JS_FreeValue(ctx, ret); + return name; + } + + JS_FreeValue(ctx, ctor); + str = JS_ToCString(ctx, name); + + if (strncmp(str, "Float32Array", 12) == 0) { + float32 = 1; + } + + JS_FreeCString(ctx, str); + JS_FreeValue(ctx, name); + } + + return qjs_buffer_from_typed_array(ctx, ret, off, size, bytes, float32); + + } else if ((buf = JS_GetArrayBuffer(ctx, &size, argv[0])) != NULL) { + return qjs_buffer_ctor(ctx, JS_UNDEFINED, argc, argv); + + } else if (JS_IsObject(argv[0])) { + obj = argv[0]; + valueOf = JS_GetPropertyStr(ctx, obj, "valueOf"); + if (JS_IsException(valueOf)) { + return valueOf; + } + + if (JS_IsFunction(ctx, valueOf)) { + ret = JS_Call(ctx, valueOf, obj, 0, NULL); + JS_FreeValue(ctx, valueOf); + if (JS_IsException(ret)) { + return ret; + } + + if (JS_IsString(ret)) { + obj = ret; + ret = qjs_buffer_from_string(ctx, obj, argv[1]); + JS_FreeValue(ctx, obj); + return ret; + } + + if (JS_IsObject(ret) + && JS_VALUE_GET_PTR(ret) != JS_VALUE_GET_PTR(obj)) + { + obj = ret; + ret = qjs_buffer_from_object(ctx, obj); + JS_FreeValue(ctx, obj); + return ret; + } + + JS_FreeValue(ctx, ret); + } + + return qjs_buffer_from_object(ctx, obj); + } + + JS_ThrowTypeError(ctx, "first argument is not a string " + "or Buffer-like object"); + return JS_EXCEPTION; +} + + +static JSValue +qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue proto, buffer_proto, ret; + + proto = JS_GetPrototype(ctx, argv[0]); + buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + + ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT && + JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto)); + + JS_FreeValue(ctx, buffer_proto); + JS_FreeValue(ctx, proto); + + return ret; +} + + +static JSValue +qjs_buffer_is_encoding(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewBool(ctx, qjs_buffer_encoding(ctx, argv[0], 0) != NULL); +} + + +static JSValue +qjs_buffer_array_range(JSContext *ctx, njs_str_t *array, JSValueConst start, + JSValueConst end, const char *name) +{ + int64_t num_start, num_end; + + num_start = 0; + + if (!JS_IsUndefined(start)) { + if (JS_ToInt64(ctx, &num_start, start)) { + return JS_EXCEPTION; + } + } + + if (num_start < 0 || (size_t) num_start > array->length) { + return JS_ThrowRangeError(ctx, "\"%sStart\" is out of range: %ld", + name, num_start); + } + + num_end = array->length; + + if (!JS_IsUndefined(end)) { + if (JS_ToInt64(ctx, &num_end, end)) { + return JS_EXCEPTION; + } + } + + if (num_end < 0 || (size_t) num_end > array->length) { + return JS_ThrowRangeError(ctx, "\"%sEnd\" is out of range: %ld", + name, num_end); + } + + if (num_start > num_end) { + num_end = num_start; + } + + array->start += num_start; + array->length = num_end - num_start; + + return JS_UNDEFINED; +} + + +static JSValue +qjs_buffer_compare_array(JSContext *ctx, JSValue val1, JSValue val2, + JSValueConst target_start, JSValueConst target_end, + JSValueConst source_start, JSValueConst source_end) +{ + int rc; + size_t size; + JSValue ret; + njs_str_t src, target; + + ret = qjs_typed_array_data(ctx, val1, &src); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_typed_array_data(ctx, val2, &target); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_buffer_array_range(ctx, &src, source_start, source_end, "source"); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_buffer_array_range(ctx, &target, target_start, target_end, + "target"); + if (JS_IsException(ret)) { + return ret; + } + + size = njs_min(src.length, target.length); + + rc = memcmp(src.start, target.start, size); + + if (rc != 0) { + return JS_NewInt32(ctx, (rc < 0) ? -1 : 1); + } + + if (target.length > src.length) { + rc = -1; + + } else if (target.length < src.length) { + rc = 1; + } + + return JS_NewInt32(ctx, rc); +} + + +static JSValue +qjs_buffer_prototype_compare(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + return qjs_buffer_compare_array(ctx, this_val, argv[0], argv[1], argv[2], + argv[3], argv[4]); +} + + +static JSValue +qjs_buffer_prototype_copy(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + size_t size; + JSValue ret; + njs_str_t src, target; + + ret = qjs_typed_array_data(ctx, this_val, &src); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_typed_array_data(ctx, argv[0], &target); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_buffer_array_range(ctx, &target, argv[1], JS_UNDEFINED, "target"); + if (JS_IsException(ret)) { + return ret; + } + + ret = qjs_buffer_array_range(ctx, &src, argv[2], argv[3], "source"); + if (JS_IsException(ret)) { + return ret; + } + + size = njs_min(src.length, target.length); + + if (src.start >= (target.start + size) + || target.start >= (src.start + size)) + { + memcpy(target.start, src.start, size); + + } else { + memmove(target.start, src.start, size); + } + + return JS_NewInt32(ctx, size); +} + + +static JSValue +qjs_buffer_prototype_equals(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret; + + ret = qjs_buffer_compare_array(ctx, this_val, argv[0], JS_UNDEFINED, + JS_UNDEFINED, JS_UNDEFINED, JS_UNDEFINED); + if (JS_IsException(ret)) { + return ret; + } + + return JS_NewBool(ctx, JS_VALUE_GET_INT(ret) == 0); +} + + +static JSValue +qjs_buffer_prototype_fill(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret, encode; + uint64_t offset, end; + njs_str_t dst; + + offset = 0; + encode = argv[3]; + + ret = qjs_typed_array_data(ctx, this_val, &dst); + if (JS_IsException(ret)) { + return ret; + } + + end = dst.length; + + if (!JS_IsUndefined(argv[1])) { + if (JS_IsString(argv[0]) && JS_IsString(argv[1])) { + encode = argv[1]; + goto fill; + } + + if (JS_ToIndex(ctx, &offset, argv[1])) { + return JS_EXCEPTION; + } + } + + if (!JS_IsUndefined(argv[2])) { + if (JS_IsString(argv[0]) && JS_IsString(argv[2])) { + encode = argv[2]; + goto fill; + } + + if (JS_ToIndex(ctx, &end, argv[2])) { + return JS_EXCEPTION; + } + } + +fill: + + ret = qjs_buffer_fill(ctx, this_val, argv[0], encode, offset, end); + if (JS_IsException(ret)) { + return ret; + } + + JS_DupValue(ctx, ret); + + return ret; +} + + +static JSValue +qjs_buffer_prototype_includes(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + JSValue ret; + + ret = qjs_buffer_prototype_index_of(ctx, this_val, argc, argv, 0); + if (JS_IsException(ret)) { + return ret; + } + + return JS_NewBool(ctx, JS_VALUE_GET_INT(ret) != -1); +} + + +static JSValue +qjs_buffer_prototype_index_of(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv, int last) +{ + JSValue ret, buffer, encode, value; + int64_t from, to, increment, length, i; + uint32_t byte; + njs_str_t self, str; + const qjs_buffer_encoding_t *encoding; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + length = self.length; + + if (length == 0) { + return JS_NewInt32(ctx, -1); + } + + if (last) { + from = length - 1; + to = -1; + increment = -1; + + } else { + from = 0; + to = length; + increment = 1; + } + + encode = argv[2]; + + if (!JS_IsUndefined(argv[1])) { + if (JS_IsString(argv[0]) && JS_IsString(argv[1])) { + encode = argv[1]; + goto encoding; + } + + if (JS_ToInt64(ctx, &from, argv[1])) { + return JS_EXCEPTION; + } + + if (last) { + if (from >= 0) { + from = njs_min(from, length - 1); + + } else if (from < 0) { + from += length; + } + + if (from <= to) { + return JS_NewInt32(ctx, -1); + } + + } else { + if (from < 0) { + from += length; + + if (from < 0) { + from = 0; + } + } + + if (from >= to) { + return JS_NewInt32(ctx, -1); + } + } + } + + if (JS_IsNumber(argv[0])) { + if (JS_ToUint32(ctx, &byte, argv[0])) { + return JS_EXCEPTION; + } + + for (i = from; i != to; i += increment) { + if (self.start[i] == (uint8_t) byte) { + return JS_NewInt32(ctx, i); + } + } + + return JS_NewInt32(ctx, -1); + } + +encoding: + + buffer = JS_UNDEFINED; + value = argv[0]; + + if (JS_IsString(value)) { + encoding = qjs_buffer_encoding(ctx, encode, 1); + if (encoding == NULL) { + return JS_EXCEPTION; + } + + buffer = qjs_buffer_from_string(ctx, value, encode); + if (JS_IsException(buffer)) { + return buffer; + } + + value = buffer; + } + + ret = qjs_typed_array_data(ctx, value, &str); + if (JS_IsException(ret)) { + JS_FreeValue(ctx, buffer); + return JS_ThrowTypeError(ctx, "\"value\" argument is not a string " + "or Buffer-like object"); + } + + if (str.length == 0) { + JS_FreeValue(ctx, buffer); + return JS_NewInt32(ctx, (last) ? length : 0); + } + + if (str.length > (size_t) length) { + JS_FreeValue(ctx, buffer); + return JS_NewInt32(ctx, -1); + } + + if (last) { + from -= str.length - 1; + from = njs_max(from, 0); + + } else { + to -= str.length - 1; + to = njs_min(to, length); + } + + for (i = from; i != to; i += increment) { + if (memcmp(&self.start[i], str.start, str.length) == 0) { + JS_FreeValue(ctx, buffer); + return JS_NewInt32(ctx, i); + } + } + + JS_FreeValue(ctx, buffer); + return JS_NewInt32(ctx, -1); +} + + +static JSValue +qjs_buffer_prototype_read_float(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + double v; + JSValue ret; + uint32_t u32; + uint64_t u64, index, size; + njs_str_t self; + njs_bool_t little, swap; + njs_conv_f32_t conv_f32; + njs_conv_f64_t conv_f64; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + if (JS_ToIndex(ctx, &index, argv[0])) { + return JS_EXCEPTION; + } + + size = magic >> 2; + + if (size + index > self.length) { + return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" + " buffer", index); + } + + little = magic & 1; + swap = little; + +#if NJS_HAVE_LITTLE_ENDIAN + swap = !swap; +#endif + + switch (size) { + case 4: + u32 = *((uint32_t *) &self.start[index]); + + if (swap) { + u32 = njs_bswap_u32(u32); + } + + conv_f32.u = u32; + v = conv_f32.f; + break; + + case 8: + default: + u64 = *((uint64_t *) &self.start[index]); + + if (swap) { + u64 = njs_bswap_u64(u64); + } + + conv_f64.u = u64; + v = conv_f64.f; + break; + } + + return JS_NewFloat64(ctx, v); +} + + +static JSValue +qjs_buffer_prototype_read_int(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSValue ret; + uint32_t u32; + uint64_t u64, index, size; + njs_str_t self; + njs_bool_t little, swap, sign; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + if (JS_ToIndex(ctx, &index, argv[0])) { + return JS_EXCEPTION; + } + + size = magic >> 2; + + if (!size) { + if (!JS_IsNumber(argv[1])) { + return JS_ThrowTypeError(ctx, "\"byteLength\" is not a number"); + } + + if (JS_ToIndex(ctx, &size, argv[1])) { + return JS_EXCEPTION; + } + + if (size > 6) { + return JS_ThrowRangeError(ctx, "\"byteLength\" must be <= 6"); + } + } + + if (size + index > self.length) { + return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" + " buffer", index); + } + + sign = (magic >> 1) & 1; + little = magic & 1; + swap = little; + +#if NJS_HAVE_LITTLE_ENDIAN + swap = !swap; +#endif + + switch (size) { + case 1: + if (sign) { + return JS_NewInt32(ctx, (int8_t) self.start[index]); + } + + return JS_NewUint32(ctx, self.start[index]); + + case 2: + u32 = njs_get_u16(&self.start[index]); + + if (swap) { + u32 = njs_bswap_u16(u32); + } + + if (sign) { + /* Sign extension. */ + u32 |= (u32 & (INT16_MAX + 1ULL)) * UINT32_MAX; + + return JS_NewInt32(ctx, (int16_t) u32); + } + + return JS_NewUint32(ctx, u32); + + case 3: + + if (little) { + u32 = (self.start[index + 2] << 16) + | (self.start[index + 1] << 8) + | self.start[index]; + + } else { + u32 = (self.start[index] << 16) + | (self.start[index + 1] << 8) + | self.start[index + 2]; + } + + if (sign) { + /* Sign extension. */ + u32 |= (u32 & (INT24_MAX + 1ULL)) * UINT32_MAX; + + return JS_NewInt32(ctx, (int32_t) u32); + } + + return JS_NewUint32(ctx, u32); + + case 4: + u32 = njs_get_u32(&self.start[index]); + + if (swap) { + u32 = njs_bswap_u32(u32); + } + + if (sign) { + /* Sign extension. */ + u32 |= (u32 & (INT32_MAX + 1ULL)) * UINT32_MAX; + + return JS_NewInt32(ctx, (int32_t) u32); + } + + return JS_NewUint32(ctx, u32); + + case 5: + if (little) { + u64 = ((uint64_t) self.start[index + 4] << 32) + | ((uint64_t) self.start[index + 3] << 24) + | (self.start[index + 2] << 16) + | (self.start[index + 1] << 8) + | self.start[index]; + + } else { + u64 = ((uint64_t) self.start[index] << 32) + | ((uint64_t) self.start[index + 1] << 24) + | (self.start[index + 2] << 16) + | (self.start[index + 3] << 8) + | self.start[index + 4]; + } + + if (sign) { + /* Sign extension. */ + u64 |= (u64 & (INT40_MAX + 1ULL)) * UINT64_MAX; + + return JS_NewFloat64(ctx, (int64_t) u64); + } + + return JS_NewFloat64(ctx, u64); + + case 6: + default: + if (little) { + u64 = ((uint64_t) self.start[index + 5] << 40) + | ((uint64_t) self.start[index + 4] << 32) + | ((uint64_t) self.start[index + 3] << 24) + | (self.start[index + 2] << 16) + | (self.start[index + 1] << 8) + | self.start[index]; + + } else { + u64 = ((uint64_t) self.start[index] << 40) + | ((uint64_t) self.start[index + 1] << 32) + | ((uint64_t) self.start[index + 2] << 24) + | (self.start[index + 3] << 16) + | (self.start[index + 4] << 8) + | self.start[index + 5]; + } + + if (sign) { + /* Sign extension. */ + u64 |= (u64 & (INT48_MAX + 1ULL)) * UINT64_MAX; + + return JS_NewFloat64(ctx, (int64_t) u64); + } + + return JS_NewFloat64(ctx, u64); + } +} + + +static JSValue +qjs_buffer_prototype_to_json(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + int rc; + JSValue obj, data, ret; + njs_str_t src; + njs_uint_t i; + + ret = qjs_typed_array_data(ctx, this_val, &src); + if (JS_IsException(ret)) { + return ret; + } + + obj = JS_NewObject(ctx); + if (JS_IsException(obj)) { + return obj; + } + + data = JS_NewArray(ctx); + if (JS_IsException(data)) { + JS_FreeValue(ctx, obj); + return data; + } + + rc = JS_DefinePropertyValueStr(ctx, obj, "type", + JS_NewString(ctx, "Buffer"), + JS_PROP_ENUMERABLE); + if (rc == -1) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, data); + return ret; + } + + rc = JS_DefinePropertyValueStr(ctx, obj, "data", data, JS_PROP_ENUMERABLE); + if (rc == -1) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, data); + return ret; + } + + for (i = 0; i < src.length; i++) { + rc = JS_SetPropertyUint32(ctx, data, i, JS_NewInt32(ctx, src.start[i])); + if (rc == -1) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, data); + return ret; + } + } + + return obj; +} + + +static JSValue +qjs_buffer_prototype_swap(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int size) +{ + uint8_t *p, *end; + JSValue ret; + njs_str_t self; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + if ((self.length % size) != 0) { + return JS_ThrowRangeError(ctx, "Buffer size must be a multiple " + "of %d-bits", (int) (size << 3)); + } + + p = self.start; + end = p + self.length; + + switch (size) { + case 2: + for (; p < end; p += 2) { + njs_set_u16(p, njs_bswap_u16(njs_get_u16(p))); + } + + break; + + case 4: + for (; p < end; p += 4) { + njs_set_u32(p, njs_bswap_u32(njs_get_u32(p))); + } + + break; + + case 8: + default: + for (; p < end; p += 8) { + njs_set_u64(p, njs_bswap_u64(njs_get_u64(p))); + } + } + + JS_DupValue(ctx, this_val); + + return this_val; +} + + +static JSValue +qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret; + njs_str_t src, data; + const qjs_buffer_encoding_t *encoding; + + ret = qjs_typed_array_data(ctx, this_val, &src); + if (JS_IsException(ret)) { + return JS_ThrowTypeError(ctx, "method toString() called on incompatible" + " object"); + } + + if (JS_IsUndefined(argv[0]) || src.length == 0) { + return JS_NewStringLen(ctx, (char *) src.start, src.length); + } + + encoding = qjs_buffer_encoding(ctx, argv[0], 1); + if (njs_slow_path(encoding == NULL)) { + return JS_EXCEPTION; + } + + if (encoding->encode_length == NULL) { + return JS_NewStringLen(ctx, (char *) src.start, src.length); + } + + data.length = encoding->encode_length(ctx, &src); + data.start = js_malloc(ctx, data.length); + if (njs_slow_path(data.start == NULL)) { + JS_ThrowOutOfMemory(ctx); + return JS_EXCEPTION; + } + + if (encoding->encode(ctx, &src, &data) != 0) { + js_free(ctx, data.start); + JS_ThrowTypeError(ctx, "failed to encode buffer"); + return JS_EXCEPTION; + } + + ret = JS_NewStringLen(ctx, (char *) data.start, data.length); + + js_free(ctx, data.start); + + return ret; +} + + +static JSValue +qjs_buffer_prototype_write(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + JSValue ret, buffer, encode; + uint64_t offset, max_length; + njs_str_t self, src; + const uint8_t *p, *end, *prev; + const qjs_buffer_encoding_t *encoding; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + offset = 0; + max_length = self.length; + encode = argv[3]; + + if (!JS_IsUndefined(argv[1])) { + if (JS_IsString(argv[0]) && JS_IsString(argv[1])) { + encode = argv[1]; + goto write; + } + + if (JS_ToIndex(ctx, &offset, argv[1])) { + return JS_EXCEPTION; + } + + max_length = self.length - offset; + } + + if (!JS_IsUndefined(argv[2])) { + if (JS_IsString(argv[0]) && JS_IsString(argv[2])) { + encode = argv[2]; + goto write; + } + + if (JS_ToIndex(ctx, &max_length, argv[2])) { + return JS_EXCEPTION; + } + } + +write: + + encoding = qjs_buffer_encoding(ctx, encode, 1); + if (encoding == NULL) { + return JS_EXCEPTION; + } + + buffer = qjs_buffer_from_string(ctx, argv[0], encode); + if (JS_IsException(buffer)) { + return buffer; + } + + (void) qjs_typed_array_data(ctx, buffer, &src); + + if (offset > self.length) { + JS_FreeValue(ctx, buffer); + return JS_ThrowRangeError(ctx, "\"offset\" is out of range"); + } + + if (src.length == 0) { + JS_FreeValue(ctx, buffer); + return JS_NewInt32(ctx, 0); + } + + if (max_length > self.length - offset) { + JS_FreeValue(ctx, buffer); + return JS_ThrowRangeError(ctx, "\"length\" is out of range"); + } + + max_length = njs_min(max_length, src.length); + + if (encoding->decode == NULL) { + /* Avoid writing incomplete UTF-8 characters. */ + p = prev = src.start; + end = p + max_length; + + while (p < end) { + p = njs_utf8_next(p, src.start + src.length); + if (p <= end) { + prev = p; + } + } + + max_length = prev - src.start; + } + + memcpy(&self.start[offset], src.start, max_length); + + JS_FreeValue(ctx, buffer); + + return JS_NewInt32(ctx, max_length); +} + + +static JSValue +qjs_buffer_prototype_write_int(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) +{ + JSValue ret; + int64_t i64; + uint32_t u32; + uint64_t index, size; + njs_str_t self; + njs_bool_t little, swap, sign; + + ret = qjs_typed_array_data(ctx, this_val, &self); + if (JS_IsException(ret)) { + return ret; + } + + if (JS_ToIndex(ctx, &index, argv[1])) { + return JS_EXCEPTION; + } + + size = magic >> 2; + + if (!size) { + if (JS_ToIndex(ctx, &size, argv[2])) { + return JS_EXCEPTION; + } + + if (size > 6) { + return JS_ThrowRangeError(ctx, "\"byteLength\" must be <= 6"); + } + } + + if (size + index > self.length) { + return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" + " buffer", index); + } + + little = magic & 1; + sign = (magic >> 1) & 1; + swap = little; + +#if NJS_HAVE_LITTLE_ENDIAN + swap = !swap; +#endif - JS_FreeValue(ctx, ctor); - str = JS_ToCString(ctx, name); + if (JS_ToInt64(ctx, &i64, argv[0])) { + return JS_EXCEPTION; + } - if (strncmp(str, "Float32Array", 12) == 0) { - float32 = 1; + switch (size) { + case 1: + if (sign) { + if (i64 < INT8_MIN || i64 > INT8_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); } - JS_FreeCString(ctx, str); - JS_FreeValue(ctx, name); + } else { + if (i64 < 0 || i64 > UINT8_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } } - return qjs_buffer_from_typed_array(ctx, ret, off, size, bytes, float32); - - } else if ((buf = JS_GetArrayBuffer(ctx, &size, argv[0])) != NULL) { - return qjs_buffer_from_array_buffer(ctx, buf, size, argv[1], argv[2]); + self.start[index] = (uint8_t) i64; + break; - } else if (JS_IsObject(argv[0])) { - obj = argv[0]; - valueOf = JS_GetPropertyStr(ctx, obj, "valueOf"); - if (JS_IsException(valueOf)) { - return valueOf; - } + case 2: + u32 = (uint16_t) i64; - if (JS_IsFunction(ctx, valueOf)) { - ret = JS_Call(ctx, valueOf, obj, 0, NULL); - JS_FreeValue(ctx, valueOf); - if (JS_IsException(ret)) { - return ret; + if (sign) { + if (i64 < INT16_MIN || i64 > INT16_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); } - if (JS_IsString(ret)) { - obj = ret; - ret = qjs_buffer_from_string(ctx, obj, argv[1]); - JS_FreeValue(ctx, obj); - return ret; + } else { + if (i64 < 0 || i64 > UINT16_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); } + } - if (JS_IsObject(ret) - && JS_VALUE_GET_PTR(ret) != JS_VALUE_GET_PTR(obj)) - { - obj = ret; - ret = qjs_buffer_from_object(ctx, obj); - JS_FreeValue(ctx, obj); - return ret; + if (swap) { + u32 = njs_bswap_u16(u32); + } + + njs_set_u16(&self.start[index], u32); + break; + + case 3: + if (sign) { + if (i64 < INT24_MIN || i64 > INT24_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); } - JS_FreeValue(ctx, ret); + } else { + if (i64 < 0 || i64 > UINT24_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } } - return qjs_buffer_from_object(ctx, obj); - } + if (little) { + self.start[index] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index + 2] = i64; - JS_ThrowTypeError(ctx, "first argument is not a string " - "or Buffer-like object"); - return JS_EXCEPTION; -} + } else { + self.start[index + 2] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index] = i64; + } + break; -static JSValue -qjs_buffer_is_buffer(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue proto, buffer_proto, ret; + case 4: + u32 = i64; - proto = JS_GetPrototype(ctx, argv[0]); - buffer_proto = JS_GetClassProto(ctx, qjs_buffer_class_id); + if (sign) { + if (i64 < INT32_MIN || i64 > INT32_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } - ret = JS_NewBool(ctx, JS_VALUE_GET_TAG(argv[0]) == JS_TAG_OBJECT && - JS_VALUE_GET_OBJ(buffer_proto) == JS_VALUE_GET_OBJ(proto)); + } else { + if (i64 < 0 || i64 > UINT32_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } + } - JS_FreeValue(ctx, buffer_proto); - JS_FreeValue(ctx, proto); + if (swap) { + u32 = njs_bswap_u32(u32); + } - return ret; -} + njs_set_u32(&self.start[index], u32); + break; + case 5: + if (sign) { + if (i64 < INT40_MIN || i64 > INT40_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } -static JSValue -qjs_buffer_prototype_to_json(JSContext *ctx, JSValueConst this_val, int argc, - JSValueConst *argv) -{ - int rc; - JSValue obj, data, ret; - njs_str_t src; - njs_uint_t i; + } else { + if (i64 < 0 || i64 > UINT40_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } + } - ret = qjs_typed_array_data(ctx, this_val, &src); - if (JS_IsException(ret)) { - return ret; - } + if (little) { + self.start[index] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index + 2] = i64; i64 >>= 8; + self.start[index + 3] = i64; i64 >>= 8; + self.start[index + 4] = i64; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - return obj; - } + } else { + self.start[index + 4] = i64; i64 >>= 8; + self.start[index + 3] = i64; i64 >>= 8; + self.start[index + 2] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index] = i64; + } - data = JS_NewArray(ctx); - if (JS_IsException(data)) { - JS_FreeValue(ctx, obj); - return data; - } + break; - rc = JS_DefinePropertyValueStr(ctx, obj, "type", - JS_NewString(ctx, "Buffer"), - JS_PROP_ENUMERABLE); - if (rc == -1) { - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, data); - return ret; - } + case 6: + default: + if (sign) { + if (i64 < INT48_MIN || i64 > INT48_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } - rc = JS_DefinePropertyValueStr(ctx, obj, "data", data, JS_PROP_ENUMERABLE); - if (rc == -1) { - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, data); - return ret; - } + } else { + if (i64 < 0 || i64 > UINT48_MAX) { + return JS_ThrowRangeError(ctx, "value is outside the range of" + " representable values"); + } + } - for (i = 0; i < src.length; i++) { - rc = JS_SetPropertyUint32(ctx, data, i, JS_NewInt32(ctx, src.start[i])); - if (rc == -1) { - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, data); - return ret; + if (little) { + self.start[index] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index + 2] = i64; i64 >>= 8; + self.start[index + 3] = i64; i64 >>= 8; + self.start[index + 4] = i64; i64 >>= 8; + self.start[index + 5] = i64; + + } else { + self.start[index + 5] = i64; i64 >>= 8; + self.start[index + 4] = i64; i64 >>= 8; + self.start[index + 3] = i64; i64 >>= 8; + self.start[index + 2] = i64; i64 >>= 8; + self.start[index + 1] = i64; i64 >>= 8; + self.start[index] = i64; } + + break; } - return obj; + return JS_NewInt32(ctx, size + index); } - static JSValue -qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) +qjs_buffer_prototype_write_float(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv, int magic) { - JSValue ret; - njs_str_t src, data; - const qjs_buffer_encoding_t *encoding; - - ret = qjs_typed_array_data(ctx, this_val, &src); + double v; + JSValue ret; + uint32_t u32; + uint64_t u64, index, size; + njs_str_t self; + njs_bool_t little, swap; + njs_conv_f32_t conv_f32; + njs_conv_f64_t conv_f64; + + ret = qjs_typed_array_data(ctx, this_val, &self); if (JS_IsException(ret)) { return ret; } - if (JS_IsUndefined(argv[0]) || src.length == 0) { - return JS_NewStringLen(ctx, (char *) src.start, src.length); + if (JS_ToFloat64(ctx, &v, argv[0])) { + return JS_EXCEPTION; } - encoding = qjs_buffer_encoding(ctx, argv[0], 1); - if (njs_slow_path(encoding == NULL)) { + if (JS_ToIndex(ctx, &index, argv[1])) { return JS_EXCEPTION; } - if (encoding->encode_length == NULL) { - return JS_NewStringLen(ctx, (char *) src.start, src.length); - } + size = magic >> 2; - data.length = encoding->encode_length(ctx, &src); - data.start = js_malloc(ctx, data.length); - if (njs_slow_path(data.start == NULL)) { - JS_ThrowOutOfMemory(ctx); - return JS_EXCEPTION; + if (size + index > self.length) { + return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" + " buffer", index); } - if (encoding->encode(ctx, &src, &data) != 0) { - js_free(ctx, data.start); - JS_ThrowTypeError(ctx, "failed to encode buffer"); - return JS_EXCEPTION; - } + little = magic & 1; + swap = little; - ret = JS_NewStringLen(ctx, (char *) data.start, data.length); +#if NJS_HAVE_LITTLE_ENDIAN + swap = !swap; +#endif - js_free(ctx, data.start); + switch (size) { + case 4: + conv_f32.f = (float) v; - return ret; + if (swap) { + conv_f32.u = njs_bswap_u32(conv_f32.u); + } + + u32 = conv_f32.u; + memcpy(&self.start[index], &u32, size); + break; + + case 8: + default: + conv_f64.f = v; + + if (swap) { + conv_f64.u = njs_bswap_u64(conv_f64.u); + } + + u64 = conv_f64.u; + memcpy(&self.start[index], &u64, size); + break; + } + + return JS_NewInt32(ctx, size + index); } @@ -424,6 +1883,11 @@ qjs_buffer_from_string(JSContext *ctx, JSValueConst str, njs_str_t src, dst; const qjs_buffer_encoding_t *encoding; + if (!JS_IsString(str)) { + JS_ThrowTypeError(ctx, "first argument is not a string"); + return JS_EXCEPTION; + } + encoding = qjs_buffer_encoding(ctx, enc, 1); if (njs_slow_path(encoding == NULL)) { return JS_EXCEPTION; @@ -552,57 +2016,6 @@ qjs_buffer_from_typed_array(JSContext *ctx, JSValueConst arr_buf, return buffer; } -static JSValue -qjs_buffer_from_array_buffer(JSContext *ctx, u_char *buf, size_t size, - JSValueConst offset, JSValueConst length) -{ - JSValue buffer, ret; - int64_t len; - uint64_t off; - njs_str_t dst; - - if (JS_ToIndex(ctx, &off, offset)) { - return JS_EXCEPTION; - } - - if ((size_t) off > size) { - JS_ThrowRangeError(ctx, "\"offset\" is outside of buffer bounds"); - return JS_EXCEPTION; - } - - if (JS_IsUndefined(length)) { - len = size - off; - - } else { - if (JS_ToInt64(ctx, &len, length)) { - return JS_EXCEPTION; - } - - if (len < 0) { - len = 0; - } - - if ((size_t) (off + len) > size) { - JS_ThrowRangeError(ctx, "\"length\" is outside of buffer bounds"); - return JS_EXCEPTION; - } - } - - buffer = qjs_buffer_alloc(ctx, len); - if (JS_IsException(buffer)) { - return buffer; - } - - ret = qjs_typed_array_data(ctx, buffer, &dst); - if (JS_IsException(ret)) { - return ret; - } - - memcpy(dst.start, buf + off, len); - - return buffer; -} - static JSValue qjs_buffer_from_object(JSContext *ctx, JSValueConst obj) @@ -872,7 +2285,7 @@ qjs_base64_decode_length(JSContext *ctx, const njs_str_t *src) static int qjs_base64url_encode(JSContext *ctx, const njs_str_t *src, njs_str_t *dst) { - qjs_base64_encode_core(dst, src, qjs_basis64url_enc, 1); + qjs_base64_encode_core(dst, src, qjs_basis64url_enc, 0); return 0; } @@ -1004,9 +2417,11 @@ qjs_hex_encode_length(JSContext *ctx, const njs_str_t *src) JSValue qjs_buffer_alloc(JSContext *ctx, size_t size) { - JSValue ret, proto; + JSValue ret, proto, value; + + value = JS_NewInt64(ctx, size); - ret = qjs_new_uint8_array(ctx, size); + ret = qjs_new_uint8_array(ctx, 1, &value); if (JS_IsException(ret)) { return ret; } @@ -1019,6 +2434,29 @@ qjs_buffer_alloc(JSContext *ctx, size_t size) } + +JSValue +qjs_buffer_create(JSContext *ctx, u_char *start, size_t size) +{ + JSValue buffer, ret; + njs_str_t dst; + + buffer = qjs_buffer_alloc(ctx, size); + if (JS_IsException(buffer)) { + return buffer; + } + + ret = qjs_typed_array_data(ctx, buffer, &dst); + if (JS_IsException(ret)) { + return ret; + } + + memcpy(dst.start, start, size); + + return buffer; +} + + JSValue qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain) { @@ -1047,24 +2485,20 @@ qjs_buffer_chb_alloc(JSContext *ctx, njs_chb_t *chain) static JSValue -qjs_new_uint8_array(JSContext *ctx, size_t size) +qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv) { - JSValue ret, value; - - value = JS_NewInt64(ctx, size); + JSValue ret; #ifdef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY - ret = JS_NewTypedArray(ctx, 1, &value, JS_TYPED_ARRAY_UINT8); + ret = JS_NewTypedArray(ctx, argc, argv, JS_TYPED_ARRAY_UINT8); #else JSValue ctor; ctor = JS_GetClassProto(ctx, qjs_uint8_array_ctor_id); - ret = JS_CallConstructor(ctx, ctor, 1, &value); + ret = JS_CallConstructor(ctx, ctor, argc, argv); JS_FreeValue(ctx, ctor); #endif - JS_FreeValue(ctx, value); - return ret; } @@ -1073,18 +2507,19 @@ static int qjs_buffer_builtin_init(JSContext *ctx) { int rc; - JSValue global_obj, buffer, proto, ctor, ta, ta_proto; + JSAtom species_atom; + JSValue global_obj, buffer, proto, ctor, ta, ta_proto, symbol, species; JSClassID u8_ta_class_id; JS_NewClassID(&qjs_buffer_class_id); JS_NewClass(JS_GetRuntime(ctx), qjs_buffer_class_id, &qjs_buffer_class); + global_obj = JS_GetGlobalObject(ctx); + proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, proto, qjs_buffer_proto, njs_nitems(qjs_buffer_proto)); - global_obj = JS_GetGlobalObject(ctx); - ctor = JS_GetPropertyStr(ctx, global_obj, "Uint8Array"); #ifndef NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY @@ -1110,15 +2545,29 @@ qjs_buffer_builtin_init(JSContext *ctx) JS_SetClassProto(ctx, qjs_buffer_class_id, proto); - buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 0, - JS_CFUNC_generic, 0); + buffer = JS_NewCFunction2(ctx, qjs_buffer, "Buffer", 3, + JS_CFUNC_constructor, 0); if (JS_IsException(buffer)) { return -1; } + JS_SetConstructor(ctx, buffer, proto); + JS_SetPropertyFunctionList(ctx, buffer, qjs_buffer_props, njs_nitems(qjs_buffer_props)); + symbol = JS_GetPropertyStr(ctx, global_obj, "Symbol"); + species = JS_GetPropertyStr(ctx, symbol, "species"); + JS_FreeValue(ctx, symbol); + species_atom = JS_ValueToAtom(ctx, species); + JS_FreeValue(ctx, species); + + ctor = JS_NewCFunction2(ctx, qjs_buffer_ctor, "Buffer species ctor", 3, + JS_CFUNC_constructor, 0); + + JS_SetProperty(ctx, buffer, species_atom, ctor); + JS_FreeAtom(ctx, species_atom); + rc = JS_SetPropertyStr(ctx, global_obj, "Buffer", buffer); if (rc == -1) { return -1; diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c6979c13..10ee6c1e 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -21015,817 +21015,6 @@ static njs_unit_test_t njs_querystring_module_test[] = }; -static njs_unit_test_t njs_buffer_module_test[] = -{ - { njs_str("new Buffer();"), - njs_str("TypeError: Buffer is not a constructor") }, - - { njs_str("var buf = Buffer.alloc();"), - njs_str("TypeError: \"size\" argument must be of type number") }, - - { njs_str("var buf = Buffer.alloc('best buffer');"), - njs_str("TypeError: \"size\" argument must be of type number") }, - - { njs_str("var buf = Buffer.alloc(-1);"), - njs_str("RangeError: invalid size") }, - - { njs_str("var buf = Buffer.alloc(4); njs.dump(buf)"), - njs_str("Buffer [0,0,0,0]") }, - - { njs_str("var buf = Buffer.alloc(4, 88); buf"), - njs_str("XXXX") }, - - { njs_str("var buf = Buffer.alloc(4, 945); njs.dump(buf)"), - njs_str("Buffer [177,177,177,177]") }, - - { njs_str("var buf = Buffer.alloc(4, -1); njs.dump(buf)"), - njs_str("Buffer [255,255,255,255]") }, - - { njs_str("var buf = Buffer.alloc(4, -1, 'utf-128'); njs.dump(buf)"), - njs_str("Buffer [255,255,255,255]") }, - - { njs_str("var buf = Buffer.alloc(10, 'α'); buf"), - njs_str("ααααα") }, - - { njs_str("var buf = Buffer.alloc(4, 'α'); njs.dump(buf)"), - njs_str("Buffer [206,177,206,177]") }, - - { njs_str("var buf = Buffer.alloc(2, 'ααααα'); njs.dump(buf)"), - njs_str("Buffer [206,177]") }, - - { njs_str("var buf = Buffer.alloc(1, 'α'); njs.dump(buf)"), - njs_str("Buffer [206]") }, - - { njs_str("var buf = Buffer.alloc(4, 'ZXZpbA==', 'base64'); buf"), - njs_str("evil") }, - - { njs_str("var buf = Buffer.alloc(8, 'ZXZpbA==', 'base64'); buf"), - njs_str("evilevil") }, - - { njs_str("var buf = Buffer.alloc(8, 'evil', 'utf-128'); buf"), - njs_str("TypeError: \"utf-128\" encoding is not supported") }, - - { njs_str("var foo = new Uint8Array(10).fill(88);" - "var buf = Buffer.alloc(8, foo); buf"), - njs_str("XXXXXXXX") }, - - { njs_str("[1,2,10,20].every(v => {" - " var src = new Uint16Array(v).fill(0xB1CE);" - " var buf = Buffer.alloc(10, src);" - " return buf.toString() === " njs_evar("'ααααα'", "'�αααα�'") - "})"), - njs_str("true") }, - - { njs_str("var foo = Buffer.alloc(10, 'α');" - "var buf = Buffer.alloc(4, foo); buf"), - njs_str("αα") }, - - { njs_str("var buf = Buffer.allocUnsafe(10).fill('α'); buf"), - njs_str("ααααα") }, - - { njs_str("var buf = Buffer.allocUnsafe(-1)"), - njs_str("RangeError: invalid size") }, - - { njs_str("[" - " ['6576696c', 'hex', 4]," - " ['6576696', 'hex', 3]," - " ['', 'hex', 0]," - " ['', 'base64', 0]," - " ['ZXZpbA==', 'base64', 4]," - " ['ZXZpbA', 'base64url', 4]," - " ['ααααα', undefined, 10]," - "].every(args => Buffer.byteLength(args[0], args[1]) == args[2])"), - njs_str("true") }, - - { njs_str("var foo = new Uint8Array(5);" - "foo[0] = 1; foo[1] = 2; foo[2] = 3; foo[3] = 4; foo[4] = 5;" - "foo = foo.subarray(1, 3);" - "var buf = Buffer.from(foo); njs.dump(buf)"), - njs_str("Buffer [2,3]") }, - - { njs_str("['utf8', 'utf-8', 'hex', 'base64', 'base64url', 'utf-88', '1hex']" - ".map(v=>Buffer.isEncoding(v))"), - njs_str("true,true,true,true,true,false,false") }, - - { njs_str("[" - " ['ABC', 'ABCD', -1]," - " ['ABCD', 'ABC', 1]," - " ['ABC', 'ACB', -1]," - " ['ACB', 'ABC', 1]," - " ['ABC', 'ABC', 0]," - " ['', 'ABC', -1]," - " ['', '', 0]," - "].every(args => {" - " if (Buffer.compare(Buffer.from(args[0]), Buffer.from(args[1])) != args[2]) {" - " throw new TypeError(" - " `Buffer.compare(Buffer.from(${args[0]}), Buffer.from(${args[1]})) != ${args[2]}`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("[" - " ['ABC', 'ABCD', -1]," - " ['ABCD', 'ABC', 1]," - " ['ABC', 'ACB', -1]," - " ['ACB', 'ABC', 1]," - " ['ABC', 'ABC', 0]," - " ['', 'ABC', -1]," - " ['', '', 0]," - "].every(args => {" - " if (Buffer.from(args[0]).compare(Buffer.from(args[1])) != args[2]) {" - " throw new TypeError(" - " `Buffer.from(${args[0]}).compare(Buffer.from(${args[1]})) != ${args[2]}`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "[" - " [0,3,0,2, -1]," - " [0,2,0,3, 1]," - " [3,4,3,4, 0]," - " [undefined, undefined, undefined, undefined, 0]," - " [-1, undefined, undefined, undefined, 'invalid index']," - " [0, -1, undefined, undefined, 'invalid index']," - " [0, 0, -1, undefined, 'invalid index']," - " [0, 0, 0, -1, 'invalid index']," - "]" - ".every(as => {" - " try {" - " if (buf.compare(buf, as[0], as[1], as[2], as[3]) != as[4]) {" - " throw new TypeError(" - " `buf.compare(${as[0]}, ${as[1]}, ${as[2]}, ${as[3]}) != ${as[4]}`);" - " }" - " } catch (e) { return e.message == as[4]}" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.from('ABCD');" - "buf1.compare(buf2, 5)"), - njs_str("RangeError: \"targetStart\" is out of range: 5") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.from('ABCD');" - "buf1.compare(buf2, 0, 3, 5)"), - njs_str("RangeError: \"sourceStart\" is out of range: 5") }, - - { njs_str("var arr = new Uint8Array(4);" - "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;" - "arr = arr.subarray(1, 4);" - "var buf = Buffer.from('ABCD');" - "buf.compare(arr, 0, 3, 1, 4)"), - njs_str("0") }, - - { njs_str("['123', 'abc', '124', '', 'AB', 'ABCD']" - ".map(v=>Buffer.from(v)).sort(Buffer.compare).map(v=>v.toString())"), - njs_str(",123,124,AB,ABCD,abc") }, - - { njs_str("Buffer.compare(Buffer.alloc(1), 'text')"), - njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") }, - - { njs_str("Buffer.compare('text', Buffer.from('ACB'))"), - njs_str("TypeError: \"source\" argument must be an instance of Buffer or Uint8Array") }, - - { njs_str("Buffer.concat()"), - njs_str("TypeError: \"list\" argument must be an instance of Array") }, - - { njs_str("Buffer.concat([])"), - njs_str("") }, - - { njs_str("Buffer.concat([new Uint16Array(10)])"), - njs_str("TypeError: \"list[0]\" argument must be an instance of Buffer or Uint8Array") }, - - { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)]).fill('abc')"), - njs_str("abc") }, - - { njs_str("Buffer.concat([Buffer.from('AB'), Buffer.from('CD')])"), - njs_str("ABCD") }, - - { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)], 2).fill('abc')"), - njs_str("ab") }, - - { njs_str("Buffer.concat([new Uint8Array(2), new Uint8Array(1)], 6).fill('abc')"), - njs_str("abcabc") }, - - { njs_str("Buffer.concat([Buffer.from('ABCD').slice(2,4), Buffer.from('ABCD').slice(0,2)])"), - njs_str("CDAB") }, - - { njs_str(njs_declare_sparse_array("list", 2) - "list[0] = Buffer.from('ABCD').slice(2,4);" - "list[1] = Buffer.from('ABCD').slice(0,2);" - "Buffer.concat(list);"), - njs_str("CDAB") }, - - { njs_str(njs_declare_sparse_array("list", 2) - "list[0] = new Uint8Array(2); list[1] = new Uint8Array(3);" - "Buffer.concat(list).fill('ab');"), - njs_str("ababa") }, - - { njs_str("Buffer.concat([], '123')"), - njs_str("TypeError: \"length\" argument must be of type number") }, - - { njs_str("Buffer.concat([], -1)"), - njs_str("RangeError: \"length\" is out of range") }, - - { njs_str("var buf = Buffer.from('α'); buf[1]"), - njs_str("177") }, - - { njs_str("var buf = Buffer.from('α'); buf[1] = 1; njs.dump(buf)"), - njs_str("Buffer [206,1]") }, - - { njs_str("var arrBuf = new ArrayBuffer(16);" - "var buf = Buffer.from(arrBuf); buf.buffer === arrBuf"), - njs_str("true") }, - - { njs_str("[" - " [[0], 4, '65,66,67,68,0,0,0,0,0,0']," - " [[5], 4, '0,0,0,0,0,65,66,67,68,0']," - " [[8], 2, '0,0,0,0,0,0,0,0,65,66']," - " [[8,2,4], 2, '0,0,0,0,0,0,0,0,67,68']," - " [[10], 0, '0,0,0,0,0,0,0,0,0,0']," - "]" - ".every(args => {" - " var buf1 = Buffer.from('ABCD');" - " var buf2 = Buffer.alloc(10, 0);" - " var as = args[0];" - " var length = buf1.copy(buf2, as[0], as[1], as[2]);" - "" - " if (length != args[1]) {" - " throw new TypeError(`buf1.copy(buf2, ${as[0]}, ${as[1]}, ${as[2]}): ${length} != ${args[1]}`)" - " }" - "" - " if (njs.dump(buf2) != `Buffer [${args[2]}]`) {" - " throw new TypeError(" - " `buf1.copy(buf2, ${as[0]}, ${as[1]}, ${as[2]}): ${njs.dump(buf2)} != Buffer [${args[2]}]`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("[" - " [[0], 'ABCDEF']," - " [[0,2], 'CDEFEF']," - " [[0,2,6], 'CDEFEF']," - " [[1,2,4], 'ACDDEF']," - " [[1,2,3], 'ACCDEF']" - "]" - ".every(args => {" - " var buf = Buffer.from('ABCDEF');" - " var as = args[0];" - " buf.copy(buf, as[0], as[1], as[2]);" - "" - " if (buf.toString() != args[1]) {" - " throw new TypeError(" - " `buf.copy(buf, ${as[0]}, ${as[1]}, ${as[2]}): buf.toString() != ${args[1]}`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.alloc(10, 0);" - "buf1.copy(buf2, -1)"), - njs_str("RangeError: invalid index") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.alloc(10, 0);" - "buf1.copy(buf2, 0, -1)"), - njs_str("RangeError: invalid index") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.alloc(10, 0);" - "buf1.copy(buf2, 0, 5)"), - njs_str("RangeError: \"sourceStart\" is out of range: 5") }, - - { njs_str("var arr = new Uint8Array(4);" - "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;" - "arr = arr.subarray(1, 4);" - "var buf1 = Buffer.from(arr);" - "var buf2 = Buffer.alloc(10, 0);" - "var length = buf1.copy(buf2, 1, 1, 2); [length, njs.dump(buf2)]"), - njs_str("1,Buffer [0,67,0,0,0,0,0,0,0,0]") }, - - { njs_str("var arr = new Uint8Array(4);" - "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;" - "arr = arr.subarray(1, 4);" - "var buf1 = Buffer.from(arr);" - "var buf2 = Buffer.alloc(10, 0);" - "var length = buf1.copy(buf2, 1, 1, 2); [length, njs.dump(buf2)]"), - njs_str("1,Buffer [0,67,0,0,0,0,0,0,0,0]") }, - - { njs_str("[" - " ['ABC', 'ABCD', false]," - " ['ABCD', 'ABC', false]," - " ['ABC', 'ACB', false]," - " ['ACB', 'ABC', false]," - " ['ABC', 'ABC', true]," - " ['', 'ABC', false]," - " ['', '', true]," - "].every(args => {" - " if (Buffer.from(args[0]).equals(Buffer.from(args[1])) != args[2]) {" - " throw new TypeError(" - " `Buffer.from(${args[0]}).compare(Buffer.from(${args[1]})) != ${args[2]}`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("Buffer.from([1,2]).equals(new ArrayBuffer(1))"), - njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") }, - - { njs_str("Buffer.from([1,2]).equals(1)"), - njs_str("TypeError: \"target\" argument must be an instance of Buffer or Uint8Array") }, - - { njs_str("var buf = Buffer.alloc(4);" - "buf.fill('ZXZpbA==', 'base64')"), - njs_str("evil") }, - - { njs_str("var buf = Buffer.alloc(4);" - "buf.fill('6576696c', 'hex')"), - njs_str("evil") }, - - { njs_str("var buf = Buffer.alloc(4);" - "buf.fill('ZXZpbA==', '')"), - njs_str("TypeError: \"\" encoding is not supported") }, - - { njs_str("var buf = Buffer.alloc(8);" - "buf.fill('6576696c', 'hex')"), - njs_str("evilevil") }, - - { njs_str("var buf = Buffer.alloc(10);" - "buf.fill('evil')"), - njs_str("evilevilev") }, - - { njs_str("var buf = Buffer.allocUnsafe(5);" - "buf[3] = 1;" - "buf.fill(''); njs.dump(buf)"), - njs_str("Buffer [0,0,0,0,0]") }, - - { njs_str("var arr = new Uint8Array(4);" - "arr[0] = 0x41; arr[1] = 0x42; arr[2] = 0x43; arr[3] = 0x44;" - "arr = arr.subarray(1, 4);" - "var buf = Buffer.allocUnsafe(6);" - "buf.fill(arr); njs.dump(buf)"), - njs_str("Buffer [66,67,68,66,67,68]") }, - - { njs_str("var buf = Buffer.alloc(6, 'ABCDEF');" - "buf.fill(buf, 2, 6)"), - njs_str("ABABCD") }, - - { njs_str("Buffer.alloc(6).fill(0x41)"), - njs_str("AAAAAA") }, - - { njs_str("Buffer.alloc(6).fill({valueOf(){return 0x42}})"), - njs_str("BBBBBB") }, - - { njs_str("njs.dump(Buffer.alloc(3).fill(-1))"), - njs_str("Buffer [255,255,255]") }, - - { njs_str("[NaN, Infinity, -Infinity, undefined, null, {}]" - ".every(v => njs.dump(Buffer.alloc(3).fill(v)) == 'Buffer [0,0,0]')"), - njs_str("true") }, - - { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 4,3))"), - njs_str("Buffer [0,0,0,0,0,0]") }, - - { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 3,3))"), - njs_str("Buffer [0,0,0,0,0,0]") }, - - { njs_str("njs.dump(Buffer.alloc(6).fill({valueOf(){throw 'Oops'}}, 2,3))"), - njs_str("Oops") }, - - { njs_str("njs.dump(Buffer.alloc(5).fill('α'))"), - njs_str("Buffer [206,177,206,177,206]") }, - - { njs_str("Buffer.alloc(4).fill('ABCD', -1)"), - njs_str("RangeError: invalid index") }, - - { njs_str("Buffer.alloc(4).fill('ABCD', 5)"), - njs_str("RangeError: \"offset\" is out of range") }, - - { njs_str("Buffer.alloc(4).fill('ABCD', 0, -1)"), - njs_str("RangeError: invalid index") }, - - { njs_str("Buffer.alloc(4).fill('ABCD', 0, 5)"), - njs_str("RangeError: \"end\" is out of range") }, - - { njs_str("Buffer.alloc(513).fill('A'.repeat(512)).length"), - njs_str("513") }, - - { njs_str("njs.dump(Buffer.alloc(4).fill((new Uint8Array(5)).fill(1)))"), - njs_str("Buffer [1,1,1,1]") }, - - { njs_str("var src = new Uint8Array(10).fill(255);" - "var u8 = new Uint8Array(src.buffer, 1, 8);" - "u8.set([1,2,3,4,5,6,7,8]);" - "njs.dump(Buffer.alloc(9).fill(u8))"), - njs_str("Buffer [1,2,3,4,5,6,7,8,1]") }, - - { njs_str("Buffer.alloc(513).fill((new Uint8Array(512)).fill(1)).length"), - njs_str("513") }, - - { njs_str("Buffer.alloc(4).fill('ABCD', undefined, undefined, 'utf-128')"), - njs_str("TypeError: \"utf-128\" encoding is not supported") }, - - { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 3);" - "buf1.fill('B', 1, 2); njs.dump(buf1)"), - njs_str("Buffer [66,66]") }, - - { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 3);" - "buf1.fill(0x42, 1, 2); njs.dump(buf1)"), - njs_str("Buffer [66,66]") }, - - { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 2);" - "var buf2 = Buffer.from('ABCD').subarray(1, 3);" - "buf2.fill(buf1, 1, 2); njs.dump(buf2)"), - njs_str("Buffer [66,66]") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "['BC', 'CB', 'ABCD', 'ABCDE', ''].map(v=>buf.indexOf(v))"), - njs_str("1,-1,0,-1,0") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "[0,5,-2,-1].map(v=>buf.indexOf('C', v))"), - njs_str("2,-1,2,-1") }, - - { njs_str("var buf = Buffer.from('evil');" - "buf.indexOf('ZXZpbA==', undefined, 'base64')"), - njs_str("0") }, - - { njs_str("var buf = Buffer.from('evil');" - "buf.indexOf('6576696c', undefined, 'hex')"), - njs_str("0") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "buf.indexOf('C', undefined, 'utf-128')"), - njs_str("TypeError: \"utf-128\" encoding is not supported") }, - - { njs_str("var buf = Buffer.from('ABCDABC');" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.indexOf(Buffer.from(v)))"), - njs_str("1,-1,0,-1,0,2,-1") }, - - { njs_str("var buf = Buffer.from('ABCDABC');" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.includes(Buffer.from(v)))"), - njs_str("true,false,true,false,true,true,false") }, - - { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.indexOf(Buffer.from(v)))"), - njs_str("1,-1,0,-1,0,2,-1") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "buf.indexOf(0x43)"), - njs_str("2") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "buf.indexOf(0x43, -2)"), - njs_str("2") }, - - { njs_str("var buf = Buffer.from('ABCD');" - "buf.indexOf(0x43, -1)"), - njs_str("-1") }, - - { njs_str("var buf1 = Buffer.from('ABCD');" - "var buf2 = Buffer.from('XXCX').subarray(2, 3);" - "buf1.indexOf(buf2)"), - njs_str("2") }, - - { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 4);" - "buf1.indexOf(0x43)"), - njs_str("1") }, - - { njs_str("var buf1 = Buffer.from('ABCD').subarray(1, 4);" - "buf1.indexOf('C')"), - njs_str("1") }, - - { njs_str("var buf = Buffer.from('ABCDABC');" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(v))"), - njs_str("5,-1,0,-1,7,6,-1") }, - - { njs_str("var buf = Buffer.from('ABCDABC');" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(Buffer.from(v)))"), - njs_str("5,-1,0,-1,7,6,-1") }, - - { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(v))"), - njs_str("5,-1,0,-1,7,6,-1") }, - - { njs_str("var buf = Buffer.from('ZABCDABC').subarray(1);" - "['BC', 'CB', 'ABCD', 'ABCDE', '', 'C', 'ABCDABCD'].map(v=>buf.lastIndexOf(Buffer.from(v)))"), - njs_str("5,-1,0,-1,7,6,-1") }, - - { njs_str("var buf = Buffer.from('CABCD');" - "[2,-2,1,-10,10,-5,-4,0].map(v=>buf.lastIndexOf('C', v))"), - njs_str("0,3,0,-1,3,0,0,0") }, - - { njs_str("var buf = Buffer.from('CABCD');" - "[2,-2,1,-10,10,-5,-4,0].map(v=>buf.lastIndexOf(Buffer.from('CZ').subarray(0,1), v))"), - njs_str("0,3,0,-1,3,0,0,0") }, - - { njs_str("var buf = Buffer.from('CABCD');" - "buf.lastIndexOf(0x43)"), - njs_str("3") }, - - { njs_str("var buf = Buffer.from('CABCD');" - "[2,1,0,4,5,-1,-5].map(v=>buf.lastIndexOf(0x43, v))"), - njs_str("0,0,0,3,3,3,0") }, - - { njs_str("var buf1 = Buffer.from('ACBCD').subarray(1, 4);" - "var buf2 = Buffer.from('C');" - "buf1.lastIndexOf(buf2)"), - njs_str("2") }, - - { njs_str("var buf1 = Buffer.from('XXCXX').subarray(2,3);" - "buf1.lastIndexOf(Buffer.from('X'))"), - njs_str("-1") }, - - { njs_str("var buf = Buffer.from('ACBCD').subarray(1, 4);" - "buf.lastIndexOf(0x43)"), - njs_str("2") }, - - { njs_str("var buf = Buffer.from('ACBCD').subarray(1, 4);" - "buf.lastIndexOf('C')"), - njs_str("2") }, - - { njs_str("Buffer.from('abcdef').lastIndexOf('abc', 1)"), - njs_str("0") }, - - { njs_str("['swap16', 'swap32', 'swap64'].every(method => {" - " var buf = Buffer.from([]);" - " buf[method]();" - " return njs.dump(buf) === 'Buffer []';" - "})"), - njs_str("true") }, - - { njs_str("['swap16', 'swap32', 'swap64'].every(method => {" - " var buf = Buffer.from([1,2,3]);" - " try { buf[method]() } " - " catch(e) {return e.message === `Buffer size must be a multiple of ${method.substr(4)}-bits`};" - "})"), - njs_str("true") }, - - { njs_str("['swap16', 'swap32', 'swap64'].map(method => {" - " var buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);" - " buf[method]();" - " return njs.dump(buf);" - "})"), - njs_str("Buffer [2,1,4,3,6,5,8,7]," - "Buffer [4,3,2,1,8,7,6,5]," - "Buffer [8,7,6,5,4,3,2,1]") }, - - { njs_str("['swap16', 'swap32', 'swap64'].map(method => {" - " var u8 = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]);" - " var buf = Buffer.from(u8.buffer, 1);" - " buf[method]();" - " return njs.dump(buf);" - "})"), - njs_str("Buffer [2,1,4,3,6,5,8,7]," - "Buffer [4,3,2,1,8,7,6,5]," - "Buffer [8,7,6,5,4,3,2,1]") }, - - { njs_str("[" - " [['base64'], 'ZXZpbA==']," - " [['base64url'], 'ZXZpbA']," - " [['hex'], '6576696c']," - " [[undefined,1,3], 'vi']," - " [[undefined,5], '']," - " [[undefined,undefined,5], 'evil']," - " [[undefined,undefined,undefined], 'evil']," - "].every(args => {" - " var buf = Buffer.from('evil');" - " var as = args[0];" - " if (buf.toString(as[0], as[1], as[2]) != args[1]) {" - " throw new TypeError(" - " `buf.toString(${as[0]}, ${as[1]}, ${as[2]}) != ${args[1]}`);" - " }" - " return true;" - "})"), - njs_str("true") }, - - { njs_str("var buf = Buffer.allocUnsafe(4);" - "var len = buf.write('ZXZpbA==', 'base64'); [len, buf]"), - njs_str("4,evil") }, - - { njs_str("var buf = Buffer.allocUnsafe(4);" - "var len = buf.write('ZXZpbA==', undefined, 'base64'); [len, buf]"), - njs_str("4,evil") }, - - { njs_str("var buf = Buffer.allocUnsafe(4);" - "var len = buf.write('ZXZpbA==', undefined, undefined, 'base64'); [len, buf]"), - njs_str("4,evil") }, - - { njs_str("Buffer.allocUnsafe(4).write()"), - njs_str("TypeError: first argument must be a string") }, - - { njs_str("Buffer.allocUnsafe(4).write({a: 1})"), - njs_str("TypeError: first argument must be a string") }, - - { njs_str("Buffer.alloc(4).write('evil', 4, 1);"), - njs_str("RangeError: \"offset\" is out of range") }, - - { njs_str("Buffer.alloc(4).write('evil', -1);"), - njs_str("RangeError: invalid index") }, - - { njs_str("var buf = Buffer.alloc(4);" - "var len = buf.write('evil', 3, 1); [len, njs.dump(buf)]"), - njs_str("1,Buffer [0,0,0,101]") }, - - { njs_str("var buf = Buffer.alloc(4);" - "var len = buf.write('evil', 0, 5); [len, buf]"), - njs_str("4,evil") }, - - { njs_str("Buffer.alloc(4).write('evil', undefined, -1);"), - njs_str("RangeError: invalid index") }, - - { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);" - "[1,2,3,4,5,6].map(byte => [buf.readUIntLE(0, byte), buf.readUIntLE(1, byte)])"), - njs_str("250,251," - "64506,64763," - "16579578,16645371," - "4261215226,4278058235," - "1095182908410,1099494718715," - "281470647991290,274877890034939") }, - - { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);" - "[1,2,3,4,5,6].map(byte => [buf.readIntLE(0, byte), buf.readIntLE(1, byte)])"), - njs_str("-6,-5," - "-1030,-773," - "-197638,-131845," - "-33752070,-16909061," - "-4328719366,-16909061," - "-4328719366,-6597086675717") }, - - { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);" - "[1,2,3,4,5,6].map(byte => [buf.readUIntBE(0, byte), buf.readUIntBE(1, byte)])"), - njs_str("250,251," - "64251,64508," - "16448508,16514301," - "4210818301,4227661310," - "1077969485310,1082281295615," - "275960188239615,277064011677689") }, - - { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);" - "[1,2,3,4,5,6].map(byte => [buf.readIntBE(0, byte), buf.readIntBE(1, byte)])"), - njs_str("-6,-5," - "-1285,-1028," - "-328708,-262915," - "-84148995,-67305986," - "-21542142466,-17230332161," - "-5514788471041,-4410965032967") }, - - { njs_str("var buf = Buffer.from([0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xF9, 0xf8]);" - "function t(sign, endianness, offset) { " - " return [1,2,4].every(size => {" - " var method = `read${sign}Int${size * 8}`;" - " if (size > 1) { method += endianness};" - " var gmethod = `read${sign}Int${endianness}`;" - " var gv = buf[gmethod](offset, size);" - " var sv = buf[method](offset);" - " if (gv != sv) {throw Error(`${gmethod}(${offset},${size}):${gv} != ${method}(${offset}):${sv}`)}" - " return true;" - " });" - "}; " - "t('U', 'LE', 0) && t('U', 'LE', 1)" - "&& t('', 'LE', 0) && t('', 'LE', 1)" - "&& t('U', 'BE', 0) && t('U', 'BE', 1)" - "&& t('', 'BE', 0) && t('', 'BE', 1)"), - njs_str("true") }, - - { njs_str("var buf = Buffer.alloc(9);" - "function t(sign, endianness, offset) { " - " return [1,2,4].every(size => {" - " var rgmethod = `read${sign}Int${endianness}`;" - " var wgmethod = `write${sign}Int${endianness}`;" - " var rmethod = `read${sign}Int${size * 8}`;" - " var wmethod = `write${sign}Int${size * 8}`;" - " if (size > 1) { rmethod += endianness; wmethod += endianness; };" - " var v = 0x7abbccddeeff & (size * 8 - 1);" - "" - " var ret = buf[wgmethod](v, offset, size);" - " if(ret !== offset + size) {" - " throw Error(`${wgmethod} returned ${ret}, need ${offset + size}`);" - " }" - "" - " var gv = buf[rgmethod](offset, size);" - "" - " buf.fill(0);" - " buf[wmethod](v, offset);" - " var sv = buf[rmethod](offset);" - " if (gv != sv) {throw Error(`${wmethod}(${v}, ${offset}):${sv} != ${wgmethod}(${v},${offset}):${gv}`)}" - " return true;" - " });" - "}; " - "t('U', 'LE', 0) && t('U', 'LE', 1)" - "&& t('', 'LE', 0) && t('', 'LE', 1)" - "&& t('U', 'BE', 0) && t('U', 'BE', 1)" - "&& t('', 'BE', 0) && t('', 'BE', 1)"), - njs_str("true") }, - - { njs_str(njs_buffer_byte_map("writeUIntLE", "+", 1)), - njs_str("Buffer [128,0,0,0,0,0]," - "Buffer [0,64,0,0,0,0]," - "Buffer [0,0,32,0,0,0]," - "Buffer [0,0,0,16,0,0]," - "Buffer [0,0,0,0,8,0]," - "Buffer [0,0,0,0,0,4]") }, - - { njs_str(njs_buffer_byte_map("writeUIntBE", "+", 1)), - njs_str("Buffer [128,0,0,0,0,0]," - "Buffer [64,0,0,0,0,0]," - "Buffer [32,0,0,0,0,0]," - "Buffer [16,0,0,0,0,0]," - "Buffer [8,0,0,0,0,0]," - "Buffer [4,0,0,0,0,0]") }, - - { njs_str(njs_buffer_byte_map("writeIntLE", "-", 2)), - njs_str("Buffer [192,0,0,0,0,0]," - "Buffer [0,224,0,0,0,0]," - "Buffer [0,0,240,0,0,0]," - "Buffer [0,0,0,248,0,0]," - "Buffer [0,0,0,0,252,0]," - "Buffer [0,0,0,0,0,254]") }, - - { njs_str(njs_buffer_byte_map("writeIntBE", "-", 2)), - njs_str("Buffer [192,0,0,0,0,0]," - "Buffer [224,0,0,0,0,0]," - "Buffer [240,0,0,0,0,0]," - "Buffer [248,0,0,0,0,0]," - "Buffer [252,0,0,0,0,0]," - "Buffer [254,0,0,0,0,0]") }, - - { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleBE()"), - njs_str("8.20788039913184e-304") }, - - { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleLE()"), - njs_str("5.447603722011605e-270") }, - - { njs_str("Buffer.from([1, 2, 3, 4]).readFloatBE()"), - njs_str("2.387939260590663e-38") }, - - { njs_str("Buffer.from([1, 2, 3, 4]).readFloatLE()"), - njs_str("1.539989614439558e-36") }, - - { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleBE(1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]).readDoubleLE(1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("Buffer.from([1, 2, 3, 4]).readFloatBE(1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("Buffer.from([1, 2, 3, 4]).readFloatLE(1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("var buf = Buffer.allocUnsafe(8);" - "buf.writeDoubleBE(123.456); njs.dump(buf)"), - njs_str("Buffer [64,94,221,47,26,159,190,119]") }, - - { njs_str("var buf = Buffer.allocUnsafe(8);" - "buf.writeDoubleLE(123.456); njs.dump(buf)"), - njs_str("Buffer [119,190,159,26,47,221,94,64]") }, - - { njs_str("var buf = Buffer.allocUnsafe(4);" - "buf.writeFloatBE(123.456); njs.dump(buf)"), - njs_str("Buffer [66,246,233,121]") }, - - { njs_str("var buf = Buffer.allocUnsafe(4);" - "buf.writeFloatLE(123.456); njs.dump(buf)"), - njs_str("Buffer [121,233,246,66]") }, - - { njs_str("var buf = Buffer.allocUnsafe(8).writeDoubleBE(123.456, 1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("var buf = Buffer.allocUnsafe(8).writeDoubleLE(123.456, 1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("var buf = Buffer.allocUnsafe(4).writeFloatBE(123.456, 1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("var buf = Buffer.allocUnsafe(4).writeFloatLE(123.456, 1)"), - njs_str("RangeError: index 1 is outside the bound of the buffer") }, - - { njs_str("var buffer = require('buffer');" - "buffer.Buffer.alloc(5).fill('ABC')"), - njs_str("ABCAB") }, - - { njs_str("var buffer = require('buffer');" - "typeof buffer.kMaxLength === 'number' "), - njs_str("true") }, - - { njs_str("var buffer = require('buffer');" - "typeof buffer.constants.MAX_LENGTH === 'number' "), - njs_str("true") }, - - { njs_str("var buffer = require('buffer');" - "typeof buffer.constants.MAX_STRING_LENGTH === 'number' "), - njs_str("true") }, -}; - - static njs_unit_test_t njs_webcrypto_test[] = { /* Statistic test @@ -24891,12 +24080,6 @@ static njs_test_suite_t njs_suites[] = njs_nitems(njs_querystring_module_test), njs_unit_test }, - { njs_str("buffer module"), - { .repeat = 1, .unsafe = 1 }, - njs_buffer_module_test, - njs_nitems(njs_buffer_module_test), - njs_unit_test }, - { njs_str("externals"), { .externals = 1, .repeat = 1, .unsafe = 1 }, njs_externals_test, diff --git a/test/buffer.t.js b/test/buffer.t.js index 1d96d342..55227b3a 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -1,5 +1,5 @@ /*--- -includes: [compatBuffer.js, runTsuite.js] +includes: [compatBuffer.js, runTsuite.js, compareArray.js] flags: [async] ---*/ @@ -10,6 +10,297 @@ function p(args, default_opts) { return params; } +let alloc_tsuite = { + name: "Buffer.alloc() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.alloc(params.size, params.fill, params.encoding); + + if (r.toString() !== params.expected) { + throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: { encoding: 'utf-8' }, + + tests: [ + { size: 3, fill: 0x61, expected: 'aaa' }, + { size: 3, fill: 'A', expected: 'AAA' }, + { size: 3, fill: 'ABCD', expected: 'ABC' }, + { size: 3, fill: '414243', encoding: 'hex', expected: 'ABC' }, + { size: 4, fill: '414243', encoding: 'hex', expected: 'ABCA' }, + { size: 3, fill: 'QUJD', encoding: 'base64', expected: 'ABC' }, + { size: 3, fill: 'QUJD', encoding: 'base64url', expected: 'ABC' }, + { size: 3, fill: Buffer.from('ABCD'), encoding: 'utf-8', expected: 'ABC' }, + { size: 3, fill: Buffer.from('ABCD'), encoding: 'utf8', expected: 'ABC' }, + { size: 3, fill: 'ABCD', encoding: 'utf-128', + exception: 'TypeError: "utf-128" encoding is not supported' }, + { size: 3, fill: Buffer.from('def'), expected: 'def' }, + ], +}; + + +let byteLength_tsuite = { + name: "Buffer.byteLength() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.byteLength(params.value, params.encoding); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: { encoding: 'utf-8' }, + + tests: [ + { value: 'abc', expected: 3 }, + { value: 'αβγ', expected: 6 }, + { value: 'αβγ', encoding: 'utf-8', expected: 6 }, + { value: 'αβγ', encoding: 'utf8', expected: 6 }, + { value: 'αβγ', encoding: 'utf-128', exception: 'TypeError: "utf-128" encoding is not supported' }, + { value: '414243', encoding: 'hex', expected: 3 }, + { value: 'QUJD', encoding: 'base64', expected: 3 }, + { value: 'QUJD', encoding: 'base64url', expected: 3 }, + { value: Buffer.from('αβγ'), expected: 6 }, + { value: Buffer.alloc(3).buffer, expected: 3 }, + { value: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), expected: 3 }, + ], +}; + + +let concat_tsuite = { + name: "Buffer.concat() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.concat(params.buffers, params.length); + + if (r.toString() !== params.expected) { + throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + tests: [ + { buffers: [ Buffer.from('abc'), + Buffer.from(new Uint8Array([0x64, 0x65, 0x66]).buffer, 1) ], + expected: 'abcef' }, + { buffers: [ Buffer.from('abc'), Buffer.from('def'), Buffer.from('') ], + expected: 'abcdef' }, + { buffers: [ Buffer.from(''), Buffer.from('abc'), Buffer.from('def') ], + length: 4, expected: 'abcd' }, + ], +}; + + +let compare_tsuite = { + name: "Buffer.compare() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.compare(params.buf1, params.buf2); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: 0 }, + { buf1: Buffer.from('abc'), + buf2: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + expected: 0 }, + { buf1: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + buf2: Buffer.from('abc'), + expected: 0 }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('def'), expected: -1 }, + { buf1: Buffer.from('def'), buf2: Buffer.from('abc'), expected: 1 }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('abcd'), expected: -1 }, + { buf1: Buffer.from('abcd'), buf2: Buffer.from('abc'), expected: 1 }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('ab'), expected: 1 }, + { buf1: Buffer.from('ab'), buf2: Buffer.from('abc'), expected: -1 }, + { buf1: Buffer.from('abc'), buf2: Buffer.from(''), expected: 1 }, + { buf1: Buffer.from(''), buf2: Buffer.from('abc'), expected: -1 }, + { buf1: Buffer.from(''), buf2: Buffer.from(''), expected: 0 }, + ], +}; + + +let comparePrototype_tsuite = { + name: "buf.compare() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.compare(params.target, params.tStart, params.tEnd, + params.sStart, params.sEnd); + + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abc'), target: Buffer.from('abc'), expected: 0 }, + { buf: Buffer.from('abc'), + target: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + expected: 0 }, + { buf: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + target: Buffer.from('abc'), expected: 0 }, + { buf: Buffer.from('abc'), target: Buffer.from('def'), expected: -1 }, + { buf: Buffer.from('def'), target: Buffer.from('abc'), expected: 1 }, + { buf: Buffer.from('abc'), target: Buffer.from('abcd'), expected: -1 }, + { buf: Buffer.from('abcd'), target: Buffer.from('abc'), expected: 1 }, + { buf: Buffer.from('abc'), target: Buffer.from('ab'), expected: 1 }, + { buf: Buffer.from('ab'), target: Buffer.from('abc'), expected: -1 }, + { buf: Buffer.from('abc'), target: Buffer.from(''), expected: 1 }, + { buf: Buffer.from(''), target: Buffer.from('abc'), expected: -1 }, + { buf: Buffer.from(''), target: Buffer.from(''), expected: 0 }, + + { buf: Buffer.from('abcdef'), target: Buffer.from('abc'), + sEnd: 3, expected: 0 }, + { buf: Buffer.from('abcdef'), target: Buffer.from('def'), + sStart: 3, expected: 0 }, + { buf: Buffer.from('abcdef'), target: Buffer.from('abc'), + sStart: 0, sEnd: 3, expected: 0 }, + { buf: Buffer.from('abcdef'), target: Buffer.from('def'), + sStart: 3, sEnd: 6, expected: 0 }, + { buf: Buffer.from('abcdef'), target: Buffer.from('def'), + sStart: 3, sEnd: 5, tStart: 0, tEnd: 2, expected: 0 }, + ], +}; + + +let copy_tsuite = { + name: "buf.copy() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.copy(params.target, params.tStart, params.sStart, params.sEnd); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + if (params.target.toString() !== params.expected_buf) { + throw Error(`unexpected buf "${params.target.toString()}" != "${params.expected_buf}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + expected: 6, expected_buf: 'abcdef' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + tStart: 0, expected: 6, expected_buf: 'abcdef' }, + { buf: Buffer.from('abc'), target: Buffer.from('123456789'), + tStart: 5, expected: 3, expected_buf: '12345abc9' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + tStart: 0, sStart: 0, expected: 6, expected_buf: 'abcdef' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + tStart: 0, sStart: 0, sEnd: 3, expected: 3, expected_buf: 'abc456' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + tStart: 2, sStart: 2, sEnd: 3, expected: 1, expected_buf: '12c456' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + tStart: 7, exception: 'RangeError: \"targetStart\" is out of bounds' }, + { buf: Buffer.from('abcdef'), target: Buffer.from('123456'), + sStart: 7, exception: 'RangeError: \"sourceStart\" is out of bounds' }, + ], +}; + + +let equals_tsuite = { + name: "buf.equals() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf1.equals(params.buf2); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + tests: [ + + { buf1: Buffer.from('abc'), buf2: Buffer.from('abc'), expected: true }, + { buf1: Buffer.from('abc'), + buf2: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + expected: true }, + { buf1: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + buf2: Buffer.from('abc'), expected: true }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('def'), expected: false }, + { buf1: Buffer.from('def'), buf2: Buffer.from('abc'), expected: false }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('abcd'), expected: false }, + { buf1: Buffer.from('abcd'), buf2: Buffer.from('abc'), expected: false }, + { buf1: Buffer.from('abc'), buf2: Buffer.from('ab'), expected: false }, + { buf1: Buffer.from('ab'), buf2: Buffer.from('abc'), expected: false }, + { buf1: Buffer.from('abc'), buf2: Buffer.from(''), expected: false }, + { buf1: Buffer.from(''), buf2: Buffer.from('abc'), expected: false }, + { buf1: Buffer.from(''), buf2: Buffer.from(''), expected: true }, + ], +}; + + +let fill_tsuite = { + name: "buf.fill() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.fill(params.value, params.offset, params.end); + + if (r.toString() !== params.expected) { + throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + tests: [ + { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa' }, + { buf: Buffer.from('abc'), value: 0x61, expected: 'aaa', offset: 0, end: 3 }, + { buf: Buffer.from('abc'), value: 0x61, expected: 'abc', offset: 0, end: 0 }, + { buf: Buffer.from('abc'), value: 'A', expected: 'AAA' }, + { buf: Buffer.from('abc'), value: 'ABCD', expected: 'ABC' }, + { buf: Buffer.from('abc'), value: '414243', offset: 'hex', expected: 'ABC' }, + { buf: Buffer.from('abc'), value: '414243', offset: 'utf-128', + exception: 'TypeError: "utf-128" encoding is not supported' }, + { buf: Buffer.from('abc'), value: 'ABCD', offset: 1, expected: 'aAB' }, + { buf: Buffer.from('abc'), value: Buffer.from('def'), expected: 'def' }, + { buf: Buffer.from('def'), + value: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + expected: 'abc' }, + { buf: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), + value: Buffer.from('def'), + expected: 'def' }, + ], +}; + + let from_tsuite = { name: "Buffer.from() tests", skip: () => (!has_buffer()), @@ -20,6 +311,12 @@ let from_tsuite = { params.modify(buf); } + if (params.args[0] instanceof ArrayBuffer) { + if (buf.buffer !== params.args[0]) { + throw Error(`unexpected buffer "${buf.buffer}" != "${params.args[0]}"`); + } + } + let r = buf.toString(params.fmt); if (r.length !== params.expected.length) { @@ -48,7 +345,6 @@ let from_tsuite = { { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, 1], fmt: "hex", expected: 'bb' }, { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, '1', '1'], fmt: "hex", expected: 'bb' }, { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, 0], fmt: "hex", expected: '' }, - { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer, 1, -1], fmt: "hex", expected: '' }, { args: [(new Uint8Array([0xaa, 0xbb, 0xcc])).buffer], fmt: "hex", modify: (buf) => { buf[1] = 0; }, @@ -128,6 +424,68 @@ let from_tsuite = { { args: ['QUJDRA#', "base64url"], expected: 'ABCD' }, ]}; + +let includes_tsuite = { + name: "buf.includes() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.includes(params.value, params.offset, params.encoding); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), value: 'abc', expected: true }, + { buf: Buffer.from('abcdef'), value: 'def', expected: true }, + { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: false }, + { buf: Buffer.from('abcdef'), value: {}, + exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' }, + ], +}; + + +let indexOf_tsuite = { + name: "buf.indexOf() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.indexOf(params.value, params.offset, params.encoding); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, + { buf: Buffer.from('abcdef'), value: 'def', expected: 3 }, + { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: -1 }, + { buf: Buffer.from('abcdef'), value: 'def', offset: 1, expected: 3 }, + { buf: Buffer.from('abcdef'), value: 'def', offset: -3, expected: 3 }, + { buf: Buffer.from('abcdef'), value: '626364', encoding: 'hex', expected: 1 }, + { buf: Buffer.from('abcdef'), value: '626364', encoding: 'utf-128', + exception: 'TypeError: "utf-128" encoding is not supported' }, + { buf: Buffer.from('abcdef'), value: 0x62, expected: 1 }, + { buf: Buffer.from('abcabc'), value: 0x61, offset: 1, expected: 3 }, + { buf: Buffer.from('abcdef'), value: Buffer.from('def'), expected: 3 }, + { buf: Buffer.from('abcdef'), value: Buffer.from(new Uint8Array([0x60, 0x62, 0x63]).buffer, 1), expected: 1 }, + { buf: Buffer.from('abcdef'), value: {}, + exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' }, + ], +}; + + let isBuffer_tsuite = { name: "Buffer.isBuffer() tests", skip: () => (!has_buffer()), @@ -151,6 +509,33 @@ let isBuffer_tsuite = { ]}; +let isEncoding_tsuite = { + name: "Buffer.isEncoding() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.isEncoding(params.value); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { value: 'utf-8', expected: true }, + { value: 'utf8', expected: true }, + { value: 'utf-128', expected: false }, + { value: 'hex', expected: true }, + { value: 'base64', expected: true }, + { value: 'base64url', expected: true }, + ], +}; + + function compare_object(a, b) { if (a === b) { return true; @@ -175,6 +560,264 @@ function compare_object(a, b) { } +let lastIndexOf_tsuite = { + name: "buf.lastIndexOf() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.lastIndexOf(params.value, params.offset, params.encoding); + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), value: 'abc', expected: 0 }, + { buf: Buffer.from('abcabc'), value: 'abc', expected: 3 }, + { buf: Buffer.from('abcdef'), value: 'def', expected: 3 }, + { buf: Buffer.from('abcdef'), value: 'abc', offset: 1, expected: 0 }, + { buf: Buffer.from('abcdef'), value: 'def', offset: 1, expected: -1 }, + { buf: Buffer.from(Buffer.alloc(7).fill('Zabcdef').buffer, 1), value: 'abcdef', expected: 0 }, + { buf: Buffer.from(Buffer.alloc(7).fill('Zabcdef').buffer, 1), value: 'abcdefg', expected: -1 }, + { buf: Buffer.from('abcdef'), value: '626364', encoding: 'hex', expected: 1 }, + { buf: Buffer.from('abcdef'), value: '626364', encoding: 'utf-128', + exception: 'TypeError: "utf-128" encoding is not supported' }, + { buf: Buffer.from('abcabc'), value: 0x61, expected: 3 }, + { buf: Buffer.from('abcabc'), value: 0x61, offset: 1, expected: 0 }, + { buf: Buffer.from('abcdef'), value: Buffer.from('def'), expected: 3 }, + { buf: Buffer.from('abcdef'), value: Buffer.from(new Uint8Array([0x60, 0x62, 0x63]).buffer, 1), expected: 1 }, + { buf: Buffer.from('abcdef'), value: {}, + exception: 'TypeError: "value" argument must be of type string or an instance of Buffer' }, + ], +}; + + +let readXIntXX_tsuite = { + name: "buf.readXIntXX() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = params.buf; + let r = [ + b.readInt8(params.offset), + b.readUInt8(params.offset), + b.readInt16LE(params.offset), + b.readInt16BE(params.offset), + b.readUInt16LE(params.offset), + b.readUInt16BE(params.offset), + b.readInt32LE(params.offset), + b.readInt32BE(params.offset), + b.readUInt32LE(params.offset), + b.readUInt32BE(params.offset), + ]; + + + if (!compareArray(r, params.expected)) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, + expected: [ -86,170,-17494,-21829,48042,43707,-573785174,-1430532899,3721182122,2864434397 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 1, + expected: [ -69,187,-13125,-17460,52411,48076,-287454021,-1144201746,4007513275,3150765550 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 2, + expected: [ -52,204,-8756,-13091,56780,52445,-1122868,-857870593,4293844428,3437096703 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 3, + exception: 'RangeError: Index out of range' }, + ], +}; + + +let readFloat_tsuite = { + name: "buf.readFloat() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = Buffer.alloc(9); + let r = b.writeFloatLE(123.125, 0); + if (r !== 4) { + throw Error(`unexpected output "${r}" != "4"`); + } + + if (b.readFloatLE(0) !== 123.125) { + throw Error(`unexpected output "${b.readFloatLE(0)}" != "123.125"`); + } + + r = b.writeFloatBE(123.125, 0); + if (r !== 4) { + throw Error(`unexpected output "${r}" != "4"`); + } + + if (b.readFloatBE(0) !== 123.125) { + throw Error(`unexpected output "${b.readFloatBE(0)}" != "123.125"`); + } + + r = b.writeDoubleLE(123.125, 1); + if (r !== 9) { + throw Error(`unexpected output "${r}" != "9"`); + } + + if (b.readDoubleLE(1) !== 123.125) { + throw Error(`unexpected output "${b.readDoubleLE(1)}" != "123.125"`); + } + + r = b.writeDoubleBE(123.125, 1); + if (r !== 9) { + throw Error(`unexpected output "${r}" != "9"`); + } + + if (b.readDoubleBE(1) !== 123.125) { + throw Error(`unexpected output "${b.readDoubleBE(1)}" != "123.125"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + {} + ], +}; + + +let readGeneric_tsuite = { + name: "buf.readGeneric() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = params.buf; + let r = [ + b.readUIntLE(params.offset, params.length), + b.readUIntBE(params.offset, params.length), + b.readIntLE(params.offset, params.length), + b.readIntBE(params.offset, params.length), + ]; + + + if (!compareArray(r, params.expected)) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 1, + expected: [ 170, 170, -86, -86 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 1, length: 2, + expected: [ 52411,48076,-13125,-17460 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 2, length: 3, + expected: [ 15654348,13426158,-1122868,-3351058 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 3, length: 4, + exception: 'RangeError: Index out of range' }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 0, + exception: 'RangeError: byteLength must be <= 6' }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 5, + expected: [ 1025923398570,733295205870,-73588229206,-366216421906 ] }, + { buf: Buffer.from([0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]), offset: 0, length: 6, + expected: [ 281401388481450,187723572702975,-73588229206,-93751404007681 ] }, + ], +}; + + +let slice_tsuite = { + name: "buf.slice() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.slice(params.start, params.end); + + if (r.toString() !== params.expected) { + throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`); + } + + params.buf[2] = 0x5a; + + if (r.constructor.name !== 'Buffer' || r.__proto__ !== params.buf.__proto__) { + throw Error(`unexpected output "${r.constructor.name}" != "Buffer"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, + { buf: Buffer.from('abcdef'), start: 1, end: 3, expected: 'bc' }, + ], +}; + + +let subarray_tsuite = { + name: "buf.subarray() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = params.buf.subarray(params.start, params.end); + + params.buf[0] = 0x5a; + + if (r.toString() !== params.expected) { + throw Error(`unexpected output "${r.toString()}" != "${params.expected}"`); + } + + if (r.constructor.name !== 'Buffer' || r.__proto__ !== params.buf.__proto__) { + throw Error(`unexpected output "${r.constructor.name}" != "Buffer"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { buf: Buffer.from('abcdef'), start: 0, end: 3, expected: 'Zbc' }, + { buf: Buffer.from('abcdef'), start: 1, expected: 'bcdef' }, + ], +}; + + +let swap_tsuite = { + name: "buf.swap() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let r = Buffer.from(params.value, 'hex')[params.swap](); + + if (r.toString('hex') !== params.expected) { + throw Error(`unexpected output "${r.toString('hex')}" != "${params.expected}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: { swap: 'swap16' }, + + tests: [ + { value: '01020304', expected: '02010403' }, + { value: '010203', exception: 'RangeError: Buffer size must be a multiple of 2' }, + { value: 'aabbccddeeff0011', swap: 'swap32', expected: 'ddccbbaa1100ffee' }, + { value: 'aabbcc', swap: 'swap32', exception: 'RangeError: Buffer size must be a multiple of 4' }, + { value: 'aabbccddeeff00112233445566778899', swap: 'swap64', expected: '1100ffeeddccbbaa9988776655443322' }, + ], +}; + + let toJSON_tsuite = { name: "Buffer.toJSON() tests", skip: () => (!has_buffer()), @@ -224,13 +867,181 @@ let toString_tsuite = { { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "hex", expected: 'ffdeba' }, { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "base64", expected: '/966' }, { value: new Uint8Array([0xff, 0xde, 0xba]), fmt: "base64url", expected: '_966' }, + { value: "ABCD", fmt: "base64", expected: 'QUJDRA==' }, + { value: "ABCD", fmt: "base64url", expected: 'QUJDRA' }, { value: '', fmt: "utf-128", exception: 'TypeError: "utf-128" encoding is not supported' }, ]}; + +let write_tsuite = { + name: "buf.write() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = Buffer.alloc(10).fill('Z'); + let r; + + if (typeof params.offset != 'undefined' && typeof params.length != 'undefined') { + r = b.write(params.value, params.offset, params.length, params.encoding); + + } else if (typeof params.offset != 'undefined') { + r = b.write(params.value, params.offset, params.encoding); + + } else { + r = b.write(params.value, params.encoding); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + if (b.toString() !== params.expected_buf) { + throw Error(`unexpected output "${b.toString()}" != "${params.expected_buf}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { value: 'abc', expected: 3, expected_buf: 'abcZZZZZZZ' }, + { value: 'abc', offset: 1, expected: 3, expected_buf: 'ZabcZZZZZZ' }, + { value: 'abc', offset: 1, length: 2, expected: 2, expected_buf: 'ZabZZZZZZZ' }, + { value: 'αβγ', offset: 1, expected: 6, expected_buf: 'ZαβγZZZ' }, + { value: 'αβγ', offset: 1, length: 1, expected: 0, expected_buf: 'ZZZZZZZZZZ' }, + { value: 'αβγ', offset: 1, length: 2, expected: 2, expected_buf: 'ZαZZZZZZZ' }, + { value: '414243', encoding: 'hex', expected: 3, expected_buf: 'ABCZZZZZZZ' }, + { value: '414243', encoding: 'hex', offset: 8, expected: 2, expected_buf: 'ZZZZZZZZAB' }, + { value: "x".repeat(12), expected: 10, expected_buf: 'xxxxxxxxxx' }, + { value: "x".repeat(12), offset: 1, expected: 9, expected_buf: 'Zxxxxxxxxx' }, + { value: "x", offset: 1, length: 2, encoding: 'utf-128', + exception: 'TypeError: "utf-128" encoding is not supported' }, + { value: "x".repeat(10), offset: 10, expected: 0, expected_buf: 'ZZZZZZZZZZ' }, + { value: "x".repeat(10), offset: 11, exception: 'RangeError: Index out of range' }, + ], +}; + + +let writeXIntXX_tsuite = { + name: "buf.writeXIntXX() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = Buffer.alloc(10).fill('Z'); + let r = b[params.write](params.value, params.offset); + + if (params.exception) { + throw Error(`expected exception "${params.exception}"`); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + if (b.toString('hex') !== params.expected_buf) { + throw Error(`unexpected output "${b.toString('hex')}" != "${params.expected_buf}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { write: 'writeInt8', value: 0xaa, exception: 'RangeError: Index out of range' }, + { write: 'writeInt8', value: 0x00, offset: 3, expected: 4, expected_buf: '5a5a5a005a5a5a5a5a5a' }, + { write: 'writeUInt8', value: 0xaa, offset: 0, expected: 1, expected_buf: 'aa5a5a5a5a5a5a5a5a5a' }, + { write: 'writeInt16LE', value: 0xaabb, exception: 'RangeError: Index out of range' }, + { write: 'writeInt16BE', value: 0x7788, offset: 1, expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' }, + { write: 'writeUInt16LE', value: 0xaabb, offset: 0, expected: 2, expected_buf: 'bbaa5a5a5a5a5a5a5a5a' }, + { write: 'writeUInt16BE', value: 0x7788, offset: 1, expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' }, + { write: 'writeInt32LE', value: 0xaabbccdd, exception: 'RangeError: Index out of range' }, + { write: 'writeInt32BE', value: 0x778899aa, offset: 1, expected: 5, expected_buf: '5a778899aa5a5a5a5a5a' }, + { write: 'writeUInt32LE', value: 0xaabbccdd, offset: 0, expected: 4, expected_buf: 'ddccbbaa5a5a5a5a5a5a' }, + { write: 'writeUInt32BE', value: 0x778899aa, offset: 1, expected: 5, expected_buf: '5a778899aa5a5a5a5a5a' }, + ], +}; + + +let writeGeneric_tsuite = { + name: "buf.writeGeneric() tests", + skip: () => (!has_buffer()), + T: async (params) => { + let b = Buffer.alloc(10).fill('Z'); + let r = b[params.write](params.value, params.offset, params.length); + + if (params.exception) { + throw Error(`expected exception "${params.exception}"`); + } + + if (r !== params.expected) { + throw Error(`unexpected output "${r}" != "${params.expected}"`); + } + + if (b.toString('hex') !== params.expected_buf) { + throw Error(`unexpected output "${b.toString('hex')}" != "${params.expected_buf}"`); + } + + return 'SUCCESS'; + }, + + prepare_args: p, + opts: {}, + + tests: [ + { write: 'writeUIntLE', value: 0xaa, length: 1, + exception: 'RangeError: Index out of range' }, + { write: 'writeUIntLE', value: 0x44, length: 1, offset: 3, + expected: 4, expected_buf: '5a5a5a445a5a5a5a5a5a' }, + { write: 'writeUIntBE', value: 0xaabb, length: 2, offset: 0, + expected: 2, expected_buf: 'aabb5a5a5a5a5a5a5a5a' }, + { write: 'writeUIntBE', value: 0x7788, length: 2, offset: 1, + expected: 3, expected_buf: '5a77885a5a5a5a5a5a5a' }, + { write: 'writeIntLE', value: 0x445566, length: 3, offset: 5, + expected: 8, expected_buf: '5a5a5a5a5a6655445a5a' }, + { write: 'writeIntBE', value: 0x778899, length: 3, offset: 1, + expected: 4, expected_buf: '5a7788995a5a5a5a5a5a' }, + { write: 'writeIntLE', value: 0x44556677, length: 4, offset: 5, + expected: 9, expected_buf: '5a5a5a5a5a776655445a' }, + { write: 'writeIntBE', value: 0xaabbccdd, length: 4, offset: 1, + exception: 'RangeError: Index out of range' }, + { write: 'writeUIntLE', value: 0xaabbccddee, length: 5, offset: 0, + expected: 5, expected_buf: 'eeddccbbaa5a5a5a5a5a' }, + { write: 'writeUIntBE', value: 0x778899aabbcc, length: 6, offset: 1, + expected: 7, expected_buf: '5a778899aabbcc5a5a5a' }, + { write: 'writeUIntBE', value: 0, length: 7, + exception: 'The value of "byteLength" is out of range' }, + + ], +}; + + run([ + alloc_tsuite, + byteLength_tsuite, + concat_tsuite, + compare_tsuite, + comparePrototype_tsuite, + copy_tsuite, + equals_tsuite, + fill_tsuite, from_tsuite, + includes_tsuite, + indexOf_tsuite, isBuffer_tsuite, + isEncoding_tsuite, + lastIndexOf_tsuite, + readXIntXX_tsuite, + readFloat_tsuite, + readGeneric_tsuite, + slice_tsuite, + subarray_tsuite, + swap_tsuite, toJSON_tsuite, toString_tsuite, + write_tsuite, + writeXIntXX_tsuite, + writeGeneric_tsuite, ]) .then($DONE, $DONE); diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index dc2034fd..aa3f5c0f 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -48,6 +48,9 @@ function merge(to, from) { } else if (from[v] instanceof Uint8Array) { r[v] = new Uint8Array(from[v]); + } else if (from[v] instanceof ArrayBuffer) { + r[v] = new ArrayBuffer(from[v].byteLength); + } else { r[v] = Object.assign(Array.isArray(from[v]) ? [] : {}, from[v]); } From noreply at nginx.com Fri Aug 16 01:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 01:36:02 +0000 (UTC) Subject: [njs] Fixed Buffer.prototype.writeFloat() and friends. Message-ID: <20240816013602.C89AA487D0@pubserv1.nginx> details: https://github.com/nginx/njs/commit/7ba3a8b943488f801a8dde64d76a965b7686094c branches: master commit: 7ba3a8b943488f801a8dde64d76a965b7686094c user: Dmitry Volyntsev date: Wed, 7 Aug 2024 22:27:45 -0700 description: Fixed Buffer.prototype.writeFloat() and friends. --- src/njs_buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index fc95aebd..fa087a19 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -1504,7 +1504,7 @@ njs_buffer_prototype_write_float(njs_vm_t *vm, njs_value_t *args, *((uint64_t *) u8) = conv_f64.u; } - njs_set_undefined(retval); + njs_set_number(retval, index + size); return NJS_OK; } From pclicoder at gmail.com Fri Aug 16 15:02:29 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Fri, 16 Aug 2024 08:02:29 -0700 Subject: [nginx] CONF: Make ssl_client_certificate directive optional with TLSv1.3 Message-ID: Hi Nginx Devs Bumping patch to the top for review. CC: @Sergey Kandaurov Thanks for contributing client certificate validation with OSCP. It is a long awaited feature. In this patch, I am trying to fix another lingering concern. It will be great, if you can have a look. # HG changeset patch # User Praveen Chaudhary # Date 1723406727 25200 # Sun Aug 11 13:05:27 2024 -0700 # Node ID 199a35c74b60437da9d22a70d257507b4afb1878 # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 Make ssl_client_certificate directive optional with TLSv1.3. - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) send Certificate Authorities (CAs) in the Certificate Request packet. This makes ssl_client_certificate directive optional when only TLS 1.3 is used for mutual TLS configurations. - Today, Nginx requires ssl_client_certificate directive to be set to CA Certificates file, if ssl_verify_client is enabled, even when using only TLS 1.3. Else Nginx does not reload or restart. diff -r b5550a7f16c7 -r 199a35c74b60 src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -787,10 +787,16 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r b5550a7f16c7 -r 199a35c74b60 src/mail/ngx_mail_ssl_module.c --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -450,12 +450,19 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } + if (ngx_ssl_client_certificate(cf, &conf->ssl, &conf->client_certificate, conf->verify_depth) diff -r b5550a7f16c7 -r 199a35c74b60 src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -932,10 +932,16 @@ if (conf->verify) { - if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)) { + /* + For TLS 1.3, It is optional to send Certificate Authorities in + Certificate Request Packet. RFC 8446#section-4.2.4 + */ + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Fri Aug 16 15:27:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 15:27:02 +0000 (UTC) Subject: [njs] Fixed compilation on 32bit platforms. Message-ID: <20240816152702.53319487DC@pubserv1.nginx> details: https://github.com/nginx/njs/commit/598fc578fcba772dc3649f74f1cbef7f5783961f branches: master commit: 598fc578fcba772dc3649f74f1cbef7f5783961f user: Dmitry Volyntsev date: Thu, 15 Aug 2024 22:02:48 -0700 description: Fixed compilation on 32bit platforms. The issue was introduced in d1c615eaa. --- src/qjs.h | 1 + src/qjs_buffer.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/qjs.h b/src/qjs.h index 00e9296a..f8eabefa 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index 83764e02..06574110 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -760,7 +760,7 @@ qjs_buffer_array_range(JSContext *ctx, njs_str_t *array, JSValueConst start, } if (num_start < 0 || (size_t) num_start > array->length) { - return JS_ThrowRangeError(ctx, "\"%sStart\" is out of range: %ld", + return JS_ThrowRangeError(ctx, "\"%sStart\" is out of range: %" PRId64, name, num_start); } @@ -773,7 +773,7 @@ qjs_buffer_array_range(JSContext *ctx, njs_str_t *array, JSValueConst start, } if (num_end < 0 || (size_t) num_end > array->length) { - return JS_ThrowRangeError(ctx, "\"%sEnd\" is out of range: %ld", + return JS_ThrowRangeError(ctx, "\"%sEnd\" is out of range: %" PRId64, name, num_end); } @@ -1141,8 +1141,8 @@ qjs_buffer_prototype_read_float(JSContext *ctx, JSValueConst this_val, size = magic >> 2; if (size + index > self.length) { - return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" - " buffer", index); + return JS_ThrowRangeError(ctx, "index %" PRIu64 " is outside the bound" + " of the buffer", index); } little = magic & 1; @@ -1217,8 +1217,8 @@ qjs_buffer_prototype_read_int(JSContext *ctx, JSValueConst this_val, } if (size + index > self.length) { - return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" - " buffer", index); + return JS_ThrowRangeError(ctx, "index %" PRIu64 " is outside the bound" + " of the buffer", index); } sign = (magic >> 1) & 1; @@ -1628,8 +1628,8 @@ qjs_buffer_prototype_write_int(JSContext *ctx, JSValueConst this_val, } if (size + index > self.length) { - return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" - " buffer", index); + return JS_ThrowRangeError(ctx, "index %" PRIu64 " is outside the bound" + " of the buffer", index); } little = magic & 1; @@ -1834,8 +1834,8 @@ qjs_buffer_prototype_write_float(JSContext *ctx, JSValueConst this_val, size = magic >> 2; if (size + index > self.length) { - return JS_ThrowRangeError(ctx, "index %lu is outside the bound of the" - " buffer", index); + return JS_ThrowRangeError(ctx, "index %" PRIu64 " is outside the bound" + " of the buffer", index); } little = magic & 1; From noreply at nginx.com Fri Aug 16 15:27:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 15:27:02 +0000 (UTC) Subject: [njs] Making ngx_js_logger() reusable by QuickJS code. Message-ID: <20240816152702.4DE4E487DA@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d47f7beda262a63d0eef72839c5b2b545b0699d9 branches: master commit: d47f7beda262a63d0eef72839c5b2b545b0699d9 user: Dmitry Volyntsev date: Mon, 8 Jul 2024 23:38:28 -0700 description: Making ngx_js_logger() reusable by QuickJS code. --- nginx/ngx_js.c | 39 ++++++++++++++++++++++++--------------- nginx/ngx_js.h | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 9a20a684..4c1f29f3 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -815,10 +815,11 @@ njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t magic, njs_value_t *retval) { - char *p; - ngx_int_t lvl; - njs_str_t msg; - njs_uint_t n, level; + char *p; + ngx_int_t lvl; + njs_str_t msg; + njs_uint_t n, level; + ngx_connection_t *c; p = njs_vm_external(vm, NJS_PROTO_ID_ANY, njs_argument(args, 0)); if (p == NULL) { @@ -840,6 +841,8 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, n = 1; } + c = ngx_external_connection(vm, p); + for (; n < nargs; n++) { if (njs_vm_value_dump(vm, &msg, njs_argument(args, n), 1, !!(magic & NGX_JS_LOG_DUMP)) @@ -848,7 +851,7 @@ ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - ngx_js_logger(vm, p, level, msg.start, msg.length); + ngx_js_logger(c, level, msg.start, msg.length); } njs_value_undefined_set(retval); @@ -1185,30 +1188,36 @@ void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external, ngx_uint_t level, const char *fmt, ...) { - u_char *p; - va_list args; - u_char buf[NGX_MAX_ERROR_STR]; + u_char *p; + va_list args; + ngx_connection_t *c; + u_char buf[NGX_MAX_ERROR_STR]; va_start(args, fmt); p = njs_vsprintf(buf, buf + sizeof(buf), fmt, args); va_end(args); - ngx_js_logger(vm, external, level, buf, p - buf); + if (external != NULL) { + c = ngx_external_connection(vm, external); + + } else { + c = NULL; + } + + ngx_js_logger(c, level, buf, p - buf); } void -ngx_js_logger(njs_vm_t *vm, njs_external_ptr_t external, ngx_uint_t level, - const u_char *start, size_t length) +ngx_js_logger(ngx_connection_t *c, ngx_uint_t level, const u_char *start, + size_t length) { ngx_log_t *log; - ngx_connection_t *c; ngx_log_handler_pt handler; handler = NULL; - if (external != NULL) { - c = ngx_external_connection(vm, external); + if (c != NULL) { log = c->log; handler = log->handler; log->handler = NULL; @@ -1222,7 +1231,7 @@ ngx_js_logger(njs_vm_t *vm, njs_external_ptr_t external, ngx_uint_t level, ngx_log_error(level, log, 0, "js: %*s", length, start); - if (external != NULL) { + if (c != NULL) { log->handler = handler; } } diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index beccbfdf..aad88b54 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -192,7 +192,7 @@ njs_int_t ngx_js_ext_log(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t level, njs_value_t *retval); void ngx_js_log(njs_vm_t *vm, njs_external_ptr_t external, ngx_uint_t level, const char *fmt, ...); -void ngx_js_logger(njs_vm_t *vm, njs_external_ptr_t external, ngx_uint_t level, +void ngx_js_logger(ngx_connection_t *c, ngx_uint_t level, const u_char *start, size_t length); char * ngx_js_import(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); char * ngx_js_preload_object(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); From noreply at nginx.com Fri Aug 16 15:27:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 16 Aug 2024 15:27:02 +0000 (UTC) Subject: [njs] Modules: adding NUL byte at the end of the global script. Message-ID: <20240816152702.49B96487D9@pubserv1.nginx> details: https://github.com/nginx/njs/commit/58b581461d8cc77037de9000e1dde9b74e314aee branches: master commit: 58b581461d8cc77037de9000e1dde9b74e314aee user: Dmitry Volyntsev date: Thu, 25 Jul 2024 19:02:35 -0700 description: Modules: adding NUL byte at the end of the global script. Even though QuickJS takes length as an argument, when parsing the code, it expects NUL byte at the end. This change is similar to 184d2a39cb5 (0.8.5). --- nginx/ngx_js.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index b372feca..9a20a684 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -2017,7 +2017,7 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, + sizeof(" globalThis. = ;\n") - 1; } - start = ngx_pnalloc(cf->pool, size); + start = ngx_pnalloc(cf->pool, size + 1); if (start == NULL) { return NGX_ERROR; } @@ -2039,6 +2039,8 @@ ngx_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, p = ngx_cpymem(p, ";\n", sizeof(";\n") - 1); } + *p = '\0'; + file = ngx_cycle->conf_prefix; options->file.start = file.data; From a.bavshin at nginx.com Mon Aug 19 18:41:01 2024 From: a.bavshin at nginx.com (Aleksei Bavshin) Date: Mon, 19 Aug 2024 11:41:01 -0700 Subject: [nginx] CONF: Make ssl_client_certificate directive optional with TLSv1.3 In-Reply-To: References: Message-ID: <2d8d7f0f-173a-41e5-9fb9-834b6bb84817@nginx.com> On 8/16/2024 8:02 AM, Praveen Chaudhary wrote: > Hi Nginx Devs > > Bumping patch to the top for review. > > CC: @Sergey Kandaurov > Thanks for contributing client certificate validation with OSCP.  It is > a long awaited feature. > In this patch, I am trying to fix another lingering concern. It will be > great, if you can have a look. Hello, Sending an empty list of CAs is explicitly mentioned starting from TLS 1.1; RFC 4346 Section 7.4.4: If the certificate_authorities list is empty then the client MAY send any certificate of the appropriate ClientCertificateType, unless there is some external arrangement to the contrary. TLS 1.0 (RFC 2246 Section 7.4.4) does not specify any behavior. While it's known that some 1.0 or SSL 3.0 clients can accept an empty list, it could be safer to limit the ability to the TLS 1.1+ configurations. As for the means of doing so, simply skipping the conf->client_certificate check is not correct. For any ssl_client_verify mode other than 'optional_no_ca' nginx must have a list of known trusted CAs, and the configuration without any is not valid. A better approach is to require either 'ssl_client_certificate' or 'ssl_trusted_certificate' to be set when the client cert verification is enabled. > > # HG changeset patch > # User Praveen Chaudhary > > # Date 1723406727 25200 > #      Sun Aug 11 13:05:27 2024 -0700 > # Node ID 199a35c74b60437da9d22a70d257507b4afb1878 > # Parent  b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 > Make ssl_client_certificate directive optional with TLSv1.3. > > - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) >   send Certificate Authorities (CAs) in the Certificate Request >   packet. This makes ssl_client_certificate directive optional >   when only TLS 1.3 is used for mutual TLS configurations. > > - Today, Nginx requires ssl_client_certificate directive to >   be set to CA Certificates file, if ssl_verify_client is >   enabled, even when using only TLS 1.3. Else Nginx does not >   reload or restart. > > diff -r b5550a7f16c7 -r 199a35c74b60 src/http/modules/ngx_http_ssl_module.c > --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 > +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -787,10 +787,16 @@ > >      if (conf->verify) { > > -        if (conf->client_certificate.len == 0 && conf->verify != 3) { > -            ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > -                          "no ssl_client_certificate for > ssl_verify_client"); > -            return NGX_CONF_ERROR; > +        if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > NGX_SSL_TLSv1_2)) { > +            /* > +            For TLS 1.3, It is optional to send Certificate Authorities in > +            Certificate Request Packet. RFC 8446#section-4.2.4 > +            */ > +            if (conf->client_certificate.len == 0 && conf->verify != 3) { > +                ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > +                            "no ssl_client_certificate for > ssl_verify_client"); > +                return NGX_CONF_ERROR; > +            } >          } > >          if (ngx_ssl_client_certificate(cf, &conf->ssl, > diff -r b5550a7f16c7 -r 199a35c74b60 src/mail/ngx_mail_ssl_module.c > --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 > +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -450,12 +450,19 @@ > >      if (conf->verify) { > > -        if (conf->client_certificate.len == 0 && conf->verify != 3) { > -            ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > -                          "no ssl_client_certificate for > ssl_verify_client"); > -            return NGX_CONF_ERROR; > +        if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > NGX_SSL_TLSv1_2)) { > +            /* > +            For TLS 1.3, It is optional to send Certificate Authorities in > +            Certificate Request Packet. RFC 8446#section-4.2.4 > +            */ > +            if (conf->client_certificate.len == 0 && conf->verify != 3) { > +                ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > +                            "no ssl_client_certificate for > ssl_verify_client"); > +                return NGX_CONF_ERROR; > +            } >          } > > + >          if (ngx_ssl_client_certificate(cf, &conf->ssl, >                                         &conf->client_certificate, >                                         conf->verify_depth) > diff -r b5550a7f16c7 -r 199a35c74b60 src/stream/ngx_stream_ssl_module.c > --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 > +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -932,10 +932,16 @@ > >      if (conf->verify) { > > -        if (conf->client_certificate.len == 0 && conf->verify != 3) { > -            ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > -                          "no ssl_client_certificate for > ssl_verify_client"); > -            return NGX_CONF_ERROR; > +        if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > NGX_SSL_TLSv1_2)) { > +            /* > +            For TLS 1.3, It is optional to send Certificate Authorities in > +            Certificate Request Packet. RFC 8446#section-4.2.4 > +            */ > +            if (conf->client_certificate.len == 0 && conf->verify != 3) { > +                ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > +                            "no ssl_client_certificate for > ssl_verify_client"); > +                return NGX_CONF_ERROR; > +            } >          } > >          if (ngx_ssl_client_certificate(cf, &conf->ssl, > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From pclicoder at gmail.com Mon Aug 19 23:22:59 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Mon, 19 Aug 2024 16:22:59 -0700 Subject: [nginx] CONF: Make ssl_client_certificate directive optional with TLSv1.3 In-Reply-To: <2d8d7f0f-173a-41e5-9fb9-834b6bb84817@nginx.com> References: <2d8d7f0f-173a-41e5-9fb9-834b6bb84817@nginx.com> Message-ID: Thanks Aleksei for the review. Agree, It makes sense to have explicit error message to require either ssl_client_certificate or ssl_trusted_certificate. Because: Nginx prints error number from SSL to identify SSL error\routine, but for a client or admin, it may be still hard to find why "SSL certificate error" is seen. V2 Patch. [Keeping same Subject] # HG changeset patch # User Praveen Chaudhary # Date 1723406727 25200 # Sun Aug 11 13:05:27 2024 -0700 # Node ID a5525b8eac0e1f10da42b7367cd5296fb29f4787 # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 Make ssl_client_certificate directive optional with TLSv1.1+. - With TLS 1.1+, Certificate Authorities(CAs) are optional in the Certificate Request packet. This makes directive ssl_client_certificate also optional for mutual TLS configurations. - For TLS 1.1, check either ssl_client_certificate or ssl_trusted_certificate is non empty. diff -r 8796dfbe7177 -r a5525b8eac0e src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -788,9 +788,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r 8796dfbe7177 -r a5525b8eac0e src/mail/ngx_mail_ssl_module.c --- a/src/mail/ngx_mail_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -451,9 +451,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r 8796dfbe7177 -r a5525b8eac0e src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -933,9 +933,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, On Mon, Aug 19, 2024 at 11:41 AM Aleksei Bavshin wrote: > On 8/16/2024 8:02 AM, Praveen Chaudhary wrote: > > Hi Nginx Devs > > > > Bumping patch to the top for review. > > > > CC: @Sergey Kandaurov > > Thanks for contributing client certificate validation with OSCP. It is > > a long awaited feature. > > In this patch, I am trying to fix another lingering concern. It will be > > great, if you can have a look. > > Hello, > > Sending an empty list of CAs is explicitly mentioned starting from TLS > 1.1; RFC 4346 Section 7.4.4: > > If the certificate_authorities list is empty then the client MAY > send any certificate of the appropriate ClientCertificateType, > unless there is some external arrangement to the contrary. > > TLS 1.0 (RFC 2246 Section 7.4.4) does not specify any behavior. While > it's known that some 1.0 or SSL 3.0 clients can accept an empty list, it > could be safer to limit the ability to the TLS 1.1+ configurations. > > As for the means of doing so, simply skipping the > conf->client_certificate check is not correct. For any ssl_client_verify > mode other than 'optional_no_ca' nginx must have a list of known trusted > CAs, and the configuration without any is not valid. > A better approach is to require either 'ssl_client_certificate' or > 'ssl_trusted_certificate' to be set when the client cert verification is > enabled. > > > > > # HG changeset patch > > # User Praveen Chaudhary > > > > # Date 1723406727 25200 > > # Sun Aug 11 13:05:27 2024 -0700 > > # Node ID 199a35c74b60437da9d22a70d257507b4afb1878 > > # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 > > Make ssl_client_certificate directive optional with TLSv1.3. > > > > - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) > > send Certificate Authorities (CAs) in the Certificate Request > > packet. This makes ssl_client_certificate directive optional > > when only TLS 1.3 is used for mutual TLS configurations. > > > - Today, Nginx requires ssl_client_certificate directive to > > be set to CA Certificates file, if ssl_verify_client is > > enabled, even when using only TLS 1.3. Else Nginx does not > > reload or restart. > > > > diff -r b5550a7f16c7 -r 199a35c74b60 > src/http/modules/ngx_http_ssl_module.c > > --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 > +0400 > > +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 > -0700 > > @@ -787,10 +787,16 @@ > > > > if (conf->verify) { > > > > - if (conf->client_certificate.len == 0 && conf->verify != 3) { > > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > - "no ssl_client_certificate for > > ssl_verify_client"); > > - return NGX_CONF_ERROR; > > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > > NGX_SSL_TLSv1_2)) { > > + /* > > + For TLS 1.3, It is optional to send Certificate Authorities > in > > + Certificate Request Packet. RFC 8446#section-4.2.4 > > + */ > > + if (conf->client_certificate.len == 0 && conf->verify != 3) > { > > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > + "no ssl_client_certificate for > > ssl_verify_client"); > > + return NGX_CONF_ERROR; > > + } > > } > > > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > > diff -r b5550a7f16c7 -r 199a35c74b60 src/mail/ngx_mail_ssl_module.c > > --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 > > +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > > @@ -450,12 +450,19 @@ > > > > if (conf->verify) { > > > > - if (conf->client_certificate.len == 0 && conf->verify != 3) { > > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > - "no ssl_client_certificate for > > ssl_verify_client"); > > - return NGX_CONF_ERROR; > > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > > NGX_SSL_TLSv1_2)) { > > + /* > > + For TLS 1.3, It is optional to send Certificate Authorities > in > > + Certificate Request Packet. RFC 8446#section-4.2.4 > > + */ > > + if (conf->client_certificate.len == 0 && conf->verify != 3) > { > > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > + "no ssl_client_certificate for > > ssl_verify_client"); > > + return NGX_CONF_ERROR; > > + } > > } > > > > + > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > > &conf->client_certificate, > > conf->verify_depth) > > diff -r b5550a7f16c7 -r 199a35c74b60 src/stream/ngx_stream_ssl_module.c > > --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 > > +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > > @@ -932,10 +932,16 @@ > > > > if (conf->verify) { > > > > - if (conf->client_certificate.len == 0 && conf->verify != 3) { > > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > - "no ssl_client_certificate for > > ssl_verify_client"); > > - return NGX_CONF_ERROR; > > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| > > NGX_SSL_TLSv1_2)) { > > + /* > > + For TLS 1.3, It is optional to send Certificate Authorities > in > > + Certificate Request Packet. RFC 8446#section-4.2.4 > > + */ > > + if (conf->client_certificate.len == 0 && conf->verify != 3) > { > > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > > + "no ssl_client_certificate for > > ssl_verify_client"); > > + return NGX_CONF_ERROR; > > + } > > } > > > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > > > > _______________________________________________ > > nginx-devel mailing list > > nginx-devel at nginx.org > > https://mailman.nginx.org/mailman/listinfo/nginx-devel > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Wed Aug 21 13:32:21 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 21 Aug 2024 17:32:21 +0400 Subject: [PATCH 1 of 6] SSL: moved certificate storage out of SSL_CTX exdata In-Reply-To: <59ac183dfee8e9641563.1721763024@linux> <42e86c051200bf00d9ae.1721763026@linux> <867e05f555e6f593589a.1721763027@linux> <298a9eaa59d2a16f85b6.1721763028@linux> Message-ID: <7qngs6ri5g3bco26p7d4ugaozemrqeoov2yhswzffn7tinkui5@xjzqhe2fmstf> On Tue, Jul 23, 2024 at 07:30:24PM +0000, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762810 0 > # Tue Jul 23 19:26:50 2024 +0000 > # Node ID 59ac183dfee8e9641563e043eb19480d91dd7cc0 > # Parent d1b8568f3042f6019a2302dda4afbadd051fe54b > SSL: moved certificate storage out of SSL_CTX exdata. > > Certain SSL objects (certificates, certificate names, and OCSP staples) are now > accessed with ngx_array_t and ngx_rbtree_t instead of cross-linking the objects > using SSL_CTX exdata. This allows sharing these objects between SSL contexts. > > 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 > @@ -131,10 +131,7 @@ int ngx_ssl_server_conf_index; > int ngx_ssl_session_cache_index; > int ngx_ssl_ticket_keys_index; > int ngx_ssl_ocsp_index; > -int ngx_ssl_certificate_index; > -int ngx_ssl_next_certificate_index; > -int ngx_ssl_certificate_name_index; > -int ngx_ssl_stapling_index; > +int ngx_ssl_index; > > > ngx_int_t > @@ -258,34 +255,11 @@ ngx_ssl_init(ngx_log_t *log) > return NGX_ERROR; > } > > - ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, > - NULL); > - if (ngx_ssl_certificate_index == -1) { > - ngx_ssl_error(NGX_LOG_ALERT, log, 0, > - "SSL_CTX_get_ex_new_index() failed"); > - return NGX_ERROR; > - } > - > - ngx_ssl_next_certificate_index = X509_get_ex_new_index(0, NULL, NULL, NULL, > - NULL); > - if (ngx_ssl_next_certificate_index == -1) { > - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); > - return NGX_ERROR; > - } > - > - ngx_ssl_certificate_name_index = X509_get_ex_new_index(0, NULL, NULL, NULL, > - NULL); > - > - if (ngx_ssl_certificate_name_index == -1) { > - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); > - return NGX_ERROR; > - } > - > - ngx_ssl_stapling_index = X509_get_ex_new_index(0, NULL, NULL, NULL, NULL); > - > - if (ngx_ssl_stapling_index == -1) { > - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); > - return NGX_ERROR; > + ngx_ssl_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); > + if (ngx_ssl_index == -1) { > + ngx_ssl_error(NGX_LOG_ALERT, log, 0, > + "SSL_CTX_get_ex_new_index() failed"); > + return NGX_ERROR; bad ident > } > > return NGX_OK; > @@ -308,12 +282,18 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_ > return NGX_ERROR; > } > > - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, NULL) == 0) { > + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_index, ssl) == 0) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "SSL_CTX_set_ex_data() failed"); > return NGX_ERROR; > } > > + ngx_rbtree_init(&ssl->name_rbtree, &ssl->name_sentinel, > + ngx_rbtree_insert_value); This part converts stored certificate names from certificate ex_data to per-SSL context's rbtree, but it is a useless change. Although using rbtree may ensure to keep the correct reference to cert->data for the given SSL context, yet certificate name is a static string, so keeping references either per SSL context or in the certificate itself doesn't alter behaviour because referenced strings have the same content, but expands ngx_ssl_t unnecessarily (and the relevant codebase). Here I restored ngx_ssl_certificate_name_index. The relevant diff is below (to simplify review). # HG changeset patch # User Sergey Kandaurov # Date 1724174931 -14400 # Tue Aug 20 21:28:51 2024 +0400 # Node ID 3b62f8c36298439b7acb668885d8f8d5a98592b6 # Parent bb017f29c004079bdc321fcd43bb9831e87ea245 imported patch index_name 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 @@ -131,6 +131,7 @@ int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_ticket_keys_index; int ngx_ssl_ocsp_index; +int ngx_ssl_certificate_name_index; int ngx_ssl_index; @@ -256,12 +257,21 @@ ngx_ssl_init(ngx_log_t *log) } ngx_ssl_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (ngx_ssl_index == -1) { ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_CTX_get_ex_new_index() failed"); return NGX_ERROR; } + ngx_ssl_certificate_name_index = X509_get_ex_new_index(0, NULL, NULL, NULL, + NULL); + + if (ngx_ssl_certificate_name_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); + return NGX_ERROR; + } + return NGX_OK; } @@ -462,6 +472,15 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ return NGX_ERROR; } + if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + name = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_name_t)); if (name == NULL) { X509_free(x509); 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 @@ -345,6 +345,7 @@ extern int ngx_ssl_session_cache_index; extern int ngx_ssl_ticket_keys_index; extern int ngx_ssl_ocsp_index; extern int ngx_ssl_index; +extern int ngx_ssl_certificate_name_index; #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -261,6 +261,8 @@ ngx_ssl_stapling_certificate(ngx_conf_t staple->timeout = 60000; staple->verify = verify; staple->cert = cert; + staple->name = X509_get_ex_data(staple->cert, + ngx_ssl_certificate_name_index); name = ngx_ssl_stapling_lookup_name(&ssl->name_rbtree, cert); if (name == NULL) { # HG changeset patch # User Sergey Kandaurov # Date 1724174965 -14400 # Tue Aug 20 21:29:25 2024 +0400 # Node ID 30307c9e9625b8dc230ce0ef6701a1f47a9760a5 # Parent 3b62f8c36298439b7acb668885d8f8d5a98592b6 imported patch name_rbtree 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 @@ -298,9 +298,6 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_ return NGX_ERROR; } - ngx_rbtree_init(&ssl->name_rbtree, &ssl->name_sentinel, - ngx_rbtree_insert_value); - ngx_rbtree_init(&ssl->staple_rbtree, &ssl->staple_sentinel, ngx_rbtree_insert_value); @@ -451,7 +448,6 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ X509 *x509, **elm; EVP_PKEY *pkey; STACK_OF(X509) *chain; - ngx_ssl_name_t *name; x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); if (x509 == NULL) { @@ -481,13 +477,6 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ return NGX_ERROR; } - name = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_name_t)); - if (name == NULL) { - X509_free(x509); - sk_X509_pop_free(chain, X509_free); - return NGX_ERROR; - } - if (ssl->certs.elts == NULL) { if (ngx_array_init(&ssl->certs, cf->pool, 16, sizeof(X509 *)) != NGX_OK) @@ -505,12 +494,6 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ return NGX_ERROR; } - name->node.key = (ngx_rbtree_key_t) x509; - name->name.len = cert->len; - name->name.data = cert->data; - - ngx_rbtree_insert(&ssl->name_rbtree, &name->node); - /* * Note that x509 is not freed here, but will be instead freed in * ngx_ssl_cleanup_ctx(). This is because we need to preserve all 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 @@ -86,12 +86,6 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; -typedef struct { - ngx_rbtree_node_t node; - ngx_str_t name; -} ngx_ssl_name_t; - - struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; @@ -99,9 +93,6 @@ struct ngx_ssl_s { ngx_array_t certs; - ngx_rbtree_t name_rbtree; - ngx_rbtree_node_t name_sentinel; - ngx_rbtree_t staple_rbtree; ngx_rbtree_node_t staple_sentinel; }; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -153,8 +153,6 @@ static ngx_int_t ngx_ssl_stapling_issuer ngx_ssl_stapling_t *staple); static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_ssl_stapling_t *staple, ngx_str_t *responder); -static ngx_ssl_name_t *ngx_ssl_stapling_lookup_name(ngx_rbtree_t *rbtree, - X509 *cert); static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, void *data); @@ -225,7 +223,6 @@ ngx_ssl_stapling_certificate(ngx_conf_t ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify) { ngx_int_t rc; - ngx_ssl_name_t *name; ngx_pool_cleanup_t *cln; ngx_ssl_stapling_t *staple; @@ -264,13 +261,6 @@ ngx_ssl_stapling_certificate(ngx_conf_t staple->name = X509_get_ex_data(staple->cert, ngx_ssl_certificate_name_index); - name = ngx_ssl_stapling_lookup_name(&ssl->name_rbtree, cert); - if (name == NULL) { - return NGX_ERROR; - } - - staple->name = name->name.data; - if (file->len) { /* use OCSP response from the file */ @@ -553,33 +543,6 @@ ngx_ssl_stapling_responder(ngx_conf_t *c } -static ngx_ssl_name_t * -ngx_ssl_stapling_lookup_name(ngx_rbtree_t *rbtree, X509 *cert) -{ - ngx_ssl_name_t *n; - ngx_rbtree_key_t key; - ngx_rbtree_node_t *node, *sentinel; - - node = rbtree->root; - sentinel = rbtree->sentinel; - key = (ngx_rbtree_key_t) cert; - - while (node != sentinel) { - - n = ngx_rbtree_data(node, ngx_ssl_name_t, node); - - if (key != node->key) { - node = (key < node->key) ? node->left : node->right; - continue; - } - - return n; - } - - return NULL; -} - - ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) > + > + ngx_rbtree_init(&ssl->staple_rbtree, &ssl->staple_sentinel, > + ngx_rbtree_insert_value); > + > ssl->buffer_size = NGX_SSL_BUFSIZE; > > /* client side options */ > @@ -458,9 +438,10 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > ngx_str_t *key, ngx_array_t *passwords) > { > char *err; > - X509 *x509; > + X509 *x509, **elm; > EVP_PKEY *pkey; > STACK_OF(X509) *chain; > + ngx_ssl_name_t *name; > > x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); > if (x509 == NULL) { > @@ -481,42 +462,46 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > return NGX_ERROR; > } > > - if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data) > - == 0) > - { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); > + name = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_name_t)); > + if (name == NULL) { > X509_free(x509); > sk_X509_pop_free(chain, X509_free); > return NGX_ERROR; > } > > - if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index, > - SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index)) > - == 0) > - { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); > + if (ssl->certs.elts == NULL) { > + if (ngx_array_init(&ssl->certs, cf->pool, 16, sizeof(X509 *)) > + != NGX_OK) > + { > + X509_free(x509); > + sk_X509_pop_free(chain, X509_free); > + return NGX_ERROR; > + } > + } > + > + elm = ngx_array_push(&ssl->certs); > + if (elm == NULL) { > X509_free(x509); > sk_X509_pop_free(chain, X509_free); > return NGX_ERROR; > } > > - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) == 0) { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "SSL_CTX_set_ex_data() failed"); > - X509_free(x509); > - sk_X509_pop_free(chain, X509_free); > - return NGX_ERROR; > - } > + name->node.key = (ngx_rbtree_key_t) x509; > + name->name.len = cert->len; > + name->name.data = cert->data; > + > + ngx_rbtree_insert(&ssl->name_rbtree, &name->node); > > /* > * Note that x509 is not freed here, but will be instead freed in > * ngx_ssl_cleanup_ctx(). This is because we need to preserve all > - * certificates to be able to iterate all of them through exdata > - * (ngx_ssl_certificate_index, ngx_ssl_next_certificate_index), > + * certificates to be able to iterate all of them through ssl->certs, > * while OpenSSL can free a certificate if it is replaced with another > * certificate of the same type. > */ > > + *elm = x509; > + > #ifdef SSL_CTX_set0_chain > > if (SSL_CTX_set0_chain(ssl->ctx, chain) == 0) { > @@ -3820,10 +3805,9 @@ ngx_ssl_session_id_context(ngx_ssl_t *ss > goto failed; > } > > - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); > - cert; > - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) > - { > + for (k = 0; k < ssl->certs.nelts; k++) { > + cert = ((X509 **) ssl->certs.elts)[k]; > + > if (X509_digest(cert, EVP_sha1(), buf, &len) == 0) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "X509_digest() failed"); > @@ -3837,9 +3821,7 @@ ngx_ssl_session_id_context(ngx_ssl_t *ss > } > } > > - if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL > - && certificates != NULL) > - { > + if (ssl->certs.nelts == 0 && certificates != NULL) { > /* > * If certificates are loaded dynamically, we use certificate > * names as specified in the configuration (with variables). > @@ -4851,14 +4833,12 @@ ngx_ssl_cleanup_ctx(void *data) > { > ngx_ssl_t *ssl = data; > > - X509 *cert, *next; > - > - cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); > - > - while (cert) { > - next = X509_get_ex_data(cert, ngx_ssl_next_certificate_index); > - X509_free(cert); > - cert = next; > + X509 *cert; > + ngx_uint_t i; > + > + for (i = 0; i < ssl->certs.nelts; i++) { > + cert = ((X509 **) ssl->certs.elts)[i]; > + X509_free(cert); bad ident > } > > SSL_CTX_free(ssl->ctx); > 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 > @@ -86,10 +86,24 @@ > typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; > > > +typedef struct { > + ngx_rbtree_node_t node; > + ngx_str_t name; > +} ngx_ssl_name_t; > + > + > struct ngx_ssl_s { > SSL_CTX *ctx; > ngx_log_t *log; > size_t buffer_size; > + > + ngx_array_t certs; > + > + ngx_rbtree_t name_rbtree; > + ngx_rbtree_node_t name_sentinel; > + > + ngx_rbtree_t staple_rbtree; > + ngx_rbtree_node_t staple_sentinel; > }; > > > @@ -330,10 +344,7 @@ extern int ngx_ssl_server_conf_index; > extern int ngx_ssl_session_cache_index; > extern int ngx_ssl_ticket_keys_index; > extern int ngx_ssl_ocsp_index; > -extern int ngx_ssl_certificate_index; > -extern int ngx_ssl_next_certificate_index; > -extern int ngx_ssl_certificate_name_index; > -extern int ngx_ssl_stapling_index; > +extern int ngx_ssl_index; > > > #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ > diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c > --- a/src/event/ngx_event_openssl_stapling.c > +++ b/src/event/ngx_event_openssl_stapling.c > @@ -15,6 +15,8 @@ > > > typedef struct { > + ngx_rbtree_node_t node; > + > ngx_str_t staple; > ngx_msec_t timeout; > > @@ -151,9 +153,13 @@ static ngx_int_t ngx_ssl_stapling_issuer > ngx_ssl_stapling_t *staple); > static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, > ngx_ssl_stapling_t *staple, ngx_str_t *responder); > +static ngx_ssl_name_t *ngx_ssl_stapling_lookup_name(ngx_rbtree_t *rbtree, > + X509 *cert); > > static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, > void *data); > +static ngx_ssl_stapling_t *ngx_ssl_stapling_lookup_staple(ngx_rbtree_t *rbtree, > + X509 *cert); > static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple); > static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx); > > @@ -195,12 +201,12 @@ ngx_int_t > ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, > ngx_str_t *responder, ngx_uint_t verify) > { > - X509 *cert; > - > - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); > - cert; > - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) > - { > + X509 *cert; > + ngx_uint_t k; > + > + for (k = 0; k < ssl->certs.nelts; k++) { > + cert = ((X509 **) ssl->certs.elts)[k]; > + > if (ngx_ssl_stapling_certificate(cf, ssl, cert, file, responder, verify) > != NGX_OK) > { > @@ -219,6 +225,7 @@ ngx_ssl_stapling_certificate(ngx_conf_t > ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify) > { > ngx_int_t rc; > + ngx_ssl_name_t *name; > ngx_pool_cleanup_t *cln; > ngx_ssl_stapling_t *staple; > > @@ -235,10 +242,8 @@ ngx_ssl_stapling_certificate(ngx_conf_t > cln->handler = ngx_ssl_stapling_cleanup; > cln->data = staple; > > - if (X509_set_ex_data(cert, ngx_ssl_stapling_index, staple) == 0) { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); > - return NGX_ERROR; > - } > + staple->node.key = (ngx_rbtree_key_t) cert; > + ngx_rbtree_insert(&ssl->staple_rbtree, &staple->node); > > #ifdef SSL_CTRL_SELECT_CURRENT_CERT > /* OpenSSL 1.0.2+ */ > @@ -256,8 +261,13 @@ ngx_ssl_stapling_certificate(ngx_conf_t > staple->timeout = 60000; > staple->verify = verify; > staple->cert = cert; > - staple->name = X509_get_ex_data(staple->cert, > - ngx_ssl_certificate_name_index); > + > + name = ngx_ssl_stapling_lookup_name(&ssl->name_rbtree, cert); > + if (name == NULL) { > + return NGX_ERROR; > + } > + > + staple->name = name->name.data; > > if (file->len) { > /* use OCSP response from the file */ > @@ -541,18 +551,52 @@ ngx_ssl_stapling_responder(ngx_conf_t *c > } > > > +static ngx_ssl_name_t * > +ngx_ssl_stapling_lookup_name(ngx_rbtree_t *rbtree, X509 *cert) > +{ > + ngx_ssl_name_t *n; > + ngx_rbtree_key_t key; > + ngx_rbtree_node_t *node, *sentinel; > + > + node = rbtree->root; > + sentinel = rbtree->sentinel; > + key = (ngx_rbtree_key_t) cert; > + > + while (node != sentinel) { > + > + n = ngx_rbtree_data(node, ngx_ssl_name_t, node); > + > + if (key != node->key) { > + node = (key < node->key) ? node->left : node->right; > + continue; > + } > + > + return n; > + } > + > + return NULL; > +} > + > + > ngx_int_t > ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, > ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) > { > - X509 *cert; > + ngx_rbtree_t *tree; > + ngx_rbtree_node_t *node; > ngx_ssl_stapling_t *staple; > > - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); > - cert; > - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) > + tree = &ssl->staple_rbtree; > + > + if (tree->root == tree->sentinel) { > + return NGX_OK; > + } > + > + for (node = ngx_rbtree_min(tree->root, tree->sentinel); > + node; > + node = ngx_rbtree_next(tree, node)) > { > - staple = X509_get_ex_data(cert, ngx_ssl_stapling_index); > + staple = (ngx_ssl_stapling_t *) node; > staple->resolver = resolver; > staple->resolver_timeout = resolver_timeout; > } > @@ -567,6 +611,8 @@ ngx_ssl_certificate_status_callback(ngx_ > int rc; > X509 *cert; > u_char *p; > + SSL_CTX *ssl_ctx; > + ngx_ssl_t *ssl; > ngx_connection_t *c; > ngx_ssl_stapling_t *staple; > > @@ -583,7 +629,19 @@ ngx_ssl_certificate_status_callback(ngx_ > return rc; > } > > - staple = X509_get_ex_data(cert, ngx_ssl_stapling_index); > + ssl_ctx = SSL_get_SSL_CTX(ssl_conn); > + > + if (ssl_ctx == NULL) { > + return rc; > + } > + > + ssl = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_index); > + > + if (ssl == NULL) { > + return rc; > + } the added checks should never fail > + > + staple = ngx_ssl_stapling_lookup_staple(&ssl->staple_rbtree, cert); > > if (staple == NULL) { > return rc; > @@ -613,6 +671,33 @@ ngx_ssl_certificate_status_callback(ngx_ > } > > > +static ngx_ssl_stapling_t * > +ngx_ssl_stapling_lookup_staple(ngx_rbtree_t *rbtree, X509 *cert) > +{ > + ngx_rbtree_key_t key; > + ngx_rbtree_node_t *node, *sentinel; > + ngx_ssl_stapling_t *n; > + > + node = rbtree->root; > + sentinel = rbtree->sentinel; > + key = (ngx_rbtree_key_t) cert; > + > + while (node != sentinel) { > + > + n = ngx_rbtree_data(node, ngx_ssl_stapling_t, node); > + > + if (key != node->key) { > + node = (key < node->key) ? node->left : node->right; > + continue; > + } > + > + return n; > + } > + > + return NULL; > +} > + > + > static void > ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple) > { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel On Mon, Aug 12, 2024 at 01:52:31PM -0700, Aleksei Bavshin wrote: > On 7/23/2024 12:30 PM, Mini Hawthorne wrote: > > # HG changeset patch > > # User Mini Hawthorne > > # Date 1721762842 0 > > # Tue Jul 23 19:27:22 2024 +0000 > > # Node ID 8eee61e223bb7cb7475e50b866fd6b9a83fa5fa0 > > # Parent 59ac183dfee8e9641563e043eb19480d91dd7cc0 > > SSL: object caching. > > > > Added ngx_openssl_cache_module, which indexes a type-aware object cache. > > It maps an id to a unique instance, and provides references to it, which > > are dropped when the cycle's pool is destroyed. Also, for those objects > > that can be cached, valid references may be pulled from cycle->old_cycle. Although they may, they don't. I tend to drop the last sentence for clarity. > > > > The cache will be used in subsequent patches. > > > > diff --git a/auto/modules b/auto/modules > > --- a/auto/modules > > +++ b/auto/modules > > @@ -1307,10 +1307,11 @@ fi > > if [ $USE_OPENSSL = YES ]; then > > ngx_module_type=CORE > > - ngx_module_name=ngx_openssl_module > > + ngx_module_name="ngx_openssl_module ngx_openssl_cache_module" > > ngx_module_incs= > > ngx_module_deps=src/event/ngx_event_openssl.h > > ngx_module_srcs="src/event/ngx_event_openssl.c > > + src/event/ngx_event_openssl_cache.c > > src/event/ngx_event_openssl_stapling.c" > > ngx_module_libs= > > ngx_module_link=YES > > 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 > > @@ -83,7 +83,8 @@ > > #endif > > -typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; > > +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; > > +typedef struct ngx_ssl_cache_type_s ngx_ssl_cache_type_t; Although a lot of type exposure was fixed during internal review, this unnecessarily exposes a new type, which can be made local to ngx_event_openssl_cache.c after SSL object cache API adjustment. See below. > > typedef struct { > > @@ -233,6 +234,9 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_co > > void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); > > ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); > > +void *ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, > > + ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data); > > + > > ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); > > ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, > > ngx_array_t *passwords); > > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > > new file mode 100644 > > --- /dev/null > > +++ b/src/event/ngx_event_openssl_cache.c > > @@ -0,0 +1,277 @@ > > + > > +/* > > + * Copyright (C) Nginx, Inc. > > + */ > > + > > + > > +#include > > +#include > > +#include > > + > > + > > +typedef struct { > > + ngx_rbtree_node_t node; > > + ngx_str_t id; > > + > > + ngx_ssl_cache_type_t *type; > > + void *value; > > +} ngx_ssl_cache_node_t; > > + > > + > > +typedef void *(*ngx_ssl_cache_create_pt)(ngx_str_t *id, char **err, void *data); > > +typedef void (*ngx_ssl_cache_free_pt)(void *data); > > +typedef void *(*ngx_ssl_cache_ref_pt)(char **err, void *data); > > + > > + > > +struct ngx_ssl_cache_type_s { > > + const char *name; This field is unused. > > + > > + ngx_ssl_cache_create_pt create; > > + ngx_ssl_cache_free_pt free; > > + ngx_ssl_cache_ref_pt ref; > > +}; > > + > > + > > +typedef struct { > > + ngx_rbtree_t rbtree; > > + ngx_rbtree_node_t sentinel; > > +} ngx_ssl_cache_t; > > + > > + > > +static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); > > +static void ngx_ssl_cache_cleanup(void *data); > > +static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, > > + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); I'd rather move this block used to configure module (and functions) to the end, same as we ought to do in other modules. > > + > > +static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, > > + ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); > > + > > + > > +static ngx_core_module_t ngx_openssl_cache_module_ctx = { > > + ngx_string("openssl_cache"), > > + ngx_openssl_cache_create_conf, > > + NULL > > +}; > > + > > + > > +ngx_module_t ngx_openssl_cache_module = { > > + NGX_MODULE_V1, > > + &ngx_openssl_cache_module_ctx, /* module context */ > > + NULL, /* module directives */ > > + NGX_CORE_MODULE, /* module type */ > > + NULL, /* init master */ > > + NULL, /* init module */ > > + NULL, /* init process */ > > + NULL, /* init thread */ > > + NULL, /* exit thread */ > > + NULL, /* exit process */ > > + NULL, /* exit master */ > > + NGX_MODULE_V1_PADDING > > +}; > > + > > + > > +static void * > > +ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) > > +{ > > + ngx_ssl_cache_t *cache; > > + ngx_pool_cleanup_t *cln; > > + > > + cache = ngx_pcalloc(cycle->pool, sizeof(ngx_ssl_cache_t)); > > + if (cache == NULL) { > > + return NULL; > > + } > > + > > + cln = ngx_pool_cleanup_add(cycle->pool, 0); > > + if (cln == NULL) { > > + return NULL; > > + } > > + > > + cln->handler = ngx_ssl_cache_cleanup; > > + cln->data = cache; > > + > > + ngx_rbtree_init(&cache->rbtree, &cache->sentinel, > > + ngx_ssl_cache_node_insert); > > + > > + return cache; > > +} > > + > > + > > +static void > > +ngx_ssl_cache_cleanup(void *data) > > +{ > > + ngx_ssl_cache_t *cache = data; > > + > > + ngx_rbtree_t *tree; > > + ngx_rbtree_node_t *node; > > + ngx_ssl_cache_node_t *cn; > > + > > + tree = &cache->rbtree; > > + > > + if (tree->root == tree->sentinel) { > > + return; > > + } > > + > > + for (node = ngx_rbtree_min(tree->root, tree->sentinel); > > + node; > > + node = ngx_rbtree_next(tree, node)) > > + { > > + cn = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); > > + > > + if (cn->type != NULL && cn->value != NULL) { > > + cn->type->free(cn->value); > > + } > > + } > > +} > > + > > + > > +static void > > +ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, > > + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) > > +{ > > + ngx_rbtree_node_t **p; > > + ngx_ssl_cache_node_t *n, *t; > > + > > + for ( ;; ) { > > + > > + n = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); > > + t = ngx_rbtree_data(temp, ngx_ssl_cache_node_t, node); > > + > > + if (node->key != temp->key) { > > + > > + p = (node->key < temp->key) ? &temp->left : &temp->right; > > + > > + } else if (n->type != t->type) { > > + > > + p = (n->type < t->type) ? &temp->left : &temp->right; > > + > > + } else { > > + > > + p = (ngx_memn2cmp(n->id.data, t->id.data, n->id.len, t->id.len) > > + < 0) ? &temp->left : &temp->right; > > + } > > + > > + if (*p == sentinel) { > > + break; > > + } > > + > > + temp = *p; > > + } > > + > > + *p = node; > > + node->parent = temp; > > + node->left = sentinel; > > + node->right = sentinel; > > + ngx_rbt_red(node); > > +} > > + > > + > > +void * > > +ngx_ssl_cache_fetch(ngx_cycle_t *cycle, ngx_pool_t *pool, > > + ngx_ssl_cache_type_t *type, char **err, ngx_str_t *id, void *data) > > +{ > > + void *value; > > + uint32_t hash; > > + ngx_ssl_cache_t *cache; > > + ngx_ssl_cache_node_t *cn; > > + > > + value = NULL; > > + > > + hash = ngx_murmur_hash2(id->data, id->len); > > + > > + cache = (ngx_ssl_cache_t *) ngx_get_conf(cycle->conf_ctx, > > + ngx_openssl_cache_module); > > + > > + if (ngx_process == NGX_PROCESS_WORKER > > + || ngx_process == NGX_PROCESS_SINGLE) > > + { > > + return type->create(id, err, data); > > + } > > This check kept bothering me, so I figured we can try to improve it. > > The logic for configuration time and per-connection fetches is quite > different and we're always aware of the context we're working in. It would > be cleaner to have separate fetch implementations, like that: > > void * > ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, char > **err, > ngx_str_t *id, void *data) > { > ... > } > > > void * > ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, > ngx_str_t *id, void *data) > { > return type->create(id, err, data); > } > > Full diff is attached, but it needs to be properly split between patches > 2-6. > > As a bonus, the change makes it easier to isolate any future logic for > dynamic certificate caching. It was noticeable on our review of early > prototypes how combining both code paths in the `ngx_ssl_cache_fetch` code > made the function unnecessarily complicated. I concur, applied. This also enables SSL object caching to actually work. The reason is that ngx_ssl_cache_fetch() is called during configuration parsing, which means that ngx_process initial value NGX_PROCESS_SINGLE is not yet updated. While here, rewritten fetch API to eliminate ngx_ssl_cache_type_t exposure. Previously it was used to index a type-specific object cache, now it is replaced with a pure index. The relevant change below, it reverts parts of this 2nd patch. (Applying it makes it meaningful to qfold the next change as well, due to non-sensical ngx_ssl_cache_types[] as is.) I will send the updates patch series separately after commenting the rest. # HG changeset patch # User Sergey Kandaurov # Date 1724175777 -14400 # Tue Aug 20 21:42:57 2024 +0400 # Node ID c0fb7ecae98b28b3220a70a62d0cdf08dd240b74 # Parent c1b4471b3ecb4407a41b123f85c34f73db3d32ad imported patch api_cln 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 @@ -83,8 +83,7 @@ #endif -typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; -typedef struct ngx_ssl_cache_type_s ngx_ssl_cache_type_t; +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; struct ngx_ssl_s { @@ -225,10 +224,10 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_co void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); -void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, - char **err, ngx_str_t *id, void *data); -void *ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, - char **err, ngx_str_t *id, void *data); +void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, + ngx_str_t *id, void *data); +void *ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, + ngx_str_t *id, void *data); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -9,6 +9,18 @@ #include +typedef void *(*ngx_ssl_cache_create_pt)(ngx_str_t *id, char **err, void *data); +typedef void (*ngx_ssl_cache_free_pt)(void *data); +typedef void *(*ngx_ssl_cache_ref_pt)(char **err, void *data); + + +typedef struct { + ngx_ssl_cache_create_pt create; + ngx_ssl_cache_free_pt free; + ngx_ssl_cache_ref_pt ref; +} ngx_ssl_cache_type_t; + + typedef struct { ngx_rbtree_node_t node; ngx_str_t id; @@ -18,18 +30,6 @@ typedef struct { } ngx_ssl_cache_node_t; -typedef void *(*ngx_ssl_cache_create_pt)(ngx_str_t *id, char **err, void *data); -typedef void (*ngx_ssl_cache_free_pt)(void *data); -typedef void *(*ngx_ssl_cache_ref_pt)(char **err, void *data); - - -struct ngx_ssl_cache_type_s { - ngx_ssl_cache_create_pt create; - ngx_ssl_cache_free_pt free; - ngx_ssl_cache_ref_pt ref; -}; - - typedef struct { ngx_rbtree_t rbtree; ngx_rbtree_node_t sentinel; @@ -68,13 +68,19 @@ ngx_module_t ngx_openssl_cache_module = }; +static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { + +}; + + void * -ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, char **err, +ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_str_t *id, void *data) { void *value; uint32_t hash; ngx_ssl_cache_t *cache; + ngx_ssl_cache_type_t *type; ngx_ssl_cache_node_t *cn; value = NULL; @@ -84,6 +90,8 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ cache = (ngx_ssl_cache_t *) ngx_get_conf(cf->cycle->conf_ctx, ngx_openssl_cache_module); + type = &ngx_ssl_cache_types[index]; + cn = ngx_ssl_cache_lookup(cache, type, id, hash); if (cn == NULL) { @@ -123,10 +131,10 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ void * -ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, +ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, ngx_str_t *id, void *data) { - return type->create(id, err, data); + return ngx_ssl_cache_types[index].create(id, err, data); } > > > + > > + cn = ngx_ssl_cache_lookup(cache, type, id, hash); > > + > > + if (cn == NULL) { > > + cn = ngx_palloc(pool, sizeof(ngx_ssl_cache_node_t) + id->len + 1); > > + if (cn == NULL) { > > + return NULL; > > + } > > + > > + cn->node.key = hash; > > + cn->id.data = (u_char *)(cn + 1); > > + cn->id.len = id->len; > > + cn->type = type; > > + cn->value = NULL; > > + > > + ngx_cpystrn(cn->id.data, id->data, id->len + 1); > > + > > + ngx_rbtree_insert(&cache->rbtree, &cn->node); > > + } > > + > > + /* try to use a reference from the cache */ > > + > > + if (cn->value != NULL) { > > + value = type->ref(err, cn->value); > > + } > > + > > + if (value == NULL) { > > + value = type->create(id, err, data); > > + } > > + > > + if (value != NULL && cn->value == NULL) { > > + /* we have a value and the node needs one; try to reference it */ > > + cn->value = type->ref(err, value); > > + } > > + > > + return value; > > +} > > + > > + > > +static ngx_ssl_cache_node_t * > > +ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, > > + ngx_str_t *id, uint32_t hash) > > +{ > > + ngx_int_t rc; > > + ngx_rbtree_node_t *node, *sentinel; > > + ngx_ssl_cache_node_t *cn; > > + > > + node = cache->rbtree.root; > > + sentinel = cache->rbtree.sentinel; > > + > > + while (node != sentinel) { > > + > > + if (hash < node->key) { > > + node = node->left; > > + continue; > > + } > > + > > + if (hash > node->key) { > > + node = node->right; > > + continue; > > + } > > + > > + /* hash == node->key */ > > + > > + cn = (ngx_ssl_cache_node_t *) node; > > + > > + if ((ngx_uint_t) type < (ngx_uint_t) cn->type) { > > + node = node->left; > > + continue; > > + } > > + > > + if ((ngx_uint_t) type > (ngx_uint_t) cn->type) { > > + node = node->right; > > + continue; > > + } > > + > > + /* type == cn->type */ > > + > > + rc = ngx_memn2cmp(id->data, cn->id.data, id->len, cn->id.len); > > + > > + if (rc == 0) { > > + return cn; > > + } > > + > > + node = (rc < 0) ? node->left : node->right; > > + } > > + > > + return NULL; > > +} > > _______________________________________________ > > nginx-devel mailing list > > nginx-devel at nginx.org > > https://mailman.nginx.org/mailman/listinfo/nginx-devel On Tue, Jul 23, 2024 at 07:30:26PM +0000, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762857 0 > # Tue Jul 23 19:27:37 2024 +0000 > # Node ID 42e86c051200bf00d9ae6e38d6c87a916391b642 > # Parent 8eee61e223bb7cb7475e50b866fd6b9a83fa5fa0 > SSL: caching certificates. > > Added ngx_ssl_cache_cert, which loads certificate chains once via BIO's > created by ngx_ssl_cache_create_bio() which will be used by the following > patches as well. > > The certificate cache provides each chain as a unique stack of shared > references. This shallow copy is required because OpenSSL's stacks aren't > reference counted; instead they contain a unique array of referenced entries. > Also note that callers must pop the first certificate off of the stack due to > awkwardness in OpenSSL certificate API. > > 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 > @@ -18,8 +18,6 @@ typedef struct { > } ngx_openssl_conf_t; > > > -static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, > - ngx_str_t *cert, STACK_OF(X509) **chain); > static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > ngx_str_t *key, ngx_array_t *passwords); > static int ngx_ssl_password_callback(char *buf, int size, int rwflag, > @@ -443,8 +441,9 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > STACK_OF(X509) *chain; > ngx_ssl_name_t *name; > > - x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); > - if (x509 == NULL) { > + chain = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_cert, > + &err, cert, NULL); This is logically not an allocating function, so its check(s) should be separated with an empty line, also to be in line wrt a nearby style consistency for SSL_CTX_get_cert_store() in p6. > + if (chain == NULL) { > if (err != NULL) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "cannot load certificate \"%s\": %s", > @@ -454,6 +453,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > return NGX_ERROR; > } > > + x509 = sk_X509_shift(chain); > + > if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "SSL_CTX_use_certificate(\"%s\") failed", cert->data); > @@ -568,8 +569,9 @@ ngx_ssl_connection_certificate(ngx_conne > EVP_PKEY *pkey; > STACK_OF(X509) *chain; > > - x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain); > - if (x509 == NULL) { > + chain = ngx_ssl_cache_fetch((ngx_cycle_t *) ngx_cycle, c->pool, > + &ngx_ssl_cache_cert, &err, cert, NULL); > + if (chain == NULL) { > if (err != NULL) { > ngx_ssl_error(NGX_LOG_ERR, c->log, 0, > "cannot load certificate \"%s\": %s", > @@ -579,6 +581,8 @@ ngx_ssl_connection_certificate(ngx_conne > return NGX_ERROR; > } > > + x509 = sk_X509_shift(chain); > + > if (SSL_use_certificate(c->ssl->connection, x509) == 0) { > ngx_ssl_error(NGX_LOG_ERR, c->log, 0, > "SSL_use_certificate(\"%s\") failed", cert->data); > @@ -630,96 +634,6 @@ ngx_ssl_connection_certificate(ngx_conne > } > > > -static X509 * > -ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, > - STACK_OF(X509) **chain) > -{ > - BIO *bio; > - X509 *x509, *temp; > - u_long n; > - > - if (ngx_strncmp(cert->data, "data:", sizeof("data:") - 1) == 0) { > - > - bio = BIO_new_mem_buf(cert->data + sizeof("data:") - 1, > - cert->len - (sizeof("data:") - 1)); > - if (bio == NULL) { > - *err = "BIO_new_mem_buf() failed"; > - return NULL; > - } > - > - } else { > - > - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) > - != NGX_OK) > - { > - *err = NULL; > - return NULL; > - } > - > - bio = BIO_new_file((char *) cert->data, "r"); > - if (bio == NULL) { > - *err = "BIO_new_file() failed"; > - return NULL; > - } > - } > - > - /* certificate itself */ > - > - x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); > - if (x509 == NULL) { > - *err = "PEM_read_bio_X509_AUX() failed"; > - BIO_free(bio); > - return NULL; > - } > - > - /* rest of the chain */ > - > - *chain = sk_X509_new_null(); > - if (*chain == NULL) { > - *err = "sk_X509_new_null() failed"; > - BIO_free(bio); > - X509_free(x509); > - return NULL; > - } > - > - for ( ;; ) { > - > - temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); > - if (temp == NULL) { > - n = ERR_peek_last_error(); > - > - if (ERR_GET_LIB(n) == ERR_LIB_PEM > - && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) > - { > - /* end of file */ > - ERR_clear_error(); > - break; > - } > - > - /* some real error */ > - > - *err = "PEM_read_bio_X509() failed"; > - BIO_free(bio); > - X509_free(x509); > - sk_X509_pop_free(*chain, X509_free); > - return NULL; > - } > - > - if (sk_X509_push(*chain, temp) == 0) { > - *err = "sk_X509_push() failed"; > - BIO_free(bio); > - X509_free(x509); > - sk_X509_pop_free(*chain, X509_free); > - return NULL; > - } > - } > - > - BIO_free(bio); > - > - return x509; > -} > - > - > static EVP_PKEY * > ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > ngx_str_t *key, ngx_array_t *passwords) > 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 > @@ -351,4 +351,7 @@ extern int ngx_ssl_ocsp_index; > extern int ngx_ssl_index; > > > +extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; Throughout the series, this is replaced with a corresponding macro. The change on top of the series would be (posted for review clarity): 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 @@ -445,7 +445,7 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ EVP_PKEY *pkey; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_cert, &err, cert, NULL); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -535,7 +535,7 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ } #endif - pkey = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_key, &err, key, passwords); + pkey = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_KEY, &err, key, passwords); if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -568,7 +568,7 @@ ngx_ssl_connection_certificate(ngx_conne EVP_PKEY *pkey; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_connection_fetch(&ngx_ssl_cache_cert, &err, cert, + chain = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_CERT, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { @@ -609,7 +609,7 @@ ngx_ssl_connection_certificate(ngx_conne #endif - pkey = ngx_ssl_cache_connection_fetch(&ngx_ssl_cache_key, &err, key, + pkey = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_KEY, &err, key, passwords); if (pkey == NULL) { if (err != NULL) { @@ -686,7 +686,7 @@ ngx_ssl_client_certificate(ngx_conf_t *c return NGX_ERROR; } - chain = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_ca, &err, cert, NULL); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -799,7 +799,7 @@ ngx_ssl_trusted_certificate(ngx_conf_t * return NGX_ERROR; } - chain = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_ca, &err, cert, NULL); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -866,7 +866,7 @@ ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *s return NGX_ERROR; } - chain = ngx_ssl_cache_fetch(cf, &ngx_ssl_cache_crl, &err, crl, NULL); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CRL, &err, crl, NULL); if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, 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 @@ -194,6 +193,12 @@ typedef struct { #define NGX_SSL_BUFSIZE 16384 +#define NGX_SSL_CACHE_CERT 0 +#define NGX_SSL_CACHE_CRL 1 +#define NGX_SSL_CACHE_KEY 2 +#define NGX_SSL_CACHE_CA 3 + + ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); @@ -345,10 +350,4 @@ extern int ngx_ssl_index; extern int ngx_ssl_certificate_name_index; -extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; -extern ngx_ssl_cache_type_t ngx_ssl_cache_crl; -extern ngx_ssl_cache_type_t ngx_ssl_cache_key; -extern ngx_ssl_cache_type_t ngx_ssl_cache_ca; - - #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -86,41 +86,38 @@ ngx_module_t ngx_openssl_cache_module = }; -ngx_ssl_cache_type_t ngx_ssl_cache_cert = { - ngx_ssl_cache_cert_create, - ngx_ssl_cache_cert_free, - ngx_ssl_cache_cert_ref, -}; +static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { - -ngx_ssl_cache_type_t ngx_ssl_cache_crl = { - ngx_ssl_cache_crl_create, - ngx_ssl_cache_crl_free, - ngx_ssl_cache_crl_ref, -}; + /* NGX_SSL_CACHE_CERT */ + { ngx_ssl_cache_cert_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref }, + /* NGX_SSL_CACHE_CRT */ + { ngx_ssl_cache_crl_create, + ngx_ssl_cache_crl_free, + ngx_ssl_cache_crl_ref }, -ngx_ssl_cache_type_t ngx_ssl_cache_key = { - ngx_ssl_cache_key_create, - ngx_ssl_cache_key_free, - ngx_ssl_cache_key_ref, -}; + /* NGX_SSL_CACHE_KEY */ + { ngx_ssl_cache_key_create, + ngx_ssl_cache_key_free, + ngx_ssl_cache_key_ref }, - -ngx_ssl_cache_type_t ngx_ssl_cache_ca = { - ngx_ssl_cache_ca_create, - ngx_ssl_cache_cert_free, - ngx_ssl_cache_cert_ref, + /* NGX_SSL_CACHE_CA */ + { ngx_ssl_cache_ca_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref } }; void * -ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ssl_cache_type_t *type, char **err, +ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_str_t *id, void *data) { void *value; uint32_t hash; ngx_ssl_cache_t *cache; + ngx_ssl_cache_type_t *type; ngx_ssl_cache_node_t *cn; value = NULL; @@ -130,6 +127,8 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ cache = (ngx_ssl_cache_t *) ngx_get_conf(cf->cycle->conf_ctx, ngx_openssl_cache_module); + type = &ngx_ssl_cache_types[index]; + cn = ngx_ssl_cache_lookup(cache, type, id, hash); if (cn == NULL) { @@ -169,10 +168,10 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_ void * -ngx_ssl_cache_connection_fetch(ngx_ssl_cache_type_t *type, char **err, +ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, ngx_str_t *id, void *data) { - return type->create(id, err, data); + return ngx_ssl_cache_types[index].create(id, err, data); } > + > + > #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > --- a/src/event/ngx_event_openssl_cache.c > +++ b/src/event/ngx_event_openssl_cache.c > @@ -46,6 +46,12 @@ static void ngx_ssl_cache_node_insert(ng > static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, > ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); > > +static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); > +static void ngx_ssl_cache_cert_free(void *data); > +static void *ngx_ssl_cache_cert_ref(char **err, void *data); > + > +static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); > + > > static ngx_core_module_t ngx_openssl_cache_module_ctx = { > ngx_string("openssl_cache"), > @@ -54,6 +60,15 @@ static ngx_core_module_t ngx_openssl_ca > }; > > > +ngx_ssl_cache_type_t ngx_ssl_cache_cert = { > + "certificate chain", > + > + ngx_ssl_cache_cert_create, > + ngx_ssl_cache_cert_free, > + ngx_ssl_cache_cert_ref, > +}; > + > + This is ought to be placed after module variables (but see above). > ngx_module_t ngx_openssl_cache_module = { > NGX_MODULE_V1, > &ngx_openssl_cache_module_ctx, /* module context */ > @@ -275,3 +290,165 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca > > return NULL; > } > + > + > +static void * > +ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) > +{ > + BIO *bio; > + X509 *x; > + u_long n; > + STACK_OF(X509) *sk; Style: thoughout nginx codebase, variables of such OpenSSL types are used to have a different consistent naming, such as "x509" and "chain". Applied to my series, to be posted separately. > + > + /* start with an empty certificate chain */ While here, rearranged comments. > + sk = sk_X509_new_null(); > + if (sk == NULL) { > + *err = "sk_X509_new_null() failed"; > + return NULL; > + } > + > + /* figure out where to load from */ > + bio = ngx_ssl_cache_create_bio(id, err); > + if (bio == NULL) { > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + > + /* certificate itself */ > + x = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); > + if (x == NULL) { > + *err = "PEM_read_bio_X509_AUX() failed"; > + BIO_free(bio); > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + > + if (sk_X509_push(sk, x) <= 0) { > + *err = "sk_X509_push() failed"; > + BIO_free(bio); > + X509_free(x); > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + > + /* rest of the chain */ > + for ( ;; ) { > + > + x = PEM_read_bio_X509(bio, NULL, NULL, NULL); > + if (x == NULL) { > + n = ERR_peek_last_error(); > + > + if (ERR_GET_LIB(n) == ERR_LIB_PEM > + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) > + { > + /* end of file */ > + ERR_clear_error(); > + break; > + } > + > + /* some real error */ > + *err = "PEM_read_bio_X509() failed"; > + BIO_free(bio); > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + > + if (sk_X509_push(sk, x) <= 0) { X509 object leak on error path > + /* memory allocation failed */ > + *err = "sk_X509_push() failed"; > + BIO_free(bio); > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + } > + > + BIO_free(bio); > + return sk; > +} > + > + > +static void > +ngx_ssl_cache_cert_free(void *data) > +{ > + sk_X509_pop_free(data, X509_free); > +} > + > + > +static void * > +ngx_ssl_cache_cert_ref(char **err, void *data) > +{ > + int n, i; > + X509 *x; > + STACK_OF(X509) *sk; > + > + /* stacks aren't reference-counted, so shallow copy into a new stack */ > + sk = sk_X509_dup(data); > + if (sk == NULL) { > + *err = "sk_X509_dup() failed"; > + return NULL; > + } > + > + /* bump the certificates' reference counts */ > + n = sk_X509_num(sk); > + > + for (i = 0; i < n; i++) { > + x = sk_X509_value(sk, i); > + > +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) > + X509_up_ref(x); > +#else > + CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509); > +#endif > + } > + > + return sk; > +} > + > + > +static BIO * > +ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) > +{ > + BIO *bio; > + ngx_str_t path; > + ngx_pool_t *temp_pool; > + > + if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { > + bio = BIO_new_mem_buf(id->data + sizeof("data:") - 1, > + id->len - (sizeof("data:") - 1)); > + > + if (bio == NULL) { > + *err = "BIO_new_mem_buf() failed"; > + } > + > + return bio; > + } > + > + /* generate a translated path */ > + temp_pool = ngx_create_pool(NGX_MAX_PATH, ngx_cycle->log); > + if (temp_pool == NULL) { > + *err = NULL; > + return NULL; > + } > + > + ngx_memcpy(&path, id, sizeof(ngx_str_t)); > + > + if (ngx_get_full_name(temp_pool, > + (ngx_str_t *) &ngx_cycle->conf_prefix, > + &path) > + != NGX_OK) > + { > + *err = NULL; > + ngx_destroy_pool(temp_pool); > + return NULL; > + } > + > + bio = BIO_new_file((char *) path.data, "r"); > + > + if (bio == NULL) { > + *err = "BIO_new_file() failed"; > + } > + > + ngx_destroy_pool(temp_pool); > + > + return bio; > +} > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel On Tue, Jul 23, 2024 at 07:30:27PM +0000, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762914 0 > # Tue Jul 23 19:28:34 2024 +0000 > # Node ID 867e05f555e6f593589a0278c865e7dcffe597f4 > # Parent 42e86c051200bf00d9ae6e38d6c87a916391b642 > SSL: caching certificate revocation lists. > > Added ngx_ssl_cache_crl which is similar to certificate caching. > It basically calls X509_CRL versions of APIs instead of X509 versions. > > 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 > @@ -886,17 +886,16 @@ ngx_ssl_trusted_certificate(ngx_conf_t * > ngx_int_t > ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl) > { > - X509_STORE *store; > - X509_LOOKUP *lookup; > + int n, i; > + char *err; > + X509_CRL *xc; > + X509_STORE *store; > + STACK_OF(X509_CRL) *xcsk; > > if (crl->len == 0) { > return NGX_OK; > } > > - if (ngx_conf_full_name(cf->cycle, crl, 1) != NGX_OK) { > - return NGX_ERROR; > - } > - > store = SSL_CTX_get_cert_store(ssl->ctx); > > if (store == NULL) { > @@ -905,20 +904,44 @@ ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *s > return NGX_ERROR; > } > > - lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); > - > - if (lookup == NULL) { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "X509_STORE_add_lookup() failed"); > + xcsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_crl, > + &err, crl, NULL); > + if (xcsk == NULL) { > + if (err != NULL) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "failed to load \"%s\": %s", crl->data, err); > + } > + > return NGX_ERROR; > } > > - if (X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM) > - == 0) > - { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "X509_LOOKUP_load_file(\"%s\") failed", crl->data); > - return NGX_ERROR; > + n = sk_X509_CRL_num(xcsk); > + > + for (i = 0; i < n; i++) { > + xc = sk_X509_CRL_value(xcsk, i); > + > + if (X509_STORE_add_crl(store, xc) != 1) { > + > +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ > + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) > + u_long error; > + > + /* not reported in OpenSSL 1.1.0i+ */ > + > + error = ERR_peek_last_error(); > + > + if (ERR_GET_LIB(error) == ERR_LIB_X509 > + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) > + { > + ERR_clear_error(); > + continue; > + } > +#endif > + > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "X509_STORE_add_crl() failed"); > + return NGX_ERROR; > + } > } This now leaks X509_CRL chain. It is not managed neither by OpenSSL nor referenced with our own SSL object cache, so should be freed explicitly. Note that X509_STORE_add_crl() adds its own refcount. A simple sk_X509_CRL_pop_free() should do the thing. > > X509_STORE_set_flags(store, > 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 > @@ -352,6 +352,7 @@ extern int ngx_ssl_index; > > > extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; > +extern ngx_ssl_cache_type_t ngx_ssl_cache_crl; > > > #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > --- a/src/event/ngx_event_openssl_cache.c > +++ b/src/event/ngx_event_openssl_cache.c > @@ -50,6 +50,10 @@ static void *ngx_ssl_cache_cert_create(n > static void ngx_ssl_cache_cert_free(void *data); > static void *ngx_ssl_cache_cert_ref(char **err, void *data); > > +static void *ngx_ssl_cache_crl_create(ngx_str_t *id, char **err, void *data); > +static void ngx_ssl_cache_crl_free(void *data); > +static void *ngx_ssl_cache_crl_ref(char **err, void *data); > + > static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); > > > @@ -69,6 +73,15 @@ ngx_ssl_cache_type_t ngx_ssl_cache_cert > }; > > > +ngx_ssl_cache_type_t ngx_ssl_cache_crl = { > + "certificate revocation list", > + > + ngx_ssl_cache_crl_create, > + ngx_ssl_cache_crl_free, > + ngx_ssl_cache_crl_ref, > +}; > + > + > ngx_module_t ngx_openssl_cache_module = { > NGX_MODULE_V1, > &ngx_openssl_cache_module_ctx, /* module context */ > @@ -405,6 +418,96 @@ ngx_ssl_cache_cert_ref(char **err, void > } > > > +static void * > +ngx_ssl_cache_crl_create(ngx_str_t *id, char **err, void *data) > +{ > + BIO *bio; > + u_long n; > + X509_CRL *xc; > + STACK_OF(X509_CRL) *sk; > + > + /* start with an empty revocation list */ > + sk = sk_X509_CRL_new_null(); > + if (sk == NULL) { > + *err = "sk_X509_CRL_new_null() failed"; > + return NULL; > + } > + > + /* figure out where to load from */ > + bio = ngx_ssl_cache_create_bio(id, err); > + if (bio == NULL) { > + sk_X509_CRL_pop_free(sk, X509_CRL_free); > + return NULL; > + } > + > + /* read all of the revocations */ > + while ((xc = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL)) != NULL) { > + if (sk_X509_CRL_push(sk, xc) <= 0) { > + *err = "sk_X509_CRL_push() failed"; > + BIO_free(bio); > + sk_X509_CRL_pop_free(sk, X509_CRL_free); > + return NULL; > + } > + } > + > + BIO_free(bio); > + > + n = ERR_peek_last_error(); > + if (sk_X509_CRL_num(sk) == 0 > + || ERR_GET_LIB(n) != ERR_LIB_PEM > + || ERR_GET_REASON(n) != PEM_R_NO_START_LINE) > + { > + /* the failure wasn't "no more revocations to load" */ > + *err = "PEM_read_bio_X509_CRL() failed"; > + sk_X509_CRL_pop_free(sk, X509_CRL_free); > + return NULL; > + } > + Same as above, plus making this function look more similar to ngx_ssl_cache_cert_create(), for maintenance purpose. > + /* success leaves errors on the error stack */ > + ERR_clear_error(); > + > + return sk; > +} > + > + > +static void > +ngx_ssl_cache_crl_free(void *data) > +{ > + sk_X509_CRL_pop_free(data, X509_CRL_free); > +} > + > + > +static void * > +ngx_ssl_cache_crl_ref(char **err, void *data) > +{ > + int n, i; > + X509_CRL *xc; > + STACK_OF(X509_CRL) *sk; > + > + /* stacks aren't reference-counted, so shallow copy into a new stack */ > + sk = sk_X509_CRL_dup(data); > + if (sk == NULL) { > + *err = "sk_X509_CRL_dup() failed"; > + return NULL; > + } > + > + /* bump the revocations' reference counts */ > + n = sk_X509_CRL_num(sk); > + > + for (i = 0; i < n; i++) { > + xc = sk_X509_CRL_value(sk, i); > + > +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) > + X509_CRL_up_ref(xc); > +#else > + CRYPTO_add(&xc->references, 1, CRYPTO_LOCK_X509_CRL); > +#endif > + } > + > + return sk; > +} > + > + > static BIO * > ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) > { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel On Tue, Jul 23, 2024 at 07:30:28PM +0000, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762945 0 > # Tue Jul 23 19:29:05 2024 +0000 > # Node ID 298a9eaa59d2a16f85b6aa3584eb5f8298e6c9bc > # Parent 867e05f555e6f593589a0278c865e7dcffe597f4 > SSL: caching private keys. > > Added ngx_ssl_cache_key which caches private keys. Special support is included > for "engine:..." keys. > > EVP_KEY objects are a reference-counted container for key material, so shallow > copies and OpenSSL stack management aren't needed as with certificates. > > 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 > @@ -18,10 +18,6 @@ typedef struct { > } ngx_openssl_conf_t; > > > -static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > - ngx_str_t *key, ngx_array_t *passwords); > -static int ngx_ssl_password_callback(char *buf, int size, int rwflag, > - void *userdata); > static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); > static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, > int ret); > @@ -536,7 +532,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > } > #endif > > - pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords); > + pkey = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_key, &err, > + key, passwords); > if (pkey == NULL) { > if (err != NULL) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > @@ -610,10 +607,11 @@ ngx_ssl_connection_certificate(ngx_conne > > #endif > > - pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords); > + pkey = ngx_ssl_cache_fetch((ngx_cycle_t *) ngx_cycle, c->pool, > + &ngx_ssl_cache_key, &err, key, passwords); > if (pkey == NULL) { > if (err != NULL) { > - ngx_ssl_error(NGX_LOG_ERR, c->log, 0, > + ngx_ssl_error(NGX_LOG_EMERG, c->log, 0, wrong log level change, seemingly a result of copy'n'paste > "cannot load certificate key \"%s\": %s", > key->data, err); > } > @@ -634,151 +632,6 @@ ngx_ssl_connection_certificate(ngx_conne > } > > > -static EVP_PKEY * > -ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > - ngx_str_t *key, ngx_array_t *passwords) > -{ > - BIO *bio; > - EVP_PKEY *pkey; > - ngx_str_t *pwd; > - ngx_uint_t tries; > - pem_password_cb *cb; > - > - if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) { > - > -#ifndef OPENSSL_NO_ENGINE > - > - u_char *p, *last; > - ENGINE *engine; > - > - p = key->data + sizeof("engine:") - 1; > - last = (u_char *) ngx_strchr(p, ':'); > - > - if (last == NULL) { > - *err = "invalid syntax"; > - return NULL; > - } > - > - *last = '\0'; > - > - engine = ENGINE_by_id((char *) p); > - > - *last++ = ':'; > - > - if (engine == NULL) { > - *err = "ENGINE_by_id() failed"; > - return NULL; > - } > - > - pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); > - > - if (pkey == NULL) { > - *err = "ENGINE_load_private_key() failed"; > - ENGINE_free(engine); > - return NULL; > - } > - > - ENGINE_free(engine); > - > - return pkey; > - > -#else > - > - *err = "loading \"engine:...\" certificate keys is not supported"; > - return NULL; > - > -#endif > - } > - > - if (ngx_strncmp(key->data, "data:", sizeof("data:") - 1) == 0) { > - > - bio = BIO_new_mem_buf(key->data + sizeof("data:") - 1, > - key->len - (sizeof("data:") - 1)); > - if (bio == NULL) { > - *err = "BIO_new_mem_buf() failed"; > - return NULL; > - } > - > - } else { > - > - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) > - != NGX_OK) > - { > - *err = NULL; > - return NULL; > - } > - > - bio = BIO_new_file((char *) key->data, "r"); > - if (bio == NULL) { > - *err = "BIO_new_file() failed"; > - return NULL; > - } > - } > - > - if (passwords) { > - tries = passwords->nelts; > - pwd = passwords->elts; > - cb = ngx_ssl_password_callback; > - > - } else { > - tries = 1; > - pwd = NULL; > - cb = NULL; > - } > - > - for ( ;; ) { > - > - pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); > - if (pkey != NULL) { > - break; > - } > - > - if (tries-- > 1) { > - ERR_clear_error(); > - (void) BIO_reset(bio); > - pwd++; > - continue; > - } > - > - *err = "PEM_read_bio_PrivateKey() failed"; > - BIO_free(bio); > - return NULL; > - } > - > - BIO_free(bio); > - > - return pkey; > -} > - > - > -static int > -ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata) > -{ > - ngx_str_t *pwd = userdata; > - > - if (rwflag) { > - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, > - "ngx_ssl_password_callback() is called for encryption"); > - return 0; > - } > - > - if (pwd == NULL) { > - return 0; > - } > - > - if (pwd->len > (size_t) size) { > - ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, > - "password is truncated to %d bytes", size); > - } else { > - size = pwd->len; > - } > - > - ngx_memcpy(buf, pwd->data, size); > - > - return size; > -} > - > - > ngx_int_t > ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, > ngx_uint_t prefer_server_ciphers) > 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 > @@ -353,6 +353,7 @@ extern int ngx_ssl_index; > > extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; > extern ngx_ssl_cache_type_t ngx_ssl_cache_crl; > +extern ngx_ssl_cache_type_t ngx_ssl_cache_key; > > > #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > --- a/src/event/ngx_event_openssl_cache.c > +++ b/src/event/ngx_event_openssl_cache.c > @@ -54,6 +54,12 @@ static void *ngx_ssl_cache_crl_create(ng > static void ngx_ssl_cache_crl_free(void *data); > static void *ngx_ssl_cache_crl_ref(char **err, void *data); > > +static void *ngx_ssl_cache_key_create(ngx_str_t *id, char **err, void *data); > +static int ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag, > + void *userdata); > +static void ngx_ssl_cache_key_free(void *data); > +static void *ngx_ssl_cache_key_ref(char **err, void *data); > + > static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); > > > @@ -82,6 +88,15 @@ ngx_ssl_cache_type_t ngx_ssl_cache_crl > }; > > > +ngx_ssl_cache_type_t ngx_ssl_cache_key = { > + "private key", > + > + ngx_ssl_cache_key_create, > + ngx_ssl_cache_key_free, > + ngx_ssl_cache_key_ref, > +}; > + > + > ngx_module_t ngx_openssl_cache_module = { > NGX_MODULE_V1, > &ngx_openssl_cache_module_ctx, /* module context */ > @@ -508,6 +523,154 @@ ngx_ssl_cache_crl_ref(char **err, void * > } > > > +static void * > +ngx_ssl_cache_key_create(ngx_str_t *id, char **err, void *data) > +{ > + ngx_array_t *passwords = data; > + > + BIO *bio; > + EVP_PKEY *pkey; > + ngx_str_t *pwd; > + ngx_uint_t tries; > + pem_password_cb *cb; > + > + if (ngx_strncmp(id->data, "engine:", sizeof("engine:") - 1) == 0) { > + > +#ifndef OPENSSL_NO_ENGINE > + > + u_char *p, *last; > + ENGINE *engine; > + > + p = id->data + sizeof("engine:") - 1; > + last = (u_char *) ngx_strchr(p, ':'); > + > + if (last == NULL) { > + *err = "invalid syntax"; > + return NULL; > + } > + > + *last = '\0'; > + > + engine = ENGINE_by_id((char *) p); > + > + *last++ = ':'; > + > + if (engine == NULL) { > + *err = "ENGINE_by_id() failed"; > + return NULL; > + } > + > + pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); > + > + if (pkey == NULL) { > + *err = "ENGINE_load_private_key() failed"; > + ENGINE_free(engine); > + return NULL; > + } > + > + ENGINE_free(engine); > + > + return pkey; > +#else > + *err = "loading \"engine:...\" certificate keys is not supported"; > + return NULL; > +#endif > + } > + same as above, plus restoring some existing style from ngx_ssl_load_certificate_key() where it was copied from > + /* figure out where to load from */ > + bio = ngx_ssl_cache_create_bio(id, err); > + if (bio == NULL) { > + return NULL; > + } > + > + if (passwords) { > + tries = passwords->nelts; > + pwd = passwords->elts; > + cb = ngx_ssl_cache_key_password_callback; > + > + } else { > + tries = 1; > + pwd = NULL; > + cb = NULL; > + } > + > + for ( ;; ) { > + > + pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); > + if (pkey != NULL) { > + break; > + } > + > + if (tries-- > 1) { > + ERR_clear_error(); > + (void) BIO_reset(bio); > + pwd++; > + continue; > + } > + > + *err = "PEM_read_bio_PrivateKey() failed"; > + BIO_free(bio); > + return NULL; > + } > + > + BIO_free(bio); > + > + return pkey; > +} > + > + > +static int > +ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag, > + void *userdata) > +{ > + ngx_str_t *pwd = userdata; > + > + if (rwflag) { > + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, > + "ngx_ssl_cache_key_password_callback() is called " > + "for encryption"); > + return 0; > + } > + > + if (pwd == NULL) { > + return 0; > + } > + > + if (pwd->len > (size_t) size) { > + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, > + "password is truncated to %d bytes", size); > + } else { > + size = pwd->len; > + } > + > + ngx_memcpy(buf, pwd->data, size); > + > + return size; > +} > + > + > +static void > +ngx_ssl_cache_key_free(void *data) > +{ > + EVP_PKEY_free(data); > +} > + > + > +static void * > +ngx_ssl_cache_key_ref(char **err, void *data) > +{ > + EVP_PKEY *pkey = data; > + > +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) > + EVP_PKEY_up_ref(pkey); > +#else > + CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); > +#endif > + > + return data; > +} > + > + > static BIO * > ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) > { > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel On Tue, Jul 23, 2024 at 07:30:29PM +0000, Mini Hawthorne wrote: > # HG changeset patch > # User Mini Hawthorne > # Date 1721762968 0 > # Tue Jul 23 19:29:28 2024 +0000 > # Node ID c4a90845888cfa20a4f622eb97954dfbd54af5c6 > # Parent 298a9eaa59d2a16f85b6aa3584eb5f8298e6c9bc > SSL: caching CA certificates. > > This can potentially provide a large amount of savings, > because CA certificates can be quite large. > > 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 > @@ -18,6 +18,8 @@ typedef struct { > } ngx_openssl_conf_t; > > > +static int ngx_ssl_x509_name_cmp(const X509_NAME *const *a, > + const X509_NAME *const *b); > static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); > static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, > int ret); > @@ -651,10 +653,23 @@ ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_ > } > > > +static int > +ngx_ssl_x509_name_cmp(const X509_NAME *const *a, const X509_NAME *const *b) > +{ > + return (X509_NAME_cmp(*a, *b)); > +} > + > + For no apparent reason, comparing to my patch posted internally to fix sending CA list with duplicate entities, here the location of ngx_ssl_x509_name_cmp() was moved before its caller. This contradicts nginx style and was reverted in my series (to be posted separately). > ngx_int_t > ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, > ngx_int_t depth) > { > + int n, i; > + char *err; > + X509 *x; > + X509_NAME *xn; > + X509_STORE *xs; > + STACK_OF(X509) *xsk; > STACK_OF(X509_NAME) *list; > > SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback); > @@ -665,33 +680,91 @@ ngx_ssl_client_certificate(ngx_conf_t *c > return NGX_OK; > } > > - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { > + list = sk_X509_NAME_new(ngx_ssl_x509_name_cmp); > + if (list == NULL) { > return NGX_ERROR; > } > > - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) > - == 0) > - { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "SSL_CTX_load_verify_locations(\"%s\") failed", > - cert->data); > + xs = SSL_CTX_get_cert_store(ssl->ctx); Although there is no evidence SSL_CTX_get_cert_store() may ever fail, because X509_STORE is allocated as part of SSL_CTX, yet we have existing checks elsewhere, so they should be repeated for consistency. Removing them can be discussed separately, after landing this series. > + xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, > + cert, NULL); > + if (xsk == NULL) { > + if (err != NULL) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "failed to load \"%s\": %s", cert->data, err); > + } > + > + sk_X509_NAME_pop_free(list, X509_NAME_free); > return NGX_ERROR; > } > > - /* > - * SSL_CTX_load_verify_locations() may leave errors in the error queue > - * while returning success > - */ > - > - ERR_clear_error(); > - > - list = SSL_load_client_CA_file((char *) cert->data); > - > - if (list == NULL) { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "SSL_load_client_CA_file(\"%s\") failed", cert->data); > - return NGX_ERROR; > - } > + n = sk_X509_num(xsk); > + > + for (i = 0; i < n; i++) { > + x = sk_X509_value(xsk, i); > + > + xn = X509_get_subject_name(x); > + if (xn == NULL) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "X509_get_subject_name() failed"); > + sk_X509_NAME_pop_free(list, X509_NAME_free); > + sk_X509_pop_free(xsk, X509_free); > + return NGX_ERROR; > + } > + > + xn = X509_NAME_dup(xn); > + if (xn == NULL) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_NAME_dup() failed"); > + sk_X509_NAME_pop_free(list, X509_NAME_free); > + sk_X509_pop_free(xsk, X509_free); > + return NGX_ERROR; > + } > + > +#ifdef OPENSSL_IS_BORINGSSL > + if (sk_X509_NAME_find(list, NULL, xn) > 0) { > +#else > + if (sk_X509_NAME_find(list, xn) >= 0) { > +#endif > + X509_NAME_free(xn); > + continue; > + } > + > + if (!sk_X509_NAME_push(list, xn)) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "sk_X509_NAME_push() failed"); > + sk_X509_NAME_pop_free(list, X509_NAME_free); > + sk_X509_pop_free(xsk, X509_free); > + X509_NAME_free(xn); > + return NGX_ERROR; > + } Reshuffled a bit, moving constructing CA list after X509_STORE is prepared, to make the code sequence look more natural, which is also in line with previously used SSL_load_client_CA_file() this code is used to re-implement. > + > + if (X509_STORE_add_cert(xs, x) != 1) { > + > +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ > + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) > + u_long error; > + > + /* not reported in OpenSSL 1.1.0i+ */ > + > + error = ERR_peek_last_error(); > + > + if (ERR_GET_LIB(error) == ERR_LIB_X509 > + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) > + { > + ERR_clear_error(); > + continue; > + } > +#endif > + > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "X509_STORE_add_cert() failed"); > + sk_X509_NAME_pop_free(list, X509_NAME_free); > + sk_X509_pop_free(xsk, X509_free); > + return NGX_ERROR; > + } > + } > + > + sk_X509_pop_free(xsk, X509_free); > > SSL_CTX_set_client_CA_list(ssl->ctx, list); > > @@ -703,6 +776,12 @@ ngx_int_t > ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, > ngx_int_t depth) > { > + int i, n; > + char *err; > + X509 *x; > + X509_STORE *xs; > + STACK_OF(X509) *xsk; > + > SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx), > ngx_ssl_verify_callback); > > @@ -712,25 +791,49 @@ ngx_ssl_trusted_certificate(ngx_conf_t * > return NGX_OK; > } > > - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { > + xs = SSL_CTX_get_cert_store(ssl->ctx); > + xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, > + cert, NULL); > + if (xsk == NULL) { > + if (err != NULL) { > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "failed to load \"%s\": %s", cert->data, err); > + } > + > return NGX_ERROR; > } > > - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) > - == 0) > - { > - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > - "SSL_CTX_load_verify_locations(\"%s\") failed", > - cert->data); > - return NGX_ERROR; > - } > - > - /* > - * SSL_CTX_load_verify_locations() may leave errors in the error queue > - * while returning success > - */ > - > - ERR_clear_error(); > + n = sk_X509_num(xsk); > + > + for (i = 0; i < n; i++) { > + x = sk_X509_value(xsk, i); > + > + if (X509_STORE_add_cert(xs, x) != 1) { > + > +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ > + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) > + u_long error; > + > + /* not reported in OpenSSL 1.1.0i+ */ > + > + error = ERR_peek_last_error(); > + > + if (ERR_GET_LIB(error) == ERR_LIB_X509 > + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) > + { > + ERR_clear_error(); > + continue; > + } > +#endif > + > + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > + "X509_STORE_add_cert() failed"); > + sk_X509_pop_free(xsk, X509_free); > + return NGX_ERROR; > + } > + } > + > + sk_X509_pop_free(xsk, X509_free); > > return NGX_OK; > } > 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 > @@ -351,6 +351,7 @@ extern int ngx_ssl_ocsp_index; > extern int ngx_ssl_index; > > > +extern ngx_ssl_cache_type_t ngx_ssl_cache_ca; > extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; > extern ngx_ssl_cache_type_t ngx_ssl_cache_crl; > extern ngx_ssl_cache_type_t ngx_ssl_cache_key; > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > --- a/src/event/ngx_event_openssl_cache.c > +++ b/src/event/ngx_event_openssl_cache.c > @@ -46,6 +46,7 @@ static void ngx_ssl_cache_node_insert(ng > static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, > ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); > > +static void *ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data); It's probably better move this declaration to a distinct block, to make it similar to other SSL object type functions. > static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); > static void ngx_ssl_cache_cert_free(void *data); > static void *ngx_ssl_cache_cert_ref(char **err, void *data); > @@ -70,6 +71,15 @@ static ngx_core_module_t ngx_openssl_ca > }; > > > +ngx_ssl_cache_type_t ngx_ssl_cache_ca = { > + "certificate CA list", > + > + ngx_ssl_cache_ca_create, > + ngx_ssl_cache_cert_free, > + ngx_ssl_cache_cert_ref, > +}; > + > + > ngx_ssl_cache_type_t ngx_ssl_cache_cert = { > "certificate chain", > > @@ -321,6 +331,58 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca > > > static void * > +ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data) > +{ > + BIO *bio; > + X509 *x; > + u_long n; > + STACK_OF(X509) *sk; > + > + /* start with an empty certificate chain */ > + sk = sk_X509_new_null(); > + if (sk == NULL) { > + *err = "sk_X509_new_null() failed"; > + return NULL; > + } > + > + /* figure out where to load from */ > + bio = ngx_ssl_cache_create_bio(id, err); > + if (bio == NULL) { > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + > + /* read all of the certificates */ > + while ((x = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL)) != NULL) { > + if (sk_X509_push(sk, x) <= 0) { > + *err = "sk_X509_push() failed"; > + BIO_free(bio); > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } > + } > + > + BIO_free(bio); > + > + n = ERR_peek_last_error(); > + if (sk_X509_num(sk) == 0 > + || ERR_GET_LIB(n) != ERR_LIB_PEM > + || ERR_GET_REASON(n) != PEM_R_NO_START_LINE) > + { > + /* the failure wasn't "no more certificates to load" */ > + *err = "PEM_read_bio_X509() failed"; > + sk_X509_pop_free(sk, X509_free); > + return NULL; > + } See above, largely follows ngx_ssl_cache_crl_create() rewrite. > + > + /* success leaves errors on the error stack */ > + ERR_clear_error(); > + > + return sk; > +} > + > + > +static void * > ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) > { > BIO *bio; From pclicoder at gmail.com Wed Aug 21 16:36:07 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Wed, 21 Aug 2024 09:36:07 -0700 Subject: [nginx] CONF: Make ssl_client_certificate directive optional with TLSv1.3 In-Reply-To: References: <2d8d7f0f-173a-41e5-9fb9-834b6bb84817@nginx.com> Message-ID: @a.bavshin at nginx.com Gentle Reminder for review. This feature to make ssl_client_certificate optional may help us here at Nvidia. Thanks in advance. Kindly let me know if any more modification is needed in fix. Note: AFAIK, mTLS was not supported with SSLv2. I kept the NGX_SSL_SSLv2 flag in fix, if Nginx today supports SSLv2. On Mon, Aug 19, 2024 at 4:22 PM Praveen Chaudhary wrote: > Thanks Aleksei for the review. > > Agree, It makes sense to have explicit error message to require either > ssl_client_certificate or ssl_trusted_certificate. Because: > Nginx prints error number from SSL to identify SSL error\routine, > but for a client or admin, it may be still hard to find why "SSL > certificate error" > is seen. > > V2 Patch. > [Keeping same Subject] > > # HG changeset patch > # User Praveen Chaudhary > # Date 1723406727 25200 > # Sun Aug 11 13:05:27 2024 -0700 > # Node ID a5525b8eac0e1f10da42b7367cd5296fb29f4787 > # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 > Make ssl_client_certificate directive optional with TLSv1.1+. > > - With TLS 1.1+, Certificate Authorities(CAs) are optional > in the Certificate Request packet. This makes directive > ssl_client_certificate also optional for mutual TLS > configurations. > > - For TLS 1.1, check either ssl_client_certificate or > ssl_trusted_certificate is non empty. > > diff -r 8796dfbe7177 -r a5525b8eac0e src/http/modules/ngx_http_ssl_module.c > --- a/src/http/modules/ngx_http_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 > +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -788,9 +788,20 @@ > if (conf->verify) { > > if (conf->client_certificate.len == 0 && conf->verify != 3) { > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > - "no ssl_client_certificate for > ssl_verify_client"); > - return NGX_CONF_ERROR; > + > + if (conf->protocols & > + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > + /* For TLS 1.1+. */ > + if (conf->trusted_certificate.len == 0) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate or " > + "ssl_trusted_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > } > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > diff -r 8796dfbe7177 -r a5525b8eac0e src/mail/ngx_mail_ssl_module.c > --- a/src/mail/ngx_mail_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 > +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -451,9 +451,20 @@ > if (conf->verify) { > > if (conf->client_certificate.len == 0 && conf->verify != 3) { > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > - "no ssl_client_certificate for > ssl_verify_client"); > - return NGX_CONF_ERROR; > + > + if (conf->protocols & > + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > + /* For TLS 1.1+. */ > + if (conf->trusted_certificate.len == 0) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate or " > + "ssl_trusted_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > } > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > diff -r 8796dfbe7177 -r a5525b8eac0e src/stream/ngx_stream_ssl_module.c > --- a/src/stream/ngx_stream_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 > +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 > @@ -933,9 +933,20 @@ > if (conf->verify) { > > if (conf->client_certificate.len == 0 && conf->verify != 3) { > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > - "no ssl_client_certificate for > ssl_verify_client"); > - return NGX_CONF_ERROR; > + > + if (conf->protocols & > + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > + /* For TLS 1.1+. */ > + if (conf->trusted_certificate.len == 0) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "no ssl_client_certificate or " > + "ssl_trusted_certificate for > ssl_verify_client"); > + return NGX_CONF_ERROR; > + } > } > > if (ngx_ssl_client_certificate(cf, &conf->ssl, > > On Mon, Aug 19, 2024 at 11:41 AM Aleksei Bavshin > wrote: > >> On 8/16/2024 8:02 AM, Praveen Chaudhary wrote: >> > Hi Nginx Devs >> > >> > Bumping patch to the top for review. >> > >> > CC: @Sergey Kandaurov >> > Thanks for contributing client certificate validation with OSCP. It is >> > a long awaited feature. >> > In this patch, I am trying to fix another lingering concern. It will be >> > great, if you can have a look. >> >> Hello, >> >> Sending an empty list of CAs is explicitly mentioned starting from TLS >> 1.1; RFC 4346 Section 7.4.4: >> >> If the certificate_authorities list is empty then the client MAY >> send any certificate of the appropriate ClientCertificateType, >> unless there is some external arrangement to the contrary. >> >> TLS 1.0 (RFC 2246 Section 7.4.4) does not specify any behavior. While >> it's known that some 1.0 or SSL 3.0 clients can accept an empty list, it >> could be safer to limit the ability to the TLS 1.1+ configurations. >> >> As for the means of doing so, simply skipping the >> conf->client_certificate check is not correct. For any ssl_client_verify >> mode other than 'optional_no_ca' nginx must have a list of known trusted >> CAs, and the configuration without any is not valid. >> A better approach is to require either 'ssl_client_certificate' or >> 'ssl_trusted_certificate' to be set when the client cert verification is >> enabled. >> >> > >> > # HG changeset patch >> > # User Praveen Chaudhary > > > >> > # Date 1723406727 25200 >> > # Sun Aug 11 13:05:27 2024 -0700 >> > # Node ID 199a35c74b60437da9d22a70d257507b4afb1878 >> > # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 >> > Make ssl_client_certificate directive optional with TLSv1.3. >> > >> > - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) >> > send Certificate Authorities (CAs) in the Certificate Request >> > packet. This makes ssl_client_certificate directive optional >> > when only TLS 1.3 is used for mutual TLS configurations. >> > > - Today, Nginx requires ssl_client_certificate directive to >> > be set to CA Certificates file, if ssl_verify_client is >> > enabled, even when using only TLS 1.3. Else Nginx does not >> > reload or restart. >> > >> > diff -r b5550a7f16c7 -r 199a35c74b60 >> src/http/modules/ngx_http_ssl_module.c >> > --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 >> +0400 >> > +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 >> -0700 >> > @@ -787,10 +787,16 @@ >> > >> > if (conf->verify) { >> > >> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > - "no ssl_client_certificate for >> > ssl_verify_client"); >> > - return NGX_CONF_ERROR; >> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >> > NGX_SSL_TLSv1_2)) { >> > + /* >> > + For TLS 1.3, It is optional to send Certificate >> Authorities in >> > + Certificate Request Packet. RFC 8446#section-4.2.4 >> > + */ >> > + if (conf->client_certificate.len == 0 && conf->verify != >> 3) { >> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > + "no ssl_client_certificate for >> > ssl_verify_client"); >> > + return NGX_CONF_ERROR; >> > + } >> > } >> > >> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >> > diff -r b5550a7f16c7 -r 199a35c74b60 src/mail/ngx_mail_ssl_module.c >> > --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 >> > +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >> > @@ -450,12 +450,19 @@ >> > >> > if (conf->verify) { >> > >> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > - "no ssl_client_certificate for >> > ssl_verify_client"); >> > - return NGX_CONF_ERROR; >> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >> > NGX_SSL_TLSv1_2)) { >> > + /* >> > + For TLS 1.3, It is optional to send Certificate >> Authorities in >> > + Certificate Request Packet. RFC 8446#section-4.2.4 >> > + */ >> > + if (conf->client_certificate.len == 0 && conf->verify != >> 3) { >> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > + "no ssl_client_certificate for >> > ssl_verify_client"); >> > + return NGX_CONF_ERROR; >> > + } >> > } >> > >> > + >> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >> > &conf->client_certificate, >> > conf->verify_depth) >> > diff -r b5550a7f16c7 -r 199a35c74b60 src/stream/ngx_stream_ssl_module.c >> > --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 >> > +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >> > @@ -932,10 +932,16 @@ >> > >> > if (conf->verify) { >> > >> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > - "no ssl_client_certificate for >> > ssl_verify_client"); >> > - return NGX_CONF_ERROR; >> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >> > NGX_SSL_TLSv1_2)) { >> > + /* >> > + For TLS 1.3, It is optional to send Certificate >> Authorities in >> > + Certificate Request Packet. RFC 8446#section-4.2.4 >> > + */ >> > + if (conf->client_certificate.len == 0 && conf->verify != >> 3) { >> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> > + "no ssl_client_certificate for >> > ssl_verify_client"); >> > + return NGX_CONF_ERROR; >> > + } >> > } >> > >> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >> > >> > _______________________________________________ >> > nginx-devel mailing list >> > nginx-devel at nginx.org >> > https://mailman.nginx.org/mailman/listinfo/nginx-devel >> _______________________________________________ >> nginx-devel mailing list >> nginx-devel at nginx.org >> https://mailman.nginx.org/mailman/listinfo/nginx-devel >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Wed Aug 21 18:24:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 21 Aug 2024 18:24:02 +0000 (UTC) Subject: [njs] Add badges to README.md Message-ID: <20240821182402.50C06487E8@pubserv1.nginx> details: https://github.com/nginx/njs/commit/0d2b8b53168261d8bc2fb9ceb15244e2bccd9c70 branches: master commit: 0d2b8b53168261d8bc2fb9ceb15244e2bccd9c70 user: Elijah Zupancic date: Tue, 20 Aug 2024 11:41:44 -0700 description: Add badges to README.md This change adds two badges indicating the current project status and level of support offered. These badges are standardized across many nginx projects. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index bca96454..5beac3c0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Community Support](https://badgen.net/badge/support/commercial/green?icon=awesome)](/SUPPORT.md) + ![NGINX JavaScript Banner](NGINX-js-1660x332.png "NGINX JavaScript Banner") # NGINX JavaScript From pluknet at nginx.com Wed Aug 21 22:04:51 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:51 +0400 Subject: [PATCH 0 of 6] SSL object cache Message-ID: Largely updated series based on my comments. From pluknet at nginx.com Wed Aug 21 22:04:52 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:52 +0400 Subject: [PATCH 1 of 6] SSL: moved certificate storage out of exdata In-Reply-To: References: Message-ID: <6baaa6efe6f0a2e8b953.1724277892@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1721762810 0 # Tue Jul 23 19:26:50 2024 +0000 # Node ID 6baaa6efe6f0a2e8b95374717cd5f73db8a3a862 # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 SSL: moved certificate storage out of exdata. Instead of cross-linking the objects using exdata, pointers to configured certificates are now stored in ngx_ssl_t, and certificate names and OCSP staples are now accessed with ngx_rbtree_t. This allows sharing these objects between SSL contexts. Based on previous work by Mini Hawthorne. 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 @@ -131,10 +131,7 @@ int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_ticket_keys_index; int ngx_ssl_ocsp_index; -int ngx_ssl_certificate_index; -int ngx_ssl_next_certificate_index; -int ngx_ssl_certificate_name_index; -int ngx_ssl_stapling_index; +int ngx_ssl_index; ngx_int_t @@ -258,36 +255,13 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } - ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, - NULL); - if (ngx_ssl_certificate_index == -1) { + ngx_ssl_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (ngx_ssl_index == -1) { ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_CTX_get_ex_new_index() failed"); return NGX_ERROR; } - ngx_ssl_next_certificate_index = X509_get_ex_new_index(0, NULL, NULL, NULL, - NULL); - if (ngx_ssl_next_certificate_index == -1) { - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); - return NGX_ERROR; - } - - ngx_ssl_certificate_name_index = X509_get_ex_new_index(0, NULL, NULL, NULL, - NULL); - - if (ngx_ssl_certificate_name_index == -1) { - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); - return NGX_ERROR; - } - - ngx_ssl_stapling_index = X509_get_ex_new_index(0, NULL, NULL, NULL, NULL); - - if (ngx_ssl_stapling_index == -1) { - ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed"); - return NGX_ERROR; - } - return NGX_OK; } @@ -308,12 +282,18 @@ ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_ return NGX_ERROR; } - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, NULL) == 0) { + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_index, ssl) == 0) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_set_ex_data() failed"); return NGX_ERROR; } + ngx_rbtree_init(&ssl->name_rbtree, &ssl->name_sentinel, + ngx_rbtree_insert_value); + + ngx_rbtree_init(&ssl->staple_rbtree, &ssl->staple_sentinel, + ngx_rbtree_insert_value); + ssl->buffer_size = NGX_SSL_BUFSIZE; /* client side options */ @@ -458,9 +438,10 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ ngx_str_t *key, ngx_array_t *passwords) { char *err; - X509 *x509; + X509 *x509, **elm; EVP_PKEY *pkey; STACK_OF(X509) *chain; + ngx_ssl_name_t *name; x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); if (x509 == NULL) { @@ -481,38 +462,42 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ return NGX_ERROR; } - if (X509_set_ex_data(x509, ngx_ssl_certificate_name_index, cert->data) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); + name = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_name_t)); + if (name == NULL) { X509_free(x509); sk_X509_pop_free(chain, X509_free); return NGX_ERROR; } - if (X509_set_ex_data(x509, ngx_ssl_next_certificate_index, - SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index)) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); + name->node.key = (ngx_rbtree_key_t) x509; + name->name.len = cert->len; + name->name.data = cert->data; + + ngx_rbtree_insert(&ssl->name_rbtree, &name->node); + + if (ssl->certs.elts == NULL) { + if (ngx_array_init(&ssl->certs, cf->pool, 1, sizeof(X509 *)) + != NGX_OK) + { + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + } + + elm = ngx_array_push(&ssl->certs); + if (elm == NULL) { X509_free(x509); sk_X509_pop_free(chain, X509_free); return NGX_ERROR; } - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_set_ex_data() failed"); - X509_free(x509); - sk_X509_pop_free(chain, X509_free); - return NGX_ERROR; - } + *elm = x509; /* * Note that x509 is not freed here, but will be instead freed in * ngx_ssl_cleanup_ctx(). This is because we need to preserve all - * certificates to be able to iterate all of them through exdata - * (ngx_ssl_certificate_index, ngx_ssl_next_certificate_index), + * certificates to be able to iterate all of them through ssl->certs, * while OpenSSL can free a certificate if it is replaced with another * certificate of the same type. */ @@ -3820,10 +3805,9 @@ ngx_ssl_session_id_context(ngx_ssl_t *ss goto failed; } - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); - cert; - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) - { + for (k = 0; k < ssl->certs.nelts; k++) { + cert = ((X509 **) ssl->certs.elts)[k]; + if (X509_digest(cert, EVP_sha1(), buf, &len) == 0) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_digest() failed"); @@ -3837,9 +3821,7 @@ ngx_ssl_session_id_context(ngx_ssl_t *ss } } - if (SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index) == NULL - && certificates != NULL) - { + if (ssl->certs.nelts == 0 && certificates != NULL) { /* * If certificates are loaded dynamically, we use certificate * names as specified in the configuration (with variables). @@ -4851,14 +4833,12 @@ ngx_ssl_cleanup_ctx(void *data) { ngx_ssl_t *ssl = data; - X509 *cert, *next; - - cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); - - while (cert) { - next = X509_get_ex_data(cert, ngx_ssl_next_certificate_index); + X509 *cert; + ngx_uint_t i; + + for (i = 0; i < ssl->certs.nelts; i++) { + cert = ((X509 **) ssl->certs.elts)[i]; X509_free(cert); - cert = next; } SSL_CTX_free(ssl->ctx); 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 @@ -86,10 +86,24 @@ typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; +typedef struct { + ngx_rbtree_node_t node; + ngx_str_t name; +} ngx_ssl_name_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; size_t buffer_size; + + ngx_array_t certs; + + ngx_rbtree_t name_rbtree; + ngx_rbtree_node_t name_sentinel; + + ngx_rbtree_t staple_rbtree; + ngx_rbtree_node_t staple_sentinel; }; @@ -330,10 +344,7 @@ extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; extern int ngx_ssl_ticket_keys_index; extern int ngx_ssl_ocsp_index; -extern int ngx_ssl_certificate_index; -extern int ngx_ssl_next_certificate_index; -extern int ngx_ssl_certificate_name_index; -extern int ngx_ssl_stapling_index; +extern int ngx_ssl_index; #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -15,6 +15,8 @@ typedef struct { + ngx_rbtree_node_t node; + ngx_str_t staple; ngx_msec_t timeout; @@ -157,6 +159,11 @@ static int ngx_ssl_certificate_status_ca static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple); static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_ssl_name_t *ngx_ssl_stapling_lookup_name(ngx_ssl_t *ssl, + X509 *cert); +static ngx_ssl_stapling_t *ngx_ssl_stapling_lookup_staple(ngx_ssl_t *ssl, + X509 *cert); + static time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time); static void ngx_ssl_stapling_cleanup(void *data); @@ -195,12 +202,12 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify) { - X509 *cert; - - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); - cert; - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) - { + X509 *cert; + ngx_uint_t k; + + for (k = 0; k < ssl->certs.nelts; k++) { + cert = ((X509 **) ssl->certs.elts)[k]; + if (ngx_ssl_stapling_certificate(cf, ssl, cert, file, responder, verify) != NGX_OK) { @@ -219,6 +226,7 @@ ngx_ssl_stapling_certificate(ngx_conf_t ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify) { ngx_int_t rc; + ngx_ssl_name_t *name; ngx_pool_cleanup_t *cln; ngx_ssl_stapling_t *staple; @@ -235,10 +243,9 @@ ngx_ssl_stapling_certificate(ngx_conf_t cln->handler = ngx_ssl_stapling_cleanup; cln->data = staple; - if (X509_set_ex_data(cert, ngx_ssl_stapling_index, staple) == 0) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed"); - return NGX_ERROR; - } + staple->node.key = (ngx_rbtree_key_t) cert; + + ngx_rbtree_insert(&ssl->staple_rbtree, &staple->node); #ifdef SSL_CTRL_SELECT_CURRENT_CERT /* OpenSSL 1.0.2+ */ @@ -256,8 +263,9 @@ ngx_ssl_stapling_certificate(ngx_conf_t staple->timeout = 60000; staple->verify = verify; staple->cert = cert; - staple->name = X509_get_ex_data(staple->cert, - ngx_ssl_certificate_name_index); + + name = ngx_ssl_stapling_lookup_name(ssl, cert); + staple->name = name->name.data; if (file->len) { /* use OCSP response from the file */ @@ -545,14 +553,21 @@ ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) { - X509 *cert; + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; ngx_ssl_stapling_t *staple; - for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); - cert; - cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index)) + tree = &ssl->staple_rbtree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) { - staple = X509_get_ex_data(cert, ngx_ssl_stapling_index); + staple = (ngx_ssl_stapling_t *) node; staple->resolver = resolver; staple->resolver_timeout = resolver_timeout; } @@ -567,6 +582,8 @@ ngx_ssl_certificate_status_callback(ngx_ int rc; X509 *cert; u_char *p; + SSL_CTX *ssl_ctx; + ngx_ssl_t *ssl; ngx_connection_t *c; ngx_ssl_stapling_t *staple; @@ -583,7 +600,10 @@ ngx_ssl_certificate_status_callback(ngx_ return rc; } - staple = X509_get_ex_data(cert, ngx_ssl_stapling_index); + ssl_ctx = SSL_get_SSL_CTX(ssl_conn); + ssl = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_index); + + staple = ngx_ssl_stapling_lookup_staple(ssl, cert); if (staple == NULL) { return rc; @@ -716,6 +736,54 @@ error: } +static ngx_ssl_name_t * +ngx_ssl_stapling_lookup_name(ngx_ssl_t *ssl, X509 *cert) +{ + ngx_rbtree_key_t key; + ngx_rbtree_node_t *node, *sentinel; + + node = ssl->name_rbtree.root; + sentinel = ssl->name_rbtree.sentinel; + key = (ngx_rbtree_key_t) cert; + + while (node != sentinel) { + + if (key != node->key) { + node = (key < node->key) ? node->left : node->right; + continue; + } + + return ngx_rbtree_data(node, ngx_ssl_name_t, node); + } + + return NULL; +} + + +static ngx_ssl_stapling_t * +ngx_ssl_stapling_lookup_staple(ngx_ssl_t *ssl, X509 *cert) +{ + ngx_rbtree_key_t key; + ngx_rbtree_node_t *node, *sentinel; + + node = ssl->staple_rbtree.root; + sentinel = ssl->staple_rbtree.sentinel; + key = (ngx_rbtree_key_t) cert; + + while (node != sentinel) { + + if (key != node->key) { + node = (key < node->key) ? node->left : node->right; + continue; + } + + return ngx_rbtree_data(node, ngx_ssl_stapling_t, node); + } + + return NULL; +} + + static time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time) { From pluknet at nginx.com Wed Aug 21 22:04:53 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:53 +0400 Subject: [PATCH 2 of 6] SSL: object caching In-Reply-To: References: Message-ID: <6fbe0bcb81696bba12d1.1724277893@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1721762842 0 # Tue Jul 23 19:27:22 2024 +0000 # Node ID 6fbe0bcb81696bba12d186e5c15323046bcac2d9 # Parent 6baaa6efe6f0a2e8b95374717cd5f73db8a3a862 SSL: object caching. Added ngx_openssl_cache_module, which indexes a type-aware object cache. It maps an id to a unique instance, and provides references to it, which are dropped when the cycle's pool is destroyed. The cache will be used in subsequent patches. Based on previous work by Mini Hawthorne. diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -1307,10 +1307,11 @@ fi if [ $USE_OPENSSL = YES ]; then ngx_module_type=CORE - ngx_module_name=ngx_openssl_module + ngx_module_name="ngx_openssl_module ngx_openssl_cache_module" ngx_module_incs= ngx_module_deps=src/event/ngx_event_openssl.h ngx_module_srcs="src/event/ngx_event_openssl.c + src/event/ngx_event_openssl_cache.c src/event/ngx_event_openssl_stapling.c" ngx_module_libs= ngx_module_link=YES 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 @@ -233,6 +233,11 @@ ngx_int_t ngx_ssl_ocsp_get_status(ngx_co void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); +void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, + ngx_str_t *id, void *data); +void *ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, + ngx_str_t *id, void *data); + ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c new file mode 100644 --- /dev/null +++ b/src/event/ngx_event_openssl_cache.c @@ -0,0 +1,285 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef void *(*ngx_ssl_cache_create_pt)(ngx_str_t *id, char **err, void *data); +typedef void (*ngx_ssl_cache_free_pt)(void *data); +typedef void *(*ngx_ssl_cache_ref_pt)(char **err, void *data); + + +typedef struct { + ngx_ssl_cache_create_pt create; + ngx_ssl_cache_free_pt free; + ngx_ssl_cache_ref_pt ref; +} ngx_ssl_cache_type_t; + + +typedef struct { + ngx_rbtree_node_t node; + ngx_str_t id; + + ngx_ssl_cache_type_t *type; + void *value; +} ngx_ssl_cache_node_t; + + +typedef struct { + ngx_rbtree_t rbtree; + ngx_rbtree_node_t sentinel; +} ngx_ssl_cache_t; + + +static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, + ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); + +static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); +static void ngx_ssl_cache_cleanup(void *data); +static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); + + +static ngx_core_module_t ngx_openssl_cache_module_ctx = { + ngx_string("openssl_cache"), + ngx_openssl_cache_create_conf, + NULL +}; + + +ngx_module_t ngx_openssl_cache_module = { + NGX_MODULE_V1, + &ngx_openssl_cache_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { + +}; + + +void * +ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, + ngx_str_t *id, void *data) +{ + void *value; + uint32_t hash; + ngx_ssl_cache_t *cache; + ngx_ssl_cache_type_t *type; + ngx_ssl_cache_node_t *cn; + + value = NULL; + + hash = ngx_murmur_hash2(id->data, id->len); + + cache = (ngx_ssl_cache_t *) ngx_get_conf(cf->cycle->conf_ctx, + ngx_openssl_cache_module); + + type = &ngx_ssl_cache_types[index]; + + cn = ngx_ssl_cache_lookup(cache, type, id, hash); + + if (cn == NULL) { + cn = ngx_palloc(cf->pool, sizeof(ngx_ssl_cache_node_t) + id->len + 1); + if (cn == NULL) { + return NULL; + } + + cn->node.key = hash; + cn->id.data = (u_char *)(cn + 1); + cn->id.len = id->len; + cn->type = type; + cn->value = NULL; + + ngx_cpystrn(cn->id.data, id->data, id->len + 1); + + ngx_rbtree_insert(&cache->rbtree, &cn->node); + } + + /* try to use a reference from the cache */ + + if (cn->value != NULL) { + value = type->ref(err, cn->value); + } + + if (value == NULL) { + value = type->create(id, err, data); + } + + if (value != NULL && cn->value == NULL) { + /* we have a value and the node needs one; try to reference it */ + cn->value = type->ref(err, value); + } + + return value; +} + + +void * +ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, + ngx_str_t *id, void *data) +{ + return ngx_ssl_cache_types[index].create(id, err, data); +} + + +static ngx_ssl_cache_node_t * +ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, + ngx_str_t *id, uint32_t hash) +{ + ngx_int_t rc; + ngx_rbtree_node_t *node, *sentinel; + ngx_ssl_cache_node_t *cn; + + node = cache->rbtree.root; + sentinel = cache->rbtree.sentinel; + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + cn = (ngx_ssl_cache_node_t *) node; + + if ((ngx_uint_t) type < (ngx_uint_t) cn->type) { + node = node->left; + continue; + } + + if ((ngx_uint_t) type > (ngx_uint_t) cn->type) { + node = node->right; + continue; + } + + /* type == cn->type */ + + rc = ngx_memn2cmp(id->data, cn->id.data, id->len, cn->id.len); + + if (rc == 0) { + return cn; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} + + +static void * +ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) +{ + ngx_ssl_cache_t *cache; + ngx_pool_cleanup_t *cln; + + cache = ngx_pcalloc(cycle->pool, sizeof(ngx_ssl_cache_t)); + if (cache == NULL) { + return NULL; + } + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_ssl_cache_cleanup; + cln->data = cache; + + ngx_rbtree_init(&cache->rbtree, &cache->sentinel, + ngx_ssl_cache_node_insert); + + return cache; +} + + +static void +ngx_ssl_cache_cleanup(void *data) +{ + ngx_ssl_cache_t *cache = data; + + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_ssl_cache_node_t *cn; + + tree = &cache->rbtree; + + if (tree->root == tree->sentinel) { + return; + } + + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + cn = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); + + if (cn->type != NULL && cn->value != NULL) { + cn->type->free(cn->value); + } + } +} + + +static void +ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_ssl_cache_node_t *n, *t; + + for ( ;; ) { + + n = ngx_rbtree_data(node, ngx_ssl_cache_node_t, node); + t = ngx_rbtree_data(temp, ngx_ssl_cache_node_t, node); + + if (node->key != temp->key) { + + p = (node->key < temp->key) ? &temp->left : &temp->right; + + } else if (n->type != t->type) { + + p = (n->type < t->type) ? &temp->left : &temp->right; + + } else { + + p = (ngx_memn2cmp(n->id.data, t->id.data, n->id.len, t->id.len) + < 0) ? &temp->left : &temp->right; + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} From pluknet at nginx.com Wed Aug 21 22:04:54 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:54 +0400 Subject: [PATCH 3 of 6] SSL: caching certificates In-Reply-To: References: Message-ID: <0d87e1495981ca541d8c.1724277894@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1721762857 0 # Tue Jul 23 19:27:37 2024 +0000 # Node ID 0d87e1495981ca541d8cdb947d94f20a686545a3 # Parent 6fbe0bcb81696bba12d186e5c15323046bcac2d9 SSL: caching certificates. Certificate chains are now loaded once. The certificate cache provides each chain as a unique stack of referenced counted elements. This shallow copy is required because OpenSSL's stacks aren't reference counted. Based on previous work by Mini Hawthorne. 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 @@ -18,8 +18,6 @@ typedef struct { } ngx_openssl_conf_t; -static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, - ngx_str_t *cert, STACK_OF(X509) **chain); static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, ngx_str_t *key, ngx_array_t *passwords); static int ngx_ssl_password_callback(char *buf, int size, int rwflag, @@ -443,8 +441,9 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ STACK_OF(X509) *chain; ngx_ssl_name_t *name; - x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); - if (x509 == NULL) { + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT, &err, cert, NULL); + + if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "cannot load certificate \"%s\": %s", @@ -454,6 +453,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ return NGX_ERROR; } + x509 = sk_X509_shift(chain); + if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_use_certificate(\"%s\") failed", cert->data); @@ -568,8 +569,10 @@ ngx_ssl_connection_certificate(ngx_conne EVP_PKEY *pkey; STACK_OF(X509) *chain; - x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain); - if (x509 == NULL) { + chain = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_CERT, &err, cert, + NULL); + + if (chain == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "cannot load certificate \"%s\": %s", @@ -579,6 +582,8 @@ ngx_ssl_connection_certificate(ngx_conne return NGX_ERROR; } + x509 = sk_X509_shift(chain); + if (SSL_use_certificate(c->ssl->connection, x509) == 0) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_use_certificate(\"%s\") failed", cert->data); @@ -630,96 +635,6 @@ ngx_ssl_connection_certificate(ngx_conne } -static X509 * -ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, - STACK_OF(X509) **chain) -{ - BIO *bio; - X509 *x509, *temp; - u_long n; - - if (ngx_strncmp(cert->data, "data:", sizeof("data:") - 1) == 0) { - - bio = BIO_new_mem_buf(cert->data + sizeof("data:") - 1, - cert->len - (sizeof("data:") - 1)); - if (bio == NULL) { - *err = "BIO_new_mem_buf() failed"; - return NULL; - } - - } else { - - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) - != NGX_OK) - { - *err = NULL; - return NULL; - } - - bio = BIO_new_file((char *) cert->data, "r"); - if (bio == NULL) { - *err = "BIO_new_file() failed"; - return NULL; - } - } - - /* certificate itself */ - - x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); - if (x509 == NULL) { - *err = "PEM_read_bio_X509_AUX() failed"; - BIO_free(bio); - return NULL; - } - - /* rest of the chain */ - - *chain = sk_X509_new_null(); - if (*chain == NULL) { - *err = "sk_X509_new_null() failed"; - BIO_free(bio); - X509_free(x509); - return NULL; - } - - for ( ;; ) { - - temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); - if (temp == NULL) { - n = ERR_peek_last_error(); - - if (ERR_GET_LIB(n) == ERR_LIB_PEM - && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) - { - /* end of file */ - ERR_clear_error(); - break; - } - - /* some real error */ - - *err = "PEM_read_bio_X509() failed"; - BIO_free(bio); - X509_free(x509); - sk_X509_pop_free(*chain, X509_free); - return NULL; - } - - if (sk_X509_push(*chain, temp) == 0) { - *err = "sk_X509_push() failed"; - BIO_free(bio); - X509_free(x509); - sk_X509_pop_free(*chain, X509_free); - return NULL; - } - } - - BIO_free(bio); - - return x509; -} - - static EVP_PKEY * ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, ngx_str_t *key, ngx_array_t *passwords) 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 @@ -202,6 +202,9 @@ typedef struct { #define NGX_SSL_BUFSIZE 16384 +#define NGX_SSL_CACHE_CERT 0 + + ngx_int_t ngx_ssl_init(ngx_log_t *log); ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -39,6 +39,12 @@ typedef struct { static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); +static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); +static void ngx_ssl_cache_cert_free(void *data); +static void *ngx_ssl_cache_cert_ref(char **err, void *data); + +static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); + static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); static void ngx_ssl_cache_cleanup(void *data); static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, @@ -70,6 +76,10 @@ ngx_module_t ngx_openssl_cache_module = static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { + /* NGX_SSL_CACHE_CERT */ + { ngx_ssl_cache_cert_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref }, }; @@ -191,6 +201,166 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca static void * +ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) +{ + BIO *bio; + X509 *x509; + u_long n; + STACK_OF(X509) *chain; + + chain = sk_X509_new_null(); + if (chain == NULL) { + *err = "sk_X509_new_null() failed"; + return NULL; + } + + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + /* certificate itself */ + + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); + if (x509 == NULL) { + *err = "PEM_read_bio_X509_AUX() failed"; + BIO_free(bio); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + BIO_free(bio); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + /* rest of the chain */ + + for ( ;; ) { + + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) + { + /* end of file */ + ERR_clear_error(); + break; + } + + /* some real error */ + + *err = "PEM_read_bio_X509() failed"; + BIO_free(bio); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + BIO_free(bio); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + +static void +ngx_ssl_cache_cert_free(void *data) +{ + sk_X509_pop_free(data, X509_free); +} + + +static void * +ngx_ssl_cache_cert_ref(char **err, void *data) +{ + int n, i; + X509 *x509; + STACK_OF(X509) *chain; + + chain = sk_X509_dup(data); + if (chain == NULL) { + *err = "sk_X509_dup() failed"; + return NULL; + } + + n = sk_X509_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_value(chain, i); + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + X509_up_ref(x509); +#else + CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509); +#endif + } + + return chain; +} + + +static BIO * +ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) +{ + BIO *bio; + ngx_str_t path; + ngx_pool_t *temp_pool; + + if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { + + bio = BIO_new_mem_buf(id->data + sizeof("data:") - 1, + id->len - (sizeof("data:") - 1)); + if (bio == NULL) { + *err = "BIO_new_mem_buf() failed"; + } + + return bio; + } + + temp_pool = ngx_create_pool(NGX_MAX_PATH, ngx_cycle->log); + if (temp_pool == NULL) { + *err = NULL; + return NULL; + } + + ngx_memcpy(&path, id, sizeof(ngx_str_t)); + + if (ngx_get_full_name(temp_pool, + (ngx_str_t *) &ngx_cycle->conf_prefix, + &path) + != NGX_OK) + { + *err = NULL; + ngx_destroy_pool(temp_pool); + return NULL; + } + + bio = BIO_new_file((char *) path.data, "r"); + if (bio == NULL) { + *err = "BIO_new_file() failed"; + } + + ngx_destroy_pool(temp_pool); + + return bio; +} + + +static void * ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) { ngx_ssl_cache_t *cache; From pluknet at nginx.com Wed Aug 21 22:04:55 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:55 +0400 Subject: [PATCH 4 of 6] SSL: caching certificate keys In-Reply-To: References: Message-ID: # HG changeset patch # User Sergey Kandaurov # Date 1721762945 0 # Tue Jul 23 19:29:05 2024 +0000 # Node ID de586726466a08cbdecb8d70f5f42e9067e9ccb8 # Parent 0d87e1495981ca541d8cdb947d94f20a686545a3 SSL: caching certificate keys. EVP_KEY objects are a reference-counted container for key material, so shallow copies and OpenSSL stack management aren't needed as with certificates. Based on previous work by Mini Hawthorne. 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 @@ -18,10 +18,6 @@ typedef struct { } ngx_openssl_conf_t; -static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, - ngx_str_t *key, ngx_array_t *passwords); -static int ngx_ssl_password_callback(char *buf, int size, int rwflag, - void *userdata); static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret); @@ -536,7 +532,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ } #endif - pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords); + pkey = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_KEY, &err, key, passwords); + if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, @@ -611,7 +608,9 @@ ngx_ssl_connection_certificate(ngx_conne #endif - pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords); + pkey = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_KEY, &err, key, + passwords); + if (pkey == NULL) { if (err != NULL) { ngx_ssl_error(NGX_LOG_ERR, c->log, 0, @@ -635,151 +634,6 @@ ngx_ssl_connection_certificate(ngx_conne } -static EVP_PKEY * -ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, - ngx_str_t *key, ngx_array_t *passwords) -{ - BIO *bio; - EVP_PKEY *pkey; - ngx_str_t *pwd; - ngx_uint_t tries; - pem_password_cb *cb; - - if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) { - -#ifndef OPENSSL_NO_ENGINE - - u_char *p, *last; - ENGINE *engine; - - p = key->data + sizeof("engine:") - 1; - last = (u_char *) ngx_strchr(p, ':'); - - if (last == NULL) { - *err = "invalid syntax"; - return NULL; - } - - *last = '\0'; - - engine = ENGINE_by_id((char *) p); - - *last++ = ':'; - - if (engine == NULL) { - *err = "ENGINE_by_id() failed"; - return NULL; - } - - pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); - - if (pkey == NULL) { - *err = "ENGINE_load_private_key() failed"; - ENGINE_free(engine); - return NULL; - } - - ENGINE_free(engine); - - return pkey; - -#else - - *err = "loading \"engine:...\" certificate keys is not supported"; - return NULL; - -#endif - } - - if (ngx_strncmp(key->data, "data:", sizeof("data:") - 1) == 0) { - - bio = BIO_new_mem_buf(key->data + sizeof("data:") - 1, - key->len - (sizeof("data:") - 1)); - if (bio == NULL) { - *err = "BIO_new_mem_buf() failed"; - return NULL; - } - - } else { - - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key) - != NGX_OK) - { - *err = NULL; - return NULL; - } - - bio = BIO_new_file((char *) key->data, "r"); - if (bio == NULL) { - *err = "BIO_new_file() failed"; - return NULL; - } - } - - if (passwords) { - tries = passwords->nelts; - pwd = passwords->elts; - cb = ngx_ssl_password_callback; - - } else { - tries = 1; - pwd = NULL; - cb = NULL; - } - - for ( ;; ) { - - pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); - if (pkey != NULL) { - break; - } - - if (tries-- > 1) { - ERR_clear_error(); - (void) BIO_reset(bio); - pwd++; - continue; - } - - *err = "PEM_read_bio_PrivateKey() failed"; - BIO_free(bio); - return NULL; - } - - BIO_free(bio); - - return pkey; -} - - -static int -ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata) -{ - ngx_str_t *pwd = userdata; - - if (rwflag) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, - "ngx_ssl_password_callback() is called for encryption"); - return 0; - } - - if (pwd == NULL) { - return 0; - } - - if (pwd->len > (size_t) size) { - ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, - "password is truncated to %d bytes", size); - } else { - size = pwd->len; - } - - ngx_memcpy(buf, pwd->data, size); - - return size; -} - - ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, ngx_uint_t prefer_server_ciphers) 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 @@ -203,6 +203,7 @@ typedef struct { #define NGX_SSL_CACHE_CERT 0 +#define NGX_SSL_CACHE_KEY 1 ngx_int_t ngx_ssl_init(ngx_log_t *log); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -43,6 +43,12 @@ static void *ngx_ssl_cache_cert_create(n static void ngx_ssl_cache_cert_free(void *data); static void *ngx_ssl_cache_cert_ref(char **err, void *data); +static void *ngx_ssl_cache_key_create(ngx_str_t *id, char **err, void *data); +static int ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag, + void *userdata); +static void ngx_ssl_cache_key_free(void *data); +static void *ngx_ssl_cache_key_ref(char **err, void *data); + static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); @@ -80,6 +86,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cac { ngx_ssl_cache_cert_create, ngx_ssl_cache_cert_free, ngx_ssl_cache_cert_ref }, + + /* NGX_SSL_CACHE_KEY */ + { ngx_ssl_cache_key_create, + ngx_ssl_cache_key_free, + ngx_ssl_cache_key_ref }, }; @@ -313,6 +324,156 @@ ngx_ssl_cache_cert_ref(char **err, void } +static void * +ngx_ssl_cache_key_create(ngx_str_t *id, char **err, void *data) +{ + ngx_array_t *passwords = data; + + BIO *bio; + EVP_PKEY *pkey; + ngx_str_t *pwd; + ngx_uint_t tries; + pem_password_cb *cb; + + if (ngx_strncmp(id->data, "engine:", sizeof("engine:") - 1) == 0) { + +#ifndef OPENSSL_NO_ENGINE + + u_char *p, *last; + ENGINE *engine; + + p = id->data + sizeof("engine:") - 1; + last = (u_char *) ngx_strchr(p, ':'); + + if (last == NULL) { + *err = "invalid syntax"; + return NULL; + } + + *last = '\0'; + + engine = ENGINE_by_id((char *) p); + + *last++ = ':'; + + if (engine == NULL) { + *err = "ENGINE_by_id() failed"; + return NULL; + } + + pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); + + if (pkey == NULL) { + *err = "ENGINE_load_private_key() failed"; + ENGINE_free(engine); + return NULL; + } + + ENGINE_free(engine); + + return pkey; + +#else + + *err = "loading \"engine:...\" certificate keys is not supported"; + return NULL; + +#endif + } + + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + return NULL; + } + + if (passwords) { + tries = passwords->nelts; + pwd = passwords->elts; + cb = ngx_ssl_cache_key_password_callback; + + } else { + tries = 1; + pwd = NULL; + cb = NULL; + } + + for ( ;; ) { + + pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd); + if (pkey != NULL) { + break; + } + + if (tries-- > 1) { + ERR_clear_error(); + (void) BIO_reset(bio); + pwd++; + continue; + } + + *err = "PEM_read_bio_PrivateKey() failed"; + BIO_free(bio); + return NULL; + } + + BIO_free(bio); + + return pkey; +} + + +static int +ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag, + void *userdata) +{ + ngx_str_t *pwd = userdata; + + if (rwflag) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "ngx_ssl_cache_key_password_callback() is called " + "for encryption"); + return 0; + } + + if (pwd == NULL) { + return 0; + } + + if (pwd->len > (size_t) size) { + ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, + "password is truncated to %d bytes", size); + } else { + size = pwd->len; + } + + ngx_memcpy(buf, pwd->data, size); + + return size; +} + + +static void +ngx_ssl_cache_key_free(void *data) +{ + EVP_PKEY_free(data); +} + + +static void * +ngx_ssl_cache_key_ref(char **err, void *data) +{ + EVP_PKEY *pkey = data; + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + EVP_PKEY_up_ref(pkey); +#else + CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); +#endif + + return data; +} + + static BIO * ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) { From pluknet at nginx.com Wed Aug 21 22:04:57 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:57 +0400 Subject: [PATCH 6 of 6] SSL: caching CA certificates In-Reply-To: References: Message-ID: <5768ea868d214021c777.1724277897@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1721762968 0 # Tue Jul 23 19:29:28 2024 +0000 # Node ID 5768ea868d214021c7774ffa7d67d17f022fdb58 # Parent 09a8f17d87eccf5ae735815b50c9ee701d2a4ff7 SSL: caching CA certificates. This can potentially provide a large amount of savings, because CA certificates can be quite large. Based on previous work by Mini Hawthorne. 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 @@ -22,6 +22,8 @@ static ngx_inline ngx_int_t ngx_ssl_cert static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret); +static int ngx_ssl_cmp_x509_name(const X509_NAME *const *a, + const X509_NAME *const *b); static void ngx_ssl_passwords_cleanup(void *data); static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess); @@ -658,6 +660,12 @@ ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth) { + int n, i; + char *err; + X509 *x509; + X509_NAME *name; + X509_STORE *store; + STACK_OF(X509) *chain; STACK_OF(X509_NAME) *list; SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback); @@ -668,33 +676,85 @@ ngx_ssl_client_certificate(ngx_conf_t *c return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { + list = sk_X509_NAME_new(ngx_ssl_cmp_x509_name); + if (list == NULL) { + return NGX_ERROR; + } + + store = SSL_CTX_get_cert_store(ssl->ctx); + + if (store == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_get_cert_store() failed"); return NGX_ERROR; } - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_load_verify_locations(\"%s\") failed", - cert->data); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); + + if (chain == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } + + sk_X509_NAME_pop_free(list, X509_NAME_free); return NGX_ERROR; } - /* - * SSL_CTX_load_verify_locations() may leave errors in the error queue - * while returning success - */ - - ERR_clear_error(); - - list = SSL_load_client_CA_file((char *) cert->data); - - if (list == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_load_client_CA_file(\"%s\") failed", cert->data); - return NGX_ERROR; - } + n = sk_X509_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_value(chain, i); + + if (X509_STORE_add_cert(store, x509) != 1) { + + if (ngx_ssl_cert_already_in_hash()) { + continue; + } + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert(\"%s\") failed", cert->data); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + name = X509_get_subject_name(x509); + if (name == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_get_subject_name(\"%s\") failed", cert->data); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + name = X509_NAME_dup(name); + if (name == NULL) { + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (sk_X509_NAME_find(list, NULL, name) > 0) { +#else + if (sk_X509_NAME_find(list, name) >= 0) { +#endif + X509_NAME_free(name); + continue; + } + + if (sk_X509_NAME_push(list, name) == 0) { + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + X509_NAME_free(name); + return NGX_ERROR; + } + + } + + sk_X509_pop_free(chain, X509_free); SSL_CTX_set_client_CA_list(ssl->ctx, list); @@ -706,6 +766,12 @@ ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth) { + int i, n; + char *err; + X509 *x509; + X509_STORE *store; + STACK_OF(X509) *chain; + SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx), ngx_ssl_verify_callback); @@ -715,25 +781,45 @@ ngx_ssl_trusted_certificate(ngx_conf_t * return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { - return NGX_ERROR; - } - - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) - == 0) - { + store = SSL_CTX_get_cert_store(ssl->ctx); + + if (store == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_load_verify_locations(\"%s\") failed", - cert->data); + "SSL_CTX_get_cert_store() failed"); + return NGX_ERROR; + } + + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); + + if (chain == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } + return NGX_ERROR; } - /* - * SSL_CTX_load_verify_locations() may leave errors in the error queue - * while returning success - */ - - ERR_clear_error(); + n = sk_X509_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_value(chain, i); + + if (X509_STORE_add_cert(store, x509) != 1) { + + if (ngx_ssl_cert_already_in_hash()) { + continue; + } + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert(\"%s\") failed", cert->data); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + } + + sk_X509_pop_free(chain, X509_free); return NGX_OK; } @@ -985,6 +1071,13 @@ ngx_ssl_info_callback(const ngx_ssl_conn } +static int +ngx_ssl_cmp_x509_name(const X509_NAME *const *a, const X509_NAME *const *b) +{ + return (X509_NAME_cmp(*a, *b)); +} + + ngx_array_t * ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file) { 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 @@ -205,6 +205,7 @@ typedef struct { #define NGX_SSL_CACHE_CERT 0 #define NGX_SSL_CACHE_KEY 1 #define NGX_SSL_CACHE_CRL 2 +#define NGX_SSL_CACHE_CA 3 ngx_int_t ngx_ssl_init(ngx_log_t *log); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -53,6 +53,8 @@ static void *ngx_ssl_cache_crl_create(ng static void ngx_ssl_cache_crl_free(void *data); static void *ngx_ssl_cache_crl_ref(char **err, void *data); +static void *ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data); + static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); @@ -100,6 +102,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cac { ngx_ssl_cache_crl_create, ngx_ssl_cache_crl_free, ngx_ssl_cache_crl_ref }, + + /* NGX_SSL_CACHE_CA */ + { ngx_ssl_cache_ca_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref } }; @@ -577,6 +584,64 @@ ngx_ssl_cache_crl_ref(char **err, void * } +static void * +ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data) +{ + BIO *bio; + X509 *x509; + u_long n; + STACK_OF(X509) *chain; + + chain = sk_X509_new_null(); + if (chain == NULL) { + *err = "sk_X509_new_null() failed"; + return NULL; + } + + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + for ( ;; ) { + + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE + && sk_X509_num(chain) > 0) + { + /* end of file */ + ERR_clear_error(); + break; + } + + /* some real error */ + + *err = "PEM_read_bio_X509_AUX() failed"; + BIO_free(bio); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + BIO_free(bio); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + static BIO * ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) { From pluknet at nginx.com Wed Aug 21 22:04:56 2024 From: pluknet at nginx.com (=?iso-8859-1?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 02:04:56 +0400 Subject: [PATCH 5 of 6] SSL: caching CRLs In-Reply-To: References: Message-ID: <09a8f17d87eccf5ae735.1724277896@enoparse.local> # HG changeset patch # User Sergey Kandaurov # Date 1721762914 0 # Tue Jul 23 19:28:34 2024 +0000 # Node ID 09a8f17d87eccf5ae735815b50c9ee701d2a4ff7 # Parent de586726466a08cbdecb8d70f5f42e9067e9ccb8 SSL: caching CRLs. Based on previous work by Mini Hawthorne. 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 @@ -18,6 +18,7 @@ typedef struct { } ngx_openssl_conf_t; +static ngx_inline ngx_int_t ngx_ssl_cert_already_in_hash(void); static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret); @@ -741,17 +742,16 @@ ngx_ssl_trusted_certificate(ngx_conf_t * ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl) { - X509_STORE *store; - X509_LOOKUP *lookup; + int n, i; + char *err; + X509_CRL *x509; + X509_STORE *store; + STACK_OF(X509_CRL) *chain; if (crl->len == 0) { return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, crl, 1) != NGX_OK) { - return NGX_ERROR; - } - store = SSL_CTX_get_cert_store(ssl->ctx); if (store == NULL) { @@ -760,21 +760,36 @@ ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *s return NGX_ERROR; } - lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); - - if (lookup == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "X509_STORE_add_lookup() failed"); + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CRL, &err, crl, NULL); + + if (chain == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load CRL \"%s\": %s", crl->data, err); + } + return NGX_ERROR; } - if (X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "X509_LOOKUP_load_file(\"%s\") failed", crl->data); - return NGX_ERROR; - } + n = sk_X509_CRL_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_CRL_value(chain, i); + + if (X509_STORE_add_crl(store, x509) != 1) { + + if (ngx_ssl_cert_already_in_hash()) { + continue; + } + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_crl(\"%s\") failed", crl->data); + sk_X509_CRL_pop_free(chain, X509_CRL_free); + return NGX_ERROR; + } + } + + sk_X509_CRL_pop_free(chain, X509_CRL_free); X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); @@ -783,6 +798,27 @@ ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *s } +static ngx_inline ngx_int_t +ngx_ssl_cert_already_in_hash(void) +{ +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) + u_long error; + + error = ERR_peek_last_error(); + + if (ERR_GET_LIB(error) == ERR_LIB_X509 + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) + { + ERR_clear_error(); + return 1; + } +#endif + + return 0; +} + + static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) { 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 @@ -204,6 +204,7 @@ typedef struct { #define NGX_SSL_CACHE_CERT 0 #define NGX_SSL_CACHE_KEY 1 +#define NGX_SSL_CACHE_CRL 2 ngx_int_t ngx_ssl_init(ngx_log_t *log); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -49,6 +49,10 @@ static int ngx_ssl_cache_key_password_ca static void ngx_ssl_cache_key_free(void *data); static void *ngx_ssl_cache_key_ref(char **err, void *data); +static void *ngx_ssl_cache_crl_create(ngx_str_t *id, char **err, void *data); +static void ngx_ssl_cache_crl_free(void *data); +static void *ngx_ssl_cache_crl_ref(char **err, void *data); + static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); @@ -91,6 +95,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cac { ngx_ssl_cache_key_create, ngx_ssl_cache_key_free, ngx_ssl_cache_key_ref }, + + /* NGX_SSL_CACHE_CRL */ + { ngx_ssl_cache_crl_create, + ngx_ssl_cache_crl_free, + ngx_ssl_cache_crl_ref }, }; @@ -474,6 +483,100 @@ ngx_ssl_cache_key_ref(char **err, void * } +static void * +ngx_ssl_cache_crl_create(ngx_str_t *id, char **err, void *data) +{ + BIO *bio; + u_long n; + X509_CRL *x509; + STACK_OF(X509_CRL) *chain; + + chain = sk_X509_CRL_new_null(); + if (chain == NULL) { + *err = "sk_X509_CRL_new_null() failed"; + return NULL; + } + + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + sk_X509_CRL_pop_free(chain, X509_CRL_free); + return NULL; + } + + for ( ;; ) { + + x509 = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE + && sk_X509_CRL_num(chain) > 0) + { + /* end of file */ + ERR_clear_error(); + break; + } + + /* some real error */ + + *err = "PEM_read_bio_X509_CRL() failed"; + BIO_free(bio); + sk_X509_CRL_pop_free(chain, X509_CRL_free); + return NULL; + } + + if (sk_X509_CRL_push(chain, x509) == 0) { + *err = "sk_X509_CRL_push() failed"; + BIO_free(bio); + X509_CRL_free(x509); + sk_X509_CRL_pop_free(chain, X509_CRL_free); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + +static void +ngx_ssl_cache_crl_free(void *data) +{ + sk_X509_CRL_pop_free(data, X509_CRL_free); +} + + +static void * +ngx_ssl_cache_crl_ref(char **err, void *data) +{ + int n, i; + X509_CRL *x509; + STACK_OF(X509_CRL) *chain; + + chain = sk_X509_CRL_dup(data); + if (chain == NULL) { + *err = "sk_X509_CRL_dup() failed"; + return NULL; + } + + n = sk_X509_CRL_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_CRL_value(chain, i); + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) + X509_CRL_up_ref(x509); +#else + CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509_CRL); +#endif + } + + return chain; +} + + static BIO * ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) { From pluknet at nginx.com Wed Aug 21 22:15:52 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Wed, 21 Aug 2024 22:15:52 +0000 Subject: [nginx] Version bump. Message-ID: details: https://hg.nginx.org/nginx/rev/331eae3dccf8 branches: changeset: 9288:331eae3dccf8 user: Sergey Kandaurov date: Tue Aug 20 21:18:30 2024 +0400 description: Version bump. diffstat: src/core/nginx.h | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diffs (14 lines): diff -r 8796dfbe7177 -r 331eae3dccf8 src/core/nginx.h --- a/src/core/nginx.h Mon Aug 12 18:21:01 2024 +0400 +++ b/src/core/nginx.h Tue Aug 20 21:18:30 2024 +0400 @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1027001 -#define NGINX_VERSION "1.27.1" +#define nginx_version 1027002 +#define NGINX_VERSION "1.27.2" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From a.bavshin at nginx.com Wed Aug 21 22:24:10 2024 From: a.bavshin at nginx.com (Aleksei Bavshin) Date: Wed, 21 Aug 2024 15:24:10 -0700 Subject: [PATCH 1 of 2] Stream: client certificate validation with OCSP In-Reply-To: <7d94e3fcad21b90fb137.1723737383@enoparse.local> References: <7d94e3fcad21b90fb137.1723737383@enoparse.local> Message-ID: On 8/15/2024 8:56 AM, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1723737051 -14400 > # Thu Aug 15 19:50:51 2024 +0400 > # Node ID 7d94e3fcad21b90fb13734ed0f9a2f019e23f882 > # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 > Stream: client certificate validation with OCSP. > > diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c > --- a/src/stream/ngx_stream_ssl_module.c > +++ b/src/stream/ngx_stream_ssl_module.c > @@ -51,6 +51,8 @@ static char *ngx_stream_ssl_password_fil > void *conf); > static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, > void *conf); > +static char *ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, > + void *conf); > static char *ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, > void *conf); > > @@ -80,6 +82,14 @@ static ngx_conf_enum_t ngx_stream_ssl_v > }; > > > +static ngx_conf_enum_t ngx_stream_ssl_ocsp[] = { > + { ngx_string("off"), 0 }, > + { ngx_string("on"), 1 }, > + { ngx_string("leaf"), 2 }, > + { ngx_null_string, 0 } > +}; > + > + > static ngx_conf_post_t ngx_stream_ssl_conf_command_post = > { ngx_stream_ssl_conf_command_check }; > > @@ -212,6 +222,27 @@ static ngx_command_t ngx_stream_ssl_com > offsetof(ngx_stream_ssl_srv_conf_t, crl), > NULL }, > > + { ngx_string("ssl_ocsp"), > + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, > + ngx_conf_set_enum_slot, > + NGX_STREAM_SRV_CONF_OFFSET, > + offsetof(ngx_stream_ssl_srv_conf_t, ocsp), > + &ngx_stream_ssl_ocsp }, > + > + { ngx_string("ssl_ocsp_responder"), > + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, > + ngx_conf_set_str_slot, > + NGX_STREAM_SRV_CONF_OFFSET, > + offsetof(ngx_stream_ssl_srv_conf_t, ocsp_responder), > + NULL }, > + > + { ngx_string("ssl_ocsp_cache"), > + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, > + ngx_stream_ssl_ocsp_cache, > + NGX_STREAM_SRV_CONF_OFFSET, > + 0, > + NULL }, > + > { ngx_string("ssl_conf_command"), > NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, > ngx_conf_set_keyval_slot, > @@ -777,6 +808,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ > * sscf->alpn = { 0, NULL }; > * sscf->ciphers = { 0, NULL }; > * sscf->shm_zone = NULL; > + * sscf->ocsp_responder = { 0, NULL }; > */ > > sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; > @@ -792,6 +824,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ > sscf->session_timeout = NGX_CONF_UNSET; > sscf->session_tickets = NGX_CONF_UNSET; > sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; > + sscf->ocsp = NGX_CONF_UNSET_UINT; > + sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; > > return sscf; > } > @@ -846,6 +880,10 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t > > ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); > > + ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0); > + ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, ""); > + ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, > + prev->ocsp_cache_zone, NULL); > > conf->ssl.log = cf->log; > > @@ -959,6 +997,23 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t > } > } > > + if (conf->ocsp) { > + > + if (conf->verify == 3) { > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, > + "\"ssl_ocsp\" is incompatible with " > + "\"ssl_verify_client optional_no_ca\""); > + return NGX_CONF_ERROR; > + } > + > + if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp, > + conf->ocsp_cache_zone) > + != NGX_OK) > + { > + return NGX_CONF_ERROR; > + } > + } > + > if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { > return NGX_CONF_ERROR; > } > @@ -1232,6 +1287,85 @@ invalid: > > > static char * > +ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > +{ > + ngx_stream_ssl_srv_conf_t *sscf = conf; > + > + size_t len; > + ngx_int_t n; > + ngx_str_t *value, name, size; > + ngx_uint_t j; > + > + if (sscf->ocsp_cache_zone != NGX_CONF_UNSET_PTR) { > + return "is duplicate"; > + } > + > + value = cf->args->elts; > + > + if (ngx_strcmp(value[1].data, "off") == 0) { > + sscf->ocsp_cache_zone = NULL; > + return NGX_CONF_OK; > + } > + > + if (value[1].len <= sizeof("shared:") - 1 > + || ngx_strncmp(value[1].data, "shared:", sizeof("shared:") - 1) != 0) > + { > + goto invalid; > + } > + > + len = 0; > + > + for (j = sizeof("shared:") - 1; j < value[1].len; j++) { > + if (value[1].data[j] == ':') { > + break; > + } > + > + len++; > + } > + > + if (len == 0 || j == value[1].len) { > + goto invalid; > + } > + > + name.len = len; > + name.data = value[1].data + sizeof("shared:") - 1; > + > + size.len = value[1].len - j - 1; > + size.data = name.data + len + 1; > + > + n = ngx_parse_size(&size); > + > + if (n == NGX_ERROR) { > + goto invalid; > + } > + > + if (n < (ngx_int_t) (8 * ngx_pagesize)) { > + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > + "OCSP cache \"%V\" is too small", &value[1]); > + > + return NGX_CONF_ERROR; > + } > + > + sscf->ocsp_cache_zone = ngx_shared_memory_add(cf, &name, n, > + &ngx_stream_ssl_module_ctx); > + if (sscf->ocsp_cache_zone == NULL) { > + return NGX_CONF_ERROR; > + } > + > + sscf->ocsp_cache_zone->init = ngx_ssl_ocsp_cache_init; > + > + return NGX_CONF_OK; > + > +invalid: > + > + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, > + "invalid OCSP cache \"%V\"", &value[1]); > + > + return NGX_CONF_ERROR; > +} > + > + > +static char * > ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) > { > #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation > @@ -1308,6 +1442,27 @@ ngx_stream_ssl_init(ngx_conf_t *cf) > ngx_stream_core_main_conf_t *cmcf; > > cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); > + cscfp = cmcf->servers.elts; > + > + for (s = 0; s < cmcf->servers.nelts; s++) { > + > + sscf = cscfp[s]->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; > + > + if (sscf->ssl.ctx == NULL) { > + continue; > + } > + > + cscf = cscfp[s]->ctx->srv_conf[ngx_stream_core_module.ctx_index]; > + > + if (sscf->ocsp) { > + if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, cscf->resolver, > + cscf->resolver_timeout) > + != NGX_OK) > + { > + return NGX_ERROR; > + } > + } > + } > > h = ngx_array_push(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers); > if (h == NULL) { > diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h > --- a/src/stream/ngx_stream_ssl_module.h > +++ b/src/stream/ngx_stream_ssl_module.h > @@ -53,6 +53,10 @@ typedef struct { > > ngx_flag_t session_tickets; > ngx_array_t *session_ticket_keys; > + > + ngx_uint_t ocsp; > + ngx_str_t ocsp_responder; > + ngx_shm_zone_t *ocsp_cache_zone; > } ngx_stream_ssl_srv_conf_t; > > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel Both patches and the corresponding tests look good. From a.bavshin at nginx.com Wed Aug 21 22:56:50 2024 From: a.bavshin at nginx.com (Aleksei Bavshin) Date: Wed, 21 Aug 2024 15:56:50 -0700 Subject: [PATCH 0 of 6] SSL object cache In-Reply-To: References: Message-ID: On 8/21/2024 3:04 PM, Sergey Kandaurov wrote: > Largely updated series based on my comments. Tests: # HG changeset patch # User Aleksei Bavshin # Date 1724280833 25200 # Wed Aug 21 15:53:53 2024 -0700 # Node ID 2a79edf2beb86ab81af8663ecd27fe632eb9e174 # Parent f5ef37b2e2604afb0dc155e1ae92c6807f0645b9 Tests: SSL object cache tests. diff --git a/ssl_cache.t b/ssl_cache.t new file mode 100644 --- /dev/null +++ b/ssl_cache.t @@ -0,0 +1,174 @@ +#!/usr/bin/perl + +# (C) Nginx, Inc. + +# Tests for SSL object cache. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use POSIX qw/ mkfifo /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +plan(skip_all => 'win32') if $^O eq 'MSWin32'; + +my $t = Test::Nginx->new(); + +plan(skip_all => "not yet") unless $t->has_version('1.27.2'); + +$t->has(qw/http http_ssl socket_ssl/)->has_daemon('openssl') + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + server { + listen 127.0.0.1:8443 ssl; + server_name localhost; + + ssl_certificate localhost.crt.fifo; + ssl_certificate_key localhost.key.fifo; + + ssl_trusted_certificate root.crt.fifo; + ssl_crl root.crl.fifo; + } + + server { + listen 127.0.0.1:8444 ssl; + server_name localhost; + + ssl_certificate localhost.crt.fifo; + ssl_certificate_key localhost.key.fifo; + + ssl_verify_client on; + ssl_client_certificate root.crt.fifo; + ssl_crl root.crl.fifo; + } +} + +EOF + +my $d = $t->testdir(); + +$t->write_file('openssl.conf', <write_file('ca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +foreach my $name ('client') { + system('openssl req -new ' + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate for $name: $!\n"; + + system("openssl ca -batch -config $d/ca.conf " + . "-keyfile $d/root.key -cert $d/root.crt " + . "-subj /CN=$name/ -in $d/$name.csr -out $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for $name: $!\n"; +} + +system("openssl ca -gencrl -config $d/ca.conf " + . "-keyfile $d/root.key -cert $d/root.crt " + . "-out $d/root.crl -crldays 1 " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't update crl: $!\n"; + +foreach my $name ('root.crt', 'root.crl', 'localhost.crt', 'localhost.key') { + mkfifo("$d/$name.fifo", 0700); + $t->run_daemon(\&fifo_writer_daemon, $t, $name); +} + +$t->write_file('t', ''); + +$t->plan(2)->run(); + +############################################################################### + +like(get(8443), qr/200 OK/, 'cached certificate'); +like(get(8444, 'client'), qr/200 OK/, 'cached CA and CRL'); + +############################################################################### + +sub get { + my ($port, $cert) = @_; + + http_get('/t', + PeerAddr => '127.0.0.1:' . port($port), + SSL => 1, + $cert ? ( + SSL_cert_file => "$d/$cert.crt", + SSL_key_file => "$d/$cert.key" + ) : () + ); +} + +############################################################################### + +sub fifo_writer_daemon { + my ($t, $name) = @_; + + my $content = $t->read_file($name); + + while (1) { + $t->write_file("$name.fifo", $content); + # reset content after the first read + $content = ""; + } +} + +############################################################################### From pluknet at nginx.com Thu Aug 22 10:54:01 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Thu, 22 Aug 2024 14:54:01 +0400 Subject: [PATCH 0 of 6] SSL object cache In-Reply-To: References: Message-ID: > On 22 Aug 2024, at 02:56, Aleksei Bavshin wrote: > > On 8/21/2024 3:04 PM, Sergey Kandaurov wrote: >> Largely updated series based on my comments. > > Tests: > > # HG changeset patch > # User Aleksei Bavshin > # Date 1724280833 25200 > # Wed Aug 21 15:53:53 2024 -0700 > # Node ID 2a79edf2beb86ab81af8663ecd27fe632eb9e174 > # Parent f5ef37b2e2604afb0dc155e1ae92c6807f0645b9 > Tests: SSL object cache tests. Looks fine. -- Sergey Kandaurov From pluknet at nginx.com Thu Aug 22 12:47:49 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 12:47:49 +0000 Subject: [nginx] Stream: client certificate validation with OCSP. Message-ID: details: https://hg.nginx.org/nginx/rev/eb53c24b158b branches: changeset: 9289:eb53c24b158b user: Sergey Kandaurov date: Thu Aug 22 14:57:45 2024 +0400 description: Stream: client certificate validation with OCSP. diffstat: src/stream/ngx_stream_ssl_module.c | 155 +++++++++++++++++++++++++++++++++++++ src/stream/ngx_stream_ssl_module.h | 4 + 2 files changed, 159 insertions(+), 0 deletions(-) diffs (235 lines): diff -r 331eae3dccf8 -r eb53c24b158b src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Tue Aug 20 21:18:30 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Thu Aug 22 14:57:45 2024 +0400 @@ -51,6 +51,8 @@ static char *ngx_stream_ssl_password_fil void *conf); static char *ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -80,6 +82,14 @@ static ngx_conf_enum_t ngx_stream_ssl_v }; +static ngx_conf_enum_t ngx_stream_ssl_ocsp[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("leaf"), 2 }, + { ngx_null_string, 0 } +}; + + static ngx_conf_post_t ngx_stream_ssl_conf_command_post = { ngx_stream_ssl_conf_command_check }; @@ -212,6 +222,27 @@ static ngx_command_t ngx_stream_ssl_com offsetof(ngx_stream_ssl_srv_conf_t, crl), NULL }, + { ngx_string("ssl_ocsp"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_enum_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, ocsp), + &ngx_stream_ssl_ocsp }, + + { ngx_string("ssl_ocsp_responder"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, ocsp_responder), + NULL }, + + { ngx_string("ssl_ocsp_cache"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_ssl_ocsp_cache, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, @@ -777,6 +808,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ * sscf->alpn = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; + * sscf->ocsp_responder = { 0, NULL }; */ sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; @@ -792,6 +824,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ sscf->session_timeout = NGX_CONF_UNSET; sscf->session_tickets = NGX_CONF_UNSET; sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; + sscf->ocsp = NGX_CONF_UNSET_UINT; + sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; return sscf; } @@ -846,6 +880,10 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t ngx_conf_merge_ptr_value(conf->conf_commands, prev->conf_commands, NULL); + ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0); + ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, ""); + ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, + prev->ocsp_cache_zone, NULL); conf->ssl.log = cf->log; @@ -959,6 +997,23 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t } } + if (conf->ocsp) { + + if (conf->verify == 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_ocsp\" is incompatible with " + "\"ssl_verify_client optional_no_ca\""); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp, + conf->ocsp_cache_zone) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1232,6 +1287,85 @@ invalid: static char * +ngx_stream_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_srv_conf_t *sscf = conf; + + size_t len; + ngx_int_t n; + ngx_str_t *value, name, size; + ngx_uint_t j; + + if (sscf->ocsp_cache_zone != NGX_CONF_UNSET_PTR) { + return "is duplicate"; + } + + value = cf->args->elts; + + if (ngx_strcmp(value[1].data, "off") == 0) { + sscf->ocsp_cache_zone = NULL; + return NGX_CONF_OK; + } + + if (value[1].len <= sizeof("shared:") - 1 + || ngx_strncmp(value[1].data, "shared:", sizeof("shared:") - 1) != 0) + { + goto invalid; + } + + len = 0; + + for (j = sizeof("shared:") - 1; j < value[1].len; j++) { + if (value[1].data[j] == ':') { + break; + } + + len++; + } + + if (len == 0 || j == value[1].len) { + goto invalid; + } + + name.len = len; + name.data = value[1].data + sizeof("shared:") - 1; + + size.len = value[1].len - j - 1; + size.data = name.data + len + 1; + + n = ngx_parse_size(&size); + + if (n == NGX_ERROR) { + goto invalid; + } + + if (n < (ngx_int_t) (8 * ngx_pagesize)) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "OCSP cache \"%V\" is too small", &value[1]); + + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone = ngx_shared_memory_add(cf, &name, n, + &ngx_stream_ssl_module_ctx); + if (sscf->ocsp_cache_zone == NULL) { + return NGX_CONF_ERROR; + } + + sscf->ocsp_cache_zone->init = ngx_ssl_ocsp_cache_init; + + return NGX_CONF_OK; + +invalid: + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid OCSP cache \"%V\"", &value[1]); + + return NGX_CONF_ERROR; +} + + +static char * ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation @@ -1308,6 +1442,27 @@ ngx_stream_ssl_init(ngx_conf_t *cf) ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + cscfp = cmcf->servers.elts; + + for (s = 0; s < cmcf->servers.nelts; s++) { + + sscf = cscfp[s]->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->ssl.ctx == NULL) { + continue; + } + + cscf = cscfp[s]->ctx->srv_conf[ngx_stream_core_module.ctx_index]; + + if (sscf->ocsp) { + if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, cscf->resolver, + cscf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + } h = ngx_array_push(&cmcf->phases[NGX_STREAM_SSL_PHASE].handlers); if (h == NULL) { diff -r 331eae3dccf8 -r eb53c24b158b src/stream/ngx_stream_ssl_module.h --- a/src/stream/ngx_stream_ssl_module.h Tue Aug 20 21:18:30 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.h Thu Aug 22 14:57:45 2024 +0400 @@ -53,6 +53,10 @@ typedef struct { ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; + + ngx_uint_t ocsp; + ngx_str_t ocsp_responder; + ngx_shm_zone_t *ocsp_cache_zone; } ngx_stream_ssl_srv_conf_t; From pluknet at nginx.com Thu Aug 22 12:47:52 2024 From: pluknet at nginx.com (=?utf-8?q?Sergey_Kandaurov?=) Date: Thu, 22 Aug 2024 12:47:52 +0000 Subject: [nginx] Stream: OCSP stapling. Message-ID: details: https://hg.nginx.org/nginx/rev/8444d2a1d57b branches: changeset: 9290:8444d2a1d57b user: Sergey Kandaurov date: Thu Aug 22 14:57:46 2024 +0400 description: Stream: OCSP stapling. diffstat: src/stream/ngx_stream_ssl_module.c | 78 +++++++++++++++++++++++++++++++++---- src/stream/ngx_stream_ssl_module.h | 5 ++ 2 files changed, 73 insertions(+), 10 deletions(-) diffs (147 lines): diff -r eb53c24b158b -r 8444d2a1d57b src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Thu Aug 22 14:57:45 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Thu Aug 22 14:57:46 2024 +0400 @@ -243,6 +243,34 @@ static ngx_command_t ngx_stream_ssl_com 0, NULL }, + { ngx_string("ssl_stapling"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling), + NULL }, + + { ngx_string("ssl_stapling_file"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_file), + NULL }, + + { ngx_string("ssl_stapling_responder"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_responder), + NULL }, + + { ngx_string("ssl_stapling_verify"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, stapling_verify), + NULL }, + { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, @@ -809,6 +837,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; * sscf->ocsp_responder = { 0, NULL }; + * sscf->stapling_file = { 0, NULL }; + * sscf->stapling_responder = { 0, NULL }; */ sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; @@ -826,6 +856,8 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_ sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; sscf->ocsp = NGX_CONF_UNSET_UINT; sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR; + sscf->stapling = NGX_CONF_UNSET; + sscf->stapling_verify = NGX_CONF_UNSET; return sscf; } @@ -885,6 +917,12 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t ngx_conf_merge_ptr_value(conf->ocsp_cache_zone, prev->ocsp_cache_zone, NULL); + ngx_conf_merge_value(conf->stapling, prev->stapling, 0); + ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0); + ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); + ngx_conf_merge_str_value(conf->stapling_responder, + prev->stapling_responder, ""); + conf->ssl.log = cf->log; if (conf->certificates) { @@ -983,18 +1021,18 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t { return NGX_CONF_ERROR; } + } - if (ngx_ssl_trusted_certificate(cf, &conf->ssl, - &conf->trusted_certificate, - conf->verify_depth) - != NGX_OK) - { - return NGX_CONF_ERROR; - } + if (ngx_ssl_trusted_certificate(cf, &conf->ssl, + &conf->trusted_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } - if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { - return NGX_CONF_ERROR; - } + if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { + return NGX_CONF_ERROR; } if (conf->ocsp) { @@ -1055,6 +1093,17 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t return NGX_CONF_ERROR; } + if (conf->stapling) { + + if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file, + &conf->stapling_responder, conf->stapling_verify) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + } + if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1454,6 +1503,15 @@ ngx_stream_ssl_init(ngx_conf_t *cf) cscf = cscfp[s]->ctx->srv_conf[ngx_stream_core_module.ctx_index]; + if (sscf->stapling) { + if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, cscf->resolver, + cscf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + if (sscf->ocsp) { if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, cscf->resolver, cscf->resolver_timeout) diff -r eb53c24b158b -r 8444d2a1d57b src/stream/ngx_stream_ssl_module.h --- a/src/stream/ngx_stream_ssl_module.h Thu Aug 22 14:57:45 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.h Thu Aug 22 14:57:46 2024 +0400 @@ -57,6 +57,11 @@ typedef struct { ngx_uint_t ocsp; ngx_str_t ocsp_responder; ngx_shm_zone_t *ocsp_cache_zone; + + ngx_flag_t stapling; + ngx_flag_t stapling_verify; + ngx_str_t stapling_file; + ngx_str_t stapling_responder; } ngx_stream_ssl_srv_conf_t; From leo.izen at gmail.com Fri Aug 23 03:34:19 2024 From: leo.izen at gmail.com (Leo Izen) Date: Thu, 22 Aug 2024 23:34:19 -0400 Subject: [PATCH] conf/mime.types: add image/jxl internet media type In-Reply-To: <351f249fca852fd70128.1723466854@gauss.local> References: <351f249fca852fd70128.1723466854@gauss.local> Message-ID: <9fcf7f94-ab50-4c94-a412-7009e78e4119@gmail.com> On 8/12/24 8:47 AM, Leo Izen wrote: > # HG changeset patch > # User Leo Izen > # Date 1723466276 14400 > # Mon Aug 12 08:37:56 2024 -0400 > # Node ID 351f249fca852fd701285299b2d5d43c7e911fd2 > # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 > conf/mime.types: add image/jxl internet media type > > image/jxl for JPEG XL images is an officially reocgnized > internet media type[1] by the IANA, and browser support > is starting to exist (e.g. safari on macOS and iOS, plus > Firefox behind a nightly flag, etc.) so servers should be > able to serve .jxl images out of the box. > > [1]: https://www.iana.org/assignments/media-types/image/jxl > [2]: https://web.archive.org/web/20240306204210/ > https://www.iana.org/assignments/media-types/image/jxl > > diff -r b5550a7f16c7 -r 351f249fca85 conf/mime.types > --- a/conf/mime.types Fri Aug 09 19:12:26 2024 +0400 > +++ b/conf/mime.types Mon Aug 12 08:37:56 2024 -0400 > @@ -16,6 +16,7 @@ > text/x-component htc; > > image/avif avif; > + image/jxl jxl; > image/png png; > image/svg+xml svg svgz; > image/tiff tif tiff; > Bumping for review? I don't know the policy on this ML on when this is appropriate, so forgive me if it hasn't been long enough. - Leo Izen From joshua at firetail.io Fri Aug 23 09:48:48 2024 From: joshua at firetail.io (Joshua O'Sullivan) Date: Fri, 23 Aug 2024 10:48:48 +0100 Subject: Guidance developing dynamic modules which load shared libraries Message-ID: Hey everyone, We're currently developing a dynamic module which itself loads another shared library, which we've written in Golang. It seems to be a bit off the beaten track - I can't find any guidance in the documentation pertaining to loading shared libraries in dynamic modules, and there's only one example in the wild I can find of this being attempted before - https://github.com/robinmonjo/ngx_http_l?tab=readme-ov-file#issues-encountered . We've got a proof-of-concept, however we're struggling with where to store the shared library - keeping it in the main configuration seems to make sense, but calls to `dlopen` in our create or init main configuration functions seem to cause NGINX to hang when it handles requests. We're currently using version 1.24.0. Thanks, Joshua O'Sullivan -------------- next part -------------- An HTML attachment was scrubbed... URL: From noreply at nginx.com Sat Aug 24 00:34:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 24 Aug 2024 00:34:02 +0000 (UTC) Subject: [njs] Modules: added nocache flag for js_set variables. Message-ID: <20240824003402.D2717487EA@pubserv1.nginx> details: https://github.com/nginx/njs/commit/5b7905c57fcb994a880804066c6abec32490180c branches: master commit: 5b7905c57fcb994a880804066c6abec32490180c user: Thomas P. date: Wed, 7 Aug 2024 11:47:08 +0200 description: Modules: added nocache flag for js_set variables. This commit adds support for an additional `nocache` flag in `js_set` directives. If set, the resulting nginx variable will have no_cacheable set to 1. This enables us to dynamically recompute a variable if the context changed (for example, in case of an internal redirection). In case of multiple calls in a location, users should cache the result in a rewrite variable: `set $cached_variable $js_variable;` --- nginx/ngx_http_js_module.c | 38 ++++++++++---- nginx/ngx_js.h | 8 +++ nginx/ngx_stream_js_module.c | 37 +++++++++---- nginx/t/js_variables_nocache.t | 99 +++++++++++++++++++++++++++++++++++ nginx/t/stream_js_variables_nocache.t | 94 +++++++++++++++++++++++++++++++++ 5 files changed, 254 insertions(+), 22 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 824b647c..7f2eded2 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -342,7 +342,7 @@ static ngx_command_t ngx_http_js_commands[] = { NULL }, { ngx_string("js_set"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2, + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, ngx_http_js_set, 0, 0, @@ -1245,13 +1245,16 @@ static ngx_int_t ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - ngx_str_t *fname = (ngx_str_t *) data; + ngx_js_set_t *vdata = (ngx_js_set_t *) data; ngx_int_t rc; njs_int_t pending; + ngx_str_t *fname; njs_str_t value; ngx_http_js_ctx_t *ctx; + fname = &vdata->fname; + rc = ngx_http_js_init_vm(r, ngx_http_js_request_proto_id); if (rc == NGX_ERROR) { @@ -1290,7 +1293,7 @@ ngx_http_js_variable_set(ngx_http_request_t *r, ngx_http_variable_value_t *v, v->len = value.length; v->valid = 1; - v->no_cacheable = 0; + v->no_cacheable = vdata->flags & NGX_NJS_VAR_NOCACHE; v->not_found = 0; v->data = value.start; @@ -4639,7 +4642,8 @@ invalid: static char * ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_str_t *value, *fname, *prev; + ngx_str_t *value; + ngx_js_set_t *data, *prev; ngx_http_variable_t *v; value = cf->args->elts; @@ -4658,18 +4662,19 @@ ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - fname = ngx_palloc(cf->pool, sizeof(ngx_str_t)); - if (fname == NULL) { + data = ngx_palloc(cf->pool, sizeof(ngx_js_set_t)); + if (data == NULL) { return NGX_CONF_ERROR; } - *fname = value[2]; + data->fname = value[2]; + data->flags = 0; if (v->get_handler == ngx_http_js_variable_set) { - prev = (ngx_str_t *) v->data; + prev = (ngx_js_set_t *) v->data; - if (fname->len != prev->len - || ngx_strncmp(fname->data, prev->data, fname->len) != 0) + if (data->fname.len != prev->fname.len + || ngx_strncmp(data->fname.data, prev->fname.data, data->fname.len) != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "variable \"%V\" is redeclared with " @@ -4678,8 +4683,19 @@ ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } } + if (cf->args->nelts == 4) { + if (ngx_strcmp(value[3].data, "nocache") == 0) { + data->flags |= NGX_NJS_VAR_NOCACHE; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unrecognized flag \"%V\"", &value[3]); + return NGX_CONF_ERROR; + } + } + v->get_handler = ngx_http_js_variable_set; - v->data = (uintptr_t) fname; + v->data = (uintptr_t) data; return NGX_CONF_OK; } diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index aad88b54..373dd499 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -31,6 +31,8 @@ #define NGX_JS_BOOL_TRUE 1 #define NGX_JS_BOOL_UNSET 2 +#define NGX_NJS_VAR_NOCACHE 1 + #define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED) @@ -137,6 +139,12 @@ typedef struct { } ngx_js_loc_conf_t; +typedef struct { + ngx_str_t fname; + unsigned flags; +} ngx_js_set_t; + + struct ngx_js_ctx_s { NGX_JS_COMMON_CTX; }; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 4de0cb4f..295b2fcf 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -199,7 +199,7 @@ static ngx_command_t ngx_stream_js_commands[] = { NULL }, { ngx_string("js_set"), - NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE23, ngx_stream_js_set, 0, 0, @@ -891,13 +891,16 @@ static ngx_int_t ngx_stream_js_variable_set(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) { - ngx_str_t *fname = (ngx_str_t *) data; + ngx_js_set_t *vdata = (ngx_js_set_t *) data; ngx_int_t rc; njs_int_t pending; + ngx_str_t *fname; njs_str_t value; ngx_stream_js_ctx_t *ctx; + fname = &vdata->fname; + rc = ngx_stream_js_init_vm(s, ngx_stream_js_session_proto_id); if (rc == NGX_ERROR) { @@ -936,7 +939,7 @@ ngx_stream_js_variable_set(ngx_stream_session_t *s, v->len = value.length; v->valid = 1; - v->no_cacheable = 0; + v->no_cacheable = vdata->flags & NGX_NJS_VAR_NOCACHE; v->not_found = 0; v->data = value.start; @@ -2186,7 +2189,8 @@ invalid: static char * ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_str_t *value, *fname, *prev; + ngx_str_t *value; + ngx_js_set_t *data, *prev; ngx_stream_variable_t *v; value = cf->args->elts; @@ -2205,18 +2209,18 @@ ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - fname = ngx_palloc(cf->pool, sizeof(ngx_str_t)); - if (fname == NULL) { + data = ngx_palloc(cf->pool, sizeof(ngx_js_set_t)); + if (data == NULL) { return NGX_CONF_ERROR; } - *fname = value[2]; + data->fname = value[2]; if (v->get_handler == ngx_stream_js_variable_set) { - prev = (ngx_str_t *) v->data; + prev = (ngx_js_set_t *) v->data; - if (fname->len != prev->len - || ngx_strncmp(fname->data, prev->data, fname->len) != 0) + if (data->fname.len != prev->fname.len + || ngx_strncmp(data->fname.data, prev->fname.data, data->fname.len) != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "variable \"%V\" is redeclared with " @@ -2225,8 +2229,19 @@ ngx_stream_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } } + if (cf->args->nelts == 4) { + if (ngx_strcmp(value[3].data, "nocache") == 0) { + data->flags |= NGX_NJS_VAR_NOCACHE; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "unrecognized flag \"%V\"", &value[3]); + return NGX_CONF_ERROR; + } + } + v->get_handler = ngx_stream_js_variable_set; - v->data = (uintptr_t) fname; + v->data = (uintptr_t) data; return NGX_CONF_OK; } diff --git a/nginx/t/js_variables_nocache.t b/nginx/t/js_variables_nocache.t new file mode 100644 index 00000000..abf2b96d --- /dev/null +++ b/nginx/t/js_variables_nocache.t @@ -0,0 +1,99 @@ +#!/usr/bin/perl + +# (C) Thomas P. + +# Tests for http njs module, setting non-cacheable nginx variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_set $nocache_var test.variable nocache; + js_set $default_var test.variable; + js_set $callcount_var test.callCount nocache; + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /default_var { + set $a $default_var; + set $b $default_var; + return 200 '"$a/$b"'; + } + + location /nocache_var { + set $a $nocache_var; + set $b $nocache_var; + return 200 '"$a/$b"'; + } + + location /callcount_var { + set $a $callcount_var; + set $b $callcount_var; + return 200 '"$a/$b"'; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no nocache njs variables')->plan(3); + +############################################################################### + +# We use backreferences to make sure the same value was returned for the two uses +like(http_get('/default_var'), qr/"(.+)\/\1"/, 'cached variable'); +# Negative lookaheads don't capture, hence the .+ after it +like(http_get('/nocache_var'), qr/"(.+)\/(?!\1).+"/, 'noncacheable variable'); + +TODO: { + local $TODO = "Needs upstream nginx patch https://mailman.nginx.org/pipermail/nginx-devel/2024-August/N7VFIYUKSZFUIAO24OJODKQGTP63R5NV.html"; + + # Without the patch, this will give 2/4 (calls are duplicated) + like(http_get('/callcount_var'), qr/"1\/2"/, 'callcount variable'); +} + +############################################################################### diff --git a/nginx/t/stream_js_variables_nocache.t b/nginx/t/stream_js_variables_nocache.t new file mode 100644 index 00000000..a9e49c9f --- /dev/null +++ b/nginx/t/stream_js_variables_nocache.t @@ -0,0 +1,94 @@ +#!/usr/bin/perl + +# (C) Thomas P. + +# Tests for stream njs module, setting non-cacheable nginx variables. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/stream stream_return/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_set $nocache_var test.variable nocache; + js_set $default_var test.variable; + js_set $callcount_var test.callCount nocache; + + js_import test.js; + + server { + listen 127.0.0.1:8081; + set $a $default_var; + set $b $default_var; + return '"$a/$b"'; + } + + server { + listen 127.0.0.1:8082; + set $a $nocache_var; + set $b $nocache_var; + return '"$a/$b"'; + } + + server { + listen 127.0.0.1:8083; + set $a $callcount_var; + set $b $callcount_var; + return '"$a/$b"'; + } +} + +EOF + +$t->write_file('test.js', <try_run('no nocache stream njs variables')->plan(3); + +############################################################################### + +# We use backreferences to make sure the same value was returned for the two uses +like(stream('127.0.0.1:' . port(8081))->read(), qr/"(.+)\/\1"/, 'cached variable'); +# Negative lookaheads don't capture, hence the .+ after it +like(stream('127.0.0.1:' . port(8082))->read(), qr/"(.+)\/(?!\1).+"/, 'noncacheable variable'); +like(stream('127.0.0.1:' . port(8083))->read(), qr/"1\/2"/, 'callcount variable'); + +$t->stop(); + +############################################################################### From arut at nginx.com Mon Aug 26 11:35:03 2024 From: arut at nginx.com (Roman Arutyunyan) Date: Mon, 26 Aug 2024 15:35:03 +0400 Subject: Guidance developing dynamic modules which load shared libraries In-Reply-To: References: Message-ID: Hi Joshua, > On 23 Aug 2024, at 1:48 PM, Joshua O'Sullivan wrote: > > Hey everyone, > > We're currently developing a dynamic module which itself loads another shared library, which we've written in Golang. It seems to be a bit off the beaten track - I can't find any guidance in the documentation pertaining to loading shared libraries in dynamic modules, and there's only one example in the wild I can find of this being attempted before - https://github.com/robinmonjo/ngx_http_l?tab=readme-ov-file#issues-encountered. > > We've got a proof-of-concept, however we're struggling with where to store the shared library - keeping it in the main configuration seems to make sense, but calls to `dlopen` in our create or init main configuration functions seem to cause NGINX to hang when it handles requests. We're currently using version 1.24.0. Indeed you can load a shared library in create_conf or init_conf and store its handle in the configuration. Also you need to register a cycle pool cleanup handler which would release the library. Could you please provide a stack trace of nginx when it hangs? ---- Roman Arutyunyan arut at nginx.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From pclicoder at gmail.com Tue Aug 27 19:07:10 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Tue, 27 Aug 2024 12:07:10 -0700 Subject: [nginx] CONF: Make ssl_client_certificate directive optional with TLSv1.3 In-Reply-To: References: <2d8d7f0f-173a-41e5-9fb9-834b6bb84817@nginx.com> Message-ID: Bringing it up again. Thanks for contributing client certificate validation with OCSP . We were waiting for this feature. Kindly merge below fix as well. Also, let me know if we need to keep check simple as this: *if (conf->client_certificate.len == 0 && conf->trusted_certificate.len == 0 && conf->verify != 3) {* *PATCH:* # HG changeset patch # User Praveen Chaudhary # Date 1723406727 25200 # Sun Aug 11 13:05:27 2024 -0700 # Node ID a5525b8eac0e1f10da42b7367cd5296fb29f4787 # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 Make ssl_client_certificate directive optional with TLSv1.1+. - With TLS 1.1+, Certificate Authorities(CAs) are optional in the Certificate Request packet. This makes directive ssl_client_certificate also optional for mutual TLS configurations. - For TLS 1.1, check either ssl_client_certificate or ssl_trusted_certificate is non empty. diff -r 8796dfbe7177 -r a5525b8eac0e src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -788,9 +788,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r 8796dfbe7177 -r a5525b8eac0e src/mail/ngx_mail_ssl_module.c --- a/src/mail/ngx_mail_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -451,9 +451,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, diff -r 8796dfbe7177 -r a5525b8eac0e src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 @@ -933,9 +933,20 @@ if (conf->verify) { if (conf->client_certificate.len == 0 && conf->verify != 3) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no ssl_client_certificate for ssl_verify_client"); - return NGX_CONF_ERROR; + + if (conf->protocols & + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } + /* For TLS 1.1+. */ + if (conf->trusted_certificate.len == 0) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate or " + "ssl_trusted_certificate for ssl_verify_client"); + return NGX_CONF_ERROR; + } } if (ngx_ssl_client_certificate(cf, &conf->ssl, On Wed, Aug 21, 2024 at 9:36 AM Praveen Chaudhary wrote: > @a.bavshin at nginx.com > > Gentle Reminder for review. This feature to make ssl_client_certificate > optional may help us here at Nvidia. > Thanks in advance. Kindly let me know if any more modification is needed > in fix. > > Note: AFAIK, mTLS was not supported with SSLv2. I kept the NGX_SSL_SSLv2 > flag in fix, if Nginx today supports SSLv2. > > On Mon, Aug 19, 2024 at 4:22 PM Praveen Chaudhary > wrote: > >> Thanks Aleksei for the review. >> >> Agree, It makes sense to have explicit error message to require either >> ssl_client_certificate or ssl_trusted_certificate. Because: >> Nginx prints error number from SSL to identify SSL error\routine, >> but for a client or admin, it may be still hard to find why "SSL >> certificate error" >> is seen. >> >> V2 Patch. >> [Keeping same Subject] >> >> # HG changeset patch >> # User Praveen Chaudhary >> # Date 1723406727 25200 >> # Sun Aug 11 13:05:27 2024 -0700 >> # Node ID a5525b8eac0e1f10da42b7367cd5296fb29f4787 >> # Parent 8796dfbe7177cb0be2a53bcdb4d25cc64a58d2a7 >> Make ssl_client_certificate directive optional with TLSv1.1+. >> >> - With TLS 1.1+, Certificate Authorities(CAs) are optional >> in the Certificate Request packet. This makes directive >> ssl_client_certificate also optional for mutual TLS >> configurations. >> >> - For TLS 1.1, check either ssl_client_certificate or >> ssl_trusted_certificate is non empty. >> >> diff -r 8796dfbe7177 -r a5525b8eac0e >> src/http/modules/ngx_http_ssl_module.c >> --- a/src/http/modules/ngx_http_ssl_module.c Mon Aug 12 18:21:01 2024 >> +0400 >> +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 >> -0700 >> @@ -788,9 +788,20 @@ >> if (conf->verify) { >> >> if (conf->client_certificate.len == 0 && conf->verify != 3) { >> - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> - "no ssl_client_certificate for >> ssl_verify_client"); >> - return NGX_CONF_ERROR; >> + >> + if (conf->protocols & >> + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> + /* For TLS 1.1+. */ >> + if (conf->trusted_certificate.len == 0) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate or " >> + "ssl_trusted_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> } >> >> if (ngx_ssl_client_certificate(cf, &conf->ssl, >> diff -r 8796dfbe7177 -r a5525b8eac0e src/mail/ngx_mail_ssl_module.c >> --- a/src/mail/ngx_mail_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 >> +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >> @@ -451,9 +451,20 @@ >> if (conf->verify) { >> >> if (conf->client_certificate.len == 0 && conf->verify != 3) { >> - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> - "no ssl_client_certificate for >> ssl_verify_client"); >> - return NGX_CONF_ERROR; >> + >> + if (conf->protocols & >> + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> + /* For TLS 1.1+. */ >> + if (conf->trusted_certificate.len == 0) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate or " >> + "ssl_trusted_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> } >> >> if (ngx_ssl_client_certificate(cf, &conf->ssl, >> diff -r 8796dfbe7177 -r a5525b8eac0e src/stream/ngx_stream_ssl_module.c >> --- a/src/stream/ngx_stream_ssl_module.c Mon Aug 12 18:21:01 2024 +0400 >> +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >> @@ -933,9 +933,20 @@ >> if (conf->verify) { >> >> if (conf->client_certificate.len == 0 && conf->verify != 3) { >> - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> - "no ssl_client_certificate for >> ssl_verify_client"); >> - return NGX_CONF_ERROR; >> + >> + if (conf->protocols & >> + (NGX_SSL_SSLv2|NGX_SSL_SSLv3|NGX_SSL_TLSv1)) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> + /* For TLS 1.1+. */ >> + if (conf->trusted_certificate.len == 0) { >> + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >> + "no ssl_client_certificate or " >> + "ssl_trusted_certificate for >> ssl_verify_client"); >> + return NGX_CONF_ERROR; >> + } >> } >> >> if (ngx_ssl_client_certificate(cf, &conf->ssl, >> >> On Mon, Aug 19, 2024 at 11:41 AM Aleksei Bavshin >> wrote: >> >>> On 8/16/2024 8:02 AM, Praveen Chaudhary wrote: >>> > Hi Nginx Devs >>> > >>> > Bumping patch to the top for review. >>> > >>> > CC: @Sergey Kandaurov >>> > Thanks for contributing client certificate validation with OSCP. It >>> is >>> > a long awaited feature. >>> > In this patch, I am trying to fix another lingering concern. It >>> will be >>> > great, if you can have a look. >>> >>> Hello, >>> >>> Sending an empty list of CAs is explicitly mentioned starting from TLS >>> 1.1; RFC 4346 Section 7.4.4: >>> >>> If the certificate_authorities list is empty then the client MAY >>> send any certificate of the appropriate ClientCertificateType, >>> unless there is some external arrangement to the contrary. >>> >>> TLS 1.0 (RFC 2246 Section 7.4.4) does not specify any behavior. While >>> it's known that some 1.0 or SSL 3.0 clients can accept an empty list, it >>> could be safer to limit the ability to the TLS 1.1+ configurations. >>> >>> As for the means of doing so, simply skipping the >>> conf->client_certificate check is not correct. For any ssl_client_verify >>> mode other than 'optional_no_ca' nginx must have a list of known trusted >>> CAs, and the configuration without any is not valid. >>> A better approach is to require either 'ssl_client_certificate' or >>> 'ssl_trusted_certificate' to be set when the client cert verification is >>> enabled. >>> >>> > >>> > # HG changeset patch >>> > # User Praveen Chaudhary >> > > >>> > # Date 1723406727 25200 >>> > # Sun Aug 11 13:05:27 2024 -0700 >>> > # Node ID 199a35c74b60437da9d22a70d257507b4afb1878 >>> > # Parent b5550a7f16c795f394f9d1ac87132dd2b7ef0e41 >>> > Make ssl_client_certificate directive optional with TLSv1.3. >>> > >>> > - As per RFC 8446 Section 4.2.4, server MAY (not SHOULD or MUST) >>> > send Certificate Authorities (CAs) in the Certificate Request >>> > packet. This makes ssl_client_certificate directive optional >>> > when only TLS 1.3 is used for mutual TLS configurations. >>> > > - Today, Nginx requires ssl_client_certificate directive to >>> > be set to CA Certificates file, if ssl_verify_client is >>> > enabled, even when using only TLS 1.3. Else Nginx does not >>> > reload or restart. >>> > >>> > diff -r b5550a7f16c7 -r 199a35c74b60 >>> src/http/modules/ngx_http_ssl_module.c >>> > --- a/src/http/modules/ngx_http_ssl_module.c Fri Aug 09 19:12:26 2024 >>> +0400 >>> > +++ b/src/http/modules/ngx_http_ssl_module.c Sun Aug 11 13:05:27 2024 >>> -0700 >>> > @@ -787,10 +787,16 @@ >>> > >>> > if (conf->verify) { >>> > >>> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >>> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > - "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > - return NGX_CONF_ERROR; >>> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >>> > NGX_SSL_TLSv1_2)) { >>> > + /* >>> > + For TLS 1.3, It is optional to send Certificate >>> Authorities in >>> > + Certificate Request Packet. RFC 8446#section-4.2.4 >>> > + */ >>> > + if (conf->client_certificate.len == 0 && conf->verify != >>> 3) { >>> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > + "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > + return NGX_CONF_ERROR; >>> > + } >>> > } >>> > >>> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >>> > diff -r b5550a7f16c7 -r 199a35c74b60 src/mail/ngx_mail_ssl_module.c >>> > --- a/src/mail/ngx_mail_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 >>> > +++ b/src/mail/ngx_mail_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >>> > @@ -450,12 +450,19 @@ >>> > >>> > if (conf->verify) { >>> > >>> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >>> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > - "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > - return NGX_CONF_ERROR; >>> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >>> > NGX_SSL_TLSv1_2)) { >>> > + /* >>> > + For TLS 1.3, It is optional to send Certificate >>> Authorities in >>> > + Certificate Request Packet. RFC 8446#section-4.2.4 >>> > + */ >>> > + if (conf->client_certificate.len == 0 && conf->verify != >>> 3) { >>> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > + "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > + return NGX_CONF_ERROR; >>> > + } >>> > } >>> > >>> > + >>> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >>> > &conf->client_certificate, >>> > conf->verify_depth) >>> > diff -r b5550a7f16c7 -r 199a35c74b60 src/stream/ngx_stream_ssl_module.c >>> > --- a/src/stream/ngx_stream_ssl_module.c Fri Aug 09 19:12:26 2024 +0400 >>> > +++ b/src/stream/ngx_stream_ssl_module.c Sun Aug 11 13:05:27 2024 -0700 >>> > @@ -932,10 +932,16 @@ >>> > >>> > if (conf->verify) { >>> > >>> > - if (conf->client_certificate.len == 0 && conf->verify != 3) { >>> > - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > - "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > - return NGX_CONF_ERROR; >>> > + if (conf->protocols & (NGX_SSL_TLSv1|NGX_SSL_TLSv1_1| >>> > NGX_SSL_TLSv1_2)) { >>> > + /* >>> > + For TLS 1.3, It is optional to send Certificate >>> Authorities in >>> > + Certificate Request Packet. RFC 8446#section-4.2.4 >>> > + */ >>> > + if (conf->client_certificate.len == 0 && conf->verify != >>> 3) { >>> > + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, >>> > + "no ssl_client_certificate for >>> > ssl_verify_client"); >>> > + return NGX_CONF_ERROR; >>> > + } >>> > } >>> > >>> > if (ngx_ssl_client_certificate(cf, &conf->ssl, >>> > >>> > _______________________________________________ >>> > nginx-devel mailing list >>> > nginx-devel at nginx.org >>> > https://mailman.nginx.org/mailman/listinfo/nginx-devel >>> _______________________________________________ >>> nginx-devel mailing list >>> nginx-devel at nginx.org >>> https://mailman.nginx.org/mailman/listinfo/nginx-devel >>> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From Namrata.Bhave at ibm.com Wed Aug 28 14:13:21 2024 From: Namrata.Bhave at ibm.com (Namrata Bhave) Date: Wed, 28 Aug 2024 14:13:21 +0000 Subject: Binaries for s390x architecture Message-ID: Hi, This is regarding Nginx Binaries on s390x(IBM Z) platform. It looks like s390x binaries are not published anymore, and latest versions are not found under "http://nginx.org/packages/" Found this PR which removed s390x: https://github.com/nginx/nginx.org/pull/56 However it is not clear from the PR why this architecture was dropped. Could someone please help in providing information about this? Thank you very much, Regards, Namrata -------------- next part -------------- An HTML attachment was scrubbed... URL: From osa at freebsd.org.ru Thu Aug 29 15:02:52 2024 From: osa at freebsd.org.ru (Sergey A. Osokin) Date: Thu, 29 Aug 2024 18:02:52 +0300 Subject: Binaries for s390x architecture In-Reply-To: References: Message-ID: Hi there, I do believe this is about of the no access to the s390 infra. Once we get access we'll take a look how we can help. Thank you. -- Sergey A. Osokin On Wed, Aug 28, 2024 at 02:13:21PM +0000, Namrata Bhave via nginx-devel wrote: > Hi, > > This is regarding Nginx Binaries on s390x(IBM Z) platform. It looks like s390x binaries are not published anymore, and latest versions are not found under "http://nginx.org/packages/" > > Found this PR which removed s390x: https://github.com/nginx/nginx.org/pull/56 > However it is not clear from the PR why this architecture was dropped. > > Could someone please help in providing information about this? > > > Thank you very much, > > Regards, > Namrata > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel From a.bavshin at nginx.com Thu Aug 29 23:28:49 2024 From: a.bavshin at nginx.com (Aleksei Bavshin) Date: Thu, 29 Aug 2024 16:28:49 -0700 Subject: [PATCH 3 of 6] SSL: caching certificates In-Reply-To: <0d87e1495981ca541d8c.1724277894@enoparse.local> References: <0d87e1495981ca541d8c.1724277894@enoparse.local> Message-ID: <5154d2a2-6b87-4a7b-814b-834f34b793a5@nginx.com> On 8/21/2024 3:04 PM, Sergey Kandaurov wrote: > # HG changeset patch > # User Sergey Kandaurov > # Date 1721762857 0 > # Tue Jul 23 19:27:37 2024 +0000 > # Node ID 0d87e1495981ca541d8cdb947d94f20a686545a3 > # Parent 6fbe0bcb81696bba12d186e5c15323046bcac2d9 > SSL: caching certificates. > > Certificate chains are now loaded once. > > The certificate cache provides each chain as a unique stack of referenced > counted elements. This shallow copy is required because OpenSSL's stacks > aren't reference counted. > > Based on previous work by Mini Hawthorne. > > 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 > @@ -18,8 +18,6 @@ typedef struct { > } ngx_openssl_conf_t; > > > -static X509 *ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, > - ngx_str_t *cert, STACK_OF(X509) **chain); > static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > ngx_str_t *key, ngx_array_t *passwords); > static int ngx_ssl_password_callback(char *buf, int size, int rwflag, > @@ -443,8 +441,9 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > STACK_OF(X509) *chain; > ngx_ssl_name_t *name; > > - x509 = ngx_ssl_load_certificate(cf->pool, &err, cert, &chain); > - if (x509 == NULL) { > + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT, &err, cert, NULL); > + > + if (chain == NULL) { > if (err != NULL) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "cannot load certificate \"%s\": %s", > @@ -454,6 +453,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_ > return NGX_ERROR; > } > > + x509 = sk_X509_shift(chain); > + > if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) { > ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, > "SSL_CTX_use_certificate(\"%s\") failed", cert->data); > @@ -568,8 +569,10 @@ ngx_ssl_connection_certificate(ngx_conne > EVP_PKEY *pkey; > STACK_OF(X509) *chain; > > - x509 = ngx_ssl_load_certificate(pool, &err, cert, &chain); > - if (x509 == NULL) { > + chain = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_CERT, &err, cert, > + NULL); > + > + if (chain == NULL) { > if (err != NULL) { > ngx_ssl_error(NGX_LOG_ERR, c->log, 0, > "cannot load certificate \"%s\": %s", > @@ -579,6 +582,8 @@ ngx_ssl_connection_certificate(ngx_conne > return NGX_ERROR; > } > > + x509 = sk_X509_shift(chain); > + > if (SSL_use_certificate(c->ssl->connection, x509) == 0) { > ngx_ssl_error(NGX_LOG_ERR, c->log, 0, > "SSL_use_certificate(\"%s\") failed", cert->data); > @@ -630,96 +635,6 @@ ngx_ssl_connection_certificate(ngx_conne > } > > > -static X509 * > -ngx_ssl_load_certificate(ngx_pool_t *pool, char **err, ngx_str_t *cert, > - STACK_OF(X509) **chain) > -{ > - BIO *bio; > - X509 *x509, *temp; > - u_long n; > - > - if (ngx_strncmp(cert->data, "data:", sizeof("data:") - 1) == 0) { > - > - bio = BIO_new_mem_buf(cert->data + sizeof("data:") - 1, > - cert->len - (sizeof("data:") - 1)); > - if (bio == NULL) { > - *err = "BIO_new_mem_buf() failed"; > - return NULL; > - } > - > - } else { > - > - if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, cert) > - != NGX_OK) > - { > - *err = NULL; > - return NULL; > - } > - > - bio = BIO_new_file((char *) cert->data, "r"); > - if (bio == NULL) { > - *err = "BIO_new_file() failed"; > - return NULL; > - } > - } > - > - /* certificate itself */ > - > - x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); > - if (x509 == NULL) { > - *err = "PEM_read_bio_X509_AUX() failed"; > - BIO_free(bio); > - return NULL; > - } > - > - /* rest of the chain */ > - > - *chain = sk_X509_new_null(); > - if (*chain == NULL) { > - *err = "sk_X509_new_null() failed"; > - BIO_free(bio); > - X509_free(x509); > - return NULL; > - } > - > - for ( ;; ) { > - > - temp = PEM_read_bio_X509(bio, NULL, NULL, NULL); > - if (temp == NULL) { > - n = ERR_peek_last_error(); > - > - if (ERR_GET_LIB(n) == ERR_LIB_PEM > - && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) > - { > - /* end of file */ > - ERR_clear_error(); > - break; > - } > - > - /* some real error */ > - > - *err = "PEM_read_bio_X509() failed"; > - BIO_free(bio); > - X509_free(x509); > - sk_X509_pop_free(*chain, X509_free); > - return NULL; > - } > - > - if (sk_X509_push(*chain, temp) == 0) { > - *err = "sk_X509_push() failed"; > - BIO_free(bio); > - X509_free(x509); > - sk_X509_pop_free(*chain, X509_free); > - return NULL; > - } > - } > - > - BIO_free(bio); > - > - return x509; > -} > - > - > static EVP_PKEY * > ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err, > ngx_str_t *key, ngx_array_t *passwords) > 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 > @@ -202,6 +202,9 @@ typedef struct { > #define NGX_SSL_BUFSIZE 16384 > > > +#define NGX_SSL_CACHE_CERT 0 > + > + > ngx_int_t ngx_ssl_init(ngx_log_t *log); > ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data); > > diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c > --- a/src/event/ngx_event_openssl_cache.c > +++ b/src/event/ngx_event_openssl_cache.c > @@ -39,6 +39,12 @@ typedef struct { > static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, > ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); > > +static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); > +static void ngx_ssl_cache_cert_free(void *data); > +static void *ngx_ssl_cache_cert_ref(char **err, void *data); > + > +static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); > + > static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); > static void ngx_ssl_cache_cleanup(void *data); > static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, > @@ -70,6 +76,10 @@ ngx_module_t ngx_openssl_cache_module = > > static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { > > + /* NGX_SSL_CACHE_CERT */ > + { ngx_ssl_cache_cert_create, > + ngx_ssl_cache_cert_free, > + ngx_ssl_cache_cert_ref }, > }; > > > @@ -191,6 +201,166 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca > > > static void * > +ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) > +{ > + BIO *bio; > + X509 *x509; > + u_long n; > + STACK_OF(X509) *chain; > + > + chain = sk_X509_new_null(); > + if (chain == NULL) { > + *err = "sk_X509_new_null() failed"; > + return NULL; > + } > + > + bio = ngx_ssl_cache_create_bio(id, err); > + if (bio == NULL) { > + sk_X509_pop_free(chain, X509_free); > + return NULL; > + } > + > + /* certificate itself */ > + > + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); > + if (x509 == NULL) { > + *err = "PEM_read_bio_X509_AUX() failed"; > + BIO_free(bio); > + sk_X509_pop_free(chain, X509_free); > + return NULL; > + } > + > + if (sk_X509_push(chain, x509) == 0) { > + *err = "sk_X509_push() failed"; > + BIO_free(bio); > + X509_free(x509); > + sk_X509_pop_free(chain, X509_free); > + return NULL; > + } > + > + /* rest of the chain */ > + > + for ( ;; ) { > + > + x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL); > + if (x509 == NULL) { > + n = ERR_peek_last_error(); > + > + if (ERR_GET_LIB(n) == ERR_LIB_PEM > + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE) > + { > + /* end of file */ > + ERR_clear_error(); > + break; > + } > + > + /* some real error */ > + > + *err = "PEM_read_bio_X509() failed"; > + BIO_free(bio); > + sk_X509_pop_free(chain, X509_free); > + return NULL; > + } > + > + if (sk_X509_push(chain, x509) == 0) { > + *err = "sk_X509_push() failed"; > + BIO_free(bio); > + X509_free(x509); > + sk_X509_pop_free(chain, X509_free); > + return NULL; > + } > + } > + > + BIO_free(bio); > + > + return chain; > +} > + > + > +static void > +ngx_ssl_cache_cert_free(void *data) > +{ > + sk_X509_pop_free(data, X509_free); > +} > + > + > +static void * > +ngx_ssl_cache_cert_ref(char **err, void *data) > +{ > + int n, i; > + X509 *x509; > + STACK_OF(X509) *chain; > + > + chain = sk_X509_dup(data); > + if (chain == NULL) { > + *err = "sk_X509_dup() failed"; > + return NULL; > + } > + > + n = sk_X509_num(chain); > + > + for (i = 0; i < n; i++) { > + x509 = sk_X509_value(chain, i); > + > +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) > + X509_up_ref(x509); > +#else > + CRYPTO_add(&x509->references, 1, CRYPTO_LOCK_X509); > +#endif > + } > + > + return chain; > +} > + > + > +static BIO * > +ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) > +{ > + BIO *bio; > + ngx_str_t path; > + ngx_pool_t *temp_pool; > + > + if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { > + > + bio = BIO_new_mem_buf(id->data + sizeof("data:") - 1, > + id->len - (sizeof("data:") - 1)); > + if (bio == NULL) { > + *err = "BIO_new_mem_buf() failed"; > + } > + > + return bio; > + } > + > + temp_pool = ngx_create_pool(NGX_MAX_PATH, ngx_cycle->log); > + if (temp_pool == NULL) { > + *err = NULL; > + return NULL; > + } > + > + ngx_memcpy(&path, id, sizeof(ngx_str_t)); > + > + if (ngx_get_full_name(temp_pool, > + (ngx_str_t *) &ngx_cycle->conf_prefix, > + &path) > + != NGX_OK) > + { > + *err = NULL; > + ngx_destroy_pool(temp_pool); > + return NULL; > + } > + > + bio = BIO_new_file((char *) path.data, "r"); > + if (bio == NULL) { > + *err = "BIO_new_file() failed"; > + } > + > + ngx_destroy_pool(temp_pool); > + > + return bio; > +} > + > + > +static void * > ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) > { > ngx_ssl_cache_t *cache; > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel The patch introduces a small difference in the behavior: we no longer resolve the full name preemptively and modify the paths in place. This results in: * possible duplication if the same object is referenced both by absolute and relative paths * relative certificate paths in the name_rbtree and in the OCSP stapling log Below is the patch addressing that and a test update. Note that a few lines dealing with NGX_SSL_CACHE_KEY should be applied to the patch 4. Otherwise, the series looks good to me. (There's one more difference in behavior; we started accepting "data:" in the trusted CA and CRL configuration directives. I've been reviewing with assumption that it is an intended consequence.) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 0f97bc3af1..48155a2cdd 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -569,7 +569,7 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, EVP_PKEY *pkey; STACK_OF(X509) *chain; - chain = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_CERT, &err, cert, + chain = ngx_ssl_cache_connection_fetch(c, NGX_SSL_CACHE_CERT, &err, cert, NULL); if (chain == NULL) { @@ -611,7 +611,7 @@ ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, #endif - pkey = ngx_ssl_cache_connection_fetch(NGX_SSL_CACHE_KEY, &err, key, + pkey = ngx_ssl_cache_connection_fetch(c, NGX_SSL_CACHE_KEY, &err, key, passwords); if (pkey == NULL) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 079d0003a1..e630767978 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -241,8 +241,8 @@ ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); void *ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_str_t *id, void *data); -void *ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, - ngx_str_t *id, void *data); +void *ngx_ssl_cache_connection_fetch(ngx_connection_t *c, ngx_uint_t index, + char **err, ngx_str_t *id, void *data); ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 2cbef83faa..7492b8399e 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -57,6 +57,9 @@ static void *ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data); static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err); +static ngx_int_t ngx_ssl_cache_full_name(ngx_pool_t *pool, ngx_uint_t index, + ngx_str_t *id); + static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); static void ngx_ssl_cache_cleanup(void *data); static void ngx_ssl_cache_node_insert(ngx_rbtree_node_t *temp, @@ -120,6 +123,10 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, ngx_ssl_cache_type_t *type; ngx_ssl_cache_node_t *cn; + if (ngx_ssl_cache_full_name(cf->pool, index, id) != NGX_OK) { + return NULL; + } + value = NULL; hash = ngx_murmur_hash2(id->data, id->len); @@ -168,9 +175,13 @@ ngx_ssl_cache_fetch(ngx_conf_t *cf, ngx_uint_t index, char **err, void * -ngx_ssl_cache_connection_fetch(ngx_uint_t index, char **err, - ngx_str_t *id, void *data) +ngx_ssl_cache_connection_fetch(ngx_connection_t *c, ngx_uint_t index, + char **err, ngx_str_t *id, void *data) { + if (ngx_ssl_cache_full_name(c->pool, index, id) != NGX_OK) { + return NULL; + } + return ngx_ssl_cache_types[index].create(id, err, data); } @@ -646,8 +657,6 @@ static BIO * ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) { BIO *bio; - ngx_str_t path; - ngx_pool_t *temp_pool; if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { @@ -660,35 +669,32 @@ ngx_ssl_cache_create_bio(ngx_str_t *id, char **err) return bio; } - temp_pool = ngx_create_pool(NGX_MAX_PATH, ngx_cycle->log); - if (temp_pool == NULL) { - *err = NULL; - return NULL; - } - - ngx_memcpy(&path, id, sizeof(ngx_str_t)); - - if (ngx_get_full_name(temp_pool, - (ngx_str_t *) &ngx_cycle->conf_prefix, - &path) - != NGX_OK) - { - *err = NULL; - ngx_destroy_pool(temp_pool); - return NULL; - } - - bio = BIO_new_file((char *) path.data, "r"); + bio = BIO_new_file((char *) id->data, "r"); if (bio == NULL) { *err = "BIO_new_file() failed"; } - ngx_destroy_pool(temp_pool); - return bio; } +static ngx_int_t +ngx_ssl_cache_full_name(ngx_pool_t *pool, ngx_uint_t index, ngx_str_t *id) +{ + if (ngx_strncmp(id->data, "data:", sizeof("data:") - 1) == 0) { + return NGX_OK; + } + + if (index == NGX_SSL_CACHE_KEY + && ngx_strncmp(id->data, "engine:", sizeof("engine:") - 1) == 0) + { + return NGX_OK; + } + + return ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, id); +} + + static void * ngx_openssl_cache_create_conf(ngx_cycle_t *cycle) { diff --git a/ssl_cache.t b/ssl_cache.t index 96bd6a83ef..44935c3386 100644 --- a/ssl_cache.t +++ b/ssl_cache.t @@ -29,8 +29,10 @@ my $t = Test::Nginx->new(); plan(skip_all => "not yet") unless $t->has_version('1.27.2'); +my $d = $t->testdir(); + $t->has(qw/http http_ssl socket_ssl/)->has_daemon('openssl') - ->write_file_expand('nginx.conf', <<'EOF'); + ->write_file_expand('nginx.conf', << "EOF"); %%TEST_GLOBALS%% @@ -64,12 +66,22 @@ http { ssl_client_certificate root.crt.fifo; ssl_crl root.crl.fifo; } + + server { + listen 127.0.0.1:8445 ssl; + server_name localhost; + + ssl_certificate $d/localhost.crt.fifo; + ssl_certificate_key $d/localhost.key.fifo; + + ssl_verify_client on; + ssl_client_certificate $d/root.crt.fifo; + ssl_crl $d/root.crl.fifo; + } } EOF -my $d = $t->testdir(); - $t->write_file('openssl.conf', <write_file('t', ''); -$t->plan(2)->run(); +$t->plan(3)->run(); ############################################################################### like(get(8443), qr/200 OK/, 'cached certificate'); like(get(8444, 'client'), qr/200 OK/, 'cached CA and CRL'); +like(get(8445, 'client'), qr/200 OK/, 'cached objects with absolute path'); ############################################################################### From Namrata.Bhave at ibm.com Fri Aug 30 10:05:46 2024 From: Namrata.Bhave at ibm.com (Namrata Bhave) Date: Fri, 30 Aug 2024 10:05:46 +0000 Subject: Binaries for s390x architecture In-Reply-To: References: Message-ID: Hi Sergey, We received an email reply on same from Nina Forsyth. S390x infra can be requested from a public program. We have shared below info and looking forward to response: "For open source related projects IBM now has a public program for requesting virtual machines for s390x. A member of the Nginx team can put in a request via the following form. When approved and sign-up is complete resources can be set up and delegated as appropriate. https://www.ibm.com/community/z/open-source/virtual-machines-request/ I hope this will help restore Nginx open source binaries on s390x architecture. Thanks in advance for all your help!" Thank you, Regards, Namrata -----Original Message----- From: Sergey A. Osokin Sent: Thursday, August 29, 2024 8:33 PM To: Namrata Bhave via nginx-devel Cc: Namrata Bhave ; Rishi Misra Subject: [EXTERNAL] Re: Binaries for s390x architecture Hi there, I do believe this is about of the no access to the s390 infra. Once we get access we'll take a look how we can help. Thank you. -- Sergey A. Osokin On Wed, Aug 28, 2024 at 02:13:21PM +0000, Namrata Bhave via nginx-devel wrote: > Hi, > > This is regarding Nginx Binaries on s390x(IBM Z) platform. It looks like s390x binaries are not published anymore, and latest versions are not found under "http://nginx.org/packages/ " > > Found this PR which removed s390x: > INVALID URI REMOVED > nginx.org_pull_56&d=DwIBaQ&c=BSDicqBQBDjDI9RkVyTcHQ&r=El_gSPevXf94V0bV > 9KCzlKjbR-Q_7CwJVow4JeBOCaU&m=Akk8sLPKGsYgo2-E2zs-YgxvMV2R9Oos0Z1AJXCC > mHGQ9tckIY539OHpnufld1nv&s=_HCK__uE4AgvbCd8ADvpoJp4liDOvkpOnWWowG6mPBw > &e= However it is not clear from the PR why this architecture was > dropped. > > Could someone please help in providing information about this? > > > Thank you very much, > > Regards, > Namrata > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > INVALID URI REMOVED > _mailman_listinfo_nginx-2Ddevel&d=DwIBaQ&c=BSDicqBQBDjDI9RkVyTcHQ&r=El_gSPevXf94V0bV9KCzlKjbR-Q_7CwJVow4JeBOCaU&m=Akk8sLPKGsYgo2-E2zs-YgxvMV2R9Oos0Z1AJXCCmHGQ9tckIY539OHpnufld1nv&s=5aMLIXAIovDq85qxz7Utg7Q_klkZxHuTLlSABCDkJNM&e= From noreply at nginx.com Fri Aug 30 16:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 30 Aug 2024 16:36:02 +0000 (UTC) Subject: [nginx] Moved LICENSE and README to root. Message-ID: <20240830163602.161764883A@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/863ab647cd659d6b326c61a1f1227629978ce132 branches: master commit: 863ab647cd659d6b326c61a1f1227629978ce132 user: Roman Arutyunyan date: Thu, 29 Aug 2024 16:24:03 +0400 description: Moved LICENSE and README to root. --- docs/text/LICENSE => LICENSE | 0 docs/text/README => README | 0 misc/GNUmakefile | 2 -- 3 files changed, 2 deletions(-) diff --git a/docs/text/LICENSE b/LICENSE similarity index 100% rename from docs/text/LICENSE rename to LICENSE diff --git a/docs/text/README b/README similarity index 100% rename from docs/text/README rename to README diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 7d4b657de..4b6a12759 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -15,8 +15,6 @@ release: export mv $(TEMP)/$(NGINX)/auto/configure $(TEMP)/$(NGINX) - mv $(TEMP)/$(NGINX)/docs/text/LICENSE $(TEMP)/$(NGINX) - mv $(TEMP)/$(NGINX)/docs/text/README $(TEMP)/$(NGINX) mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) mv $(TEMP)/$(NGINX)/docs/man $(TEMP)/$(NGINX) From noreply at nginx.com Fri Aug 30 16:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 30 Aug 2024 16:36:02 +0000 (UTC) Subject: [nginx] Removed C-style comments from LICENSE. Message-ID: <20240830163602.1A20C4883B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/6bb4be1a79e8c8e8a07f4df1c77a80c65b3b6f47 branches: master commit: 6bb4be1a79e8c8e8a07f4df1c77a80c65b3b6f47 user: Roman Arutyunyan date: Thu, 29 Aug 2024 17:14:25 +0400 description: Removed C-style comments from LICENSE. --- LICENSE | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/LICENSE b/LICENSE index 985470ef9..a7ec58a75 100644 --- a/LICENSE +++ b/LICENSE @@ -1,26 +1,24 @@ -/* - * Copyright (C) 2002-2021 Igor Sysoev - * Copyright (C) 2011-2024 Nginx, Inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ +Copyright (C) 2002-2021 Igor Sysoev +Copyright (C) 2011-2024 Nginx, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. From noreply at nginx.com Fri Aug 30 16:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 30 Aug 2024 16:36:02 +0000 (UTC) Subject: [nginx] Switched GNUmakefile from hg to git. Message-ID: <20240830163602.1219848839@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/81a933e1f6fa26012d6736006456171e1f1b8977 branches: master commit: 81a933e1f6fa26012d6736006456171e1f1b8977 user: Roman Arutyunyan date: Thu, 29 Aug 2024 16:03:58 +0400 description: Switched GNUmakefile from hg to git. --- misc/GNUmakefile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index fa4c36d49..7d4b657de 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -30,12 +30,12 @@ release: export export: rm -rf $(TEMP) - hg archive -X '.hg*' $(TEMP)/$(NGINX) + git archive --prefix=$(TEMP)/$(NGINX)/ HEAD | tar -x -f - --exclude '.git*' RELEASE: - hg ci -m nginx-$(VER)-RELEASE - hg tag -m "release-$(VER) tag" release-$(VER) + git commit -m nginx-$(VER)-RELEASE + git tag -m "release-$(VER) tag" release-$(VER) $(MAKE) -f misc/GNUmakefile release @@ -93,8 +93,8 @@ zip: export sed -i '' -e "s/$$/`printf '\r'`/" $(TEMP)/$(NGINX)/conf/* - mv $(TEMP)/$(NGINX)/docs/text/LICENSE $(TEMP)/$(NGINX)/docs.new - mv $(TEMP)/$(NGINX)/docs/text/README $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/LICENSE $(TEMP)/$(NGINX)/docs.new + mv $(TEMP)/$(NGINX)/README $(TEMP)/$(NGINX)/docs.new mv $(TEMP)/$(NGINX)/docs/html $(TEMP)/$(NGINX) rm -r $(TEMP)/$(NGINX)/docs From noreply at nginx.com Fri Aug 30 16:36:02 2024 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 30 Aug 2024 16:36:02 +0000 (UTC) Subject: [nginx] Removed .hgtags file. Message-ID: <20240830163602.0F57E48835@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/900f4dc48c07d6f6961744fd152d3afb10a2bd9f branches: master commit: 900f4dc48c07d6f6961744fd152d3afb10a2bd9f user: Roman Arutyunyan date: Thu, 29 Aug 2024 16:03:27 +0400 description: Removed .hgtags file. --- .hgtags | 482 ---------------------------------------------------------------- 1 file changed, 482 deletions(-) diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 2c9cb3bc6..000000000 --- a/.hgtags +++ /dev/null @@ -1,482 +0,0 @@ -551102312e19b704cd22bd7254a9444b9ea14e96 release-0.1.0 -23fb87bddda14ce9faec90f774085634106aded4 release-0.1.1 -295d97d70c698585705345f1a8f92b02e63d6d0d release-0.1.2 -ded1284520cc939ad5ae6ddab39925375e64237d release-0.1.3 -0491b909ef7612d8411f1f59054186c1f3471b52 release-0.1.4 -a88a3e4e158fade0aaa6f3eb25597d5ced2c1075 release-0.1.5 -1f31dc6d33a3a4e65240b08066bf186df9e33b79 release-0.1.6 -5aecc125bc33d81d6214c91d73eb44230a903dde release-0.1.7 -bbd6b0b4a2b15ef8c8f1aaf7b027b6da47303524 release-0.1.8 -2ff194b74f1e60cd04670986973e3b1a6aa3bece release-0.1.9 -31ee1b50354fb829564b81a6f34e8d6ceb2d3f48 release-0.1.10 -8e8f3af115b5b903b2b8f3335de971f18891246f release-0.1.11 -c3c2848fc081e19aec5ffa97e468ad20ddb81df0 release-0.1.12 -ad1e9ebf93bb5ae4c748d471fad2de8a0afc4d2a release-0.1.13 -c5240858380136a67bec261c59b1532560b57885 release-0.1.14 -fd661d14a7fad212e326a7dad6234ea0de992fbf release-0.1.15 -621229427cba1b0af417ff2a101fc4f17a7d93c8 release-0.1.16 -4ebe09b07e3021f1a63b459903ec58f162183b26 release-0.1.17 -31ff3e943e1675a2caf745ba7a981244445d4c98 release-0.1.18 -45a460f82aec80b0f61136aa09f412436d42203a release-0.1.19 -0f836f0288eee4980f57736d50a7a60fa082d8e9 release-0.1.20 -975f62e77f0244f1b631f740be77c72c8f2da1de release-0.1.21 -fc9909c369b2b4716304ac8e38da57b8fb781211 release-0.1.22 -d7c90bb5ce83dab08715e98f9c7b81c7df4b37be release-0.1.23 -64d9afb209da0cd4a917202b7b77e51cc23e2229 release-0.1.24 -d4ea69372b946dc4ec37fc3f5ddd93ff7c3da675 release-0.1.25 -b1648294f6935e993e436fd8a68bca75c74c826d release-0.1.26 -ee66921ecd47a7fa459f70f4a9d660f91f6a1b94 release-0.1.27 -cd3117ad9aab9c58c6f7e677e551e1adbdeaba54 release-0.1.28 -9b8c906f6e63ec2c71cecebfff35819a7d32227d release-0.1.29 -c12967aadd8726daf2d85e3f3e622d89c42db176 release-0.1.30 -fbbf16224844e7d560c00043e8ade8a560415bba release-0.1.31 -417a087c9c4d9abb9b0b9b3f787aff515c43c035 release-0.1.32 -dadfa78d227027348d7f9d1e7b7093d06ba545a0 release-0.1.33 -12234c998d83bfbbaa305273b3dd1b855ca325dc release-0.1.34 -6f00349b98e5f706b82115c6e4dc84456fc0d770 release-0.1.35 -2019117e6b38cc3e89fe4f56a23b271479c627a6 release-0.1.36 -09b42134ac0c42625340f16628e29690a04f8db5 release-0.1.37 -7fa11e5c6e9612ecff5eb58274cc846ae742d1d2 release-0.1.38 -e5d7d0334fdb946133c17523c198800142ac9fe9 release-0.1.39 -c3bd8cdabb8f73e5600a91f198eb7df6fac65e92 release-0.1.40 -d6e48c08d718bf5a9e58c20a37e8ae172bff1139 release-0.1.41 -563ad09abf5042eb41e8ecaf5b4e6c9deaa42731 release-0.1.42 -c9ad0d9c7d59b2fa2a5fe669f1e88debd03e6c04 release-0.1.43 -371c1cee100d7a1b0e6cad4d188e05c98a641ee7 release-0.1.44 -b09ee85d0ac823e36861491eedfc4dfafe282997 release-0.1.45 -511a89da35ada16ae806667d699f9610b4f8499a release-0.2.0 -0148586012ab3dde69b394ec5a389d44bb11c869 release-0.2.1 -818fbd4750b99d14d2736212c939855a11b1f1ef release-0.2.2 -e16a8d574da511622b97d6237d005f40f2cddb30 release-0.2.3 -483cca23060331f2078b1c2984870d80f288ad41 release-0.2.4 -45033d85b30e3f12c407b7cfc518d76e0eda0263 release-0.2.5 -7bd37aef1e7e87858c12b124e253e98558889b50 release-0.2.6 -ecd9c160f25b7a7075dd93383d98a0fc8d8c0a41 release-0.3.0 -c1f965ef97188fd7ef81342dcf8719da18c554d2 release-0.3.1 -e48ebafc69393fc94fecfdf9997c4179fd1ce473 release-0.3.2 -9c2f3ed7a24711d3b42b124d5f831155c8beff95 release-0.3.3 -7c1369d37c7eb0017c28ebcaa0778046f5aafdcc release-0.3.4 -1af2fcb3be8a63796b6b23a488049c92a6bc12f4 release-0.3.5 -174f1e853e1e831b01000aeccfd06a9c8d4d95a2 release-0.3.6 -458b6c3fea65a894c99dd429334a77bb164c7e83 release-0.3.7 -58475592100cb792c125101b6d2d898f5adada30 release-0.3.8 -fcd6fc7ff7f9b132c35193d834e6e7d05026c716 release-0.3.9 -4d9ea73a627a914d364e83e20c58eb1283f4031d release-0.3.10 -4c5c2c55975c1152b5ca5d5d55b32d4dd7945f7a release-0.3.11 -326634fb9d47912ad94221dc2f8fa4bec424d40c release-0.3.12 -4e296b7d25bf62390ca2afb599e395426b94f785 release-0.3.13 -401de5a43ba5a8acdb9c52465193c0ea7354afe7 release-0.3.14 -284cc140593bb16ac71094acd509ab415ff4837d release-0.3.15 -d4e858a5751a7fd08e64586795ed7d336011fbc0 release-0.3.16 -8c0cdd81580eb76d774cfc5724de68e7e5cbbdc2 release-0.3.17 -425af804d968f30eeff01e33b808bc2e8c467f2c release-0.3.18 -ebc68d8ca4962fe3531b7e13444f7ac4395d9c6e release-0.3.19 -9262f520ce214d3d5fd7c842891519336ef85ca6 release-0.3.20 -869b6444d2341a587183859d4df736c7f3381169 release-0.3.21 -77f77f53214a0e3a68fef8226c15532b54f2c365 release-0.3.22 -858700ae46b453ea111b966b6d03f2c21ddcb94e release-0.3.23 -5dac8c7fb71b86aafed8ea352305e7f85759f72e release-0.3.24 -77cdfe394a94a625955e7585e09983b3af9b889b release-0.3.25 -608cf78b24ef7baaf9705e4715a361f26bb16ba9 release-0.3.26 -3f8a2132b93d66ac19bec006205a304a68524a0b release-0.3.27 -c73c5c58c619c22dd3a5a26c91bb0567a62c6930 release-0.3.28 -5ef026a2ac7481f04154f29ab49377bf99aaf96f release-0.3.29 -51b27717f140b71a2e9158807d79da17c888ce4c release-0.3.30 -7a16e281c01f1c7ab3b79c64b43ddb754ea7935e release-0.3.31 -93e85a79757c49d502e42a1cb8264a0f133b0b00 release-0.3.32 -0216fd1471f386168545f772836156761eddec08 release-0.3.33 -fbed40ce7cb4fd7203fecc22a617b9ce5b950fb3 release-0.3.34 -387450de0b4d21652f0b6242a5e26a31e3be8d8c release-0.3.35 -65bf042c0b4f39f18a235464c52f980e9fa24f6b release-0.3.36 -5d2b8078c1c2593b95ec50acfeeafbefa65be344 release-0.3.37 -f971949ffb585d400e0f15508a56232a0f897c80 release-0.3.38 -18268abd340cb351e0c01b9c44e9f8cc05492364 release-0.3.39 -e60fe4cf1d4ea3c34be8c49047c712c6d46c1727 release-0.3.40 -715d243270806d38be776fc3ed826d97514a73d6 release-0.3.41 -5e8fb59c18c19347a5607fb5af075fe1e2925b9a release-0.3.42 -947c6fd27699e0199249ad592151f844c8a900b0 release-0.3.43 -4946078f0a79e6cc952d3e410813aac9b8bda650 release-0.3.44 -95d7da23ea5315a6e9255ce036ed2c51f091f180 release-0.3.45 -1e720b0be7ecd92358da8a60944669fa493e78cd release-0.3.46 -39b7d7b33c918d8f4abc86c4075052d8c19da3c7 release-0.3.47 -7cbef16c71a1f43a07f8141f02e0135c775f0f5b release-0.3.48 -4c8cd5ae5cc100add5c08c252d991b82b1838c6b release-0.3.49 -400711951595aef7cd2ef865b84b31df52b15782 release-0.3.50 -649c9063d0fda23620eaeaf0f6393be0a672ebe7 release-0.3.51 -9079ee4735aefa98165bb2cb26dee4f58d58c1d7 release-0.3.52 -6d5c1535bb9dcd891c5963971f767421a334a728 release-0.3.53 -5fd7a5e990477189c40718c8c3e01002a2c20b81 release-0.3.54 -63a820b0bc6ca629c8e45a069b52d622ddc27a2d release-0.3.55 -562806624c4afb1687cba83bc1852f5d0fecbac3 release-0.3.56 -cec32b3753acf610ac1a6227d14032c1a89d6319 release-0.3.57 -b80f94fa2197b99db5e033fec92e0426d1fe5026 release-0.3.58 -e924670896abe2769ea0fcfd2058b405bed8e8ec release-0.3.59 -921a7ce4baf42fd1091b7e40f89c858c6b23053e release-0.3.60 -df95dcff753a6dc5e94257302aea02c18c7a7c87 release-0.3.61 -7e24168b0853ee7e46c9c7b943ef077dc64f17f5 release-0.4.0 -8183d4ba50f8500465efb27e66dd23f98775dd21 release-0.4.1 -610267a772c7bf911b499d37f66c21ce8f2ebaf7 release-0.4.2 -39dd0b045441e21512e0a6061a03d0df63414d8b release-0.4.3 -5e42c1615f4de0079bd4d8913886d588ce6a295d release-0.4.4 -40266f92b829a870808b3d4ee54c8fccdecbd2d6 release-0.4.5 -56e33c6efee7ff63cdc52bd1cf172bde195079df release-0.4.6 -119bad43bfd493400c57a05848eada2c35a46810 release-0.4.7 -0f404f82a1343cb4e4b277a44e3417385798e5e5 release-0.4.8 -d24a717314365c857b9f283d6072c2a427d5e342 release-0.4.9 -d6f0a00015fdef861fd67fb583b9690638650656 release-0.4.10 -e372368dadd7b2ecd0182b2f1b11db86fc27b2c3 release-0.4.11 -fd57967d850d2361072c72562d1ed03598473478 release-0.4.12 -979045fdcbd20cf7188545c1c589ff240251f890 release-0.4.13 -93c94cfa9f78f0a5740595dde4466ec4fba664f8 release-0.4.14 -589ee12e8d7c2ae5e4f4676bcc7a1279a76f9e8e release-0.5.0 -13416db8a807e5acb4021bc3c581203de57e2f50 release-0.5.1 -06c58edc88831fb31c492a8eddcf2c6056567f18 release-0.5.2 -e2ac5fa41bcba14adbbb722d45c083c30c07bb5c release-0.5.3 -393dbc659df15ccd411680b5c1ce87ed86d4c144 release-0.5.4 -38cc7bd8e04f2c519fd4526c12841a876be353cb release-0.5.5 -6d1fcec2ea79101c756316c015f72e75f601a5ab release-0.5.6 -aed8a9de62456c4b360358bc112ccca32ce02e8d release-0.5.7 -7642f45af67d805452df2667486201c36efaff85 release-0.5.8 -779216610662c3a459935d506f66a9b16b9c9576 release-0.5.9 -9eeb585454f3daa30cf768e95c088a092fe229b9 release-0.5.10 -bb491c8197e38ca10ae63b1f1ecb36bf6fdaf950 release-0.5.11 -613369e08810f36bbcc9734ef1059a03ccbf5e16 release-0.5.12 -bd796ef5c9c9dd34bfac20261b98685e0410122a release-0.5.13 -8a730c49f906d783b47e4b44d735efd083936c64 release-0.5.14 -cb447039152d85e9145139ff2575a6199b9af9d4 release-0.5.15 -64854c7c95d04f838585ca08492823000503fa61 release-0.5.16 -d1ffcf84ea1244f659145c36ff28de6fcdf528b2 release-0.5.17 -796a6e30ca9d29504195c10210dbc8deced0ae83 release-0.5.18 -1f81c711d2a039e1f93b9b515065a2235372d455 release-0.5.19 -8e8f6082654aedb4438c8fca408cfc316c7c5a2a release-0.5.20 -e9551132f7dd40da5719dd5bcf924c86f1436f85 release-0.5.21 -533a252896c4d1cff1586ae42129d610f7497811 release-0.5.22 -f461a49b6c747e0b67f721f2be172902afea5528 release-0.5.23 -2d5ef73671f690b65bf6d9e22e7155f68f484d5a release-0.5.24 -77bf42576050862c268e267ef3e508b145845a25 release-0.5.25 -2aefee4d4ed69eb7567680bf27a2efd212232488 release-0.6.0 -7ac0fe9bec9a2b5f8e191f6fdd6922bfd916a6cb release-0.6.1 -4882735ebc71eeec0fbfe645bdfdb31306872d82 release-0.6.2 -b94731c73d0922f472ff938b9d252ba29020f20c release-0.6.3 -13e649b813d6ccba5db33a61e08ebe09d683cd5b release-0.6.4 -80de622646b0059fd4c553eff47c391bf7503b89 release-0.6.5 -3b05edb2619d5935023b979ee7a9611b61b6c9e5 release-0.6.6 -1dcfd375100c4479611f71efb99271d0a3059215 release-0.6.7 -0228185d4c5772947b842e856ad74cf7f7fd52f3 release-0.6.8 -d1879c52326ecac45c713203670f54220879911e release-0.6.9 -5a80c6ccbe2ad24fa3d4ff6f9fe4a2b07408d19d release-0.6.10 -f88a8b0b39601b19cd740e4db614ab0b5b874686 release-0.6.11 -5557460a7247a1602ae96efd1d0ccf781344cb58 release-0.6.12 -451b02cc770a794cd41363461b446948ae1d8bc8 release-0.6.13 -537b6ef014c4a133e0ab0b7dc817508e0647e315 release-0.6.14 -5e68764f0d6e91a983170fa806e7450a9e9b33fe release-0.6.15 -158aa4e8cc46fcf9504a61469d22daf3476b17bf release-0.6.16 -d8fcca555542619228d9fab89e1665b993f8c3ee release-0.6.17 -60707ebc037086cf004736a0d4979e2a608da033 release-0.6.18 -3c2a99d3a71af846855be35e62edb9a12f363f44 release-0.6.19 -3e0a27f9358ffc1b5249e0ea2311ce7da5c8967e release-0.6.20 -143f4d65b1c875d6563ccb7f653d9157afc72194 release-0.6.21 -95e6160d2b7d0af8ffd1b95a23cadadf8f0b3f6d release-0.6.22 -69a03d5e3b6e6660079ef1ef172db7ac08d8370e release-0.6.23 -3e2a58fb48f1e1a99ebf851e0d47a7034c52ae22 release-0.6.24 -3b8607c05a8bebcfa59235c2126a70d737f0ccf5 release-0.6.25 -07ad5b2606614c4be4ee720c46cf4af126059d31 release-0.6.26 -be531addfabe5214f409d457140c1038af10d199 release-0.6.27 -58f05255d3a345d04baef5cff0ca1ae0ac7ecebb release-0.6.28 -eb2bd21dc8d03f6c94016f04ffb9adaf83a2b606 release-0.6.29 -55408deb3cd171efa9b81d23d7a1dd1ccde0b839 release-0.6.30 -d4288915bba73c4c3c9cf5d39d34e86879eb2b45 release-0.6.31 -0a189588830b8629c4dfea68feb49af36b59e4a9 release-0.7.0 -6ab27a06f3346cf9ec8737f5dbcc82dd4031e30f release-0.7.1 -a07e258cef3b0a0b6e76a6ff4ba4651c5facc85a release-0.7.2 -9992c4583513d2804fc2e7fec860fbc7ab043009 release-0.7.3 -4dc24d50230fbadfc037a414a86390db2de69dd2 release-0.7.4 -9527137b4354a648a229c7169850c7c65272c00d release-0.7.5 -c2f0f7cf306f302254beae512bda18713922375c release-0.7.6 -bbcf6d75556fdcee8bd4aba8f6c27014be9920ee release-0.7.7 -43bde71f0bbe5a33b161760d7f9f980d50386597 release-0.7.8 -769f0dd7081e9011394f264aa22aa66fd79730d8 release-0.7.9 -511edfa732da637f5f0c9476335df7dca994706d release-0.7.10 -0e7023bf6b2461309c29885935443449a41be807 release-0.7.11 -9ad1bd2b21d93902863807528e426862aedee737 release-0.7.12 -d90ea21e24ea35379aef50c5d70564158e110a15 release-0.7.13 -c07d2d20d95c83d804079bbdcecbce4a0c8282f0 release-0.7.14 -0cd7bb051f67eac2b179fb9f9cc988b9ba18ed76 release-0.7.15 -eab2e87deba73ae6abd9cc740e8d4365bed96322 release-0.7.16 -91d7a9eb8ade90e9421d7b1e3c2e47a6bc427876 release-0.7.17 -fc10f7b5cb1305fb930f8ac40b46882d0828d61e release-0.7.18 -9dba9779e37e5969a2d408c792084fd7acfec062 release-0.7.19 -61838d1bcbddc7bc4dd9f30d535573a6fddca8f9 release-0.7.20 -5f665d0fa6a5f6e748157f2ccbc445b2db8125d0 release-0.7.21 -24763afa5efe91e54f00b2ae5b87666eb6c08c3b release-0.7.22 -0562fb355a25266150cbe8c8d4e00f55e3654df3 release-0.7.23 -19c452ecd083550816873a8a31eb3ed9879085e6 release-0.7.24 -46b68faf271d6fdcaaf3ad2c69f6167ea9e9fa28 release-0.7.25 -d04bfca0c7e3ae2e4422bc1d383553139d6f0a19 release-0.7.26 -9425d9c7f8ead95b00a3929a9a5e487e0e3c8499 release-0.7.27 -fbc3e7e8b3ee756568a875f87d8a954a2f9d3bf6 release-0.7.28 -5176dfdf153fc785b18604197d58806f919829ad release-0.7.29 -87e07ccdf0a4ec53458d9d7a4ea66e1239910968 release-0.7.30 -9fddd7e1a7a27f8463867f41a461aad57df461b2 release-0.7.31 -780b2ba1ec6daf6e3773774e26b05b9ff0d5483e release-0.7.32 -83027471a25385b1c671968be761e9aa7a8591a7 release-0.7.33 -1e9a362c3dcee221ca6e34308c483ed93867aca2 release-0.7.34 -c7ee9e15717b54ead5f4a554686e74abe66c6b07 release-0.7.35 -b84548abe9b9d4f4e203f848696e52c8c82c308f release-0.7.36 -3286f0bab8e77dbc7ebb370b1dc379592ccff123 release-0.7.37 -11a4e2ed5b166b9c9f119171aa399a9e3aa4684a release-0.7.38 -f822655d4120629977794c32d3b969343b6c30db release-0.7.39 -8a350e49d2b6751296db6d8e27277ccf63ed412a release-0.7.40 -c4a56c197eeafd71fc1caef7a9d890a330e3c23d release-0.7.41 -a9575a57a5443df39611774cf3840e9088132b0e release-0.7.42 -7503d95d6eadad14c28b2db183ba09848265274b release-0.7.43 -9be652e9114435fc6f1fdec84c0458d56702db91 release-0.7.44 -797e070d480a34b31ddac0d364784773f1bbbcf9 release-0.7.45 -9b5037e7ec7db25875c40f9d1cf20a853388b124 release-0.7.46 -d1d0e6d7ff0ca3c0dd1be1ef1cfff2e3fd0b4e1c release-0.7.47 -9816fb28eda599bfd53940e6d3b6617d1ecb6323 release-0.7.48 -452b9d09df8e3f2fb04b2a33d04d2f3a6436eb34 release-0.7.49 -e4350efa7cf7a0e868c2236a1137de8a33bd8ec6 release-0.7.50 -f51f2bec766c8b6d7e1799d904f18f8ea631bd44 release-0.7.51 -18e39e566781c9c187e2eb62bebd9d669d68f08c release-0.7.52 -b073eaa1dcea296a3488b83d455fab6621a73932 release-0.7.53 -01c6fe6c2a55998434cd3b05dd10ca487ac3fb6c release-0.7.54 -3ed9377e686f2521e6ec15873084381033fb490d release-0.7.55 -a1e44954549c35023b409f728c678be8bf898148 release-0.7.56 -fbb1918a85e38a7becdb1a001dbaf5933f23a919 release-0.7.57 -87f4a49a9cc34a5b11c8784cc5ea89e97b4b2bd8 release-0.7.58 -0c22cb4862c8beb4ee1b9e4627125162a29a5304 release-0.7.59 -82d56c2425ef857cd430b8530a3f9e1127145a67 release-0.8.0 -f4acb784b53cd952559567971b97dde1e818a2b6 release-0.8.1 -b3503597c1a0f0f378afdc5e5e5b85e2c095a4be release-0.8.2 -c98da980514a02ba81c421b25bf91803ffffddf3 release-0.8.3 -db34ec0c53c4b9dec12ffdf70caf89a325ab9577 release-0.8.4 -0914802433b8678ba2cdf91280766f00f4b9b76e release-0.8.5 -ff52ee9e6422f3759f43a442b7ba615595b3a3d4 release-0.8.6 -7607237b4829fff1f60999f4663c50ed9d5182f7 release-0.8.7 -1cef1807bc12cb05ac52fb0e7a0f111d3760b569 release-0.8.8 -a40f8475511d74a468ade29c1505e8986600d7a3 release-0.8.9 -2d9faf2260df6c3e5d4aa1781493c31f27a557d0 release-0.8.10 -d0d61c32331a6505381b5218318f7b69db167ca8 release-0.8.11 -ca7a1c6c798a7eb5b294d4ac3179ec87ecf297d3 release-0.8.12 -81c8277cd8ed55febcb2dd9d9213076f6c0ccb09 release-0.8.13 -3089486a8dc5844b5b6e9f78d536b4b26f7ffa16 release-0.8.14 -d364c2c12dd9723a2dfac3f096f5e55d4cfe6838 release-0.8.15 -52163a1027c3efd6b4c461b60a2ca6266c23e193 release-0.8.16 -06564e9a2d9ec5852132c212e85eda0bf1300307 release-0.8.17 -7aaa959da85e09e29bcac3b1cadec35b0a25b64d release-0.8.18 -4bc73c644329a510da4e96b7241b80ead7772f83 release-0.8.19 -ea3d168fb99c32a5c3545717ecc61e85a375e5dd release-0.8.20 -27951ca037e63dae45ff5b6279124c224ae1255a release-0.8.21 -d56c8b5df517c2bf6e7bc2827b8bf3e08cda90e1 release-0.8.22 -3c6ac062b379b126212cbb27e98a3c8275ef381a release-0.8.23 -89b9173476de14688b1418fbf7df10f91d1719ef release-0.8.24 -aa550cb4159ae0d566006e091fb1c7a888771050 release-0.8.25 -06ce92293f6a65651b08c466f90f55bd69984b98 release-0.8.26 -ea50b0d79ef1d7d901cd0e4dcd7373447849d719 release-0.8.27 -e68b1c35cad86105ff1c5b240f53442f4c36356e release-0.8.28 -78d3582a30afe63fc0adb17c3ac8891a64e47146 release-0.8.29 -9852c5965a3292a1b6127dbb4da9fce4912d898a release-0.8.30 -4f84115914490e572bcbee5069157b7334df2744 release-0.8.31 -59dee6f7f3afeb1fad6ed5983756e48c81ad2a5c release-0.8.32 -a4456378d234c07038456cf32bfe3c651f1d5e82 release-0.8.33 -21cb50799a20575a42f9733342d37a426f79db4d release-0.8.34 -7cb3cb8d78ef7ae63561733ed91fd07933896bc8 release-0.8.35 -aed68639d4eb6afe944b7fb50499c16f7f3f503c release-0.8.36 -265b7fd2ae21c75bbffa5115b83a0123d6c4acb4 release-0.8.37 -fa5f1ca353c0c5aa5415f51d72fd7bbcc02d1ed7 release-0.8.38 -af10bf9d4c6532850aa1f70cdf7504bd109b284c release-0.8.39 -4846ec9f83cb5bc4c8519d5641b35fb9b190430c release-0.8.40 -718b4cb3faf7efe4e0648140f064bf7a92c3f7e8 release-0.8.41 -b5a3065749093282ddd19845e0b77ffc2e54333e release-0.8.42 -34df9fb22fed415cdad52def04095dc6d4b48222 release-0.8.43 -00ec8cd76fb89af27363b76c40d9f88bf4679c3b release-0.8.44 -e16dd52a0d226c23dcae9a11252564a04753bbed release-0.8.45 -f034d9173df0a433e0bbcf5974f12ea9eb9076c0 release-0.8.46 -4434dc967087315efcd0658206a67fe6c85528f3 release-0.8.47 -0b65c962e0cd6783a854877b52c903cb058eec8c release-0.8.48 -a2b7e94b9807e981866bf07e37b715847d1b7120 release-0.8.49 -e7bdb8edc1bab2bc352a9fb6ce765c46575c35bf release-0.8.50 -21dacebd12f65cb57ceb8d2688db5b07fad6e06d release-0.8.51 -67dd7533b99c8945b5b8b5b393504d4e003a1c50 release-0.8.52 -010468d890dbac33a4cae6dfb2017db70721b2fe release-0.8.53 -62b599022a2fa625b526c2ad1711dc6db7d66786 release-0.9.0 -71281dd73b17a0ead5535d531afaee098da723cb release-0.9.1 -16cff36b0e49fc9fdeee13b2e92690286bcc1b3d release-0.9.2 -b7b306325972661117694879d3e22faf4cf0df32 release-0.9.3 -fe671505a8ea86a76f0358b3ec4de84a9037ac2b release-0.9.4 -70542931bc5436d1bbd38f152245d93ac063968d release-0.9.5 -27e2f3b7a3db1819c5d0ba28327ceaba84a13c4e release-0.9.6 -657d05d63915ce2f6c4d763091059f5f85bb10e5 release-0.9.7 -e0fd9f36005923b8f98d1ba1ea583cb7625f318f release-1.0.0 -f8f89eb4e0c27e857ec517d893d4f9a454985084 release-1.0.1 -c50df367648e53d55e80b60a447c9c66caa0d326 release-1.0.2 -80d586db316512b5a9d39f00fe185f7f91523f52 release-1.0.3 -c9c2805ac9245cc48ce6efeba2b4a444f859d6aa release-1.0.4 -fa2c37b1122c2c983b6e91d1188e387d72dde4d6 release-1.0.5 -f31aea5b06654c9163be5acd6d9b7aaf0fdf6b33 release-1.1.0 -44bf95f670656fae01ccb266b3863843ea13d324 release-1.1.1 -da1289482a143dfa016769649bdff636c26f53c8 release-1.1.2 -bac8ba08a6570bac2ecd3bf2ad64b0ac3030c903 release-1.1.3 -911060bc8221d4113a693ae97952a1fa88663ca8 release-1.1.4 -e47531dfabbf8e5f8b8aff9ff353642ea4aa7abb release-1.1.5 -f9ddecfe331462f870a95e4c1c3ba1bb8f19f2d3 release-1.1.6 -378c297bb7459fb99aa9c77decac0d35391a3932 release-1.1.7 -71600ce67510af093d4bc0117a78b3b4678c6b3a release-1.1.8 -482d7d907f1ab92b78084d8b8631ed0eb7dd08f7 release-1.1.9 -c7e65deabf0db5109e8d8f6cf64cd3fb7633a3d1 release-1.1.10 -9590f0cf5aab8e6e0b0c8ae59c70187b2b97d886 release-1.1.11 -ade8fc136430cfc04a8d0885c757968b0987d56c release-1.1.12 -6a6836e65827fd3cb10a406e7bbbe36e0dad8736 release-1.1.13 -6845f4ac909233f5a08ed8a51de137713a888328 release-1.1.14 -2397e9c72f1bc5eac67006e12ad3e33e0ea9ba74 release-1.1.15 -7b7c49639a7bceecabf4963c60b26b65a77d6ce0 release-1.1.16 -f7e1113a9a1648cad122543e7080e895cf2d88f4 release-1.1.17 -2b22743c3079b41233ded0fc35af8aa89bcfab91 release-1.1.18 -0f0b425659e0b26f5bc8ea14a42dbf34de2eaba6 release-1.1.19 -f582d662cc408eb7a132c21f4b298b71d0701abb release-1.2.0 -9ee68d629722f583d43d92271f2eb84281afc630 release-1.3.0 -61b6a3438afef630774e568eefd89c53e3b93287 release-1.3.1 -7ccd50a0a455f2f2d3b241f376e1193ad956196d release-1.2.1 -0000000000000000000000000000000000000000 release-1.2.1 -50107e2d96bbfc2c59e46f889b1a5f68dd10cf19 release-1.3.2 -2c5e1e88c8cf710caf551c5c67eba00443601efe release-1.3.3 -a43447fb82aa03eabcd85352758ae14606a84d35 release-1.3.4 -90f3b4ea7992a7bf9385851a3e77173363091eea release-1.3.5 -3aeb14f88daeb973e4708310daa3dc68ac1200f7 release-1.3.6 -dafd375f1c882b15fa4a9b7aa7c801c55082395e release-1.3.7 -ab7ce0eb4cf78a656750ab1d8e55ef61f7e535ec release-1.3.8 -1b1a9337a7399ad3cdc5e3a2f9fbaaec990271d5 release-1.3.9 -2c053b2572694eb9cd4aed26a498b6cb1f51bbcc release-1.3.10 -36409ac209872ce53019f084e4e07467c5d9d25e release-1.3.11 -560dc55e90c13860a79d8f3e0d67a81c7b0257bb release-1.3.12 -dc195ffe0965b2b9072f8e213fe74ecce38f6773 release-1.3.13 -e04428778567dd4de329bbbe97ad653e22801612 release-1.3.14 -cd84e467c72967b9f5fb4d96bfc708c93edeb634 release-1.3.15 -23159600bdea695db8f9d2890aaf73424303e49c release-1.3.16 -7809529022b83157067e7d1e2fb65d57db5f4d99 release-1.4.0 -48a84bc3ff074a65a63e353b9796ff2b14239699 release-1.5.0 -99eed1a88fc33f32d66e2ec913874dfef3e12fcc release-1.5.1 -5bdca4812974011731e5719a6c398b54f14a6d61 release-1.5.2 -644a079526295aca11c52c46cb81e3754e6ad4ad release-1.5.3 -376a5e7694004048a9d073e4feb81bb54ee3ba91 release-1.5.4 -60e0409b9ec7ee194c6d8102f0656598cc4a6cfe release-1.5.5 -70c5cd3a61cb476c2afb3a61826e59c7cda0b7a7 release-1.5.6 -9ba2542d75bf62a3972278c63561fc2ef5ec573a release-1.5.7 -eaa76f24975948b0ce8be01838d949122d44ed67 release-1.5.8 -5a1759f33b7fa6270e1617c08d7e655b7b127f26 release-1.5.9 -b798fc020e3a84ef68e6c9f47865a319c826d33c release-1.5.10 -f995a10d4c7e9a817157a6ce7b753297ad32897e release-1.5.11 -97b47d95e4449cbde976657cf8cbbc118351ffe0 release-1.5.12 -fd722b890eabc600394349730a093f50dac31639 release-1.5.13 -d161d68df8be32e5cbf72b07db1a707714827803 release-1.7.0 -0351a6d89c3dbcc7a76295024ba6b70e27b9a497 release-1.7.1 -0bd223a546192fdf2e862f33938f4ec2a3b5b283 release-1.7.2 -fe7cd01828d5ca7491059f0690bb4453645eb28b release-1.7.3 -cbb146b120296852e781079d5138b04495bab6df release-1.7.4 -fe129aa02db9001d220f1db7c3c056f79482c111 release-1.7.5 -a8d111bb68847f61d682a3c8792fecb2e52efa2c release-1.7.6 -6d2fbc30f8a7f70136cf08f32d5ff3179d524873 release-1.7.7 -d5ea659b8bab2d6402a2266efa691f705e84001e release-1.7.8 -34b201c1abd1e2d4faeae4650a21574771a03c0e release-1.7.9 -860cfbcc4606ee36d898a9cd0c5ae8858db984d6 release-1.7.10 -2b3b737b5456c05cd63d3d834f4fb4d3776953d0 release-1.7.11 -3ef00a71f56420a9c3e9cec311c9a2109a015d67 release-1.7.12 -53d850fe292f157d2fb999c52788ec1dc53c91ed release-1.9.0 -884a967c369f73ab16ea859670d690fb094d3850 release-1.9.1 -3a32d6e7404a79a0973bcd8d0b83181c5bf66074 release-1.9.2 -e27a215601292872f545a733859e06d01af1017d release-1.9.3 -5cb7e2eed2031e32d2e5422caf9402758c38a6ad release-1.9.4 -942475e10cb47654205ede7ccbe7d568698e665b release-1.9.5 -b78018cfaa2f0ec20494fccb16252daa87c48a31 release-1.9.6 -54117529e40b988590ea2d38aae909b0b191663f release-1.9.7 -1bdc497c81607d854e3edf8b9a3be324c3d136b6 release-1.9.8 -ef107f3ddc237a3007e2769ec04adde0dcf627fa release-1.9.9 -be00ca08e41a69e585b6aff70a725ed6c9e1a876 release-1.9.10 -fe66cff450a95beed36a2515210eb2d7ef62c9d3 release-1.9.11 -ead3907d74f90a14d1646f1b2b56ba01d3d11702 release-1.9.12 -5936b7ed929237f1a73b467f662611cdc0309e51 release-1.9.13 -4106db71cbcb9c8274700199ac17e520902c6c0f release-1.9.14 -13070ecfda67397985f0e986eb9c42ecb46d05b5 release-1.9.15 -271ee30c6791847980cd139d31807541f5e569bf release-1.11.0 -cb783d9cc19761e14e1285d91c38f4b84d0b8756 release-1.11.1 -4d3b3a13a8cf5fc3351a7f167d1c13325e00f21c release-1.11.2 -b83a067949a3384a49fd3d943eb8d0997b31f87b release-1.11.3 -953512ca02c6f63b4fcbbc3e10d0d9835896bf99 release-1.11.4 -5253015a339aaca0a3111473d3e931b6d4752393 release-1.11.5 -5e371426b3bcba4312ce08606194b89b758927d1 release-1.11.6 -5c8f60faf33ca8926473d2da27b4c3c417bd4630 release-1.11.7 -4591da489a30f790def29bc5987f43409b503cae release-1.11.8 -20a45c768e5ed26b740679d0e22045c98727c3cc release-1.11.9 -1ad0999a7ded3d4fb01c7acf8ff57c80b643da7e release-1.11.10 -d8b321a876d6254e9e98795e3b194ef053290354 release-1.11.11 -7f394e433f0003222aa6531931ecc0b24740d5e4 release-1.11.12 -3d0e8655f897959e48cc74e87670bb5492a58871 release-1.11.13 -3671096a45bce570a2afa20b9faf42c7fb0f7e66 release-1.13.0 -539f7893ecb96bee60965528c8958d7eb2f1ce6b release-1.13.1 -5be2b25bdc65775a85f18f68a4be4f58c7384415 release-1.13.2 -8457ce87640f9bfe6221c4ac4466ced20e03bebe release-1.13.3 -bbc642c813c829963ce8197c0ca237ab7601f3d4 release-1.13.4 -0d45b4cf7c2e4e626a5a16e1fe604402ace1cea5 release-1.13.5 -f87da7d9ca02b8ced4caa6c5eb9013ccd47b0117 release-1.13.6 -47cca243d0ed39bf5dcb9859184affc958b79b6f release-1.13.7 -20ca4bcff108d3e66977f4d97508637093492287 release-1.13.8 -fb1212c7eca4c5328fe17d6cd95b010c67336aac release-1.13.9 -31c929e16910c38492581ef474e72fa67c28f124 release-1.13.10 -64179f242cb55fc206bca59de9bfdc4cf5ebcec7 release-1.13.11 -051e5fa03b92b8a564f6b12debd483d267391e82 release-1.13.12 -990b3e885636d763b97ed02d0d2cfc161a4e0c09 release-1.15.0 -4189160cb946bb38d0bc0a452b5eb4cdd8979fb5 release-1.15.1 -b234199c7ed8a156a6bb98f7ff58302c857c954f release-1.15.2 -28b3e17ca7eba1e6a0891afde0e4bc5bcc99c861 release-1.15.3 -49d49835653857daa418e68d6cbfed4958c78fca release-1.15.4 -f062e43d74fc2578bb100a9e82a953efa1eb9e4e release-1.15.5 -2351853ce6867b6166823bdf94333c0a76633c0a release-1.15.6 -051a039ce1c7e09144de4a4846669ec7116cecea release-1.15.7 -ee551e3f6dba336c0d875e266d7d55385f379b42 release-1.15.8 -d2fd76709909767fc727a5b4affcf1dc9ca488a7 release-1.15.9 -75f5c7f628411c79c7044102049f7ab4f7a246e7 release-1.15.10 -5155d0296a5ef9841f035920527ffdb771076b44 release-1.15.11 -0130ca3d58437b3c7c707cdddd813d530c68da9a release-1.15.12 -054c1c46395caff79bb4caf16f40b331f71bb6dd release-1.17.0 -7816bd7dabf6ee86c53c073b90a7143161546e06 release-1.17.1 -2fc9f853a6b7cd29dc84e0af2ed3cf78e0da6ca8 release-1.17.2 -ed4303aa1b31a9aad5440640c0840d9d0af45fed release-1.17.3 -ce2ced3856909f36f8130c99eaa4dbdbae636ddc release-1.17.4 -9af0dddbddb2c368bfedd2801bc100ffad01e19b release-1.17.5 -de68d0d94320cbf033599c6f3ca37e5335c67fd7 release-1.17.6 -e56295fe0ea76bf53b06bffa77a2d3a9a335cb8c release-1.17.7 -fdacd273711ddf20f778c1fb91529ab53979a454 release-1.17.8 -5e8d52bca714d4b85284ddb649d1ba4a3ca978a8 release-1.17.9 -c44970de01474f6f3e01b0adea85ec1d03e3a5f2 release-1.17.10 -cbe6ba650211541310618849168631ce0b788f35 release-1.19.0 -062920e2f3bf871ef7a3d8496edec1b3065faf80 release-1.19.1 -a7b46539f507e6c64efa0efda69ad60b6f4ffbce release-1.19.2 -3cbc2602325f0ac08917a4397d76f5155c34b7b1 release-1.19.3 -dc0cc425fa63a80315f6efb68697cadb6626cdf2 release-1.19.4 -8e5b068f761cd512d10c9671fbde0b568c1fd08b release-1.19.5 -f618488eb769e0ed74ef0d93cd118d2ad79ef94d release-1.19.6 -3fa6e2095a7a51acc630517e1c27a7b7ac41f7b3 release-1.19.7 -8c65d21464aaa5923775f80c32474adc7a320068 release-1.19.8 -da571b8eaf8f30f36c43b3c9b25e01e31f47149c release-1.19.9 -ffcbb9980ee2bad27b4d7b1cd680b14ff47b29aa release-1.19.10 -df34dcc9ac072ffd0945e5a1f3eb7987e8275375 release-1.21.0 -a68ac0677f8553b1f84d357bc9da114731ab5f47 release-1.21.1 -bfbc52374adcbf2f9060afd62de940f6fab3bba5 release-1.21.2 -2217a9c1d0b86026f22700b3c089545db1964f55 release-1.21.3 -39be8a682c58308d9399cddd57e37f9fdb7bdf3e release-1.21.4 -d986378168fd4d70e0121cabac274c560cca9bdf release-1.21.5 -714eb4b2c09e712fb2572a2164ce2bf67638ccac release-1.21.6 -5da2c0902e8e2aa4534008a582a60c61c135960e release-1.23.0 -a63d0a70afea96813ba6667997bc7d68b5863f0d release-1.23.1 -aa901551a7ebad1e8b0f8c11cb44e3424ba29707 release-1.23.2 -ff3afd1ce6a6b65057741df442adfaa71a0e2588 release-1.23.3 -ac779115ed6ee4f3039e9aea414a54e560450ee2 release-1.23.4 -12dcf92b0c2c68552398f19644ce3104459807d7 release-1.25.0 -f8134640e8615448205785cf00b0bc810489b495 release-1.25.1 -1d839f05409d1a50d0f15a2bf36547001f99ae40 release-1.25.2 -294a3d07234f8f65d7b0e0b0e2c5b05c12c5da0a release-1.25.3 -173a0a7dbce569adbb70257c6ec4f0f6bc585009 release-1.25.4 -8618e4d900cc71082fbe7dc72af087937d64faf5 release-1.25.5 -2166e329fb4ed7d6da7c823ee6499f7d06d7bc00 release-1.27.0 -417b1045b18ecbca0ca583073a221ffbfbaee5d9 release-1.27.1 From teward at thomas-ward.net Fri Aug 30 17:15:14 2024 From: teward at thomas-ward.net (Thomas Ward) Date: Fri, 30 Aug 2024 17:15:14 +0000 Subject: Inquiry: Are nginx.org repos migrating from mercurial to git? Message-ID: Seeing a lot of messages related to moving from mercurial to git here in the mailing list. Are the nginx.org code repos shifting to Git instead of Mercurial? Sent from my Galaxy -------------- next part -------------- An HTML attachment was scrubbed... URL: From pclicoder at gmail.com Fri Aug 30 17:32:45 2024 From: pclicoder at gmail.com (Praveen Chaudhary) Date: Fri, 30 Aug 2024 10:32:45 -0700 Subject: Inquiry: Are nginx.org repos migrating from mercurial to git? In-Reply-To: References: Message-ID: +1 for this question. On Fri, Aug 30, 2024 at 10:15 AM Thomas Ward via nginx-devel < nginx-devel at nginx.org> wrote: > Seeing a lot of messages related to moving from mercurial to git here in > the mailing list. Are the nginx.org code repos shifting to Git instead of > Mercurial? > > > > Sent from my Galaxy > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From pluknet at nginx.com Fri Aug 30 18:23:23 2024 From: pluknet at nginx.com (Sergey Kandaurov) Date: Fri, 30 Aug 2024 22:23:23 +0400 Subject: Inquiry: Are nginx.org repos migrating from mercurial to git? In-Reply-To: References: Message-ID: > On 30 Aug 2024, at 21:15, Thomas Ward via nginx-devel wrote: > > Seeing a lot of messages related to moving from mercurial to git here in the mailing list. Are the nginx.org code repos shifting to Git instead of Mercurial? > Hello. nginx.org repo, as well as other nginx repos, are documented here: http://nginx.org/en/docs/contributing_changes.html -- Sergey Kandaurov From pgnd at dev-mail.net Fri Aug 30 18:53:09 2024 From: pgnd at dev-mail.net (pgnd) Date: Fri, 30 Aug 2024 14:53:09 -0400 Subject: Inquiry: Are nginx.org repos migrating from mercurial to git? In-Reply-To: References: Message-ID: <2a69cbe6-3ef6-48a9-8e86-7742b33390e9@dev-mail.net> >> Seeing a lot of messages related to moving from mercurial to git here in the mailing list. Are the nginx.org code repos shifting to Git instead of Mercurial? it's unclear, isn't it? this https://nginx.org/en/docs/contributing_changes.html says mercurial is in use, "Mercurial is used to store source code" and doesn't even mention github as the source repo. well, except for "Have you heard? njs has moved to GitHub. Read more." https://mailman.nginx.org/pipermail/nginx/2024-June/WBTVAEMFIOQDL3VEBP7Z65Q4E56T73L6.html "This week we took the next step in our commitment to the Open Source community by moving the official njs code repository to GitHub (https://github.com/nginx/njs)." it _does_ suggest GitHub is used to store the sources for this website. Documentation changes should be submitted as a pull request. pointing to https://github.com/nginx/nginx.org this also exists, https://github.com/nginx/nginx where it states it's The official NGINX Open Source repository. and doesn't mention being a mirror. and, there's Hg to git transition #112 https://github.com/nginx/nginx/pull/112 which @arut arut merged commit 6bb4be1 into nginx:master August 30, 2024 10:06 AM From arut at nginx.com Sat Aug 31 03:37:30 2024 From: arut at nginx.com (Roman Arutyunyan) Date: Sat, 31 Aug 2024 07:37:30 +0400 Subject: Inquiry: Are nginx.org repos migrating from mercurial to git? In-Reply-To: References: Message-ID: <8484045E-CA3E-43BE-AF3E-BB1ADD8B9B25@nginx.com> Hello, Thanks for your interest. Indeed we are working on migration of nginx source code to GitHub. Stay tuned for the official announcement! > On 30 Aug 2024, at 9:15 PM, Thomas Ward via nginx-devel wrote: > > Seeing a lot of messages related to moving from mercurial to git here in the mailing list. Are the nginx.org code repos shifting to Git instead of Mercurial? > > > > Sent from my Galaxy > > _______________________________________________ > nginx-devel mailing list > nginx-devel at nginx.org > https://mailman.nginx.org/mailman/listinfo/nginx-devel ---- Roman Arutyunyan arut at nginx.com -------------- next part -------------- An HTML attachment was scrubbed... URL: