Skip to content

feature: compress certificates set from ssl_certificate_by_lua* - #2530

Open
u5surf wants to merge 1 commit into
openresty:masterfrom
u5surf:ssl-cert-compression
Open

u5surf wants to merge 1 commit into
openresty:masterfrom
u5surf:ssl-cert-compression

Conversation

@u5surf

@u5surf u5surf commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2516.

Problem

With ssl_certificate_compression on; (nginx 1.29.1+) and OpenSSL 3.2+, a statically configured certificate is sent as a TLS 1.3 CompressedCertificate (RFC 8879). One installed from ssl_certificate_by_lua* is not.

OpenSSL sends only a chain that was pre-compressed beforehand — get_compressed_certificate_alg() chooses among the algorithms whose comp_cert[] entry is populated. nginx populates those at configuration time for ssl_certificate; ngx.ssl.clear_certs() drops them together with the certificates they belong to, and set_cert() does not create new ones.

Approach

SSL_compress_certs() cannot do this from a certificate callback: it reaches ssl_get_cert_to_compress(), which gives up unless SSL_in_before(), and by then the handshake has started. Implemented that way first, it failed on every handshake.

ngx_http_lua_ffi_ssl_compress_certs(r, alg, err) therefore compresses on a throw-away SSL made from the connection's SSL_CTX — a fresh object is in the before state — and installs the result on the live connection with SSL_set1_compressed_cert(), which checks only sc->server and so is allowed mid-handshake. SSL_CTX_compress_certs() works the same way internally.

alg is the RFC 8879 number, or 0 for every algorithm the library has, matching SSL_CTX_compress_certs(). LibreSSL, BoringSSL/AWS-LC and OpenSSL < 3.2.0 get at least OpenSSL 3.2.0 required but found ....

The Lua API ngx.ssl.compress_certs() is in openresty/lua-resty-core#540.

Verification

nginx 1.31.6 with this module against an OpenSSL 3.5.4 configured with zlib-dynamic, certificate installed from Lua, observed with openssl s_client -trace:

server certificate message
ssl_certificate_compression on; alone Certificate, Length=1566
plus ssl.compress_certs("zlib") CompressedCertificate, Length=1396

Tests

t/194-ssl-cert-compression.t covers the dynamic certificate, an unknown algorithm, and compressing with no certificate on the connection. Whether OpenSSL was built with zlib, brotli or zstd is not visible from nginx -V, so the first block accepts either outcome unless TEST_NGINX_CERT_COMP_ALGS=1 says the library has one; the other two are deterministic everywhere.

I hereby granted the copyright of the changes in this pull request
to the authors of this lua-nginx-module project.

TLS 1.3 certificate compression (RFC 8879) never reached the
certificates installed from Lua. OpenSSL only sends a
CompressedCertificate for a chain that was pre-compressed beforehand:
get_compressed_certificate_alg() picks an algorithm only among the ones
whose comp_cert entry is populated. nginx populates those for
ssl_certificate when ssl_certificate_compression is on, and
ngx.ssl.clear_certs() drops them together with the certificates they
belong to, so a dynamic chain always went out in the clear.

Add ngx_http_lua_ffi_ssl_compress_certs(), which pre-compresses the
chain the connection currently holds. SSL_compress_certs() cannot do
this from a certificate callback: it reaches
ssl_get_cert_to_compress(), which gives up unless SSL_in_before(), and
by then the handshake has started. So compress on a throw-away
connection holding the same chain and install the result with
SSL_set1_compressed_cert(), which has no such restriction.

Requires OpenSSL 3.2.0 or later, where the RFC 8879 API arrived; every
other TLS library gets the version error instead.
@u5surf
u5surf marked this pull request as ready for review September 25, 2026 21:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support TLS 1.3 certificate compression for dynamic certificates

1 participant