[PATCH 5 of 6] SSL: caching private keys

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


# HG changeset patch
# User Mini Hawthorne <mini at f5.com>
# Date 1721762945 0
#      Tue Jul 23 19:29:05 2024 +0000
# Node ID 298a9eaa59d2a16f85b6aa3584eb5f8298e6c9bc
# Parent  867e05f555e6f593589a0278c865e7dcffe597f4
SSL: caching private keys.

Added ngx_ssl_cache_key which caches private keys.  Special support is included
for "engine:..." keys.

EVP_KEY objects are a reference-counted container for key material, so shallow
copies and OpenSSL stack management aren't needed as with certificates.

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,10 +18,6 @@ typedef struct {
 } ngx_openssl_conf_t;
 
 
-static EVP_PKEY *ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err,
-    ngx_str_t *key, ngx_array_t *passwords);
-static int ngx_ssl_password_callback(char *buf, int size, int rwflag,
-    void *userdata);
 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);
@@ -536,7 +532,8 @@ ngx_ssl_certificate(ngx_conf_t *cf, ngx_
     }
 #endif
 
-    pkey = ngx_ssl_load_certificate_key(cf->pool, &err, key, passwords);
+    pkey = ngx_ssl_cache_fetch(cf->cycle, cf->pool, &ngx_ssl_cache_key, &err,
+                               key, passwords);
     if (pkey == NULL) {
         if (err != NULL) {
             ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
@@ -610,10 +607,11 @@ ngx_ssl_connection_certificate(ngx_conne
 
 #endif
 
-    pkey = ngx_ssl_load_certificate_key(pool, &err, key, passwords);
+    pkey = ngx_ssl_cache_fetch((ngx_cycle_t *) ngx_cycle, c->pool,
+                               &ngx_ssl_cache_key, &err, key, passwords);
     if (pkey == NULL) {
         if (err != NULL) {
-            ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
+            ngx_ssl_error(NGX_LOG_EMERG, c->log, 0,
                           "cannot load certificate key \"%s\": %s",
                           key->data, err);
         }
@@ -634,151 +632,6 @@ ngx_ssl_connection_certificate(ngx_conne
 }
 
 
-static EVP_PKEY *
-ngx_ssl_load_certificate_key(ngx_pool_t *pool, char **err,
-    ngx_str_t *key, ngx_array_t *passwords)
-{
-    BIO              *bio;
-    EVP_PKEY         *pkey;
-    ngx_str_t        *pwd;
-    ngx_uint_t        tries;
-    pem_password_cb  *cb;
-
-    if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) {
-
-#ifndef OPENSSL_NO_ENGINE
-
-        u_char  *p, *last;
-        ENGINE  *engine;
-
-        p = key->data + sizeof("engine:") - 1;
-        last = (u_char *) ngx_strchr(p, ':');
-
-        if (last == NULL) {
-            *err = "invalid syntax";
-            return NULL;
-        }
-
-        *last = '\0';
-
-        engine = ENGINE_by_id((char *) p);
-
-        *last++ = ':';
-
-        if (engine == NULL) {
-            *err = "ENGINE_by_id() failed";
-            return NULL;
-        }
-
-        pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0);
-
-        if (pkey == NULL) {
-            *err = "ENGINE_load_private_key() failed";
-            ENGINE_free(engine);
-            return NULL;
-        }
-
-        ENGINE_free(engine);
-
-        return pkey;
-
-#else
-
-        *err = "loading \"engine:...\" certificate keys is not supported";
-        return NULL;
-
-#endif
-    }
-
-    if (ngx_strncmp(key->data, "data:", sizeof("data:") - 1) == 0) {
-
-        bio = BIO_new_mem_buf(key->data + sizeof("data:") - 1,
-                              key->len - (sizeof("data:") - 1));
-        if (bio == NULL) {
-            *err = "BIO_new_mem_buf() failed";
-            return NULL;
-        }
-
-    } else {
-
-        if (ngx_get_full_name(pool, (ngx_str_t *) &ngx_cycle->conf_prefix, key)
-            != NGX_OK)
-        {
-            *err = NULL;
-            return NULL;
-        }
-
-        bio = BIO_new_file((char *) key->data, "r");
-        if (bio == NULL) {
-            *err = "BIO_new_file() failed";
-            return NULL;
-        }
-    }
-
-    if (passwords) {
-        tries = passwords->nelts;
-        pwd = passwords->elts;
-        cb = ngx_ssl_password_callback;
-
-    } else {
-        tries = 1;
-        pwd = NULL;
-        cb = NULL;
-    }
-
-    for ( ;; ) {
-
-        pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd);
-        if (pkey != NULL) {
-            break;
-        }
-
-        if (tries-- > 1) {
-            ERR_clear_error();
-            (void) BIO_reset(bio);
-            pwd++;
-            continue;
-        }
-
-        *err = "PEM_read_bio_PrivateKey() failed";
-        BIO_free(bio);
-        return NULL;
-    }
-
-    BIO_free(bio);
-
-    return pkey;
-}
-
-
-static int
-ngx_ssl_password_callback(char *buf, int size, int rwflag, void *userdata)
-{
-    ngx_str_t *pwd = userdata;
-
-    if (rwflag) {
-        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
-                      "ngx_ssl_password_callback() is called for encryption");
-        return 0;
-    }
-
-    if (pwd == NULL) {
-        return 0;
-    }
-
-    if (pwd->len > (size_t) size) {
-        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
-                      "password is truncated to %d bytes", size);
-    } else {
-        size = pwd->len;
-    }
-
-    ngx_memcpy(buf, pwd->data, size);
-
-    return size;
-}
-
-
 ngx_int_t
 ngx_ssl_ciphers(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *ciphers,
     ngx_uint_t prefer_server_ciphers)
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
@@ -353,6 +353,7 @@ extern int  ngx_ssl_index;
 
 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;
 
 
 #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
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
@@ -54,6 +54,12 @@ static void *ngx_ssl_cache_crl_create(ng
 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_key_create(ngx_str_t *id, char **err, void *data);
+static int ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag,
+    void *userdata);
+static void ngx_ssl_cache_key_free(void *data);
+static void *ngx_ssl_cache_key_ref(char **err, void *data);
+
 static BIO *ngx_ssl_cache_create_bio(ngx_str_t *id, char **err);
 
 
