From 5917e9de5a45bb7288c1c433db840d1a4c6290f3 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Mon, 9 Sep 2024 19:05:58 +0400 Subject: [PATCH] SSL: caching CA certificates. This can potentially provide a large amount of savings, because CA certificates can be quite large. Based on previous work by Mini Hawthorne. --- src/event/ngx_event_openssl.c | 164 +++++++++++++++++++++------- src/event/ngx_event_openssl.h | 1 + src/event/ngx_event_openssl_cache.c | 66 +++++++++++ 3 files changed, 194 insertions(+), 37 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 72189e1a4..2b1d107df 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -22,6 +22,8 @@ static ngx_inline ngx_int_t ngx_ssl_cert_already_in_hash(void); 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); +static int ngx_ssl_cmp_x509_name(const X509_NAME *const *a, + const X509_NAME *const *b); static void ngx_ssl_passwords_cleanup(void *data); static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess); @@ -656,6 +658,12 @@ 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 *x509; + X509_NAME *name; + X509_STORE *store; + STACK_OF(X509) *chain; STACK_OF(X509_NAME) *list; SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback); @@ -666,34 +674,84 @@ ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { - 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(); - - list = SSL_load_client_CA_file((char *) cert->data); - + list = sk_X509_NAME_new(ngx_ssl_cmp_x509_name); if (list == NULL) { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_load_client_CA_file(\"%s\") failed", cert->data); return NGX_ERROR; } + store = SSL_CTX_get_cert_store(ssl->ctx); + + if (store == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_get_cert_store() failed"); + return NGX_ERROR; + } + + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); + if (chain == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } + + sk_X509_NAME_pop_free(list, X509_NAME_free); + return NGX_ERROR; + } + + n = sk_X509_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_value(chain, i); + + if (X509_STORE_add_cert(store, x509) != 1) { + + if (ngx_ssl_cert_already_in_hash()) { + continue; + } + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert(\"%s\") failed", cert->data); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + name = X509_get_subject_name(x509); + if (name == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_get_subject_name(\"%s\") failed", cert->data); + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + + name = X509_NAME_dup(name); + if (name == NULL) { + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (sk_X509_NAME_find(list, NULL, name) > 0) { +#else + if (sk_X509_NAME_find(list, name) >= 0) { +#endif + X509_NAME_free(name); + continue; + } + + if (sk_X509_NAME_push(list, name) == 0) { + sk_X509_NAME_pop_free(list, X509_NAME_free); + sk_X509_pop_free(chain, X509_free); + X509_NAME_free(name); + return NGX_ERROR; + } + } + + sk_X509_pop_free(chain, X509_free); + SSL_CTX_set_client_CA_list(ssl->ctx, list); return NGX_OK; @@ -704,6 +762,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 *x509; + X509_STORE *store; + STACK_OF(X509) *chain; + SSL_CTX_set_verify(ssl->ctx, SSL_CTX_get_verify_mode(ssl->ctx), ngx_ssl_verify_callback); @@ -713,25 +777,44 @@ ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, return NGX_OK; } - if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { - return NGX_ERROR; - } + store = SSL_CTX_get_cert_store(ssl->ctx); - if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) - == 0) - { + if (store == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_load_verify_locations(\"%s\") failed", - cert->data); + "SSL_CTX_get_cert_store() failed"); return NGX_ERROR; } - /* - * SSL_CTX_load_verify_locations() may leave errors in the error queue - * while returning success - */ + chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CA, &err, cert, NULL); + if (chain == NULL) { + if (err != NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "cannot load certificate \"%s\": %s", + cert->data, err); + } - ERR_clear_error(); + return NGX_ERROR; + } + + n = sk_X509_num(chain); + + for (i = 0; i < n; i++) { + x509 = sk_X509_value(chain, i); + + if (X509_STORE_add_cert(store, x509) != 1) { + + if (ngx_ssl_cert_already_in_hash()) { + continue; + } + + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_add_cert(\"%s\") failed", cert->data); + sk_X509_pop_free(chain, X509_free); + return NGX_ERROR; + } + } + + sk_X509_pop_free(chain, X509_free); return NGX_OK; } @@ -987,6 +1070,13 @@ ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret) } +static int +ngx_ssl_cmp_x509_name(const X509_NAME *const *a, const X509_NAME *const *b) +{ + return (X509_NAME_cmp(*a, *b)); +} + + ngx_array_t * ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index e2f0e5821..6d171229c 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -196,6 +196,7 @@ typedef struct { #define NGX_SSL_CACHE_CERT 0 #define NGX_SSL_CACHE_PKEY 1 #define NGX_SSL_CACHE_CRL 2 +#define NGX_SSL_CACHE_CA 3 ngx_int_t ngx_ssl_init(ngx_log_t *log); diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index b2615d2bf..f43bdb5e7 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -70,6 +70,9 @@ static void *ngx_ssl_cache_crl_create(ngx_ssl_cache_key_t *id, char **err, static void ngx_ssl_cache_crl_free(void *data); static void *ngx_ssl_cache_crl_ref(char **err, void *data); +static void *ngx_ssl_cache_ca_create(ngx_ssl_cache_key_t *id, char **err, + void *data); + static BIO *ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err); static void *ngx_openssl_cache_create_conf(ngx_cycle_t *cycle); @@ -117,6 +120,11 @@ static ngx_ssl_cache_type_t ngx_ssl_cache_types[] = { { ngx_ssl_cache_crl_create, ngx_ssl_cache_crl_free, ngx_ssl_cache_crl_ref }, + + /* NGX_SSL_CACHE_CA */ + { ngx_ssl_cache_ca_create, + ngx_ssl_cache_cert_free, + ngx_ssl_cache_cert_ref } }; @@ -623,6 +631,64 @@ ngx_ssl_cache_crl_ref(char **err, void *data) } +static void * +ngx_ssl_cache_ca_create(ngx_ssl_cache_key_t *id, char **err, void *data) +{ + BIO *bio; + X509 *x509; + u_long n; + STACK_OF(X509) *chain; + + chain = sk_X509_new_null(); + if (chain == NULL) { + *err = "sk_X509_new_null() failed"; + return NULL; + } + + bio = ngx_ssl_cache_create_bio(id, err); + if (bio == NULL) { + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + for ( ;; ) { + + x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL); + if (x509 == NULL) { + n = ERR_peek_last_error(); + + if (ERR_GET_LIB(n) == ERR_LIB_PEM + && ERR_GET_REASON(n) == PEM_R_NO_START_LINE + && sk_X509_num(chain) > 0) + { + /* end of file */ + ERR_clear_error(); + break; + } + + /* some real error */ + + *err = "PEM_read_bio_X509_AUX() failed"; + BIO_free(bio); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + + if (sk_X509_push(chain, x509) == 0) { + *err = "sk_X509_push() failed"; + BIO_free(bio); + X509_free(x509); + sk_X509_pop_free(chain, X509_free); + return NULL; + } + } + + BIO_free(bio); + + return chain; +} + + static BIO * ngx_ssl_cache_create_bio(ngx_ssl_cache_key_t *id, char **err) {