# HG changeset patch # User Mini Hawthorne <m...@f5.com> # Date 1721762968 0 # Tue Jul 23 19:29:28 2024 +0000 # Node ID c4a90845888cfa20a4f622eb97954dfbd54af5c6 # Parent 298a9eaa59d2a16f85b6aa3584eb5f8298e6c9bc SSL: caching CA certificates.
This can potentially provide a large amount of savings, because CA certificates can be quite large. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -18,6 +18,8 @@ typedef struct { } ngx_openssl_conf_t; +static int ngx_ssl_x509_name_cmp(const X509_NAME *const *a, + const X509_NAME *const *b); static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store); static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret); @@ -651,10 +653,23 @@ ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_ } +static int +ngx_ssl_x509_name_cmp(const X509_NAME *const *a, const X509_NAME *const *b) +{ + return (X509_NAME_cmp(*a, *b)); +} + + ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth) { + int n, i; + char *err; + X509 *x; + X509_NAME *xn; + X509_STORE *xs; + STACK_OF(X509) *xsk; STACK_OF(X509_NAME) *list; SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback); @@ -665,33 +680,91 @@ ngx_ssl_client_certificate(ngx_conf_t *c return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { + list = sk_X509_NAME_new(ngx_ssl_x509_name_cmp); + if (list == NULL) { return NGX_ERROR; } - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_load_verify_locations(\"%s\") failed", - cert->data); + xs = SSL_CTX_get_cert_store(ssl->ctx); + xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, + cert, NULL); + if (xsk == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "failed to load \"%s\": %s", cert->data, err); + } + + sk_X509_NAME_pop_free(list, X509_NAME_free); return NGX_ERROR; } - /* - * SSL_CTX_load_verify_locations() may leave errors in the error queue - * while returning success - */ - - ERR_clear_error(); - - list = SSL_load_client_CA_file((char *) cert->data); - - if (list == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_load_client_CA_file(\"%s\") failed", cert->data); - return NGX_ERROR; - } + n = sk_X509_num(xsk); + + for (i = 0; i < n; i++) { + x = sk_X509_value(xsk, i); + + xn = X509_get_subject_name(x); + if (xn == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_get_subject_name() failed"); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(xsk, X509_free); + return NGX_ERROR; + } + + xn = X509_NAME_dup(xn); + if (xn == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_NAME_dup() failed"); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(xsk, X509_free); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (sk_X509_NAME_find(list, NULL, xn) > 0) { +#else + if (sk_X509_NAME_find(list, xn) >= 0) { +#endif + X509_NAME_free(xn); + continue; + } + + if (!sk_X509_NAME_push(list, xn)) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "sk_X509_NAME_push() failed"); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(xsk, X509_free); + X509_NAME_free(xn); + return NGX_ERROR; + } + + if (X509_STORE_add_cert(xs, x) != 1) { + +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) + u_long error; + + /* not reported in OpenSSL 1.1.0i+ */ + + error = ERR_peek_last_error(); + + if (ERR_GET_LIB(error) == ERR_LIB_X509 + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) + { + ERR_clear_error(); + continue; + } +#endif + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert() failed"); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(xsk, X509_free); + return NGX_ERROR; + } + } + + sk_X509_pop_free(xsk, X509_free); SSL_CTX_set_client_CA_list(ssl->ctx, list); @@ -703,6 +776,12 @@ ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth) { + int i, n; + char *err; + X509 *x; + X509_STORE *xs; + STACK_OF(X509) *xsk; + SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx), ngx_ssl_verify_callback); @@ -712,25 +791,49 @@ ngx_ssl_trusted_certificate(ngx_conf_t * return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { + xs = SSL_CTX_get_cert_store(ssl->ctx); + xsk = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_ca, &err, + cert, NULL); + if (xsk == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "failed to load \"%s\": %s", cert->data, err); + } + return NGX_ERROR; } - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_load_verify_locations(\"%s\") failed", - cert->data); - return NGX_ERROR; - } - - /* - * SSL_CTX_load_verify_locations() may leave errors in the error queue - * while returning success - */ - - ERR_clear_error(); + n = sk_X509_num(xsk); + + for (i = 0; i < n; i++) { + x = sk_X509_value(xsk, i); + + if (X509_STORE_add_cert(xs, x) != 1) { + +#if !(OPENSSL_VERSION_NUMBER >= 0x1010009fL \ + || LIBRESSL_VERSION_NUMBER >= 0x3050000fL) + u_long error; + + /* not reported in OpenSSL 1.1.0i+ */ + + error = ERR_peek_last_error(); + + if (ERR_GET_LIB(error) == ERR_LIB_X509 + && ERR_GET_REASON(error) == X509_R_CERT_ALREADY_IN_HASH_TABLE) + { + ERR_clear_error(); + continue; + } +#endif + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert() failed"); + sk_X509_pop_free(xsk, X509_free); + return NGX_ERROR; + } + } + + sk_X509_pop_free(xsk, X509_free); return NGX_OK; } diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -351,6 +351,7 @@ extern int ngx_ssl_ocsp_index; extern int ngx_ssl_index; +extern ngx_ssl_cache_type_t ngx_ssl_cache_ca; extern ngx_ssl_cache_type_t ngx_ssl_cache_cert; extern ngx_ssl_cache_type_t ngx_ssl_cache_crl; extern ngx_ssl_cache_type_t ngx_ssl_cache_key; diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -46,6 +46,7 @@ static void ngx_ssl_cache_node_insert(ng static ngx_ssl_cache_node_t *ngx_ssl_cache_lookup(ngx_ssl_cache_t *cache, ngx_ssl_cache_type_t *type, ngx_str_t *id, uint32_t hash); +static void *ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data); static void *ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data); static void ngx_ssl_cache_cert_free(void *data); static void *ngx_ssl_cache_cert_ref(char **err, void *data); @@ -70,6 +71,15 @@ static ngx_core_module_t ngx_openssl_ca }; +ngx_ssl_cache_type_t ngx_ssl_cache_ca = { + "certificate CA list", + + ngx_ssl_cache_ca_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref, +}; + + ngx_ssl_cache_type_t ngx_ssl_cache_cert = { "certificate chain", @@ -321,6 +331,58 @@ ngx_ssl_cache_lookup(ngx_ssl_cache_t *ca static void * +ngx_ssl_cache_ca_create(ngx_str_t *id, char **err, void *data) +{ + BIO *bio; + X509 *x; + u_long n; + STACK_OF(X509) *sk; + + /* start with an empty certificate chain */ + sk = sk_X509_new_null(); + if (sk == NULL) { + *err = "sk_X509_new_null() failed"; + return NULL; + } + + /* figure out where to load from */ + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + sk_X509_pop_free(sk, X509_free); + return NULL; + } + + /* read all of the certificates */ + while ((x = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL)) != NULL) { + if (sk_X509_push(sk, x) <= 0) { + *err = "sk_X509_push() failed"; + BIO_free(bio); + sk_X509_pop_free(sk, X509_free); + return NULL; + } + } + + BIO_free(bio); + + n = ERR_peek_last_error(); + if (sk_X509_num(sk) == 0 + || ERR_GET_LIB(n) != ERR_LIB_PEM + || ERR_GET_REASON(n) != PEM_R_NO_START_LINE) + { + /* the failure wasn't "no more certificates to load" */ + *err = "PEM_read_bio_X509() failed"; + sk_X509_pop_free(sk, X509_free); + return NULL; + } + + /* success leaves errors on the error stack */ + ERR_clear_error(); + + return sk; +} + + +static void * ngx_ssl_cache_cert_create(ngx_str_t *id, char **err, void *data) { BIO *bio; _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel