SSL: support for compressed server certificates with OpenSSL.

The ssl_certificate_compression directive allows to send compressed
server certificates, which are pre-compressed on on nginx startup.
To simplify configuration, the SSL_OP_NO_TX_CERTIFICATE_COMPRESSION
option is automatically cleared if certificates were pre-compressed.

SSL_CTX_compress_certs() may return an error in legitimate cases,
e.g., when none of compression algorithms is available or if the
resulting compressed size is larger than the original one, thus it
is silently ignored.

Certificate compression is supported in Chrome with brotli only,
in Safari with zlib only, and in Firefox with all listed algorithms.
It is supported since Ubuntu 24.10, which has OpenSSL with enabled
zlib and zstd support.

The actual list of algorithms supported in OpenSSL depends on how
the library was configured; it can be brotli, zlib, zstd as listed
in RFC 8879.
This commit is contained in:
Sergey Kandaurov 2025-07-09 19:02:09 +04:00
parent e26118af2e
commit 0e29e08005
8 changed files with 84 additions and 0 deletions

View File

@ -664,6 +664,31 @@ retry:
} }
ngx_int_t
ngx_ssl_certificate_compression(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_uint_t enable)
{
if (!enable) {
return NGX_OK;
}
#ifdef SSL_OP_NO_RX_CERTIFICATE_COMPRESSION
if (SSL_CTX_compress_certs(ssl->ctx, 0) != 0) {
SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TX_CERTIFICATE_COMPRESSION);
}
#else
ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
"\"ssl_certificate_compression\" ignored, not supported");
#endif
return NGX_OK;
}
ngx_int_t ngx_int_t
ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
ngx_uint_t prefer_server_ciphers) ngx_uint_t prefer_server_ciphers)

View File

@ -236,6 +236,8 @@ ngx_int_t ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_int_t ngx_ssl_connection_certificate(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *cert, ngx_str_t *key, ngx_ssl_cache_t *cache, ngx_str_t *cert, ngx_str_t *key, ngx_ssl_cache_t *cache,
ngx_array_t *passwords); ngx_array_t *passwords);
ngx_int_t ngx_ssl_certificate_compression(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_uint_t enable);
ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers, ngx_int_t ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
ngx_uint_t prefer_server_ciphers); ngx_uint_t prefer_server_ciphers);

View File

