new file mode 100644
@@ -0,0 +1,342 @@
+From fc625fa247f68bc6a44bda391cd8c09bfc5ce5d5 Mon Sep 17 00:00:00 2001
+From: Joshua Rogers <MegaManSec@users.noreply.github.com>
+Date: Tue, 19 May 2026 11:47:50 +0200
+Subject: [PATCH] tls: fix incomplete mTLS config in conn reuse and session
+ cache
+
+cert_type, key, key_type, key_passwd and key_blob lived in
+ssl_config_data but not in ssl_primary_config, so they were invisible to
+match_ssl_primary_config() and to the TLS session cache peer key.
+
+Two easy handles sharing a connection pool could reuse each other's
+authenticated connections when they differed only on SSLKEY, SSLKEYTYPE,
+KEYPASSWD, SSLCERTTYPE or SSLKEYBLOB. The second handle would silently
+inherit the first handle's authenticated identity.
+
+Promote all five fields into ssl_primary_config so the conn-reuse
+predicate and session cache key cover the complete client credential
+set. Also replace the fixed ":CCERT" session cache marker with the
+actual clientcert path so sessions are not shared across different
+client certificates.
+
+Verified by test 3303 and 3304
+
+Reported-By: Joshua Rogers (AISLE Research)
+Closes #21667
+
+CVE: CVE-2026-8932
+Upstream-Status: Backport [https://github.com/curl/curl/commit/7541ae569d82fb308a5e2d94916027da4fa3ba3e]
+
+Backport Changes:
+- curl 8.7.1 keeps the session cache in vtls.c and stores backend key properties
+ outside ssl_primary_config. Duplicate those pointers into the primary
+ configuration so the upstream match, clone, and free behavior covers
+ connection and session reuse without moving every backend access.
+- Omitted backend-only field-access moves in ldap.c, vssh/, and vtls backends
+ because the target keeps the original ssl_config_data members as aliases.
+- Adapt unit3303 to the target curlcheck and test-data harnesses and allocation
+ helpers, and extend it with unit3304's case-insensitive cert_type/key_type
+ checks.
+- Omit newer vtls_scache.c/vtls_scache.h and unit3304 because their peer-key
+ API does not exist in 8.7.1. The target session cache instead uses the clone
+ and match functions in vtls.c, which the adapted unit3303 exercises.
+
+(cherry picked from commit 7541ae569d82fb308a5e2d94916027da4fa3ba3e)
+Signed-off-by: Devansh Patel <devanshp@cisco.com>
+---
+ lib/urldata.h | 5 ++
+ lib/vtls/vtls.c | 25 ++++++++
+ tests/data/Makefile.inc | 3 +-
+ tests/data/test3303 | 23 ++++++++
+ tests/unit/Makefile.inc | 4 +-
+ tests/unit/unit3303.c | 127 ++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 185 insertions(+), 2 deletions(-)
+ create mode 100644 tests/data/test3303
+ create mode 100644 tests/unit/unit3303.c
+
+diff --git a/lib/urldata.h b/lib/urldata.h
+index d2d9424197..9015515e17 100644
+--- a/lib/urldata.h
++++ b/lib/urldata.h
+@@ -286,6 +286,11 @@ struct ssl_primary_config {
+ char *CAfile; /* certificate to verify peer against */
+ char *issuercert; /* optional issuer certificate filename */
+ char *clientcert;
++ char *cert_type; /* format for certificate (default: PEM) */
++ char *key; /* private key file name */
++ struct curl_blob *key_blob;
++ char *key_type; /* format for private key (default: PEM) */
++ char *key_passwd; /* plain text private key password */
+ char *cipher_list; /* list of ciphers to use */
+ char *cipher_list13; /* list of TLS 1.3 cipher suites to use */
+ char *pinned_key;
+diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
+index d13a3cb1b7..c89e8abcc6 100644
+--- a/lib/vtls/vtls.c
++++ b/lib/vtls/vtls.c
+@@ -187,10 +187,15 @@ match_ssl_primary_config(struct Curl_easy *data,
+ blobcmp(c1->cert_blob, c2->cert_blob) &&
+ blobcmp(c1->ca_info_blob, c2->ca_info_blob) &&
+ blobcmp(c1->issuercert_blob, c2->issuercert_blob) &&
++ blobcmp(c1->key_blob, c2->key_blob) &&
+ Curl_safecmp(c1->CApath, c2->CApath) &&
+ Curl_safecmp(c1->CAfile, c2->CAfile) &&
+ Curl_safecmp(c1->issuercert, c2->issuercert) &&
+ Curl_safecmp(c1->clientcert, c2->clientcert) &&
++ curl_strequal(c1->cert_type, c2->cert_type) &&
++ Curl_safecmp(c1->key, c2->key) &&
++ curl_strequal(c1->key_type, c2->key_type) &&
++ !Curl_timestrcmp(c1->key_passwd, c2->key_passwd) &&
+ #ifdef USE_TLS_SRP
+ !Curl_timestrcmp(c1->username, c2->username) &&
+ !Curl_timestrcmp(c1->password, c2->password) &&
+@@ -234,10 +239,15 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source,
+ CLONE_BLOB(cert_blob);
+ CLONE_BLOB(ca_info_blob);
+ CLONE_BLOB(issuercert_blob);
++ CLONE_BLOB(key_blob);
+ CLONE_STRING(CApath);
+ CLONE_STRING(CAfile);
+ CLONE_STRING(issuercert);
+ CLONE_STRING(clientcert);
++ CLONE_STRING(cert_type);
++ CLONE_STRING(key);
++ CLONE_STRING(key_type);
++ CLONE_STRING(key_passwd);
+ CLONE_STRING(cipher_list);
+ CLONE_STRING(cipher_list13);
+ CLONE_STRING(pinned_key);
+@@ -257,12 +267,17 @@ static void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
+ Curl_safefree(sslc->CAfile);
+ Curl_safefree(sslc->issuercert);
+ Curl_safefree(sslc->clientcert);
++ Curl_safefree(sslc->cert_type);
++ Curl_safefree(sslc->key);
++ Curl_safefree(sslc->key_type);
++ Curl_safefree(sslc->key_passwd);
+ Curl_safefree(sslc->cipher_list);
+ Curl_safefree(sslc->cipher_list13);
+ Curl_safefree(sslc->pinned_key);
+ Curl_safefree(sslc->cert_blob);
+ Curl_safefree(sslc->ca_info_blob);
+ Curl_safefree(sslc->issuercert_blob);
++ Curl_safefree(sslc->key_blob);
+ Curl_safefree(sslc->curves);
+ Curl_safefree(sslc->CRLfile);
+ #ifdef USE_TLS_SRP
+@@ -297,6 +312,11 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
+ data->set.ssl.key_passwd = data->set.str[STRING_KEY_PASSWD];
+ data->set.ssl.primary.clientcert = data->set.str[STRING_CERT];
+ data->set.ssl.key_blob = data->set.blobs[BLOB_KEY];
++ data->set.ssl.primary.cert_type = data->set.ssl.cert_type;
++ data->set.ssl.primary.key = data->set.ssl.key;
++ data->set.ssl.primary.key_type = data->set.ssl.key_type;
++ data->set.ssl.primary.key_passwd = data->set.ssl.key_passwd;
++ data->set.ssl.primary.key_blob = data->set.ssl.key_blob;
+
+ #ifndef CURL_DISABLE_PROXY
+ data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY];
+@@ -322,6 +342,11 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
+ data->set.proxy_ssl.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY];
+ data->set.proxy_ssl.primary.clientcert = data->set.str[STRING_CERT_PROXY];
+ data->set.proxy_ssl.key_blob = data->set.blobs[BLOB_KEY_PROXY];
++ data->set.proxy_ssl.primary.cert_type = data->set.proxy_ssl.cert_type;
++ data->set.proxy_ssl.primary.key = data->set.proxy_ssl.key;
++ data->set.proxy_ssl.primary.key_type = data->set.proxy_ssl.key_type;
++ data->set.proxy_ssl.primary.key_passwd = data->set.proxy_ssl.key_passwd;
++ data->set.proxy_ssl.primary.key_blob = data->set.proxy_ssl.key_blob;
+ #ifdef USE_TLS_SRP
+ data->set.proxy_ssl.primary.username =
+ data->set.str[STRING_TLSAUTH_USERNAME_PROXY];
+diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
+index b68e6b4562..02189d8cc6 100644
+--- a/tests/data/Makefile.inc
++++ b/tests/data/Makefile.inc
+@@ -265,4 +265,5 @@ test3024 test3025 test3026 test3027 test3028 test3029 test3030 \
+ \
+ test3100 test3101 test3102 test3103 \
+ test3200 \
+-test3201 test3202
++test3201 test3202 \
++test3303
+diff --git a/tests/data/test3303 b/tests/data/test3303
+new file mode 100644
+index 0000000000..9b52bcb817
+--- /dev/null
++++ b/tests/data/test3303
+@@ -0,0 +1,23 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++unittest
++TLS
++mTLS
++</keywords>
++</info>
++
++# Client-side
++<client>
++<server>
++none
++</server>
++<features>
++unittest
++</features>
++<name>
++conn-reuse match distinguishes mTLS key, cert_type, key_type and key_passwd fields
++</name>
++</client>
++</testcase>
+diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc
+index 1926b49b3a..afbe29d2b7 100644
+--- a/tests/unit/Makefile.inc
++++ b/tests/unit/Makefile.inc
+@@ -39,7 +39,7 @@ UNITPROGS = unit1300 unit1302 unit1303 unit1304 unit1305 unit1307 \
+ unit1650 unit1651 unit1652 unit1653 unit1654 unit1655 unit1656 \
+ unit1660 unit1661 \
+ unit2600 unit2601 unit2602 unit2603 \
+- unit3200
++ unit3200 unit3303
+
+ unit1300_SOURCES = unit1300.c $(UNITFILES)
+
+@@ -134,3 +134,5 @@ unit2602_SOURCES = unit2602.c $(UNITFILES)
+ unit2603_SOURCES = unit2603.c $(UNITFILES)
+
+ unit3200_SOURCES = unit3200.c $(UNITFILES)
++
++unit3303_SOURCES = unit3303.c $(UNITFILES)
+diff --git a/tests/unit/unit3303.c b/tests/unit/unit3303.c
+new file mode 100644
+index 0000000000..060a9361fc
+--- /dev/null
++++ b/tests/unit/unit3303.c
+@@ -0,0 +1,127 @@
++/***************************************************************************
++ * _ _ ____ _
++ * Project ___| | | | _ \| |
++ * / __| | | | |_) | |
++ * | (__| |_| | _ <| |___
++ * \___|\___/|_| \_\_____|
++ *
++ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
++ *
++ * This software is licensed as described in the file COPYING, which
++ * you should have received as part of this distribution. The terms
++ * are also available at https://curl.se/docs/copyright.html.
++ *
++ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
++ * copies of the Software, and permit persons to whom the Software is
++ * furnished to do so, under the terms of the COPYING file.
++ *
++ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
++ * KIND, either express or implied.
++ *
++ * SPDX-License-Identifier: curl
++ *
++ ***************************************************************************/
++#include "curlcheck.h"
++#include "urldata.h"
++
++#ifdef USE_SSL
++#include "vtls/vtls.h"
++#endif
++
++static CURLcode unit_setup(void)
++{
++ return curl_global_init(CURL_GLOBAL_ALL);
++}
++
++static void unit_stop(void)
++{
++ curl_global_cleanup();
++}
++
++UNITTEST_START
++#ifdef USE_SSL
++{
++ CURL *curl;
++ struct connectdata *conn;
++ struct ssl_primary_config *primary;
++ char *saved;
++ static char alt_passwd[] = "wrong";
++ static char alt_key[] = "other.key";
++ static char alt_ktype[] = "DER";
++ static char alt_ctype[] = "P12";
++ static char lc_ctype[] = "pem";
++ static char lc_ktype[] = "pem";
++
++ curl = curl_easy_init();
++ abort_unless(curl, "curl_easy_init failed");
++
++ curl_easy_setopt(curl, CURLOPT_SSLCERT, "client.pem");
++ curl_easy_setopt(curl, CURLOPT_SSLKEY, "client.key");
++ curl_easy_setopt(curl, CURLOPT_KEYPASSWD, "secret");
++ curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
++ curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
++
++ abort_unless(!Curl_ssl_easy_config_complete((struct Curl_easy *)curl),
++ "Curl_ssl_easy_config_complete failed");
++
++ conn = calloc(1, sizeof(*conn));
++ abort_unless(conn, "connection allocation failed");
++ abort_unless(!Curl_ssl_conn_config_init((struct Curl_easy *)curl, conn),
++ "Curl_ssl_conn_config_init failed");
++
++ fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "identical mTLS config should match");
++
++ primary = &((struct Curl_easy *)curl)->set.ssl.primary;
++
++ saved = primary->key_passwd;
++ primary->key_passwd = alt_passwd;
++ fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "different key_passwd must not reuse conn");
++ primary->key_passwd = saved;
++
++ saved = primary->key;
++ primary->key = alt_key;
++ fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "different key must not reuse conn");
++ primary->key = saved;
++
++ saved = primary->key_type;
++ primary->key_type = alt_ktype;
++ fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "different key_type must not reuse conn");
++ primary->key_type = saved;
++
++ saved = primary->cert_type;
++ primary->cert_type = alt_ctype;
++ fail_unless(!Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "different cert_type must not reuse conn");
++ primary->cert_type = saved;
++
++ primary->cert_type = lc_ctype;
++ fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "cert_type comparison must be case-insensitive");
++ primary->cert_type = saved;
++
++ primary->key_type = lc_ktype;
++ fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "key_type comparison must be case-insensitive");
++ primary->key_type = saved;
++
++ fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "restored mTLS config should match");
++
++ Curl_ssl_conn_config_cleanup(conn);
++ free(conn);
++ curl_easy_cleanup(curl);
++}
++#endif /* USE_SSL */
++UNITTEST_STOP
@@ -45,6 +45,7 @@ SRC_URI = " \
file://CVE-2026-8924.patch \
file://CVE-2026-8927-dependent.patch \
file://CVE-2026-8927.patch \
+ file://CVE-2026-8932.patch \
"
SRC_URI:append:class-nativesdk = " \