Skip to content

Commit bee3525

Browse files
authored
feature: compress certificates set from ssl_certificate_by_lua*.
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.
1 parent 52e75d1 commit bee3525

2 files changed

Lines changed: 628 additions & 0 deletions

File tree

‎src/ngx_http_lua_ssl_certby.c‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,6 +784,148 @@ ngx_http_lua_ffi_ssl_set_der_private_key(ngx_http_request_t *r,
784784
}
785785

786786

787+
#if !(defined LIBRESSL_VERSION_NUMBER || defined OPENSSL_IS_BORINGSSL \
788+
|| OPENSSL_VERSION_NUMBER < 0x30200000L)
789+
790+
static ngx_int_t
791+
ngx_http_lua_ssl_compress_cert(ngx_ssl_conn_t *ssl_conn, SSL *tmp_conn,
792+
int alg, char **err)
793+
{
794+
size_t len, orig_len;
795+
unsigned char *data = NULL;
796+
797+
len = SSL_get1_compressed_cert(tmp_conn, alg, &data, &orig_len);
798+
if (len == 0) {
799+
*err = "SSL_get1_compressed_cert() failed";
800+
return NGX_ERROR;
801+
}
802+
803+
if (SSL_set1_compressed_cert(ssl_conn, alg, data, len, orig_len) != 1) {
804+
OPENSSL_free(data);
805+
*err = "SSL_set1_compressed_cert() failed";
806+
return NGX_ERROR;
807+
}
808+
809+
OPENSSL_free(data);
810+
811+
return NGX_OK;
812+
}
813+
814+
#endif
815+
816+
817+
int
818+
ngx_http_lua_ffi_ssl_compress_certs(ngx_http_request_t *r, int alg, char **err)
819+
{
820+
#if (defined LIBRESSL_VERSION_NUMBER || defined OPENSSL_IS_BORINGSSL \
821+
|| OPENSSL_VERSION_NUMBER < 0x30200000L)
822+
823+
*err = "at least OpenSSL 3.2.0 required but found " OPENSSL_VERSION_TEXT;
824+
return NGX_ERROR;
825+
826+
#else
827+
828+
int i, n;
829+
X509 *x509;
830+
SSL *tmp_conn = NULL;
831+
STACK_OF(X509) *chain;
832+
ngx_ssl_conn_t *ssl_conn;
833+
834+
if (r->connection == NULL || r->connection->ssl == NULL) {
835+
*err = "bad request";
836+
return NGX_ERROR;
837+
}
838+
839+
ssl_conn = r->connection->ssl->connection;
840+
if (ssl_conn == NULL) {
841+
*err = "bad ssl conn";
842+
return NGX_ERROR;
843+
}
844+
845+
/*
846+
* alg 0 (TLSEXT_comp_cert_none) means "every algorithm enabled in the
847+
* library", like the alg argument of SSL_CTX_compress_certs()
848+
*/
849+
850+
if (alg < TLSEXT_comp_cert_none || alg >= TLSEXT_comp_cert_limit) {
851+
*err = "unknown certificate compression algorithm";
852+
return NGX_ERROR;
853+
}
854+
855+
x509 = SSL_get_certificate(ssl_conn);
856+
if (x509 == NULL) {
857+
*err = "no certificate set on this connection";
858+
return NGX_ERROR;
859+
}
860+
861+
/*
862+
* OpenSSL pre-compresses certificates only on a connection that has not
863+
* started its handshake yet: ssl_get_cert_to_compress() gives up unless
864+
* SSL_in_before(), and ssl_certificate_by_lua* runs from the middle of a
865+
* handshake. So compress on a throw-away connection holding the same
866+
* chain, and hand the result to the live one with
867+
* SSL_set1_compressed_cert(), which carries no such restriction.
868+
*/
869+
870+
tmp_conn = SSL_new(SSL_get_SSL_CTX(ssl_conn));
871+
if (tmp_conn == NULL) {
872+
*err = "SSL_new() failed";
873+
goto failed;
874+
}
875+
876+
if (SSL_use_certificate(tmp_conn, x509) != 1) {
877+
*err = "SSL_use_certificate() failed";
878+
goto failed;
879+
}
880+
881+
if (SSL_get0_chain_certs(ssl_conn, &chain) == 1
882+
&& chain != NULL
883+
&& SSL_set1_chain(tmp_conn, chain) != 1)
884+
{
885+
*err = "SSL_set1_chain() failed";
886+
goto failed;
887+
}
888+
889+
n = 0;
890+
891+
for (i = TLSEXT_comp_cert_none + 1; i < TLSEXT_comp_cert_limit; i++) {
892+
893+
if (alg != TLSEXT_comp_cert_none && i != alg) {
894+
continue;
895+
}
896+
897+
if (ngx_http_lua_ssl_compress_cert(ssl_conn, tmp_conn, i, err)
898+
== NGX_OK)
899+
{
900+
n++;
901+
}
902+
}
903+
904+
if (n == 0) {
905+
/* *err was set by the last attempt */
906+
goto failed;
907+
}
908+
909+
SSL_free(tmp_conn);
910+
911+
ERR_clear_error();
912+
913+
return NGX_OK;
914+
915+
failed:
916+
917+
if (tmp_conn) {
918+
SSL_free(tmp_conn);
919+
}
920+
921+
ERR_clear_error();
922+
923+
return NGX_ERROR;
924+
925+
#endif
926+
}
927+
928+
787929
int
788930
ngx_http_lua_ffi_ssl_raw_server_addr(ngx_http_request_t *r, char **addr,
789931
size_t *addrlen, int *addrtype, char **err)

0 commit comments

Comments
 (0)