@@ -82,6 +88,15 @@ ngx_ssl_cache_type_t  ngx_ssl_cache_crl 
 };
 
 
+ngx_ssl_cache_type_t  ngx_ssl_cache_key = {
+    "private key",
+
+    ngx_ssl_cache_key_create,
+    ngx_ssl_cache_key_free,
+    ngx_ssl_cache_key_ref,
+};
+
+
 ngx_module_t  ngx_openssl_cache_module = {
     NGX_MODULE_V1,
     &ngx_openssl_cache_module_ctx,         /* module context */
@@ -508,6 +523,154 @@ ngx_ssl_cache_crl_ref(char **err, void *
 }
 
 
+static void *
+ngx_ssl_cache_key_create(ngx_str_t *id, char **err, void *data)
+{
+    ngx_array_t  *passwords = data;
+
+    BIO              *bio;
+    EVP_PKEY         *pkey;
+    ngx_str_t        *pwd;
+    ngx_uint_t        tries;
+    pem_password_cb  *cb;
+
+    if (ngx_strncmp(id->data, "engine:", sizeof("engine:") - 1) == 0) {
+
+#ifndef OPENSSL_NO_ENGINE
+
+        u_char  *p, *last;
+        ENGINE  *engine;
+
+        p = id->data + sizeof("engine:") - 1;
+        last = (u_char *) ngx_strchr(p, ':');
+
+        if (last == NULL) {
+            *err = "invalid syntax";
+            return NULL;
+        }
+
+        *last = '\0';
+
+        engine = ENGINE_by_id((char *) p);
+
+        *last++ = ':';
+
+        if (engine == NULL) {
+            *err = "ENGINE_by_id() failed";
+            return NULL;
+        }
+
+        pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0);
+
+        if (pkey == NULL) {
+            *err = "ENGINE_load_private_key() failed";
+            ENGINE_free(engine);
+            return NULL;
+        }
+
+        ENGINE_free(engine);
+
+        return pkey;
+#else
+        *err = "loading \"engine:...\" certificate keys is not supported";
+        return NULL;
+#endif
+    }
+
+    /* figure out where to load from */
+    bio = ngx_ssl_cache_create_bio(id, err);
+    if (bio == NULL) {
+        return NULL;
+    }
+
+    if (passwords) {
+        tries = passwords->nelts;
+        pwd = passwords->elts;
+        cb = ngx_ssl_cache_key_password_callback;
+
+    } else {
+        tries = 1;
+        pwd = NULL;
+        cb = NULL;
+    }
+
+    for ( ;; ) {
+
+        pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, pwd);
+        if (pkey != NULL) {
+            break;
+        }
+
+        if (tries-- > 1) {
+            ERR_clear_error();
+            (void) BIO_reset(bio);
+            pwd++;
+            continue;
+        }
+
+        *err = "PEM_read_bio_PrivateKey() failed";
+        BIO_free(bio);
+        return NULL;
+    }
+
+    BIO_free(bio);
+
+    return pkey;
+}
+
+
+static int
+ngx_ssl_cache_key_password_callback(char *buf, int size, int rwflag,
+    void *userdata)
+{
+    ngx_str_t  *pwd = userdata;
+
+    if (rwflag) {
+        ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
+                      "ngx_ssl_cache_key_password_callback() is called "
+                      "for encryption");
+        return 0;
+    }
+
+    if (pwd == NULL) {
+        return 0;
+    }
+
+    if (pwd->len > (size_t) size) {
+        ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0,
+                      "password is truncated to %d bytes", size);
+    } else {
+        size = pwd->len;
+    }
+
+    ngx_memcpy(buf, pwd->data, size);
+
+    return size;
+}
+
+
+static void
+ngx_ssl_cache_key_free(void *data)
+{
+    EVP_PKEY_free(data);
+}
+
+
+static void *
+ngx_ssl_cache_key_ref(char **err, void *data)
+{
+    EVP_PKEY  *pkey = data;
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+    EVP_PKEY_up_ref(pkey);
+#else
+    CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
+#endif
+
+    return data;
+}
+
+
 static BIO *
 ngx_ssl_cache_create_bio(ngx_str_t *id, char **err)
 {


More information about the nginx-devel mailing list