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 @@ -1653,6 +1653,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) { @@ -5708,6 +5804,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) { 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;