changeset 9444:741cc85a6778

SSL: support for compressed server certificates with OpenSSL. The ssl_certificate_compression directive allows to send compressed server certificates. In OpenSSL, they are pre-compressed on 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.
author Sergey Kandaurov <pluknet@nginx.com>
date Wed, 09 Jul 2025 19:02:09 +0400
parents bade3faa937d
children 646f12914bf8
files src/event/ngx_event_openssl.c src/event/ngx_event_openssl.h src/http/modules/ngx_http_ssl_module.c src/http/modules/ngx_http_ssl_module.h src/mail/ngx_mail_ssl_module.c src/mail/ngx_mail_ssl_module.h src/stream/ngx_stream_ssl_module.c src/stream/ngx_stream_ssl_module.h
diffstat 8 files changed, 89 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_openssl.c	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/event/ngx_event_openssl.c	Wed Jul 09 19:02:09 2025 +0400
@@ -665,6 +665,36 @@
 
 
 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_TX_CERTIFICATE_COMPRESSION
+
+    if (SSL_CTX_compress_certs(ssl->ctx, 0) == 0) {
+        ngx_ssl_error(NGX_LOG_WARN, ssl->log, 0,
+                      "SSL_CTX_compress_certs() failed, ignored");
+        return NGX_OK;
+    }
+
+    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\" is not supported "
+                  "on this platform, ignored");
+
+#endif
+
+    return NGX_OK;
+}
+
+
+ngx_int_t
 ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
     ngx_uint_t prefer_server_ciphers)
 {
--- a/src/event/ngx_event_openssl.h	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/event/ngx_event_openssl.h	Wed Jul 09 19:02:09 2025 +0400
@@ -236,6 +236,8 @@
 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_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_uint_t prefer_server_ciphers);
--- a/src/http/modules/ngx_http_ssl_module.c	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/http/modules/ngx_http_ssl_module.c	Wed Jul 09 19:02:09 2025 +0400
@@ -124,6 +124,13 @@
       0,
       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_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -621,6 +628,7 @@
      */
 
     sscf->prefer_server_ciphers = NGX_CONF_UNSET;
+    sscf->certificate_compression = NGX_CONF_UNSET;
     sscf->early_data = NGX_CONF_UNSET;
     sscf->reject_handshake = NGX_CONF_UNSET;
     sscf->buffer_size = NGX_CONF_UNSET_SIZE;
@@ -658,6 +666,9 @@
     ngx_conf_merge_value(conf->prefer_server_ciphers,
                          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->reject_handshake, prev->reject_handshake, 0);
 
@@ -792,6 +803,13 @@
         {
             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;
--- a/src/http/modules/ngx_http_ssl_module.h	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/http/modules/ngx_http_ssl_module.h	Wed Jul 09 19:02:09 2025 +0400
@@ -18,6 +18,7 @@
     ngx_ssl_t                       ssl;
 
     ngx_flag_t                      prefer_server_ciphers;
+    ngx_flag_t                      certificate_compression;
     ngx_flag_t                      early_data;
     ngx_flag_t                      reject_handshake;
 
--- a/src/mail/ngx_mail_ssl_module.c	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/mail/ngx_mail_ssl_module.c	Wed Jul 09 19:02:09 2025 +0400
@@ -97,6 +97,13 @@
       0,
       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_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -314,6 +321,7 @@
     scf->passwords = NGX_CONF_UNSET_PTR;
     scf->conf_commands = NGX_CONF_UNSET_PTR;
     scf->prefer_server_ciphers = NGX_CONF_UNSET;
+    scf->certificate_compression = NGX_CONF_UNSET;
     scf->verify = NGX_CONF_UNSET_UINT;
     scf->verify_depth = NGX_CONF_UNSET_UINT;
     scf->builtin_session_cache = NGX_CONF_UNSET;
@@ -343,6 +351,9 @@
     ngx_conf_merge_value(conf->prefer_server_ciphers,
                          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_BITMASK_SET|NGX_SSL_DEFAULT_PROTOCOLS));
 
@@ -446,6 +457,13 @@
         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 != 3
--- a/src/mail/ngx_mail_ssl_module.h	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/mail/ngx_mail_ssl_module.h	Wed Jul 09 19:02:09 2025 +0400
@@ -21,6 +21,7 @@
 
 typedef struct {
     ngx_flag_t       prefer_server_ciphers;
+    ngx_flag_t       certificate_compression;
 
     ngx_ssl_t        ssl;
 
--- a/src/stream/ngx_stream_ssl_module.c	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/stream/ngx_stream_ssl_module.c	Wed Jul 09 19:02:09 2025 +0400
@@ -133,6 +133,13 @@
       0,
       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_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_str_slot,
@@ -881,6 +888,7 @@
     sscf->passwords = NGX_CONF_UNSET_PTR;
     sscf->conf_commands = NGX_CONF_UNSET_PTR;
     sscf->prefer_server_ciphers = NGX_CONF_UNSET;
+    sscf->certificate_compression = NGX_CONF_UNSET;
     sscf->reject_handshake = NGX_CONF_UNSET;
     sscf->verify = NGX_CONF_UNSET_UINT;
     sscf->verify_depth = NGX_CONF_UNSET_UINT;
@@ -914,6 +922,9 @@
     ngx_conf_merge_value(conf->prefer_server_ciphers,
                          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_bitmask_value(conf->protocols, prev->protocols,
@@ -1039,6 +1050,13 @@
         {
             return NGX_CONF_ERROR;
         }
+
+        if (ngx_ssl_certificate_compression(cf, &conf->ssl,
+                                            conf->certificate_compression)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
     }
 
     if (conf->verify) {
--- a/src/stream/ngx_stream_ssl_module.h	Tue Jul 15 15:55:26 2025 +0400
+++ b/src/stream/ngx_stream_ssl_module.h	Wed Jul 09 19:02:09 2025 +0400
@@ -18,6 +18,7 @@
     ngx_msec_t        handshake_timeout;
 
     ngx_flag_t        prefer_server_ciphers;
+    ngx_flag_t        certificate_compression;
     ngx_flag_t        reject_handshake;
 
     ngx_ssl_t         ssl;