From: "Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)" <deeratho@cisco.com>
To: openembedded-core@lists.openembedded.org
Subject: [OE-core][wrynose][PATCH 4/8] curl: fix CVE-2026-8932
Date: Wed, 5 Aug 2026 14:00:59 +0530 [thread overview]
Message-ID: <20260805083103.2633995-5-deeratho@cisco.com> (raw)
In-Reply-To: <20260805083103.2633995-1-deeratho@cisco.com>
From: Deepak Rathore <deeratho@cisco.com>
This patch applies the upstream curl security fix backport for
CVE-2026-8932. The upstream dependency commit is referenced
in [1], the upstream fix commit is referenced in [2], and the
public curl advisory is referenced in [3]. The individual
backported commit links are recorded in the embedded patch
headers.
[1] https://github.com/curl/curl/commit/67bf021e97bbebadade7e40217a4967042ac6a07
[2] https://github.com/curl/curl/commit/7541ae569d82fb308a5e2d94916027da4fa3ba3e
[3] https://curl.se/docs/CVE-2026-8932.html
Signed-off-by: Deepak Rathore <deeratho@cisco.com>
---
.../curl/curl/CVE-2026-8932-dependent.patch | 71 +
.../curl/curl/CVE-2026-8932.patch | 1148 +++++++++++++++++
meta/recipes-support/curl/curl_8.19.0.bb | 2 +
3 files changed, 1221 insertions(+)
create mode 100644 meta/recipes-support/curl/curl/CVE-2026-8932-dependent.patch
create mode 100644 meta/recipes-support/curl/curl/CVE-2026-8932.patch
diff --git a/meta/recipes-support/curl/curl/CVE-2026-8932-dependent.patch b/meta/recipes-support/curl/curl/CVE-2026-8932-dependent.patch
new file mode 100644
index 0000000000..c64ced329a
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-8932-dependent.patch
@@ -0,0 +1,71 @@
+From a79f8dae1aa632155be430143b4685f0b91d1694 Mon Sep 17 00:00:00 2001
+From: Daniel Stenberg <daniel@haxx.se>
+Date: Wed, 6 May 2026 23:59:22 +0200
+Subject: [PATCH] mbedtls: null terminate the private key blob
+
+Unfortunately, mbedtls_pk_parse_key() requires the data to be
+null-terminated if the data is PEM encoded (even when provided the exact
+length), so this function needs to make a copy that has one.
+
+Reported-by: Elise Vance
+Closes #21515
+
+CVE: CVE-2026-8932
+Upstream-Status: Backport [https://github.com/curl/curl/commit/67bf021e97bbebadade7e40217a4967042ac6a07]
+
+Backport Changes:
+- Added as a prerequisite for upstream commit [1] because Wrynose curl
+ 8.19.0 did not already contain this mbedTLS private-key blob fix.
+
+[1] https://github.com/curl/curl/commit/7541ae569d82fb308a5e2d94916027da4fa3ba3e
+
+(cherry picked from commit 67bf021e97bbebadade7e40217a4967042ac6a07)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ lib/vtls/mbedtls.c | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
+index 2bac406c35..54f0ac9fd4 100644
+--- a/lib/vtls/mbedtls.c
++++ b/lib/vtls/mbedtls.c
+@@ -697,11 +697,17 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ }
+ else {
+ const struct curl_blob *ssl_key_blob = ssl_config->key_blob;
+- const unsigned char *key_data =
+- (const unsigned char *)ssl_key_blob->data;
+ const char *passwd = ssl_config->key_passwd;
++ /* Unfortunately, mbedtls_pk_parse_key() requires the data to be
++ null-terminated if the data is PEM encoded (even when provided the
++ exact length). */
++ unsigned char *newblob = curlx_memdup0(ssl_key_blob->data,
++ ssl_key_blob->len);
++ if(!newblob)
++ return CURLE_OUT_OF_MEMORY;
++
+ #if MBEDTLS_VERSION_NUMBER >= 0x04000000
+- ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len,
++ ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len,
+ (const unsigned char *)passwd,
+ passwd ? strlen(passwd) : 0);
+ if(ret == 0 &&
+@@ -713,7 +719,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ PSA_KEY_USAGE_SIGN_HASH)))
+ ret = MBEDTLS_ERR_PK_TYPE_MISMATCH;
+ #else
+- ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len,
++ ret = mbedtls_pk_parse_key(&backend->pk, newblob, ssl_key_blob->len,
+ (const unsigned char *)passwd,
+ passwd ? strlen(passwd) : 0,
+ mbedtls_ctr_drbg_random,
+@@ -722,6 +728,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY)))
+ ret = MBEDTLS_ERR_PK_TYPE_MISMATCH;
+ #endif
++ curlx_free(newblob);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+--
+2.35.6
diff --git a/meta/recipes-support/curl/curl/CVE-2026-8932.patch b/meta/recipes-support/curl/curl/CVE-2026-8932.patch
new file mode 100644
index 0000000000..8fec1a5adb
--- /dev/null
+++ b/meta/recipes-support/curl/curl/CVE-2026-8932.patch
@@ -0,0 +1,1148 @@
+From c1cf1ddf723cdfcbd071394a6b4e73ea995e100a Mon Sep 17 00:00:00 2001
+From: Joshua Rogers <MegaManSec@users.noreply.github.com>
+Date: Tue, 14 Jul 2026 04:38:15 -0700
+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:
+- Split prerequisite upstream commit [1] before this patch. This patch
+ now applies on top of that dependency and does not fold in the
+ mbedTLS private-key blob null-termination change.
+- Kept Wrynose Curl_safefree() helper usage and local result-variable
+ style while preserving the upstream mTLS credential matching and
+ session-cache key behavior.
+- Wrynose curl 8.19.0 does not have upstream Curl_peer. The
+ Curl_ssl_peer_key_build() helper keeps Wrynose connectdata and
+ conn_to_host/conn_to_port keying while adding the mTLS key fields.
+- Kept Wrynose test registry ordering and added only the new upstream
+ test3303/test3304 and unit3303/unit3304 regression test entries.
+
+[1] https://github.com/curl/curl/commit/67bf021e97bbebadade7e40217a4967042ac6a07
+
+(cherry picked from commit 7541ae569d82fb308a5e2d94916027da4fa3ba3e)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ lib/ldap.c | 4 +-
+ lib/urldata.h | 10 +--
+ lib/vssh/libssh.c | 6 +-
+ lib/vssh/libssh2.c | 2 +-
+ lib/vtls/gtls.c | 14 ++--
+ lib/vtls/mbedtls.c | 24 +++---
+ lib/vtls/openssl.c | 7 +-
+ lib/vtls/rustls.c | 13 ++--
+ lib/vtls/schannel.c | 10 +--
+ lib/vtls/vtls.c | 37 ++++++---
+ lib/vtls/vtls_scache.c | 105 +++++++++++++++++++++-----
+ lib/vtls/vtls_scache.h | 16 ++++
+ lib/vtls/wolfssl.c | 14 ++--
+ tests/data/Makefile.am | 3 +
+ tests/data/test3303 | 20 +++++
+ tests/data/test3304 | 20 +++++
+ tests/unit/Makefile.inc | 3 +-
+ tests/unit/unit3303.c | 127 +++++++++++++++++++++++++++++++
+ tests/unit/unit3304.c | 163 ++++++++++++++++++++++++++++++++++++++++
+ 19 files changed, 517 insertions(+), 81 deletions(-)
+ create mode 100644 tests/data/test3303
+ create mode 100644 tests/data/test3304
+ create mode 100644 tests/unit/unit3303.c
+ create mode 100644 tests/unit/unit3304.c
+
+diff --git a/lib/ldap.c b/lib/ldap.c
+index e223078b03..5bf76572c8 100644
+--- a/lib/ldap.c
++++ b/lib/ldap.c
+@@ -329,8 +329,8 @@ static CURLcode ldap_do(struct Curl_easy *data, bool *done)
+ #ifdef LDAP_OPT_X_TLS
+ if(conn->ssl_config.verifypeer) {
+ /* OpenLDAP SDK supports BASE64 files. */
+- if(data->set.ssl.cert_type &&
+- !curl_strequal(data->set.ssl.cert_type, "PEM")) {
++ if(data->set.ssl.primary.cert_type &&
++ !curl_strequal(data->set.ssl.primary.cert_type, "PEM")) {
+ failf(data, "LDAP local: ERROR OpenLDAP only supports PEM cert-type");
+ result = CURLE_SSL_CERTPROBLEM;
+ goto quit;
+diff --git a/lib/urldata.h b/lib/urldata.h
+index 65005c4b34..dce181d99d 100644
+--- a/lib/urldata.h
++++ b/lib/urldata.h
+@@ -230,9 +230,14 @@ struct ssl_primary_config {
+ char *signature_algorithms; /* list of signature algorithms to use */
+ char *pinned_key;
+ char *CRLfile; /* CRL to check certificate revocation */
++ char *cert_type; /* format for certificate (default: PEM) */
++ char *key; /* private key filename */
++ char *key_type; /* format for private key (default: PEM) */
++ char *key_passwd; /* plain text private key password */
+ struct curl_blob *cert_blob;
+ struct curl_blob *ca_info_blob;
+ struct curl_blob *issuercert_blob;
++ struct curl_blob *key_blob;
+ #ifdef USE_TLS_SRP
+ char *username; /* TLS username (for, e.g., SRP) */
+ char *password; /* TLS password (for, e.g., SRP) */
+@@ -252,11 +257,6 @@ struct ssl_config_data {
+ long certverifyresult; /* result from the certificate verification */
+ curl_ssl_ctx_callback fsslctx; /* function to initialize ssl ctx */
+ void *fsslctxp; /* parameter for call back */
+- char *cert_type; /* format for certificate (default: PEM) */
+- char *key; /* private key filename */
+- struct curl_blob *key_blob;
+- char *key_type; /* format for private key (default: PEM) */
+- char *key_passwd; /* plain text private key password */
+ BIT(certinfo); /* gather lots of certificate info */
+ BIT(earlydata); /* use TLS 1.3 early data */
+ BIT(enable_beast); /* allow this flaw for interoperability's sake */
+diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c
+index 3a2a52e1f8..db767f7d00 100644
+--- a/lib/vssh/libssh.c
++++ b/lib/vssh/libssh.c
+@@ -791,7 +791,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
+ /* Two choices, (1) private key was given on CMD,
+ * (2) use the "default" keys. */
+ if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
+- if(sshc->pubkey && !data->set.ssl.key_passwd) {
++ if(sshc->pubkey && !data->set.ssl.primary.key_passwd) {
+ rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL, sshc->pubkey);
+ if(rc == SSH_AUTH_AGAIN)
+ return SSH_AGAIN;
+@@ -804,7 +804,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
+
+ rc = ssh_pki_import_privkey_file(data->
+ set.str[STRING_SSH_PRIVATE_KEY],
+- data->set.ssl.key_passwd, NULL,
++ data->set.ssl.primary.key_passwd, NULL,
+ NULL, &sshc->privkey);
+ if(rc != SSH_OK) {
+ failf(data, "Could not load private key file %s",
+@@ -817,7 +817,7 @@ static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
+ }
+ else {
+ rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL,
+- data->set.ssl.key_passwd);
++ data->set.ssl.primary.key_passwd);
+ if(rc == SSH_AUTH_AGAIN)
+ return SSH_AGAIN;
+
+diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c
+index 63f5735b83..3c25fe7448 100644
+--- a/lib/vssh/libssh2.c
++++ b/lib/vssh/libssh2.c
+@@ -1144,7 +1144,7 @@ static CURLcode ssh_state_pkey_init(struct Curl_easy *data,
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+- sshc->passphrase = data->set.ssl.key_passwd;
++ sshc->passphrase = data->set.ssl.primary.key_passwd;
+ if(!sshc->passphrase)
+ sshc->passphrase = "";
+
+diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c
+index a0a465c360..bfeb948542 100644
+--- a/lib/vtls/gtls.c
++++ b/lib/vtls/gtls.c
+@@ -988,10 +988,11 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf,
+ if(result)
+ return result;
+ }
+- if(ssl_config->cert_type && curl_strequal(ssl_config->cert_type, "P12")) {
++ if(ssl_config->primary.cert_type &&
++ curl_strequal(ssl_config->primary.cert_type, "P12")) {
+ rc = gnutls_certificate_set_x509_simple_pkcs12_file(
+ gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER,
+- ssl_config->key_passwd ? ssl_config->key_passwd : "");
++ ssl_config->primary.key_passwd ? ssl_config->primary.key_passwd : "");
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data,
+ "error reading X.509 potentially-encrypted key or certificate "
+@@ -1009,14 +1010,15 @@ static CURLcode gtls_client_init(struct Curl_cfilter *cf,
+ rc = gnutls_certificate_set_x509_key_file2(
+ gtls->shared_creds->creds,
+ config->clientcert,
+- ssl_config->key ? ssl_config->key : config->clientcert,
+- gnutls_do_file_type(ssl_config->cert_type),
+- ssl_config->key_passwd,
++ ssl_config->primary.key ? ssl_config->primary.key :
++ config->clientcert,
++ gnutls_do_file_type(ssl_config->primary.cert_type),
++ ssl_config->primary.key_passwd,
+ supported_key_encryption_algorithms);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data,
+ "error reading X.509 %skey file: %s",
+- ssl_config->key_passwd ? "potentially-encrypted " : "",
++ ssl_config->primary.key_passwd ? "potentially-encrypted " : "",
+ gnutls_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
+index 54f0ac9fd4..d2199df884 100644
+--- a/lib/vtls/mbedtls.c
++++ b/lib/vtls/mbedtls.c
+@@ -484,7 +484,7 @@ static CURLcode mbed_load_cacert(struct Curl_cfilter *cf,
+ const char * const ssl_capath = conn_config->CApath;
+ #ifdef MBEDTLS_PEM_PARSE_C
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+- const char * const ssl_cert_type = ssl_config->cert_type;
++ const char * const ssl_cert_type = ssl_config->primary.cert_type;
+ #endif
+ int ret = -1;
+ char errorbuf[128];
+@@ -579,7 +579,7 @@ static CURLcode mbed_load_clicert(struct Curl_cfilter *cf,
+ char * const ssl_cert = ssl_config->primary.clientcert;
+ const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
+ #ifdef MBEDTLS_PEM_PARSE_C
+- const char * const ssl_cert_type = ssl_config->cert_type;
++ const char * const ssl_cert_type = ssl_config->primary.cert_type;
+ #endif
+ int ret = -1;
+ char errorbuf[128];
+@@ -660,12 +660,12 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+
+ mbedtls_pk_init(&backend->pk);
+
+- if(ssl_config->key || ssl_config->key_blob) {
+- if(ssl_config->key) {
++ if(ssl_config->primary.key || ssl_config->primary.key_blob) {
++ if(ssl_config->primary.key) {
+ #ifdef MBEDTLS_FS_IO
+ #if MBEDTLS_VERSION_NUMBER >= 0x04000000
+- ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key,
+- ssl_config->key_passwd);
++ ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key,
++ ssl_config->primary.key_passwd);
+ if(ret == 0 &&
+ !(mbedtls_pk_can_do_psa(&backend->pk,
+ PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_ANY_HASH),
+@@ -675,8 +675,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ PSA_KEY_USAGE_SIGN_HASH)))
+ ret = MBEDTLS_ERR_PK_TYPE_MISMATCH;
+ #else
+- ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key,
+- ssl_config->key_passwd,
++ ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->primary.key,
++ ssl_config->primary.key_passwd,
+ mbedtls_ctr_drbg_random,
+ &rng.drbg);
+ if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) ||
+@@ -687,7 +687,7 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "mbedTLS: error reading private key %s: (-0x%04X) %s",
+- ssl_config->key, -ret, errorbuf);
++ ssl_config->primary.key, -ret, errorbuf);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ #else
+@@ -696,8 +696,8 @@ static CURLcode mbed_load_privkey(struct Curl_cfilter *cf,
+ #endif
+ }
+ else {
+- const struct curl_blob *ssl_key_blob = ssl_config->key_blob;
+- const char *passwd = ssl_config->key_passwd;
++ const struct curl_blob *ssl_key_blob = ssl_config->primary.key_blob;
++ const char *passwd = ssl_config->primary.key_passwd;
+ /* Unfortunately, mbedtls_pk_parse_key() requires the data to be
+ null-terminated if the data is PEM encoded (even when provided the
+ exact length). */
+@@ -931,7 +931,7 @@ static CURLcode mbed_configure_ssl(struct Curl_cfilter *cf,
+ #endif
+ );
+
+- if(ssl_config->key || ssl_config->key_blob) {
++ if(ssl_config->primary.key || ssl_config->primary.key_blob) {
+ mbedtls_ssl_conf_own_cert(&backend->config, &backend->clicert,
+ &backend->pk);
+ }
+diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
+index 50bf1e0476..9cbab14b41 100644
+--- a/lib/vtls/openssl.c
++++ b/lib/vtls/openssl.c
+@@ -3724,7 +3724,7 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ char * const ssl_cert = ssl_config->primary.clientcert;
+ const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
+- const char * const ssl_cert_type = ssl_config->cert_type;
++ const char * const ssl_cert_type = ssl_config->primary.cert_type;
+ unsigned int ssl_version_min;
+ char error_buffer[256];
+
+@@ -3888,8 +3888,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
+ if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
+ result = client_cert(data, octx->ssl_ctx,
+ ssl_cert, ssl_cert_blob, ssl_cert_type,
+- ssl_config->key, ssl_config->key_blob,
+- ssl_config->key_type, ssl_config->key_passwd);
++ ssl_config->primary.key, ssl_config->primary.key_blob,
++ ssl_config->primary.key_type,
++ ssl_config->primary.key_passwd);
+ if(result)
+ /* failf() is already done in client_cert() */
+ return result;
+diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c
+index 061b444bce..564b10aa47 100644
+--- a/lib/vtls/rustls.c
++++ b/lib/vtls/rustls.c
+@@ -841,14 +841,14 @@ init_config_builder_client_auth(struct Curl_easy *data,
+ const struct rustls_certified_key *certified_key = NULL;
+ CURLcode result = CURLE_OK;
+
+- if(conn_config->clientcert && !ssl_config->key) {
++ if(conn_config->clientcert && !ssl_config->primary.key) {
+ failf(data, "rustls: must provide key with certificate '%s'",
+ conn_config->clientcert);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+- else if(!conn_config->clientcert && ssl_config->key) {
++ else if(!conn_config->clientcert && ssl_config->primary.key) {
+ failf(data, "rustls: must provide certificate with key '%s'",
+- ssl_config->key);
++ ssl_config->primary.key);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+@@ -862,8 +862,9 @@ init_config_builder_client_auth(struct Curl_easy *data,
+ goto cleanup;
+ }
+
+- if(!read_file_into(ssl_config->key, &key_contents)) {
+- failf(data, "rustls: failed to read key file: '%s'", ssl_config->key);
++ if(!read_file_into(ssl_config->primary.key, &key_contents)) {
++ failf(data, "rustls: failed to read key file: '%s'",
++ ssl_config->primary.key);
+ result = CURLE_SSL_CERTPROBLEM;
+ goto cleanup;
+ }
+@@ -1053,7 +1054,7 @@ static CURLcode cr_init_backend(struct Curl_cfilter *cf,
+ }
+ }
+
+- if(conn_config->clientcert || ssl_config->key) {
++ if(conn_config->clientcert || ssl_config->primary.key) {
+ result = init_config_builder_client_auth(data,
+ conn_config,
+ ssl_config,
+diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c
+index 650a0e38ca..a4f57c7c0a 100644
+--- a/lib/vtls/schannel.c
++++ b/lib/vtls/schannel.c
+@@ -414,8 +414,8 @@ static CURLcode get_client_cert(struct Curl_easy *data,
+ }
+ }
+
+- if((fInCert || blob) && data->set.ssl.cert_type &&
+- !curl_strequal(data->set.ssl.cert_type, "P12")) {
++ if((fInCert || blob) && data->set.ssl.primary.cert_type &&
++ !curl_strequal(data->set.ssl.primary.cert_type, "P12")) {
+ failf(data, "schannel: certificate format compatibility error "
+ "for %s",
+ blob ? "(memory blob)" : data->set.ssl.primary.clientcert);
+@@ -465,15 +465,15 @@ static CURLcode get_client_cert(struct Curl_easy *data,
+ datablob.pbData = (BYTE *)certdata;
+ datablob.cbData = (DWORD)certsize;
+
+- if(data->set.ssl.key_passwd)
+- pwd_len = strlen(data->set.ssl.key_passwd);
++ if(data->set.ssl.primary.key_passwd)
++ pwd_len = strlen(data->set.ssl.primary.key_passwd);
+ pszPassword = (WCHAR *)curlx_malloc(sizeof(WCHAR) * (pwd_len + 1));
+ if(pszPassword) {
+ int str_w_len = 0;
+ if(pwd_len > 0)
+ str_w_len = MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+- data->set.ssl.key_passwd,
++ data->set.ssl.primary.key_passwd,
+ (int)pwd_len,
+ pszPassword, (int)(pwd_len + 1));
+
+diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
+index f7201d18d6..6dcc67dccd 100644
+--- a/lib/vtls/vtls.c
++++ b/lib/vtls/vtls.c
+@@ -204,6 +204,7 @@ static bool 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) &&
+@@ -217,7 +218,11 @@ static bool match_ssl_primary_config(struct Curl_easy *data,
+ curl_strequal(c1->curves, c2->curves) &&
+ curl_strequal(c1->signature_algorithms, c2->signature_algorithms) &&
+ curl_strequal(c1->CRLfile, c2->CRLfile) &&
+- curl_strequal(c1->pinned_key, c2->pinned_key))
++ curl_strequal(c1->pinned_key, c2->pinned_key) &&
++ 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))
+ return TRUE;
+
+ return FALSE;
+@@ -252,6 +257,7 @@ 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);
+@@ -262,6 +268,10 @@ static bool clone_ssl_primary_config(struct ssl_primary_config *source,
+ CLONE_STRING(curves);
+ CLONE_STRING(signature_algorithms);
+ CLONE_STRING(CRLfile);
++ CLONE_STRING(cert_type);
++ CLONE_STRING(key);
++ CLONE_STRING(key_type);
++ CLONE_STRING(key_passwd);
+ #ifdef USE_TLS_SRP
+ CLONE_STRING(username);
+ CLONE_STRING(password);
+@@ -282,9 +292,14 @@ static void free_primary_ssl_config(struct ssl_primary_config *sslc)
+ 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->signature_algorithms);
+ Curl_safefree(sslc->CRLfile);
++ Curl_safefree(sslc->cert_type);
++ Curl_safefree(sslc->key);
++ Curl_safefree(sslc->key_type);
++ Curl_safefree(sslc->key_passwd);
+ #ifdef USE_TLS_SRP
+ Curl_safefree(sslc->username);
+ Curl_safefree(sslc->password);
+@@ -336,12 +351,12 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
+ sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME];
+ sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD];
+ #endif
+- sslc->cert_type = data->set.str[STRING_CERT_TYPE];
+- sslc->key = data->set.str[STRING_KEY];
+- sslc->key_type = data->set.str[STRING_KEY_TYPE];
+- sslc->key_passwd = data->set.str[STRING_KEY_PASSWD];
++ sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE];
++ sslc->primary.key = data->set.str[STRING_KEY];
++ sslc->primary.key_type = data->set.str[STRING_KEY_TYPE];
++ sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD];
+ sslc->primary.clientcert = data->set.str[STRING_CERT];
+- sslc->key_blob = data->set.blobs[BLOB_KEY];
++ sslc->primary.key_blob = data->set.blobs[BLOB_KEY];
+
+ #ifndef CURL_DISABLE_PROXY
+ sslc = &data->set.proxy_ssl;
+@@ -377,12 +392,12 @@ CURLcode Curl_ssl_easy_config_complete(struct Curl_easy *data)
+ sslc->primary.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY];
+ sslc->primary.issuercert_blob = data->set.blobs[BLOB_SSL_ISSUERCERT_PROXY];
+ sslc->primary.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY];
+- sslc->cert_type = data->set.str[STRING_CERT_TYPE_PROXY];
+- sslc->key = data->set.str[STRING_KEY_PROXY];
+- sslc->key_type = data->set.str[STRING_KEY_TYPE_PROXY];
+- sslc->key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY];
++ sslc->primary.cert_type = data->set.str[STRING_CERT_TYPE_PROXY];
++ sslc->primary.key = data->set.str[STRING_KEY_PROXY];
++ sslc->primary.key_type = data->set.str[STRING_KEY_TYPE_PROXY];
++ sslc->primary.key_passwd = data->set.str[STRING_KEY_PASSWD_PROXY];
+ sslc->primary.clientcert = data->set.str[STRING_CERT_PROXY];
+- sslc->key_blob = data->set.blobs[BLOB_KEY_PROXY];
++ sslc->primary.key_blob = data->set.blobs[BLOB_KEY_PROXY];
+ #ifdef USE_TLS_SRP
+ sslc->primary.username = data->set.str[STRING_TLSAUTH_USERNAME_PROXY];
+ sslc->primary.password = data->set.str[STRING_TLSAUTH_PASSWORD_PROXY];
+diff --git a/lib/vtls/vtls_scache.c b/lib/vtls/vtls_scache.c
+index 673abcfe0d..75e5b89553 100644
+--- a/lib/vtls/vtls_scache.c
++++ b/lib/vtls/vtls_scache.c
+@@ -50,6 +50,7 @@
+ struct Curl_ssl_scache_peer {
+ char *ssl_peer_key; /* id for peer + relevant TLS configuration */
+ char *clientcert;
++ char *key_passwd;
+ char *srp_username;
+ char *srp_password;
+ struct Curl_llist sessions;
+@@ -123,6 +124,48 @@ out:
+ return r;
+ }
+
++static CURLcode cf_ssl_peer_key_add_mtls(struct dynbuf *buf,
++ struct ssl_primary_config *ssl,
++ bool *is_local)
++{
++ CURLcode result = CURLE_OK;
++ if(ssl->clientcert && ssl->clientcert[0]) {
++ result = cf_ssl_peer_key_add_path(buf, "CCERT", ssl->clientcert, is_local);
++ if(result)
++ goto out;
++ }
++ if(ssl->key && ssl->key[0]) {
++ result = cf_ssl_peer_key_add_path(buf, "KEY", ssl->key, is_local);
++ if(result)
++ goto out;
++ }
++ if(ssl->key_blob) {
++ result = cf_ssl_peer_key_add_hash(buf, "KEYBlob", ssl->key_blob);
++ if(result)
++ goto out;
++ }
++ if(ssl->cert_type && ssl->cert_type[0]) {
++ size_t i;
++ result = curlx_dyn_add(buf, ":CT-");
++ for(i = 0; !result && ssl->cert_type[i]; i++) {
++ char c = Curl_raw_toupper(ssl->cert_type[i]);
++ result = curlx_dyn_addn(buf, &c, 1);
++ }
++ if(result)
++ goto out;
++ }
++ if(ssl->key_type && ssl->key_type[0]) {
++ size_t i;
++ result = curlx_dyn_add(buf, ":KT-");
++ for(i = 0; !result && ssl->key_type[i]; i++) {
++ char c = Curl_raw_toupper(ssl->key_type[i]);
++ result = curlx_dyn_addn(buf, &c, 1);
++ }
++ }
++out:
++ return result;
++}
++
+ #define CURL_SSLS_LOCAL_SUFFIX ":L"
+ #define CURL_SSLS_GLOBAL_SUFFIX ":G"
+
+@@ -134,12 +177,12 @@ static bool cf_ssl_peer_key_is_global(const char *peer_key)
+ (peer_key[len - 2] == ':');
+ }
+
+-CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
+- const struct ssl_peer *peer,
+- const char *tls_id,
+- char **ppeer_key)
++CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl,
++ const struct ssl_peer *peer,
++ const struct connectdata *conn,
++ const char *tls_id,
++ char **ppeer_key)
+ {
+- struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
+ struct dynbuf buf;
+ size_t key_len;
+ bool is_local = FALSE;
+@@ -187,13 +230,13 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
+ goto out;
+ }
+ if(!ssl->verifypeer || !ssl->verifyhost) {
+- if(cf->conn->bits.conn_to_host) {
+- r = curlx_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
++ if(conn && conn->bits.conn_to_host) {
++ r = curlx_dyn_addf(&buf, ":CHOST-%s", conn->conn_to_host.name);
+ if(r)
+ goto out;
+ }
+- if(cf->conn->bits.conn_to_port) {
+- r = curlx_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
++ if(conn && conn->bits.conn_to_port) {
++ r = curlx_dyn_addf(&buf, ":CPORT-%d", conn->conn_to_port);
+ if(r)
+ goto out;
+ }
+@@ -260,11 +303,9 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
+ goto out;
+ }
+
+- if(ssl->clientcert && ssl->clientcert[0]) {
+- r = curlx_dyn_add(&buf, ":CCERT");
+- if(r)
+- goto out;
+- }
++ r = cf_ssl_peer_key_add_mtls(&buf, ssl, &is_local);
++ if(r)
++ goto out;
+ #ifdef USE_TLS_SRP
+ if(ssl->username || ssl->password) {
+ r = curlx_dyn_add(&buf, ":SRP-AUTH");
+@@ -295,6 +336,15 @@ out:
+ return r;
+ }
+
++CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
++ const struct ssl_peer *peer,
++ const char *tls_id,
++ char **ppeer_key)
++{
++ struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
++ return Curl_ssl_peer_key_build(ssl, peer, cf->conn, tls_id, ppeer_key);
++}
++
+ struct Curl_ssl_scache {
+ unsigned int magic;
+ struct Curl_ssl_scache_peer *peers;
+@@ -402,6 +452,7 @@ static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
+ }
+ peer->sobj_free = NULL;
+ Curl_safefree(peer->clientcert);
++ Curl_safefree(peer->key_passwd);
+ #ifdef USE_TLS_SRP
+ Curl_safefree(peer->srp_username);
+ Curl_safefree(peer->srp_password);
+@@ -430,8 +481,8 @@ static void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer)
+ * - its peer key is not yet known, because sessions were
+ * imported using only the salt+hmac
+ * - the peer key is global, e.g. carrying no relative paths */
+- peer->exportable = (!peer->clientcert && !peer->srp_username &&
+- !peer->srp_password &&
++ peer->exportable = (!peer->clientcert && !peer->key_passwd &&
++ !peer->srp_username && !peer->srp_password &&
+ (!peer->ssl_peer_key ||
+ cf_ssl_peer_key_is_global(peer->ssl_peer_key)));
+ }
+@@ -440,6 +491,7 @@ static CURLcode
+ cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
+ const char *ssl_peer_key,
+ const char *clientcert,
++ const char *key_passwd,
+ const char *srp_username,
+ const char *srp_password,
+ const unsigned char *salt,
+@@ -468,6 +520,11 @@ cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
+ if(!peer->clientcert)
+ goto out;
+ }
++ if(key_passwd) {
++ peer->key_passwd = curlx_strdup(key_passwd);
++ if(!peer->key_passwd)
++ goto out;
++ }
+ if(srp_username) {
+ peer->srp_username = curlx_strdup(srp_username);
+ if(!peer->srp_username)
+@@ -598,7 +655,7 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
+ struct ssl_primary_config *conn_config)
+ {
+ if(!conn_config) {
+- if(peer->clientcert)
++ if(peer->clientcert || peer->key_passwd)
+ return FALSE;
+ #ifdef USE_TLS_SRP
+ if(peer->srp_username || peer->srp_password)
+@@ -608,6 +665,8 @@ static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
+ }
+ else if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
+ return FALSE;
++ if(Curl_timestrcmp(peer->key_passwd, conn_config->key_passwd))
++ return FALSE;
+ #ifdef USE_TLS_SRP
+ if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
+ Curl_timestrcmp(peer->srp_password, conn_config->password))
+@@ -734,13 +793,21 @@ static CURLcode cf_ssl_add_peer(struct Curl_easy *data,
+
+ peer = cf_ssl_get_free_peer(scache);
+ if(peer) {
++ char buffer[64];
+ const char *ccert = conn_config ? conn_config->clientcert : NULL;
++ const char *kpasswd = conn_config ? conn_config->key_passwd : NULL;
+ const char *username = NULL, *password = NULL;
+ #ifdef USE_TLS_SRP
+ username = conn_config ? conn_config->username : NULL;
+ password = conn_config ? conn_config->password : NULL;
+ #endif
+- result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
++ if(!ccert && conn_config && conn_config->cert_blob) {
++ /* when using a client cert blob, create a name for it */
++ curl_msnprintf(buffer, sizeof(buffer),
++ "cert-%p", conn_config->cert_blob->data);
++ ccert = buffer; /* data is strduped by cf_ssl_scache_peer_init */
++ }
++ result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert, kpasswd,
+ username, password, NULL, NULL);
+ if(result)
+ goto out;
+@@ -1115,7 +1182,7 @@ CURLcode Curl_ssl_session_import(struct Curl_easy *data,
+ if(!peer) {
+ peer = cf_ssl_get_free_peer(scache);
+ if(peer) {
+- r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
++ r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL, NULL,
+ NULL, NULL, salt, hmac);
+ if(r)
+ goto out;
+diff --git a/lib/vtls/vtls_scache.h b/lib/vtls/vtls_scache.h
+index b9db0f19b3..d33f2bb1a7 100644
+--- a/lib/vtls/vtls_scache.h
++++ b/lib/vtls/vtls_scache.h
+@@ -66,6 +66,22 @@ CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
+ const char *tls_id,
+ char **ppeer_key);
+
++/**
++ * Like Curl_ssl_peer_key_make() but takes the primary config and peer
++ * descriptors directly, without requiring a Curl_cfilter. Exposed for
++ * unit testing.
++ * @param ssl the primary SSL config to key on
++ * @param peer the peer the filter wants to talk to
++ * @param conn connection data for connect-to keying, or NULL
++ * @param tls_id identifier of TLS implementation for sessions
++ * @param ppeer_key on successful return, the key generated
++ */
++CURLcode Curl_ssl_peer_key_build(struct ssl_primary_config *ssl,
++ const struct ssl_peer *peer,
++ const struct connectdata *conn,
++ const char *tls_id,
++ char **ppeer_key);
++
+ /* Return if there is a session cache shall be used.
+ * An ssl session might not be configured or not available for
+ * "connect-only" transfers.
+diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c
+index aa841a754a..74bd247e68 100644
+--- a/lib/vtls/wolfssl.c
++++ b/lib/vtls/wolfssl.c
+@@ -919,10 +919,10 @@ static CURLcode client_certificate(struct Curl_easy *data,
+ #ifndef NO_FILESYSTEM
+ if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) {
+ const char *cert_file = ssl_config->primary.clientcert;
+- const char *key_file = ssl_config->key;
++ const char *key_file = ssl_config->primary.key;
+ const struct curl_blob *cert_blob = ssl_config->primary.cert_blob;
+- const struct curl_blob *key_blob = ssl_config->key_blob;
+- int file_type = wssl_do_file_type(ssl_config->cert_type);
++ const struct curl_blob *key_blob = ssl_config->primary.key_blob;
++ int file_type = wssl_do_file_type(ssl_config->primary.cert_type);
+ int rc;
+
+ switch(file_type) {
+@@ -953,7 +953,7 @@ static CURLcode client_certificate(struct Curl_easy *data,
+ key_file = cert_file;
+ }
+ else
+- file_type = wssl_do_file_type(ssl_config->key_type);
++ file_type = wssl_do_file_type(ssl_config->primary.key_type);
+
+ rc = key_blob ?
+ wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data,
+@@ -967,8 +967,8 @@ static CURLcode client_certificate(struct Curl_easy *data,
+ #else /* NO_FILESYSTEM */
+ if(ssl_config->primary.cert_blob) {
+ const struct curl_blob *cert_blob = ssl_config->primary.cert_blob;
+- const struct curl_blob *key_blob = ssl_config->key_blob;
+- int file_type = wssl_do_file_type(ssl_config->cert_type);
++ const struct curl_blob *key_blob = ssl_config->primary.key_blob;
++ int file_type = wssl_do_file_type(ssl_config->primary.cert_type);
+ int rc;
+
+ switch(file_type) {
+@@ -993,7 +993,7 @@ static CURLcode client_certificate(struct Curl_easy *data,
+ if(!key_blob)
+ key_blob = cert_blob;
+ else
+- file_type = wssl_do_file_type(ssl_config->key_type);
++ file_type = wssl_do_file_type(ssl_config->primary.key_type);
+
+ if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data,
+ (long)key_blob->len,
+diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
+index 4fd7dfe912..2217666382 100644
+--- a/tests/data/Makefile.am
++++ b/tests/data/Makefile.am
+@@ -282,6 +282,9 @@ test3100 test3101 test3102 test3103 test3104 test3105 \
+ \
+ test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 test3208 \
+ test3209 test3210 test3211 test3212 test3213 test3214 test3215 test3216 \
++\
++test3303 test3304 \
++\
+ test4000 test4001
+
+ EXTRA_DIST = $(TESTCASES) DISABLED data-xml1 data320.html \
+diff --git a/tests/data/test3303 b/tests/data/test3303
+new file mode 100644
+index 0000000000..697049f013
+--- /dev/null
++++ b/tests/data/test3303
+@@ -0,0 +1,20 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++unittest
++TLS
++mTLS
++</keywords>
++</info>
++
++# Client-side
++<client>
++<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/data/test3304 b/tests/data/test3304
+new file mode 100644
+index 0000000000..4380c0819f
+--- /dev/null
++++ b/tests/data/test3304
+@@ -0,0 +1,20 @@
++<?xml version="1.0" encoding="US-ASCII"?>
++<testcase>
++<info>
++<keywords>
++unittest
++TLS
++mTLS
++</keywords>
++</info>
++
++# Client-side
++<client>
++<features>
++unittest
++</features>
++<name>
++TLS session cache peer key discriminates on mTLS key, key_type and cert_type fields
++</name>
++</client>
++</testcase>
+diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc
+index 149636cbcc..bba30f6706 100644
+--- a/tests/unit/Makefile.inc
++++ b/tests/unit/Makefile.inc
+@@ -43,4 +43,5 @@ TESTS_C = \
+ unit1979.c unit1980.c \
+ unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \
+ unit3200.c unit3205.c \
+- unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c
++ unit3211.c unit3212.c unit3213.c unit3214.c unit3216.c \
++ unit3303.c unit3304.c
+diff --git a/tests/unit/unit3303.c b/tests/unit/unit3303.c
+new file mode 100644
+index 0000000000..41bced542d
+--- /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 "unitcheck.h"
++#include "urldata.h"
++
++#ifdef USE_SSL
++#include "vtls/vtls.h"
++#endif
++
++static CURLcode test_unit3303(const char *arg)
++{
++ UNITTEST_BEGIN_SIMPLE
++
++#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";
++
++ curl_global_init(CURL_GLOBAL_ALL);
++ curl = curl_easy_init();
++ if(!curl) {
++ curl_global_cleanup();
++ goto unit_test_abort;
++ }
++
++ 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");
++
++ if(Curl_ssl_easy_config_complete((struct Curl_easy *)curl)) {
++ curl_easy_cleanup(curl);
++ curl_global_cleanup();
++ goto unit_test_abort;
++ }
++
++ conn = curlx_calloc(1, sizeof(*conn));
++ if(!conn || Curl_ssl_conn_config_init((struct Curl_easy *)curl, conn)) {
++ if(conn)
++ Curl_ssl_conn_config_cleanup(conn);
++ curlx_free(conn);
++ curl_easy_cleanup(curl);
++ curl_global_cleanup();
++ goto unit_test_abort;
++ }
++
++ /* Baseline: identical config must match. */
++ 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;
++
++ /* Different key_passwd must not match. */
++ 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;
++
++ /* Different key path must not match. */
++ 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;
++
++ /* Different key type must not match. */
++ 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;
++
++ /* Different cert type must not match. */
++ 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;
++
++ /* All fields restored: must match again. */
++ fail_unless(Curl_ssl_conn_config_match((struct Curl_easy *)curl, conn,
++ FALSE),
++ "restored mTLS config should match");
++
++ Curl_ssl_conn_config_cleanup(conn);
++ curlx_free(conn);
++ curl_easy_cleanup(curl);
++ curl_global_cleanup();
++ }
++#endif /* USE_SSL */
++
++ UNITTEST_END_SIMPLE
++}
+diff --git a/tests/unit/unit3304.c b/tests/unit/unit3304.c
+new file mode 100644
+index 0000000000..4defa1e426
+--- /dev/null
++++ b/tests/unit/unit3304.c
+@@ -0,0 +1,163 @@
++/***************************************************************************
++ * _ _ ____ _
++ * 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
++ *
++ ***************************************************************************/
++
++/* Unit tests for TLS session cache peer key discrimination on mTLS fields.
++ * Verifies that Curl_ssl_peer_key_build() produces distinct keys when two
++ * handles differ only on key, key_type or cert_type. key_passwd is NOT
++ * embedded in the peer key; it is compared separately at session lookup via
++ * cf_ssl_scache_match_auth(), following the same pattern as SRP
++ * credentials. */
++
++#include "unitcheck.h"
++#include "urldata.h"
++
++#ifdef USE_SSL
++#include "vtls/vtls.h"
++#include "vtls/vtls_scache.h"
++#endif
++
++static CURLcode test_unit3304(const char *arg)
++{
++ UNITTEST_BEGIN_SIMPLE
++
++#ifdef USE_SSL
++ {
++ struct ssl_peer peer;
++ struct ssl_primary_config ssl;
++ char *key1 = NULL;
++ char *key2 = NULL;
++ static char base_hostname[] = "example.com";
++ static char base_cert[] = "client.pem";
++ static char base_key[] = "client.key";
++ static char base_passwd[] = "secret";
++ static char base_ctype[] = "PEM";
++ static char base_ktype[] = "PEM";
++ 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";
++
++ memset(&peer, 0, sizeof(peer));
++ peer.hostname = base_hostname;
++ peer.port = 443;
++ peer.transport = TRNSPRT_TCP;
++
++ memset(&ssl, 0, sizeof(ssl));
++ ssl.verifypeer = TRUE;
++ ssl.verifyhost = TRUE;
++ ssl.clientcert = base_cert;
++ ssl.key = base_key;
++ ssl.key_passwd = base_passwd;
++ ssl.cert_type = base_ctype;
++ ssl.key_type = base_ktype;
++
++ /* Baseline: same config produces same key. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && !strcmp(key1, key2),
++ "identical config should produce identical peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++
++ /* key_passwd is NOT in the peer key: lookup uses timing-safe comparison
++ * via cf_ssl_scache_match_auth(), same as SRP credentials. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.key_passwd = NULL;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && !strcmp(key1, key2),
++ "key_passwd must not affect the peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ ssl.key_passwd = base_passwd;
++
++ /* Different key path must produce a different peer key. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.key = alt_key;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && strcmp(key1, key2),
++ "different key must produce different peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ ssl.key = base_key;
++
++ /* Different key_type must produce a different peer key. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.key_type = alt_ktype;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && strcmp(key1, key2),
++ "different key_type must produce different peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ ssl.key_type = base_ktype;
++
++ /* Different cert_type must produce a different peer key. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.cert_type = alt_ctype;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && strcmp(key1, key2),
++ "different cert_type must produce different peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ ssl.cert_type = base_ctype;
++
++ /* cert_type is case-insensitive: "PEM" and "pem" must produce the
++ * same peer key, consistent with the conn-reuse comparison. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.cert_type = lc_ctype;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && !strcmp(key1, key2),
++ "cert_type case must not affect peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ ssl.cert_type = base_ctype;
++
++ /* key_type is case-insensitive: "PEM" and "pem" must produce the
++ * same peer key. */
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key1),
++ "peer key build failed");
++ ssl.key_type = lc_ktype;
++ fail_unless(!Curl_ssl_peer_key_build(&ssl, &peer, NULL, "test", &key2),
++ "peer key build failed");
++ fail_unless(key1 && key2 && !strcmp(key1, key2),
++ "key_type case must not affect peer key");
++ curlx_free(key1); key1 = NULL;
++ curlx_free(key2); key2 = NULL;
++ }
++#endif /* USE_SSL */
++
++ UNITTEST_END_SIMPLE
++}
+--
+2.35.6
diff --git a/meta/recipes-support/curl/curl_8.19.0.bb b/meta/recipes-support/curl/curl_8.19.0.bb
index 193366f657..994b1cff28 100644
--- a/meta/recipes-support/curl/curl_8.19.0.bb
+++ b/meta/recipes-support/curl/curl_8.19.0.bb
@@ -25,6 +25,8 @@ SRC_URI = " \
file://CVE-2026-4873.patch \
file://CVE-2026-8286.patch \
file://CVE-2026-8927.patch \
+ file://CVE-2026-8932-dependent.patch \
+ file://CVE-2026-8932.patch \
"
SRC_URI:append:class-nativesdk = " \
--
2.35.6
next prev parent reply other threads:[~2026-08-05 8:31 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 8:30 [OE-core][wrynose][PATCH 0/8] curl: Security fixes Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:30 ` [OE-core][wrynose][PATCH 1/8] curl: fix CVE-2026-8286 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-25 15:04 ` Yoann Congal
2026-08-26 8:47 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-27 6:53 ` Yoann Congal
2026-08-27 10:51 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:30 ` [OE-core][wrynose][PATCH 2/8] curl: set CVE_STATUS for CVE-2026-8924 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:30 ` [OE-core][wrynose][PATCH 3/8] curl: fix CVE-2026-8927 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:30 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco) [this message]
2026-08-05 8:31 ` [OE-core][wrynose][PATCH 5/8] curl: fix CVE-2026-8458 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-28 15:07 ` Yoann Congal
2026-08-05 8:31 ` [OE-core][wrynose][PATCH 6/8] curl: fix CVE-2026-11856 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-28 15:24 ` Yoann Congal
2026-08-31 9:49 ` Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:31 ` [OE-core][wrynose][PATCH 7/8] curl: set CVE_STATUS for CVE-2026-9547 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-08-05 8:31 ` [OE-core][wrynose][PATCH 8/8] curl: set CVE_STATUS for CVE-2026-12064 Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260805083103.2633995-5-deeratho@cisco.com \
--to=deeratho@cisco.com \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.