[PATCH 6 of 6] SSL: caching CA certificates

Mini Hawthorne mini at nginx.com
Tue Jul 23 19:30:29 UTC 2024


# HG changeset patch
# User Mini Hawthorne <mini at 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;


More information about the nginx-devel mailing list