[PATCH] Support for Encrypted Client Hello with BoringSSL (resubmitting with corrected lines)
Yaroslav Rosomakho
yaroslavros at gmail.com
Mon Nov 20 23:35:34 UTC 2023
# HG changeset patch
# User Yaroslav Rosomakho <yaroslavros at gmail.com>
# Date 1700523131 0
# Mon Nov 20 23:32:11 2023 +0000
# Node ID 8e9f043550f062594800ba9ae97bc4a250df123e
# Parent 7ec761f0365f418511e30b82e9adf80bc56681df
Added support for Encrypted Client Hello with BoringSSL.
This patch introduces support for ECH TLS extension with the BoringSSL
library. ECH is already supported and enabled by default in modern
Chrome and Firefox.
ECH support is implemented through ssl_ech configuration directive,
$ssl_ech and $ssl_ech_config variables.
To enable ECH for a given server configure ssl_ech as follows:
ssl_ech public_name config_id [key=file] [noretry]
public_name is mandatory. It needs to be set to FQDN to be populated
in clear-text SNI of Outer ClientHello. It's highly recommended to
have a server block matching that public_name and providing a valid
certificate for it, otherwise the ECH retry mechanism will not work.
config_id is mandatory. It is a number between 0 and 255 identifying
ECH configuration. Running multiple configurations with the same id is
possible but will reduce performance as the server will need to try
multiple encryption keys.
key=file is optional. It specifies a file with PEM encoded X25519
private key. If it is not specified, the key will be generated
dynamically on each restart/configuration reload. It is highly
recommended to generate and use a static key unless you have DNS
automation to update HTTPS DNS records each time a new key is
generated.
noretry is an optional flag to remove given configuration from retry
list or generated ECHConfigList for DNS record. It should be used for
historic rotated out keys that may still be used by clients due to
caching. Valid configuration requires at least one ssl_ech entry
without a noretry flag.
It is possible to have multiple ssl_ech configurations in a given
server block. ssl_ech configurations from multiple server blocks under
the same listener will be automatically aggregated. Note that TLS 1.3
must be enabled for ssl_ech to be accepted.
The only KEM supported for ECH in BoringSSL is X25519, HKDF-SHA256, so
X25519 key is required. To generate one with OpenSSL run openssl
genpkey -out ech.key -algorithm X25519
After parsing configurationECHConfigList will be dumped into error_log
similarly to
server ech.example.com ECH config for HTTPS DNS record
ech="AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA="
For ECH to work this encoded configuration needs to be added to the
HTTPS record. Typical HTTPS record looks like this:
kdig +short crypto.cloudflare.com https
1 . alpn=http/1.1,h2 ipv4hint=162.159.137.85,162.159.138.85
ech=AEX+DQBB8QAgACBl2nj6LhmbUqJJseiydASRUkdmEQGq/u/e5fXDLsFJSAAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=
ipv6hint=2606:4700:7::a29f:8955,2606:4700:7::a29f:8a55
If everything is configured correctly, Chrome 117+ and Firefox 118+
will be using public_name in the clear-text SNI of Outer ClientHello -
confirm this with Wireshark. Note that for ECH to work Firefox
requires DoH. Chrome supports ECH with any DNS as long as it resolves
HTTPS records.
There are two variables set for ECH: $ssl_ech and $ssl_ech_config
$ssl_ech is set to "1" if ECH was successfully negotiated
$ssl_ech_config is set to server-specific ECHConfigList mentioned
above. It can be used to generate json according to
draft-ietf-tls-wkech-04
location /.well-known/origin-svcb {
add_header Content-Type application/json;
return 200 '{"endpoints":[{"ech":"$ssl_ech_config"}]}';
}
This patch implements ECH support only for the HTTP module, but can be
easily extended to cover other SSL servers.
diff -r 7ec761f0365f -r 8e9f043550f0 contrib/vim/syntax/nginx.vim
--- a/contrib/vim/syntax/nginx.vim Thu Oct 26 23:35:09 2023 +0300
+++ b/contrib/vim/syntax/nginx.vim Mon Nov 20 23:32:11 2023 +0000
@@ -587,6 +587,7 @@
syn keyword ngxDirective contained ssl_dhparam
syn keyword ngxDirective contained ssl_early_data
syn keyword ngxDirective contained ssl_ecdh_curve
+syn keyword ngxDirective contained ssl_ech
syn keyword ngxDirective contained ssl_engine
syn keyword ngxDirective contained ssl_handshake_timeout
syn keyword ngxDirective contained ssl_ocsp
diff -r 7ec761f0365f -r 8e9f043550f0 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c Thu Oct 26 23:35:09 2023 +0300
+++ b/src/event/ngx_event_openssl.c Mon Nov 20 23:32:11 2023 +0000
@@ -135,6 +135,9 @@
int ngx_ssl_next_certificate_index;
int ngx_ssl_certificate_name_index;
int ngx_ssl_stapling_index;
+#ifdef SSL_R_ECH_REJECTED
+int ngx_ssl_ech_index;
+#endif
ngx_int_t
@@ -288,6 +291,15 @@
return NGX_ERROR;
}
+#ifdef SSL_R_ECH_REJECTED
+ ngx_ssl_ech_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+ if (ngx_ssl_ech_index == -1) {
+ ngx_ssl_error(NGX_LOG_ALERT, log, 0, "X509_get_ex_new_index() failed");
+ return NGX_ERROR;
+ }
+#endif
+
return NGX_OK;
}
@@ -453,6 +465,305 @@
}
+#ifdef SSL_R_ECH_REJECTED
+ngx_int_t
+ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs, ngx_str_t *value)
+{
+ ngx_int_t config_id;
+ ngx_ssl_ech_conf_t *ech_config;
+ ngx_uint_t i;
+
+ if (ngx_strchr(value[1].data, '/')) {
+ ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
+ "public name \"%V\" has suspicious symbols",
+ &value[1]);
+ }
+
+ config_id = ngx_atoi(value[2].data, value[2].len);
+ if (config_id < 0 || config_id > 255) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "ECH config_id \"%V\" is invalid. Valid values"
+ "are between 0 and 255",
+ &value[2]);
+ return NGX_ERROR;
+ }
+
+ if (*ech_configs == NGX_CONF_UNSET_PTR) {
+ *ech_configs = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_ssl_ech_conf_t));
+ if (ech_configs == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ ech_config = ngx_array_push(*ech_configs);
+ if (ech_config == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(ech_config, sizeof(ngx_ssl_ech_conf_t));
+
+ ech_config->public_name = value[1];
+ ech_config->config_id = config_id;
+
+ for (i = 3; i < cf->args->nelts; i++) {
+ if (ngx_strncmp(value[i].data, "key=", 4) == 0) {
+ ech_config->ech_key.data = value[i].data + 4;
+ ech_config->ech_key.len = value[i].len - 4;
+ } else if (ngx_strcmp(value[i].data, "noretry") == 0) {
+ ech_config->noretry = 1;
+ } else {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "unexpected configuration parameter \"%V\"",
+ &value[i]);
+ return NGX_ERROR;
+ }
+
+ }
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs,
+ ngx_array_t *passwords) {
+ ngx_uint_t i;
+ ngx_ssl_ech_conf_t *ech_config;
+ EVP_PKEY *pkey;
+ char *err;
+
+ ech_config = ech_configs->elts;
+ for (i = 0; i < ech_configs->nelts; i++) {
+ if (ech_config[i].ech_key.len > 0) {
+ pkey = ngx_ssl_load_certificate_key(cf->pool, &err,
+ &ech_config[i].ech_key, passwords);
+ if (pkey == NULL) {
+ if (err != NULL)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot load ECH key from \"%V\": %s",
+ &ech_config[i].ech_key, err);
+ }
+ return NGX_ERROR;
+ }
+ if (EVP_PKEY_id(pkey) != EVP_PKEY_X25519) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "only X25519 keys supported for ECH");
+ EVP_PKEY_free(pkey);
+ return NGX_ERROR;
+ }
+ if (!EVP_PKEY_get_raw_private_key(pkey, NULL,
+ &ech_config[i].ech_key.len)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot not read private key from \"%V\"",
+ &ech_config[i].ech_key);
+ EVP_PKEY_free(pkey);
+ return NGX_ERROR;
+ }
+ ech_config[i].ech_key.data = ngx_pnalloc(cf->temp_pool,
+ ech_config[i].ech_key.len);
+ if (ech_config[i].ech_key.data == NULL) {
+ EVP_PKEY_free(pkey);
+ return NGX_ERROR;
+ }
+ if (!EVP_PKEY_get_raw_private_key(pkey,
+ ech_config[i].ech_key.data, &ech_config[i].ech_key.len)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot not extract private key from \"%V\"",
+ &ech_config[i].ech_key);
+ EVP_PKEY_free(pkey);
+ return NGX_ERROR;
+ }
+ EVP_PKEY_free(pkey);
+ }
+ }
+
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs,
+ ngx_array_t *server_names, ngx_array_t *ssls, ngx_ssl_t *default_ssl)
+{
+ ngx_array_t **ech_configs;
+ ngx_uint_t c, c2, s, s2, found, retry_found, has_keys;
+ EVP_HPKE_KEY *hpke_key;
+ ngx_str_t ech_config, dns_config,
+ *base64_dns_config, **server_name;
+ SSL_ECH_KEYS *ech_keys;
+ ngx_ssl_ech_conf_t *configs, *configs2;
+ ngx_ssl_t **ssl;
+
+ default_ssl->ech_keys = SSL_ECH_KEYS_new();
+ ech_configs = port_ech_configs->elts;
+ server_name = server_names->elts;
+ ssl = ssls->elts;
+ has_keys = 0;
+
+ for (s = 0; s < port_ech_configs->nelts; s++) {
+ if (ech_configs[s] && ech_configs[s]->nelts > 0) {
+ has_keys = 1;
+ retry_found = 0;
+ ech_keys = SSL_ECH_KEYS_new();
+ configs = ech_configs[s]->elts;
+ for (c = 0; c < ech_configs[s]->nelts; c++) {
+ if (!retry_found && !configs[c].noretry) {
+ retry_found = 1;
+ }
+ hpke_key = EVP_HPKE_KEY_new();
+ if (hpke_key == NULL) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot allocate HPKE key");
+ return NGX_ERROR;
+ }
+ if(!configs[c].ech_key.len) {
+ for (s2 = 0; s2 < port_ech_configs->nelts; s2++) {
+ if (s2 == s || !ech_configs[s2]) {
+ continue;
+ }
+ configs2 = ech_configs[s2]->elts;
+ for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) {
+ if (configs[c].config_id ==
+ configs2[c2].config_id &&
+ ngx_strcmp(configs[c].public_name.data,
+ configs2[c2].public_name.data) == 0 &&
+ configs2[c2].ech_key.len > 0) {
+ configs[c].ech_key.len =
+ configs2[c2].ech_key.len;
+ configs[c].ech_key.data =
+ configs2[c2].ech_key.data;
+ break;
+ }
+ }
+ }
+ }
+ if(!configs[c].ech_key.len) {
+ if (!EVP_HPKE_KEY_generate(hpke_key,
+ EVP_hpke_x25519_hkdf_sha256())) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot generate ECH private key");
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+ configs[c].ech_key.data = ngx_pnalloc(cf->temp_pool,
+ EVP_HPKE_MAX_PRIVATE_KEY_LENGTH);
+ if (configs[c].ech_key.data == NULL) {
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+ if (!EVP_HPKE_KEY_private_key(hpke_key,
+ configs[c].ech_key.data,
+ &configs[c].ech_key.len,
+ EVP_HPKE_MAX_PRIVATE_KEY_LENGTH)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot export generated ECH private key");
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+ } else {
+ if (!EVP_HPKE_KEY_init(hpke_key,
+ EVP_hpke_x25519_hkdf_sha256(),
+ configs[c].ech_key.data, configs[c].ech_key.len)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot import ECH private key");
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+ }
+
+ if (!SSL_marshal_ech_config(&ech_config.data,
+ &ech_config.len, configs[c].config_id,
+ hpke_key, (const char*) configs[c].public_name.data,
+ configs[c].public_name.len)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot marshall ECH config");
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+
+ if (!SSL_ECH_KEYS_add(ech_keys, !configs[c].noretry,
+ ech_config.data, ech_config.len, hpke_key)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot add key to server ECH config");
+ OPENSSL_free(ech_config.data);
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+
+ found = 0;
+ for (s2 = 0; s2 < s; s2++) {
+ configs2 = ech_configs[s2]->elts;
+ for (c2 = 0; c2 < ech_configs[s2]->nelts; c2++) {
+ if (configs[c].config_id == configs2[c2].config_id &&
+ ngx_strcmp(configs[c].public_name.data,
+ configs2[c2].public_name.data) == 0 &&
+ ngx_strcmp(configs[c].ech_key.data,
+ configs2[c2].ech_key.data) == 0) {
+ found = 1;
+ }
+ }
+ }
+ if (!found) {
+ if (!SSL_ECH_KEYS_add(default_ssl->ech_keys,
+ !configs[c].noretry, ech_config.data,
+ ech_config.len, hpke_key)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "cannot add key to ECH config");
+ OPENSSL_free(ech_config.data);
+ EVP_HPKE_KEY_free(hpke_key);
+ return NGX_ERROR;
+ }
+ }
+ OPENSSL_free(ech_config.data);
+ EVP_HPKE_KEY_free(hpke_key);
+ }
+ if (!retry_found) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "at least one ssl_ech entry without"
+ " noretry required for server %V", server_name[s]);
+ return NGX_ERROR;
+ }
+ SSL_ECH_KEYS_marshal_retry_configs(ech_keys,
+ &dns_config.data, &dns_config.len);
+ base64_dns_config = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+ if (base64_dns_config == NULL) {
+ return NGX_ERROR;
+ }
+ base64_dns_config->len = ngx_base64_encoded_length(dns_config.len);
+ base64_dns_config->data = ngx_palloc(cf->pool,
+ base64_dns_config->len);
+ if (base64_dns_config->data == NULL) {
+ return NGX_ERROR;
+ }
+ ngx_encode_base64(base64_dns_config, &dns_config);
+ ngx_ssl_error(NGX_LOG_WARN, cf->log, 0,
+ "server %V ECH config for HTTPS DNS record ech=\"%V\"",
+ server_name[s], base64_dns_config);
+ if (SSL_CTX_set_ex_data(ssl[s]->ctx, ngx_ssl_ech_index,
+ base64_dns_config) == 0) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_set_ex_data() failed");
+ return NGX_ERROR;
+ }
+
+ OPENSSL_free(dns_config.data);
+ }
+ }
+
+ if (default_ssl->ctx && has_keys) {
+ if (!SSL_CTX_set1_ech_keys(default_ssl->ctx, default_ssl->ech_keys)) {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "could not attach ECH keys to context");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
ngx_int_t
ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
ngx_str_t *key, ngx_array_t *passwords)
@@ -801,7 +1112,6 @@
}
} else {
-
if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key)
!= NGX_OK)
{
@@ -4861,6 +5171,11 @@
}
SSL_CTX_free(ssl->ctx);
+#ifdef SSL_R_ECH_REJECTED
+ if (ssl->ech_keys) {
+ SSL_ECH_KEYS_free(ssl->ech_keys);
+ }
+#endif
}
@@ -5943,6 +6258,42 @@
}
+ngx_int_t
+ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+
+ s->len = 0;
+
+#ifdef SSL_R_ECH_REJECTED
+ if (SSL_ech_accepted(c->ssl->connection)) {
+ ngx_str_set(s, "1");
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s)
+{
+ s->len = 0;
+#ifdef SSL_R_ECH_REJECTED
+ ngx_str_t *ech_config;
+ ech_config = SSL_CTX_get_ex_data(c->ssl->session_ctx, ngx_ssl_ech_index);
+ if (ech_config) {
+ s->len = ech_config->len;
+ s->data = ngx_pnalloc(pool, s->len);
+ if (s->data == NULL) {
+ return NGX_ERROR;
+ }
+ ngx_memcpy(s->data, ech_config->data, s->len);
+ }
+#endif
+ return NGX_OK;
+}
+
+
static time_t
ngx_ssl_parse_time(
#if OPENSSL_VERSION_NUMBER > 0x10100000L
diff -r 7ec761f0365f -r 8e9f043550f0 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Thu Oct 26 23:35:09 2023 +0300
+++ b/src/event/ngx_event_openssl.h Mon Nov 20 23:32:11 2023 +0000
@@ -39,6 +39,9 @@
#include <openssl/rand.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
+#ifdef SSL_R_ECH_REJECTED
+#include <openssl/hpke.h>
+#endif
#define NGX_SSL_NAME "OpenSSL"
@@ -88,6 +91,9 @@
struct ngx_ssl_s {
SSL_CTX *ctx;
+#ifdef SSL_R_ECH_REJECTED
+ SSL_ECH_KEYS *ech_keys;
+#endif
ngx_log_t *log;
size_t buffer_size;
};
@@ -174,6 +180,16 @@
} ngx_ssl_session_cache_t;
+#ifdef SSL_R_ECH_REJECTED
+typedef struct {
+ ngx_str_t public_name;
+ ngx_uint_t config_id;
+ ngx_str_t ech_key;
+ ngx_flag_t noretry;
+} ngx_ssl_ech_conf_t;
+#endif
+
+
#define NGX_SSL_SSLv2 0x0002
#define NGX_SSL_SSLv3 0x0004
#define NGX_SSL_TLSv1 0x0008
@@ -191,6 +207,14 @@
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);
+#ifdef SSL_R_ECH_REJECTED
+ngx_int_t ngx_ssl_ech(ngx_conf_t *cf, ngx_array_t **ech_configs,
+ ngx_str_t *value);
+ngx_int_t ngx_ssl_attach_ech(ngx_conf_t *cf, ngx_array_t *port_ech_configs,
+ ngx_array_t *server_names, ngx_array_t *ssls, ngx_ssl_t *default_ssl);
+ngx_int_t ngx_ssl_prepare_ech(ngx_conf_t *cf, ngx_array_t *ech_configs,
+ ngx_array_t *passwords);
+#endif
ngx_int_t ngx_ssl_certificates(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_array_t *certs, ngx_array_t *keys, ngx_array_t *passwords);
ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
@@ -307,6 +331,10 @@
ngx_str_t *s);
ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);
+ngx_int_t ngx_ssl_ech_negotiated(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
+ngx_int_t ngx_ssl_ech_config(ngx_connection_t *c, ngx_pool_t *pool,
+ ngx_str_t *s);
ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
@@ -334,6 +362,9 @@
extern int ngx_ssl_next_certificate_index;
extern int ngx_ssl_certificate_name_index;
extern int ngx_ssl_stapling_index;
+#ifdef SSL_R_ECH_REJECTED
+extern int ngx_ssl_ech_index;
+#endif
#endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff -r 7ec761f0365f -r 8e9f043550f0 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c Thu Oct 26 23:35:09 2023 +0300
+++ b/src/http/modules/ngx_http_ssl_module.c Mon Nov 20 23:32:11 2023 +0000
@@ -49,6 +49,8 @@
void *conf);
static char *ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+static char *ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post,
void *data);
@@ -290,6 +292,14 @@
offsetof(ngx_http_ssl_srv_conf_t, reject_handshake),
NULL },
+ { ngx_string("ssl_ech"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE2|NGX_CONF_TAKE3|
+ NGX_CONF_TAKE4,
+ ngx_http_ssl_ech,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
ngx_null_command
};
@@ -399,6 +409,12 @@
{ ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable,
(uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 },
+ { ngx_string("ssl_ech"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_ech_negotiated, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
+ { ngx_string("ssl_ech_config"), NULL, ngx_http_ssl_variable,
+ (uintptr_t) ngx_ssl_ech_config, NGX_HTTP_VAR_CHANGEABLE, 0 },
+
ngx_http_null_variable
};
@@ -629,6 +645,9 @@
sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR;
sscf->stapling = NGX_CONF_UNSET;
sscf->stapling_verify = NGX_CONF_UNSET;
+#ifdef SSL_R_ECH_REJECTED
+ sscf->ech_configs = NGX_CONF_UNSET_PTR;
+#endif
return sscf;
}
@@ -693,6 +712,9 @@
ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, "");
ngx_conf_merge_str_value(conf->stapling_responder,
prev->stapling_responder, "");
+#ifdef SSL_R_ECH_REJECTED
+ ngx_conf_merge_ptr_value(conf->ech_configs, prev->ech_configs, NULL);
+#endif
conf->ssl.log = cf->log;
@@ -886,6 +908,14 @@
return NGX_CONF_ERROR;
}
+#ifdef SSL_R_ECH_REJECTED
+ if (conf->ech_configs &&
+ ngx_ssl_prepare_ech(cf, conf->ech_configs,
+ conf->passwords) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+#endif
+
if (ngx_ssl_conf_commands(cf, &conf->ssl, conf->conf_commands) != NGX_OK) {
return NGX_CONF_ERROR;
}
@@ -1207,6 +1237,23 @@
}
+static char *
+ngx_http_ssl_ech(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+#ifndef SSL_R_ECH_REJECTED
+ return "is not supported on this platform";
+#else
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ if (ngx_ssl_ech(cf, &sscf->ech_configs, cf->args->elts) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+#endif
+}
+
+
static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
@@ -1218,6 +1265,14 @@
ngx_http_core_loc_conf_t *clcf;
ngx_http_core_srv_conf_t **cscfp, *cscf;
ngx_http_core_main_conf_t *cmcf;
+#ifdef SSL_R_ECH_REJECTED
+ ngx_array_t **ech_config, *ech_configs,
+ *server_names, *ssls;
+ ngx_str_t **server_name;
+ ngx_http_core_srv_conf_t *cscf2;
+ ngx_http_ssl_srv_conf_t *sscf2;
+ ngx_ssl_t **ssl;
+#endif
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
cscfp = cmcf->servers.elts;
@@ -1280,6 +1335,47 @@
cscf = addr[a].default_server;
sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+ cscfp = addr[a].servers.elts;
+
+#ifdef SSL_R_ECH_REJECTED
+ ech_configs = ngx_array_create(cf->temp_pool,
+ addr[a].servers.nelts, sizeof(ngx_array_t *));
+ server_names = ngx_array_create(cf->temp_pool,
+ addr[a].servers.nelts, sizeof(ngx_str_t *));
+ ssls = ngx_array_create(cf->temp_pool,
+ addr[a].servers.nelts, sizeof(ngx_ssl_t *));
+ for (s = 0; s < addr[a].servers.nelts; s++) {
+ cscf2 = cscfp[s];
+ sscf2 = cscf2->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+ if (!(sscf2->protocols & NGX_SSL_TLSv1_3)) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "ECH requires \"ssl_protocols\" to include TLSv1.3"
+ " for server %V", &cscf2->server_name);
+ return NGX_ERROR;
+ }
+ ech_config = ngx_array_push(ech_configs);
+ if (ech_config == NULL) {
+ return NGX_ERROR;
+ }
+ *ech_config = sscf2->ech_configs;
+ server_name = ngx_array_push(server_names);
+ if (server_name == NULL) {
+ return NGX_ERROR;
+ }
+ *server_name = &cscf2->server_name;
+ ssl = ngx_array_push(ssls);
+ if (ssl == NULL) {
+ return NGX_ERROR;
+ }
+ *ssl = &sscf2->ssl;
+ }
+ if (ngx_ssl_attach_ech(cf, ech_configs,
+ server_names, ssls, &sscf->ssl) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ ngx_array_destroy(ech_configs);
+ ngx_array_destroy(server_names);
+#endif
if (sscf->certificates) {
@@ -1307,7 +1403,6 @@
* check all non-default server blocks
*/
- cscfp = addr[a].servers.elts;
for (s = 0; s < addr[a].servers.nelts; s++) {
cscf = cscfp[s];
diff -r 7ec761f0365f -r 8e9f043550f0 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h Thu Oct 26 23:35:09 2023 +0300
+++ b/src/http/modules/ngx_http_ssl_module.h Mon Nov 20 23:32:11 2023 +0000
@@ -62,6 +62,10 @@
ngx_flag_t stapling_verify;
ngx_str_t stapling_file;
ngx_str_t stapling_responder;
+
+#ifdef SSL_R_ECH_REJECTED
+ ngx_array_t *ech_configs;
+#endif
} ngx_http_ssl_srv_conf_t;
More information about the nginx-devel
mailing list