summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorsftcd <stephen.farrell@cs.tcd.ie>2025-11-26 14:12:07 +0000
committerRoman Arutyunyan <arutyunyan.roman@gmail.com>2025-12-01 16:33:40 +0400
commitab4f5b2d32c1f621ebdf5816a34b568015b98c63 (patch)
treea2468ce46360587635183191db34da55126ae55d /src
parentbcb41c91939009b7d01074c9a8f3cef1da13ec50 (diff)
downloadnginx-ab4f5b2d32c1f621ebdf5816a34b568015b98c63.tar.gz
nginx-ab4f5b2d32c1f621ebdf5816a34b568015b98c63.tar.bz2
Add basic ECH shared-mode via OpenSSL.
Diffstat (limited to 'src')
-rw-r--r--src/event/ngx_event_openssl.c171
-rw-r--r--src/event/ngx_event_openssl.h6
-rw-r--r--src/http/modules/ngx_http_ssl_module.c21
-rw-r--r--src/http/modules/ngx_http_ssl_module.h1
-rw-r--r--src/stream/ngx_stream_ssl_module.c21
-rw-r--r--src/stream/ngx_stream_ssl_module.h1
6 files changed, 221 insertions, 0 deletions
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c
index a5af0ed3d..fe2302966 100644
--- a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -1654,6 +1654,102 @@ ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
ngx_int_t
+ngx_ssl_ech_files(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *filenames)
+{
+#ifdef SSL_OP_ECH_GREASE
+ int numkeys;
+ BIO *in;
+ ngx_int_t rc;
+ ngx_str_t *filename;
+ ngx_uint_t i;
+ OSSL_ECHSTORE *es;
+
+ if (filenames == NULL) {
+ return NGX_OK;
+ }
+
+ es = OSSL_ECHSTORE_new(NULL, NULL);
+ if (es == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "OSSL_ECHSTORE_new() failed");
+ return NGX_ERROR;
+ }
+
+ rc = NGX_ERROR;
+ filename = filenames->elts;
+
+ for (i = 0; i < filenames->nelts; i++) {
+
+ if (ngx_conf_full_name(cf->cycle, &filename[i], 1) != NGX_OK) {
+ goto cleanup;
+ }
+
+ in = BIO_new_file((char *) filename[i].data, "r");
+ if (in == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "BIO_new_file(\"%s\") failed", filename[i].data);
+ goto cleanup;
+ }
+
+ /*
+ * We only set the ECHConfigList from the first file read to use
+ * in ECH retry-configs.
+ *
+ * That allows many sensible key rotation schemes so that the
+ * values sent in ECH retry-configs are smaller and current.
+ * For example, if the first file name has the current ECH
+ * private key, and a second one has the previously used key
+ * that some clients may still use due to DNS caching.
+ */
+
+ if (OSSL_ECHSTORE_read_pem(es, in, i ? OSSL_ECH_NO_RETRY
+ : OSSL_ECH_FOR_RETRY)
+ != 1)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "OSSL_ECHSTORE_read_pem(%s) failed",
+ filename[i].data);
+ BIO_free(in);
+ goto cleanup;
+ }
+
+ BIO_free(in);
+ }
+
+ /*
+ * load the ECH store after checking there's at least one ECH
+ * private key in there (the PEM file spec allows zero or one
+ * private key per file)
+ */
+
+ if (OSSL_ECHSTORE_num_keys(es, &numkeys) != 1) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "OSSL_ECHSTORE_num_keys(%s) failed");
+ goto cleanup;
+ }
+
+ if (numkeys > 0 && SSL_CTX_set1_echstore(ssl->ctx, es) != 1) {
+ ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+ "SSL_CTX_set1_echstore() failed");
+ goto cleanup;
+ }
+
+ rc = NGX_OK;
+
+cleanup:
+
+ OSSL_ECHSTORE_free(es);
+ return rc;
+
+#else
+ ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+ "\"ssl_ech_file\" is not supported on this platform, "
+ "ignored");
+ return NGX_OK;
+#endif
+}
+
+
+ngx_int_t
ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name)
{
#ifndef OPENSSL_NO_ECDH
@@ -5709,6 +5805,81 @@ ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
ngx_int_t
+ngx_ssl_get_ech_status(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+#ifdef SSL_OP_ECH_GREASE
+ int echrv;
+ char *inner_sni, *outer_sni;
+
+ inner_sni = NULL;
+ outer_sni = NULL;
+
+ echrv = SSL_ech_get1_status(c->ssl->connection, &inner_sni, &outer_sni);
+
+ switch (echrv) {
+ case SSL_ECH_STATUS_NOT_TRIED:
+ ngx_str_set(s, "NOT_TRIED");
+ break;
+ case SSL_ECH_STATUS_SUCCESS:
+ ngx_str_set(s, "SUCCESS");
+ break;
+ case SSL_ECH_STATUS_GREASE:
+ ngx_str_set(s, "GREASE");
+ break;
+ case SSL_ECH_STATUS_BACKEND:
+ ngx_str_set(s, "BACKEND");
+ break;
+ default:
+ ngx_str_set(s, "FAILED");
+ break;
+ }
+
+ OPENSSL_free(inner_sni);
+ OPENSSL_free(outer_sni);
+#else
+ s->len = 0;
+#endif
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_get_ech_outer_server_name(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s)
+{
+#if defined(SSL_OP_ECH_GREASE)
+ int echrv;
+ char *inner_sni, *outer_sni;
+
+ inner_sni = NULL;
+ outer_sni = NULL;
+
+ echrv = SSL_ech_get1_status(c->ssl->connection, &inner_sni, &outer_sni);
+
+ if (echrv == SSL_ECH_STATUS_SUCCESS && outer_sni) {
+ s->len = ngx_strlen(outer_sni);
+
+ s->data = ngx_pnalloc(pool, s->len);
+ if (s->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->data, outer_sni, s->len);
+
+ } else {
+ s->len = 0;
+ }
+
+ OPENSSL_free(inner_sni);
+ OPENSSL_free(outer_sni);
+#else
+ s->len = 0;
+#endif
+ return NGX_OK;
+}
+
+
+ngx_int_t
ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
{
size_t len;
diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h
index a156c4bb9..d86ffb8da 100644
--- a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -271,6 +271,8 @@ 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);
ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
+ngx_int_t ngx_ssl_ech_files(ngx_conf_t *cf, ngx_ssl_t *ssl,
+ ngx_array_t *filename);
ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
ngx_int_t ngx_ssl_early_data(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_uint_t enable);
@@ -338,6 +340,10 @@ ngx_int_t ngx_ssl_get_early_data(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);
ngx_int_t ngx_ssl_get_server_name(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);
+ngx_int_t ngx_ssl_get_ech_status(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_get_ech_outer_server_name(ngx_connection_t *c,
+ ngx_pool_t *pool, ngx_str_t *s);
ngx_int_t ngx_ssl_get_alpn_protocol(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);
ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool,
diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c
index c71a5de08..43fcafd50 100644
--- a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -117,6 +117,13 @@ static ngx_command_t ngx_http_ssl_commands[] = {
0,
NULL },
+ { ngx_string("ssl_ech_file"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ offsetof(ngx_http_ssl_srv_conf_t, ech_files),
+ NULL },
+
{ ngx_string("ssl_password_file"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_http_ssl_password_file,
@@ -377,6 +384,13 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = {
{ ngx_string("ssl_alpn_protocol"), NULL, ngx_http_ssl_variable,
(uintptr_t) ngx_ssl_get_alpn_protocol, NGX_HTTP_VAR_CHANGEABLE, 0 },
+ { ngx_string("ssl_ech_status"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_ech_status, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_ech_outer_server_name"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_get_ech_outer_server_name,
+ NGX_HTTP_VAR_CHANGEABLE, 0 },
+
{ ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable,
(uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 },
@@ -643,6 +657,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
sscf->certificates = NGX_CONF_UNSET_PTR;
sscf->certificate_keys = NGX_CONF_UNSET_PTR;
sscf->certificate_cache = NGX_CONF_UNSET_PTR;
+ sscf->ech_files = NGX_CONF_UNSET_PTR;
sscf->passwords = NGX_CONF_UNSET_PTR;
sscf->conf_commands = NGX_CONF_UNSET_PTR;
sscf->builtin_session_cache = NGX_CONF_UNSET;
@@ -694,6 +709,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache,
NULL);
+ ngx_conf_merge_ptr_value(conf->ech_files, prev->ech_files, NULL);
+
ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
@@ -880,6 +897,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
return NGX_CONF_ERROR;
}
+ if (ngx_ssl_ech_files(cf, &conf->ssl, conf->ech_files) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
return NGX_CONF_ERROR;
}
diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h
index 9b26529fa..a078d44f8 100644
--- a/src/http/modules/ngx_http_ssl_module.h
+++ b/src/http/modules/ngx_http_ssl_module.h
@@ -49,6 +49,7 @@ typedef struct {
ngx_str_t ciphers;
+ ngx_array_t *ech_files;
ngx_array_t *passwords;
ngx_array_t *conf_commands;
diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c
index 6a5160f27..b7e5db449 100644
--- a/src/stream/ngx_stream_ssl_module.c
+++ b/src/stream/ngx_stream_ssl_module.c
@@ -126,6 +126,13 @@ static ngx_command_t ngx_stream_ssl_commands[] = {
0,
NULL },
+ { ngx_string("ssl_ech_file"),
+ NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_array_slot,
+ NGX_STREAM_SRV_CONF_OFFSET,
+ offsetof(ngx_stream_ssl_srv_conf_t, ech_files),
+ NULL },
+
{ ngx_string("ssl_password_file"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_stream_ssl_password_file,
@@ -372,6 +379,13 @@ static ngx_stream_variable_t ngx_stream_ssl_vars[] = {
{ ngx_string("ssl_alpn_protocol"), NULL, ngx_stream_ssl_variable,
(uintptr_t) ngx_ssl_get_alpn_protocol, NGX_STREAM_VAR_CHANGEABLE, 0 },
+ { ngx_string("ssl_ech_status"), NULL, ngx_stream_ssl_variable,
+ (uintptr_t) ngx_ssl_get_ech_status, NGX_STREAM_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_ech_outer_server_name"), NULL, ngx_stream_ssl_variable,
+ (uintptr_t) ngx_ssl_get_ech_outer_server_name,
+ NGX_STREAM_VAR_CHANGEABLE, 0 },
+
{ ngx_string("ssl_client_cert"), NULL, ngx_stream_ssl_variable,
(uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 },
@@ -888,6 +902,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf)
sscf->certificates = NGX_CONF_UNSET_PTR;
sscf->certificate_keys = NGX_CONF_UNSET_PTR;
sscf->certificate_cache = NGX_CONF_UNSET_PTR;
+ sscf->ech_files = NGX_CONF_UNSET_PTR;
sscf->passwords = NGX_CONF_UNSET_PTR;
sscf->conf_commands = NGX_CONF_UNSET_PTR;
sscf->prefer_server_ciphers = NGX_CONF_UNSET;
@@ -943,6 +958,8 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_ptr_value(conf->certificate_cache, prev->certificate_cache,
NULL);
+ ngx_conf_merge_ptr_value(conf->ech_files, prev->ech_files, NULL);
+
ngx_conf_merge_ptr_value(conf->passwords, prev->passwords, NULL);
ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
@@ -1124,6 +1141,10 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
return NGX_CONF_ERROR;
}
+ if (ngx_ssl_ech_files(cf, &conf->ssl, conf->ech_files) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
return NGX_CONF_ERROR;
}
diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h
index 31f138cfd..6fdd8f88c 100644
--- a/src/stream/ngx_stream_ssl_module.h
+++ b/src/stream/ngx_stream_ssl_module.h
@@ -49,6 +49,7 @@ typedef struct {
ngx_str_t ciphers;
+ ngx_array_t *ech_files;
ngx_array_t *passwords;
ngx_array_t *conf_commands;