changeset 9465:c97c240aabbe

SSL: fixed "key values mismatch" with object cache inheritance. In rare cases, it was possible to get into this error state on reload with improperly updated file timestamps for certificate and key pairs. The fix is to retry on X509_R_KEY_VALUES_MISMATCH, similar to 5d5d9adcc. Additionally, loading SSL certificate is updated to avoid certificates discarded on retry to appear in ssl->certs and in extra chain.
author Sergey Kandaurov <pluknet@nginx.com>
date Thu, 29 May 2025 17:49:48 +0400
parents fd6242dfb464
children 0d188d92c12d
files src/event/ngx_event_openssl.c src/event/ngx_event_openssl_cache.c
diffstat 2 files changed, 63 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/src/event/ngx_event_openssl.c	Thu Sep 25 12:51:40 2025 -0700
+++ b/src/event/ngx_event_openssl.c	Thu May 29 17:49:48 2025 +0400
@@ -458,10 +458,18 @@
 {
     char            *err;
     X509            *x509, **elm;
+    u_long           n;
     EVP_PKEY        *pkey;
+    ngx_uint_t       mask;
     STACK_OF(X509)  *chain;
 
-    chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT, &err, cert, NULL);
+    mask = 0;
+    elm = NULL;
+
+retry:
+
+    chain = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_CERT | mask,
+                                &err, cert, NULL);
     if (chain == NULL) {
         if (err != NULL) {
             ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
@@ -501,11 +509,16 @@
         }
     }
 
-    elm = ngx_array_push(&ssl->certs);
     if (elm == NULL) {
-        X509_free(x509);
-        sk_X509_pop_free(chain, X509_free);
-        return NGX_ERROR;
+        elm = ngx_array_push(&ssl->certs);
+        if (elm == NULL) {
+            X509_free(x509);
+            sk_X509_pop_free(chain, X509_free);
+            return NGX_ERROR;
+        }
+
+    } else {
+        X509_free(*elm);
     }
 
     *elm = x509;
@@ -528,11 +541,21 @@
     }
 
 #else
-    {
-    int  n;
 
     /* SSL_CTX_set0_chain() is only available in OpenSSL 1.0.2+ */
 
+#ifdef SSL_CTRL_CLEAR_EXTRA_CHAIN_CERTS
+    /* OpenSSL 1.0.1+ */
+    SSL_CTX_clear_extra_chain_certs(ssl->ctx);
+#else
+
+    if (ssl->ctx->extra_certs) {
+        sk_X509_pop_free(ssl->ctx->extra_certs, X509_free);
+        ssl->ctx->extra_certs = NULL;
+    }
+
+#endif
+
     n = sk_X509_num(chain);
 
     while (n--) {
@@ -548,10 +571,11 @@
     }
 
     sk_X509_free(chain);
-    }
+
 #endif
 
-    pkey = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_PKEY, &err, key, passwords);
+    pkey = ngx_ssl_cache_fetch(cf, NGX_SSL_CACHE_PKEY | mask,
+                               &err, key, passwords);
     if (pkey == NULL) {
         if (err != NULL) {
             ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
@@ -563,9 +587,23 @@
     }
 
     if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) {
+        EVP_PKEY_free(pkey);
+
+        /* there can be mismatched pairs on uneven cache update */
+
+        n = ERR_peek_last_error();
+
+        if (ERR_GET_LIB(n) == ERR_LIB_X509
+            && ERR_GET_REASON(n) == X509_R_KEY_VALUES_MISMATCH
+            && mask == 0)
+        {
+            ERR_clear_error();
+            mask = NGX_SSL_CACHE_INVALIDATE;
+            goto retry;
+        }
+
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                       "SSL_CTX_use_PrivateKey(\"%s\") failed", key->data);
-        EVP_PKEY_free(pkey);
         return NGX_ERROR;
     }
 
--- a/src/event/ngx_event_openssl_cache.c	Thu Sep 25 12:51:40 2025 -0700
+++ b/src/event/ngx_event_openssl_cache.c	Thu May 29 17:49:48 2025 +0400
@@ -193,6 +193,7 @@
     time_t                 mtime;
     uint32_t               hash;
     ngx_int_t              rc;
+    ngx_uint_t             invalidate;
     ngx_file_uniq_t        uniq;
     ngx_file_info_t        fi;
     ngx_ssl_cache_t       *cache, *old_cache;
@@ -202,10 +203,17 @@
 
     *err = NULL;
 
+    invalidate = index & NGX_SSL_CACHE_INVALIDATE;
+    index &= ~NGX_SSL_CACHE_INVALIDATE;
+
     if (ngx_ssl_cache_init_key(cf->pool, index, path, &id) != NGX_OK) {
         return NULL;
     }
 
+    if (id.type == NGX_SSL_CACHE_DATA) {
+        invalidate = 0;
+    }
+
     cache = (ngx_ssl_cache_t *) ngx_get_conf(cf->cycle->conf_ctx,
                                              ngx_openssl_cache_module);
 
@@ -215,7 +223,12 @@
     cn = ngx_ssl_cache_lookup(cache, type, &id, hash);
 
     if (cn != NULL) {
-        return type->ref(err, cn->value);
+        if (!invalidate) {
+            return type->ref(err, cn->value);
+        }
+
+        type->free(cn->value);
+        ngx_rbtree_delete(&cache->rbtree, &cn->node);
     }
 
     value = NULL;
@@ -236,7 +249,7 @@
 
     old_cache = ngx_ssl_cache_get_old_conf(cf->cycle);
 
-    if (old_cache && old_cache->inheritable) {
+    if (old_cache && old_cache->inheritable && !invalidate) {
         cn = ngx_ssl_cache_lookup(old_cache, type, &id, hash);
 
         if (cn != NULL) {