[nginx] SSL: caching CA certificates.

noreply at nginx.com noreply at nginx.com
Tue Oct 1 14:00:02 UTC 2024


details:   https://github.com/nginx/nginx/commit/5917e9de5a45bb7288c1c433db840d1a4c6290f3
branches:  master
commit:    5917e9de5a45bb7288c1c433db840d1a4c6290f3
user:      Sergey Kandaurov <pluknet at nginx.com>
date:      Mon, 9 Sep 2024 19:05:58 +0400
description:
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       | 146 +++++++++++++++++++++++++++++-------
 src/event/ngx_event_openssl.h       |   1 +
 src/event/ngx_event_openssl_cache.c |  66 ++++++++++++++++
 3 files changed, 185 insertions(+), 28 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) {
+    list = sk_X509_NAME_new(ngx_ssl_cmp_x509_name);
+    if (list == NULL) {
         return NGX_ERROR;
     }
 
-    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
-        == 0)
-    {
+    store = SSL_CTX_get_cert_store(ssl->ctx);
+
+    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();
+        sk_X509_NAME_pop_free(list, X509_NAME_free);
+        return NGX_ERROR;
+    }
 
-    list = SSL_load_client_CA_file((char *) cert->data);
+    n = sk_X509_num(chain);
 
-    if (list == NULL) {
-        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
-                      "SSL_load_client_CA_file(\"%s\") failed", cert->data);
-        return NGX_ERROR;
+    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) {
+    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;
     }
 
-    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);
+    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);
+        }
+
         return NGX_ERROR;
     }
 
-    /*
-     * SSL_CTX_load_verify_locations() may leave errors in the error queue
-     * while returning success
-     */
+    n = sk_X509_num(chain);
 
-    ERR_clear_error();
+    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)
 {


More information about the nginx-devel mailing list