@ -124,6 +124,13 @@ static ngx_command_t ngx_http_ssl_commands[] = {
0, 0,
NULL }, NULL },
{ ngx_string("ssl_certificate_compression"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_HTTP_SRV_CONF_OFFSET,
offsetof(ngx_http_ssl_srv_conf_t, certificate_compression),
NULL },
{ ngx_string("ssl_dhparam"), { ngx_string("ssl_dhparam"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot, ngx_conf_set_str_slot,
@ -621,6 +628,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
*/ */
sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET;
sscf->certificate_compression = NGX_CONF_UNSET;
sscf->early_data = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET;
sscf->reject_handshake = NGX_CONF_UNSET; sscf->reject_handshake = NGX_CONF_UNSET;
sscf->buffer_size = NGX_CONF_UNSET_SIZE; sscf->buffer_size = NGX_CONF_UNSET_SIZE;
@ -658,6 +666,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->prefer_server_ciphers, ngx_conf_merge_value(conf->prefer_server_ciphers,
prev->prefer_server_ciphers, 0); prev->prefer_server_ciphers, 0);
ngx_conf_merge_value(conf->certificate_compression,
prev->certificate_compression, 0);
ngx_conf_merge_value(conf->early_data, prev->early_data, 0); ngx_conf_merge_value(conf->early_data, prev->early_data, 0);
ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0);
@ -792,6 +803,13 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{ {
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
if (ngx_ssl_certificate_compression(cf, &conf->ssl,
conf->certificate_compression)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
} }
conf->ssl.buffer_size = conf->buffer_size; conf->ssl.buffer_size = conf->buffer_size;

View File

@ -18,6 +18,7 @@ typedef struct {
ngx_ssl_t ssl; ngx_ssl_t ssl;
ngx_flag_t prefer_server_ciphers; ngx_flag_t prefer_server_ciphers;
ngx_flag_t certificate_compression;
ngx_flag_t early_data; ngx_flag_t early_data;
ngx_flag_t reject_handshake; ngx_flag_t reject_handshake;

View File

@ -97,6 +97,13 @@ static ngx_command_t ngx_mail_ssl_commands[] = {
0, 0,
NULL }, NULL },
{ ngx_string("ssl_certificate_compression"),
NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
NGX_MAIL_SRV_CONF_OFFSET,
offsetof(ngx_mail_ssl_conf_t, certificate_compression),
NULL },
{ ngx_string("ssl_dhparam"), { ngx_string("ssl_dhparam"),
NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot, ngx_conf_set_str_slot,
@ -314,6 +321,7 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf)
scf->passwords = NGX_CONF_UNSET_PTR; scf->passwords = NGX_CONF_UNSET_PTR;
scf->conf_commands = NGX_CONF_UNSET_PTR; scf->conf_commands = NGX_CONF_UNSET_PTR;
scf->prefer_server_ciphers = NGX_CONF_UNSET; scf->prefer_server_ciphers = NGX_CONF_UNSET;
scf->certificate_compression = NGX_CONF_UNSET;
scf->verify = NGX_CONF_UNSET_UINT; scf->verify = NGX_CONF_UNSET_UINT;
scf->verify_depth = NGX_CONF_UNSET_UINT; scf->verify_depth = NGX_CONF_UNSET_UINT;
scf->builtin_session_cache = NGX_CONF_UNSET; scf->builtin_session_cache = NGX_CONF_UNSET;
@ -343,6 +351,9 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->prefer_server_ciphers, ngx_conf_merge_value(conf->prefer_server_ciphers,
prev->prefer_server_ciphers, 0); prev->prefer_server_ciphers, 0);
ngx_conf_merge_value(conf->certificate_compression,
prev->certificate_compression, 0);
ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
(NGX_CONF_BITMASK_SET|NGX_SSL_DEFAULT_PROTOCOLS)); (NGX_CONF_BITMASK_SET|NGX_SSL_DEFAULT_PROTOCOLS));
@ -446,6 +457,13 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
if (ngx_ssl_certificate_compression(cf, &conf->ssl,
conf->certificate_compression)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
if (conf->verify) { if (conf->verify) {
if (conf->verify != 3 if (conf->verify != 3

View File

@ -21,6 +21,7 @@
typedef struct { typedef struct {
ngx_flag_t prefer_server_ciphers; ngx_flag_t prefer_server_ciphers;
ngx_flag_t certificate_compression;
ngx_ssl_t ssl; ngx_ssl_t ssl;

View File

@ -133,6 +133,13 @@ static ngx_command_t ngx_stream_ssl_commands[] = {
0, 0,
NULL }, NULL },
{ ngx_string("ssl_certificate_compression"),
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, certificate_compression),
NULL },
{ ngx_string("ssl_dhparam"), { ngx_string("ssl_dhparam"),
NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
ngx_conf_set_str_slot, ngx_conf_set_str_slot,
@ -881,6 +888,7 @@ ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf)
sscf->passwords = NGX_CONF_UNSET_PTR; sscf->passwords = NGX_CONF_UNSET_PTR;
sscf->conf_commands = NGX_CONF_UNSET_PTR; sscf->conf_commands = NGX_CONF_UNSET_PTR;
sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET;
sscf->certificate_compression = NGX_CONF_UNSET;
sscf->reject_handshake = NGX_CONF_UNSET; sscf->reject_handshake = NGX_CONF_UNSET;
sscf->verify = NGX_CONF_UNSET_UINT; sscf->verify = NGX_CONF_UNSET_UINT;
sscf->verify_depth = NGX_CONF_UNSET_UINT; sscf->verify_depth = NGX_CONF_UNSET_UINT;
@ -914,6 +922,9 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
ngx_conf_merge_value(conf->prefer_server_ciphers, ngx_conf_merge_value(conf->prefer_server_ciphers,
prev->prefer_server_ciphers, 0); prev->prefer_server_ciphers, 0);
ngx_conf_merge_value(conf->certificate_compression,
prev->certificate_compression, 0);
ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0);
ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
@ -1039,6 +1050,13 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
{ {
return NGX_CONF_ERROR; return NGX_CONF_ERROR;
} }
if (ngx_ssl_certificate_compression(cf, &conf->ssl,
conf->certificate_compression)
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
} }
if (conf->verify) { if (conf->verify) {

View File

@ -18,6 +18,7 @@ typedef struct {
ngx_msec_t handshake_timeout; ngx_msec_t handshake_timeout;
ngx_flag_t prefer_server_ciphers; ngx_flag_t prefer_server_ciphers;
ngx_flag_t certificate_compression;
ngx_flag_t reject_handshake; ngx_flag_t reject_handshake;
ngx_ssl_t ssl; ngx_ssl_t ssl;