[Pkg-swan-devel] [Git][debian/strongswan][debian/trixie] 13 commits: d/patches: add fix for undefined memory access when verifying PKCS#7 containers (CVE-2026-78123)

Yves-Alexis Perez (@corsac) gitlab at salsa.debian.org
Mon Sep 7 20:39:16 BST 2026



Yves-Alexis Perez pushed to branch debian/trixie at Debian / strongswan


Commits:
9f7f19ae by Yves-Alexis Perez at 2026-08-31T17:57:28+02:00
d/patches: add fix for undefined memory access when verifying PKCS#7 containers (CVE-2026-78123)

- - - - -
f8428dc0 by Yves-Alexis Perez at 2026-08-31T17:59:41+02:00
d/patches: add patch to fix memory leaks after enumerating certificates in PKCS#7 containers (CVE-2026-78124)

- - - - -
d5046418 by Yves-Alexis Perez at 2026-09-01T08:50:55+02:00
d/patches: fix NULL-pointer dereference in EAP-AKA (CVE-2026-78126)

- - - - -
21c225ea by Yves-Alexis Perez at 2026-09-01T08:52:04+02:00
d/patches: avoid memory leak in IKE message logging (CVE-2026-78127)

- - - - -
ba833cf1 by Yves-Alexis Perez at 2026-09-01T08:57:41+02:00
d/patches: fix potential DoS by validating PKCS#5 parsed parameters (CVE-2026-78129)

- - - - -
e01ac1fd by Yves-Alexis Perez at 2026-09-01T08:58:56+02:00
d/patches: fix NULL-pointer dereference in x509 code (CVE-2026-78130)

- - - - -
17fba15f by Yves-Alexis Perez at 2026-09-01T09:00:01+02:00
d/patches: fix memory leak when parsing x509 attribute certificates (CVE-2026-78131)

- - - - -
f882891d by Yves-Alexis Perez at 2026-09-01T09:01:59+02:00
d/patches: fix infinite loop when parsing x509 attribute certificates (CVE-2026-78132)

- - - - -
7b3965d9 by Yves-Alexis Perez at 2026-09-01T09:02:58+02:00
d/patches: fix use-after-free in IKEv2 rekeying (CVE-2026-78133)

- - - - -
45e26a19 by Yves-Alexis Perez at 2026-09-01T09:04:14+02:00
d/patches: fix several issues with binding identities to the IKE SA (CVE-2026-78134)

- - - - -
6aa0dbad by Yves-Alexis Perez at 2026-09-01T09:05:36+02:00
d/patches: properly reject CREATE_CHILD_SA requests on unestablished IKE_SA (CVE-2026-78135)

- - - - -
db3e0859 by Yves-Alexis Perez at 2026-09-01T13:29:22+02:00
finalize changelog

- - - - -
fbf0ff6e by Yves-Alexis Perez at 2026-09-01T13:29:41+02:00
upload strongSwan 6.0.1-6+deb13u7 to trixie-security

- - - - -


13 changed files:

- debian/changelog
- + debian/patches/0018-openssl-Fix-undefined-memory-access-when-verifying-P.patch
- + debian/patches/0019-openssl-Fix-memory-leaks-after-enumerating-certifica.patch
- + debian/patches/0020-eap-aka-Only-accept-AKA-Synchronization-Failure-if-e.patch
- + debian/patches/0021-message-Avoid-memory-leak-if-string-buffer-for-messa.patch
- + debian/patches/0022-pkcs5-Validate-parsed-parameters-to-avoid-DoS-attack.patch
- + debian/patches/0023-x509-Avoid-NULL-pointer-dereference-if-issuerName-is.patch
- + debian/patches/0024-x509-Fix-memory-leaks-when-parsing-attribute-certifi.patch
- + debian/patches/0025-x509-Prevent-infinite-loop-when-parsing-ietfAttrSynt.patch
- + debian/patches/0026-ikev2-Prevent-use-after-free-during-collision-after-.patch
- + debian/patches/0027-eap-ttls-peap-Return-auth-cfg-with-details-on-TLS-an.patch
- + debian/patches/0028-ikev2-Properly-reject-CREATE_CHILD_SA-requests-on-un.patch
- debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,26 @@
+strongswan (6.0.1-6+deb13u7) trixie-security; urgency=medium
+
+  * d/patches: add fix for undefined memory access when verifying PKCS#7
+    containers (CVE-2026-78123)
+  * d/patches: add patch to fix memory leaks after enumerating certificates in
+    PKCS#7 containers (CVE-2026-78124)
+  * d/patches: fix NULL-pointer dereference in EAP-AKA (CVE-2026-78126)
+  * d/patches: avoid memory leak in IKE message logging (CVE-2026-78127)
+  * d/patches: fix potential DoS by validating PKCS#5 parsed parameters
+    (CVE-2026-78129)
+  * d/patches: fix NULL-pointer dereference in x509 code (CVE-2026-78130)
+  * d/patches: fix memory leak when parsing x509 attribute certificates
+    (CVE-2026-78131)
+  * d/patches: fix infinite loop when parsing x509 attribute certificates
+    (CVE-2026-78132)
+  * d/patches: fix use-after-free in IKEv2 rekeying (CVE-2026-78133)
+  * d/patches: fix several issues with binding identities to the IKE SA
+    (CVE-2026-78134)
+  * d/patches: properly reject CREATE_CHILD_SA requests on unestablished
+    IKE_SA (CVE-2026-78135)
+
+ -- Yves-Alexis Perez <corsac at debian.org>  Tue, 01 Sep 2026 13:29:28 +0200
+
 strongswan (6.0.1-6+deb13u6) trixie-security; urgency=medium
 
   * d/patches: add fix for double-free when cloning empty IDs (CVE-2026-47895)


=====================================
debian/patches/0018-openssl-Fix-undefined-memory-access-when-verifying-P.patch
=====================================
@@ -0,0 +1,45 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Mon, 1 Jun 2026 17:51:35 +0200
+Subject: openssl: Fix undefined memory access when verifying PKCS#7
+ containers
+
+If the signerInfo or recipientInfo structure doesn't contain
+issuerAndSerialNumber but instead a subjectKeyIdentifier, then the called
+functions will leave the passed name and serial numbers unchanged.  While
+openssl_x509_name2id() prevents a NULL-pointer dereference, it tries to
+DER-encode the object at the passed pointer via i2d_X509_NAME().
+Depending on the stack contents, this likely causes a segmentation fault.
+
+Fixes: 3c820cdc232a ("Implement PKCS#7 decryption using openssl")
+Fixes: c61723c69fb5 ("Implement OpenSSL PKCS#7 signed-data parsing and verification")
+Fixes: CVE-2026-78123
+---
+ src/libstrongswan/plugins/openssl/openssl_pkcs7.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/src/libstrongswan/plugins/openssl/openssl_pkcs7.c b/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
+index d9abcf8..b15c8d7 100644
+--- a/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
++++ b/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
+@@ -222,8 +222,8 @@ static auth_cfg_t *verify_signature(CMS_SignerInfo *si,
+ 	auth_cfg_t *auth, *found = NULL;
+ 	identification_t *issuer, *serial;
+ 	chunk_t attrs = chunk_empty, sig, attr;
+-	X509_NAME *name;
+-	ASN1_INTEGER *snr;
++	X509_NAME *name = NULL;
++	ASN1_INTEGER *snr = NULL;
+ 	int i;
+ 
+ 	if (CMS_SignerInfo_get0_signer_id(si, NULL, &name, &snr) != 1)
+@@ -628,8 +628,8 @@ static bool decrypt(private_openssl_pkcs7_t *this,
+ 			identification_t *serial, *issuer;
+ 			private_key_t *private;
+ 			X509_ALGOR *alg;
+-			X509_NAME *name;
+-			ASN1_INTEGER *sn;
++			X509_NAME *name = NULL;
++			ASN1_INTEGER *sn = NULL;
+ 			u_char zero = 0;
+ 			int oid;
+ 


=====================================
debian/patches/0019-openssl-Fix-memory-leaks-after-enumerating-certifica.patch
=====================================
@@ -0,0 +1,28 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Mon, 15 Jun 2026 16:02:01 +0200
+Subject: openssl: Fix memory leaks after enumerating certificates in PKCS#7
+ container
+
+This can be triggered via IKEv1.
+
+Fixes: 04884be3b5f7 ("Implement openssl PKCS#7 certficiate enumeration")
+Fixes: CVE-2026-78124
+---
+ src/libstrongswan/plugins/openssl/openssl_pkcs7.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/src/libstrongswan/plugins/openssl/openssl_pkcs7.c b/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
+index b15c8d7..cefe1ae 100644
+--- a/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
++++ b/src/libstrongswan/plugins/openssl/openssl_pkcs7.c
+@@ -132,6 +132,10 @@ typedef struct {
+ METHOD(enumerator_t, cert_destroy, void,
+ 	cert_enumerator_t *this)
+ {
++	if (this->certs)
++	{
++		sk_X509_pop_free(this->certs, X509_free);
++	}
+ 	DESTROY_IF(this->cert);
+ 	free(this);
+ }


=====================================
debian/patches/0020-eap-aka-Only-accept-AKA-Synchronization-Failure-if-e.patch
=====================================
@@ -0,0 +1,31 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Thu, 4 Jun 2026 10:00:02 +0200
+Subject: eap-aka: Only accept AKA-Synchronization-Failure if expected
+
+This fixes a NULL-pointer dereference if the client sends such an error
+before the server issued a challenge and allocated this->rand.
+
+Fixes: 26e246769224 ("ported EAP-AKA branch into trunk")
+Fixes: 4735965fc048 ("EAP servers check if the received EAP message was expected")
+Fixes: CVE-2026-78126
+---
+ src/libcharon/plugins/eap_aka/eap_aka_server.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/src/libcharon/plugins/eap_aka/eap_aka_server.c b/src/libcharon/plugins/eap_aka/eap_aka_server.c
+index 0712ccc..95d4b03 100644
+--- a/src/libcharon/plugins/eap_aka/eap_aka_server.c
++++ b/src/libcharon/plugins/eap_aka/eap_aka_server.c
+@@ -505,6 +505,12 @@ static status_t process_synchronize(private_eap_aka_server_t *this,
+ 	simaka_attribute_t type;
+ 	chunk_t data, auts = chunk_empty;
+ 
++	if (this->pending != AKA_CHALLENGE)
++	{
++		DBG1(DBG_IKE, "received %N, but not expected",
++			 simaka_subtype_names, AKA_SYNCHRONIZATION_FAILURE);
++		return FAILED;
++	}
+ 	if (this->synchronized)
+ 	{
+ 		DBG1(DBG_IKE, "received %N, but peer did already resynchronize",


=====================================
debian/patches/0021-message-Avoid-memory-leak-if-string-buffer-for-messa.patch
=====================================
@@ -0,0 +1,108 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Mon, 8 Jun 2026 09:19:42 +0200
+Subject: message: Avoid memory leak if string buffer for message is too small
+
+This leaked 40 or 80 bytes per parsed message for the enumerators that
+were not destroyed.  While triggering an OOM condition will require quite
+a lot of messages and the DoS protection also helps avoiding that this
+is triggered quickly, it all depends on the memory constraints of the
+system and the time available to the attacker.  Also, if IKEv1 is allowed,
+it could get quicker as the lack of message IDs doesn't allow dismissing
+unexpected messages before parsing them.
+
+Fixes: 092958c89d52 ("fixed payload debug message")
+Fixes: 6a4a47511f75 ("Show contents of the CP payload in message_t stringification")
+Fixes: CVE-2026-78127
+---
+ src/libcharon/encoding/message.c | 23 ++++++++++++-----------
+ 1 file changed, 12 insertions(+), 11 deletions(-)
+
+diff --git a/src/libcharon/encoding/message.c b/src/libcharon/encoding/message.c
+index 5d05e20..7b1ad2b 100644
+--- a/src/libcharon/encoding/message.c
++++ b/src/libcharon/encoding/message.c
+@@ -1398,7 +1398,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 						   payload->get_type(payload));
+ 		if (written >= len || written < 0)
+ 		{
+-			return buf;
++			goto err;
+ 		}
+ 		pos += written;
+ 		len -= written;
+@@ -1424,7 +1424,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 			}
+ 			if (written >= len || written < 0)
+ 			{
+-				return buf;
++				goto err;
+ 			}
+ 			pos += written;
+ 			len -= written;
+@@ -1454,7 +1454,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 							   eap->get_code(eap), method);
+ 			if (written >= len || written < 0)
+ 			{
+-				return buf;
++				goto err;
+ 			}
+ 			pos += written;
+ 			len -= written;
+@@ -1495,7 +1495,8 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 								   attribute->get_type(attribute));
+ 				if (written >= len || written < 0)
+ 				{
+-					return buf;
++					attributes->destroy(attributes);
++					goto err;
+ 				}
+ 				pos += written;
+ 				len -= written;
+@@ -1507,7 +1508,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 				written = snprintf(pos, len, ")");
+ 				if (written >= len || written < 0)
+ 				{
+-					return buf;
++					goto err;
+ 				}
+ 				pos += written;
+ 				len -= written;
+@@ -1529,7 +1530,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 			}
+ 			if (written >= len || written < 0)
+ 			{
+-				return buf;
++				goto err;
+ 			}
+ 			pos += written;
+ 			len -= written;
+@@ -1544,7 +1545,7 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 							   frag->get_total_fragments(frag));
+ 			if (written >= len || written < 0)
+ 			{
+-				return buf;
++				goto err;
+ 			}
+ 			pos += written;
+ 			len -= written;
+@@ -1557,16 +1558,16 @@ static char* get_string(private_message_t *this, char *buf, int len)
+ 			written = snprintf(pos, len, "(%d)", unknown->get_type(unknown));
+ 			if (written >= len || written < 0)
+ 			{
+-				return buf;
++				goto err;
+ 			}
+ 			pos += written;
+ 			len -= written;
+ 		}
+ 	}
+-	enumerator->destroy(enumerator);
+-
+-	/* remove last space */
+ 	snprintf(pos, len, " ]");
++
++err:
++	enumerator->destroy(enumerator);
+ 	return buf;
+ }
+ #endif


=====================================
debian/patches/0022-pkcs5-Validate-parsed-parameters-to-avoid-DoS-attack.patch
=====================================
@@ -0,0 +1,149 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Tue, 23 Jun 2026 11:55:31 +0200
+Subject: pkcs5: Validate parsed parameters to avoid DoS attacks
+
+With the unbounded iterations, an attacker can craft a PKCS#7 file and
+send it during IKEv1 to block the processing thread practically for an
+unlimited amount of time.
+
+As the key length is used for an allocation on the stack, not limiting
+it could cause a crash.  We validate it after parsing the params, but
+since `encryption_algorithm_from_oid()` only returns trusted key lengths
+that are lower than the limit, that's fine.
+
+The unlimited salt length had no direct impact (the maximum is bound by
+the accepted message size), but we now limit it as well before cloning.
+
+Fixes: 4076e3ee9121 ("Extract PKCS#5 handling from pkcs8 plugin to separate helper class")
+Fixes: fd1ff46f6143 ("Added support for PKCS#5 v2 schemes when decrypting PKCS#8 files.")
+Fixes: cab127cba66c ("Added support for encrypted PKCS#8 files (for some PKCS#5 v1.5 schemes).")
+Fixes: CVE-2026-78129
+---
+ src/libstrongswan/crypto/pkcs5.c | 65 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 64 insertions(+), 1 deletion(-)
+
+diff --git a/src/libstrongswan/crypto/pkcs5.c b/src/libstrongswan/crypto/pkcs5.c
+index 4851d3f..d2e3f39 100644
+--- a/src/libstrongswan/crypto/pkcs5.c
++++ b/src/libstrongswan/crypto/pkcs5.c
+@@ -14,6 +14,8 @@
+  * for more details.
+  */
+ 
++#include <inttypes.h>
++
+ #include "pkcs5.h"
+ 
+ #include <utils/debug.h>
+@@ -22,6 +24,15 @@
+ #include <asn1/asn1_parser.h>
+ #include <credentials/containers/pkcs12.h>
+ 
++/** maximum accepted length for salts in parsed parameters */
++#define PKCS5_SALT_LEN_MAX			128
++
++/** maximum accepted iteration count in parsed parameters */
++#define PKCS5_ITERATIONS_MAX		1000000
++
++/** maximum key length accepted in parsed parameters */
++#define PKCS5_KEY_LEN_MAX			64
++
+ typedef struct private_pkcs5_t private_pkcs5_t;
+ 
+ /**
+@@ -379,6 +390,41 @@ METHOD(pkcs5_t, decrypt, bool,
+ 						   keymat, key, iv);
+ }
+ 
++/**
++ * Make sure the salt has an appropriate length
++ */
++static bool validate_salt_length(chunk_t salt)
++{
++	if (salt.len > PKCS5_SALT_LEN_MAX)
++	{
++		DBG1(DBG_ASN, "  salt length %zu exceeds maximum of %zu bytes",
++			 salt.len, (size_t)PKCS5_SALT_LEN_MAX);
++		return FALSE;
++	}
++	return TRUE;
++}
++
++/**
++ * Validate that parsed parameters are in an allowed range
++ */
++static bool validate_params(private_pkcs5_t *this)
++{
++	if (!this->iterations || this->iterations > PKCS5_ITERATIONS_MAX)
++	{
++		DBG1(DBG_ASN, "  iteration count %" PRIu64 " is out of range "
++			 "(1-%" PRIu64 ")", this->iterations,
++			 (uint64_t)PKCS5_ITERATIONS_MAX);
++		return FALSE;
++	}
++	if (this->keylen > PKCS5_KEY_LEN_MAX)
++	{
++		DBG1(DBG_ASN, "  key length %zu exceeds maximum of %zu bytes",
++			 this->keylen, (size_t)PKCS5_KEY_LEN_MAX);
++		return FALSE;
++	}
++	return TRUE;
++}
++
+ /**
+  * ASN.1 definition of a PBEParameter structure
+  */
+@@ -399,7 +445,7 @@ static bool parse_pbes1_params(private_pkcs5_t *this, chunk_t blob, int level0)
+ 	asn1_parser_t *parser;
+ 	chunk_t object;
+ 	int objectID;
+-	bool success;
++	bool success = FALSE;
+ 
+ 	parser = asn1_parser_create(pbeParameterObjects, blob);
+ 	parser->set_top_level(parser, level0);
+@@ -410,6 +456,10 @@ static bool parse_pbes1_params(private_pkcs5_t *this, chunk_t blob, int level0)
+ 		{
+ 			case PBEPARAM_SALT:
+ 			{
++				if (!validate_salt_length(object))
++				{
++					goto end;
++				}
+ 				this->salt = chunk_clone(object);
+ 				break;
+ 			}
+@@ -421,6 +471,11 @@ static bool parse_pbes1_params(private_pkcs5_t *this, chunk_t blob, int level0)
+ 		}
+ 	}
+ 	success = parser->success(parser);
++	if (success)
++	{
++		success = validate_params(this);
++	}
++end:
+ 	parser->destroy(parser);
+ 	return success;
+ }
+@@ -471,6 +526,10 @@ static bool parse_pbkdf2_params(private_pkcs5_t *this, chunk_t blob, int level0)
+ 		{
+ 			case PBKDF2_SALT:
+ 			{
++				if (!validate_salt_length(object))
++				{
++					goto end;
++				}
+ 				this->salt = chunk_clone(object);
+ 				break;
+ 			}
+@@ -500,6 +559,10 @@ static bool parse_pbkdf2_params(private_pkcs5_t *this, chunk_t blob, int level0)
+ 		}
+ 	}
+ 	success = parser->success(parser);
++	if (success)
++	{
++		success = validate_params(this);
++	}
+ end:
+ 	parser->destroy(parser);
+ 	return success;


=====================================
debian/patches/0023-x509-Avoid-NULL-pointer-dereference-if-issuerName-is.patch
=====================================
@@ -0,0 +1,43 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Fri, 19 Jun 2026 11:57:48 +0200
+Subject: x509: Avoid NULL-pointer dereference if issuerName is missing in
+ attribute certificate
+
+If neither authoritiyKeyIdentifier nor issuerName are encoded in an
+attribute certificate, the validation in `acert_validator.c:verify()`
+will cause a NULL-pointer dereference via `issued_by()` (the lookup
+with NULL identity will enumerate all trusted certificates).
+
+Fixes: 26930a8c3e42 ("certificate factory can load certs from file")
+Fixes: CVE-2026-78130
+---
+ src/libstrongswan/plugins/x509/x509_ac.c | 9 +++++++--
+ 1 file changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/src/libstrongswan/plugins/x509/x509_ac.c b/src/libstrongswan/plugins/x509/x509_ac.c
+index 3fc5de2..8765ad0 100644
+--- a/src/libstrongswan/plugins/x509/x509_ac.c
++++ b/src/libstrongswan/plugins/x509/x509_ac.c
+@@ -913,7 +913,11 @@ METHOD(certificate_t, has_issuer, id_match_t,
+ 	{
+ 		return ID_MATCH_PERFECT;
+ 	}
+-	return this->issuerName->matches(this->issuerName, issuer);
++	if (this->issuerName)
++	{
++		return this->issuerName->matches(this->issuerName, issuer);
++	}
++	return ID_MATCH_NONE;
+ }
+ 
+ METHOD(certificate_t, issued_by, bool,
+@@ -950,7 +954,8 @@ METHOD(certificate_t, issued_by, bool,
+ 	}
+ 	else
+ 	{
+-		if (!this->issuerName->equals(this->issuerName,
++		if (!this->issuerName ||
++			!this->issuerName->equals(this->issuerName,
+ 									  issuer->get_subject(issuer)))
+ 		{
+ 			return FALSE;


=====================================
debian/patches/0024-x509-Fix-memory-leaks-when-parsing-attribute-certifi.patch
=====================================
@@ -0,0 +1,77 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Fri, 12 Jun 2026 15:55:43 +0200
+Subject: x509: Fix memory leaks when parsing attribute certificates
+
+This can be triggered by an attribute certificate with lots of GeneralName
+entries or AuthorityKeyIdentifier extensions.  There is no verification
+before the certificate is parsed.
+
+Fixes: 3134379ac7f1 ("x509: Fix some whitespaces and do some minor style cleanups in acert")
+Fixes: 26930a8c3e42 ("certificate factory can load certs from file")
+Fixes: CVE-2026-78131
+---
+ src/libstrongswan/plugins/x509/x509_ac.c | 33 +++++++++-----------------------
+ 1 file changed, 9 insertions(+), 24 deletions(-)
+
+diff --git a/src/libstrongswan/plugins/x509/x509_ac.c b/src/libstrongswan/plugins/x509/x509_ac.c
+index 8765ad0..11a847e 100644
+--- a/src/libstrongswan/plugins/x509/x509_ac.c
++++ b/src/libstrongswan/plugins/x509/x509_ac.c
+@@ -186,41 +186,25 @@ extern bool x509_parse_generalNames(chunk_t blob, int level0, bool implicit,
+ static bool parse_directoryName(chunk_t blob, int level, bool implicit,
+ 								identification_t **name)
+ {
+-	identification_t *directoryName;
+-	enumerator_t *enumerator;
+-	bool first = TRUE;
+ 	linked_list_t *list;
+ 
+ 	list = linked_list_create();
+ 	if (!x509_parse_generalNames(blob, level, implicit, list))
+ 	{
+-		list->destroy(list);
++		list->destroy_offset(list, offsetof(identification_t, destroy));
+ 		return FALSE;
+ 	}
+-
+-	enumerator = list->create_enumerator(list);
+-	while (enumerator->enumerate(enumerator, &directoryName))
+-	{
+-		if (first)
+-		{
+-			*name = directoryName;
+-			first = FALSE;
+-		}
+-		else
+-		{
+-			DBG1(DBG_ASN, "more than one directory name - first selected");
+-			directoryName->destroy(directoryName);
+-			break;
+-		}
+-	}
+-	enumerator->destroy(enumerator);
+-	list->destroy(list);
+-
+-	if (first)
++	if (list->remove_first(list, (void**)name) != SUCCESS)
+ 	{
+ 		DBG1(DBG_ASN, "no directoryName found");
++		list->destroy(list);
+ 		return FALSE;
+ 	}
++	if (list->get_count(list))
++	{
++		DBG1(DBG_ASN, "more than one directory name - first selected");
++	}
++	list->destroy_offset(list, offsetof(identification_t, destroy));
+ 	return TRUE;
+ }
+ 
+@@ -539,6 +523,7 @@ static bool parse_certificate(private_x509_ac_t *this)
+ 						DBG2(DBG_ASN, "  need to parse crlDistributionPoints");
+ 						break;
+ 					case OID_AUTHORITY_KEY_ID:
++						chunk_free(&this->authKeyIdentifier);
+ 						this->authKeyIdentifier =
+ 								x509_parse_authorityKeyIdentifier(object,
+ 											level, &this->authKeySerialNumber);


=====================================
debian/patches/0025-x509-Prevent-infinite-loop-when-parsing-ietfAttrSynt.patch
=====================================
@@ -0,0 +1,69 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Wed, 1 Jul 2026 16:24:23 +0200
+Subject: x509: Prevent infinite loop when parsing ietfAttrSyntax in attribute
+ certificates
+
+This is the same issue that was fixed with 407fcca200fd ("asn1-parser:
+Fix CHOICE parsing") for other CHOICE elements.  This one was missed and
+can be triggered pre-auth by sending an attribute certificate to a peer.
+Since it's parsed before verifying it, the certificate doesn't have to
+be valid.
+
+For versions older than 5.5.3, this patch requires prior application of
+the fix for CVE-2017-9023, which introduced proper CHOICE handling in
+the ASN.1 parser.
+
+Fixes: a17598bc6992 ("x509: Integrate IETF attribute handling, and obsolete ietf_attributes_t")
+Fixes: CVE-2026-78132
+---
+ src/libstrongswan/plugins/x509/x509_ac.c | 36 +++++++++++++++-----------------
+ 1 file changed, 17 insertions(+), 19 deletions(-)
+
+diff --git a/src/libstrongswan/plugins/x509/x509_ac.c b/src/libstrongswan/plugins/x509/x509_ac.c
+index 11a847e..46c1f58 100644
+--- a/src/libstrongswan/plugins/x509/x509_ac.c
++++ b/src/libstrongswan/plugins/x509/x509_ac.c
+@@ -249,26 +249,24 @@ static void parse_roleSyntax(chunk_t blob, int level0)
+  */
+ static const asn1Object_t ietfAttrSyntaxObjects[] =
+ {
+-	{ 0, "ietfAttrSyntax",		ASN1_SEQUENCE,		ASN1_NONE }, /*  0 */
+-	{ 1,   "policyAuthority",	ASN1_CONTEXT_C_0,	ASN1_OPT |
+-													ASN1_BODY }, /*  1 */
+-	{ 1,   "end opt",			ASN1_EOC,			ASN1_END  }, /*  2 */
+-	{ 1,   "values",			ASN1_SEQUENCE,		ASN1_LOOP }, /*  3 */
+-	{ 2,     "octets",			ASN1_OCTET_STRING,	ASN1_OPT |
+-													ASN1_BODY }, /*  4 */
+-	{ 2,     "end choice",		ASN1_EOC,			ASN1_END  }, /*  5 */
+-	{ 2,     "oid",				ASN1_OID,			ASN1_OPT |
+-													ASN1_BODY }, /*  6 */
+-	{ 2,     "end choice",		ASN1_EOC,			ASN1_END  }, /*  7 */
+-	{ 2,     "string",			ASN1_UTF8STRING,	ASN1_OPT |
+-													ASN1_BODY }, /*  8 */
+-	{ 2,     "end choice",		ASN1_EOC,			ASN1_END  }, /*  9 */
+-	{ 1,   "end loop",			ASN1_EOC,			ASN1_END  }, /* 10 */
+-	{ 0, "exit",				ASN1_EOC,			ASN1_EXIT }
++	{ 0, "ietfAttrSyntax",		ASN1_SEQUENCE,		ASN1_NONE            }, /*  0 */
++	{ 1,   "policyAuthority",	ASN1_CONTEXT_C_0,	ASN1_OPT|ASN1_BODY   }, /*  1 */
++	{ 1,   "end opt",			ASN1_EOC,			ASN1_END             }, /*  2 */
++	{ 1,   "values",			ASN1_SEQUENCE,		ASN1_LOOP            }, /*  3 */
++	{ 2,     "value choice",	ASN1_EOC,			ASN1_CHOICE          }, /*  4 */
++	{ 3,       "octets",		ASN1_OCTET_STRING,	ASN1_OPT|ASN1_BODY   }, /*  5 */
++	{ 3,       "end choice",	ASN1_EOC,			ASN1_END|ASN1_CH     }, /*  6 */
++	{ 3,       "oid",			ASN1_OID,			ASN1_OPT|ASN1_BODY   }, /*  7 */
++	{ 3,       "end choice",	ASN1_EOC,			ASN1_END|ASN1_CH     }, /*  8 */
++	{ 3,       "string",		ASN1_UTF8STRING,	ASN1_OPT|ASN1_BODY   }, /*  9 */
++	{ 3,       "end choice",	ASN1_EOC,			ASN1_END|ASN1_CH     }, /* 10 */
++	{ 2,     "end choices",		ASN1_EOC,			ASN1_END|ASN1_CHOICE }, /* 11 */
++	{ 1,   "end loop",			ASN1_EOC,			ASN1_END             }, /* 12 */
++	{ 0, "exit",				ASN1_EOC,			ASN1_EXIT            }
+ };
+-#define IETF_ATTR_OCTETS	 4
+-#define IETF_ATTR_OID		 6
+-#define IETF_ATTR_STRING	 8
++#define IETF_ATTR_OCTETS	 5
++#define IETF_ATTR_OID		 7
++#define IETF_ATTR_STRING	 9
+ 
+ /**
+  * Parse group memberships, IETF attributes


=====================================
debian/patches/0026-ikev2-Prevent-use-after-free-during-collision-after-.patch
=====================================
@@ -0,0 +1,614 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Fri, 5 Jun 2026 08:15:46 +0200
+Subject: ikev2: Prevent use-after-free during collision after passive
+ multi-KE rekeying failed
+
+During a multi-KE rekey collision, where the initial response to the
+active rekeying is delayed (or withheld), the active task already keeps
+track of the passive task to eventually resolve the collision (it can
+only do so once all nonces are known).
+
+If the passive task then fails, e.g. due to a missing or invalid KE
+payload, and completes with SUCCESS, `collide()` previously recognized
+that the passive task is not yet complete returned FALSE, which caused
+the task manager to destroy the task.  However, the reference in the
+active task would remain.  So once the active rekeying progresses and
+the collision is resolved, that dangling pointer would get dereferenced
+for an indirect method call.  This happens via the `get_lower_nonce`
+function pointer of the `child_create_t` instance in the private task
+struct.  So besides having to be authenticated, an attacker has to get
+two indirections right to exploit this flaw for a potential RCE.
+Otherwise, the effects are a crash or basically undefined behavior
+triggered by the method call.
+
+By passing whether the passive task is done (and would get destroyed),
+the active tasks can properly clear the held reference.
+
+Note that this patch includes another fix for a state change during
+Child SA rekeying that's included in 6.1.0 (4611f41b1e14 ("child-rekey:
+Only reset state of SAs not actively rekeyed if passive rekeying
+fails")).
+
+Fixes: d2b2e1b3fae8 ("ikev2: Make CHILD_SAs properly trackable during rekey collisions")
+Fixes: ca3e6d2d144e ("ike-rekey: Support IKE_SA rekeying with multiple key exchanges")
+Fixes: CVE-2026-78133
+---
+ src/libcharon/sa/ikev2/task_manager_v2.c      |  13 +--
+ src/libcharon/sa/ikev2/tasks/child_rekey.c    |  47 ++++++--
+ src/libcharon/sa/ikev2/tasks/child_rekey.h    |   3 +-
+ src/libcharon/sa/ikev2/tasks/ike_rekey.c      |  40 +++++--
+ src/libcharon/sa/ikev2/tasks/ike_rekey.h      |   3 +-
+ src/libcharon/tests/suites/test_child_rekey.c | 136 ++++++++++++++++++++++++
+ src/libcharon/tests/suites/test_ike_rekey.c   | 147 ++++++++++++++++++++++++++
+ 7 files changed, 361 insertions(+), 28 deletions(-)
+
+diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c
+index 86f9f8b..85aaa24 100644
+--- a/src/libcharon/sa/ikev2/task_manager_v2.c
++++ b/src/libcharon/sa/ikev2/task_manager_v2.c
+@@ -927,7 +927,8 @@ static status_t process_response(private_task_manager_t *this,
+  * Handle exchange collisions, returns TRUE if the given passive task was
+  * adopted by the active task and the task manager lost control over it.
+  */
+-static bool handle_collisions(private_task_manager_t *this, task_t *task)
++static bool handle_collisions(private_task_manager_t *this, task_t *task,
++							  bool done)
+ {
+ 	enumerator_t *enumerator;
+ 	task_t *active;
+@@ -951,7 +952,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
+ 					if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE)
+ 					{
+ 						ike_rekey_t *rekey = (ike_rekey_t*)active;
+-						adopted = rekey->collide(rekey, task);
++						adopted = rekey->collide(rekey, task, done);
+ 						break;
+ 					}
+ 					continue;
+@@ -959,7 +960,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
+ 					if (type == TASK_CHILD_REKEY)
+ 					{
+ 						child_rekey_t *rekey = (child_rekey_t*)active;
+-						adopted = rekey->collide(rekey, task);
++						adopted = rekey->collide(rekey, task, done);
+ 						break;
+ 					}
+ 					continue;
+@@ -1011,14 +1012,14 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
+ 			case SUCCESS:
+ 				/* task completed, remove it */
+ 				array_remove_at(this->passive_tasks, enumerator);
+-				if (!handle_collisions(this, task))
++				if (!handle_collisions(this, task, TRUE))
+ 				{
+ 					task->destroy(task);
+ 				}
+ 				break;
+ 			case NEED_MORE:
+ 				/* processed, but task needs another exchange */
+-				if (handle_collisions(this, task))
++				if (handle_collisions(this, task, FALSE))
+ 				{
+ 					array_remove_at(this->passive_tasks, enumerator);
+ 				}
+@@ -1029,7 +1030,7 @@ static status_t build_response(private_task_manager_t *this, message_t *request)
+ 				/* FALL */
+ 			case DESTROY_ME:
+ 				/* destroy IKE_SA, but SEND response first */
+-				if (handle_collisions(this, task))
++				if (handle_collisions(this, task, FALSE))
+ 				{
+ 					array_remove_at(this->passive_tasks, enumerator);
+ 				}
+diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c
+index 6c73d06..51edfa1 100644
+--- a/src/libcharon/sa/ikev2/tasks/child_rekey.c
++++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c
+@@ -406,7 +406,7 @@ METHOD(task_t, build_r, status_t,
+ 	child_sa_t *child_sa;
+ 	child_sa_state_t state = CHILD_INSTALLED;
+ 	uint32_t reqid;
+-	bool followup_sent = FALSE;
++	bool active, followup_sent = FALSE;
+ 
+ 	if (!this->child_sa)
+ 	{
+@@ -426,7 +426,8 @@ METHOD(task_t, build_r, status_t,
+ 		message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
+ 		return SUCCESS;
+ 	}
+-	if (actively_rekeying(this, &followup_sent) && followup_sent)
++	active = actively_rekeying(this, &followup_sent);
++	if (active && followup_sent)
+ 	{
+ 		DBG1(DBG_IKE, "peer initiated rekeying, but we did too and already "
+ 			 "sent IKE_FOLLOWUP_KE");
+@@ -475,8 +476,9 @@ METHOD(task_t, build_r, status_t,
+ 		/* like installing the outbound SA, we only trigger the child-rekey
+ 		 * event once the old SA is deleted */
+ 	}
+-	else if (this->child_sa->get_state(this->child_sa) == CHILD_REKEYING)
+-	{	/* rekeying failed, reuse old child */
++	else if (!active &&
++			 this->child_sa->get_state(this->child_sa) == CHILD_REKEYING)
++	{	/* rekeying failed, reuse old child, unless we are actively rekeying */
+ 		this->child_sa->set_state(this->child_sa, state);
+ 	}
+ 	return SUCCESS;
+@@ -1119,8 +1121,22 @@ METHOD(child_rekey_t, handle_delete, child_rekey_collision_t,
+ 	return CHILD_REKEY_COLLISION_NONE;
+ }
+ 
++/**
++ * Clear the colliding passive task if it did not complete successfully.
++ */
++static void clear_collision(private_child_rekey_t *this, task_t *other)
++{
++	if (this->collision == other)
++	{
++		DBG1(DBG_IKE, "colliding passive rekeying for CHILD_SA %s{%u} "
++			 "failed", this->child_sa->get_name(this->child_sa),
++			 this->child_sa->get_unique_id(this->child_sa));
++		this->collision = NULL;
++	}
++}
++
+ METHOD(child_rekey_t, collide, bool,
+-	private_child_rekey_t *this, task_t *other)
++	private_child_rekey_t *this, task_t *other, bool done)
+ {
+ 	private_child_rekey_t *rekey = (private_child_rekey_t*)other;
+ 	child_sa_t *other_child;
+@@ -1134,16 +1150,25 @@ METHOD(child_rekey_t, collide, bool,
+ 	other_child = rekey->child_create->get_child(rekey->child_create);
+ 	if (!other_child)
+ 	{
+-		/* ignore passive tasks that did not successfully create a CHILD_SA */
++		/* ignore passive tasks that did not successfully create a CHILD_SA,
++		 * if we are already tracking it in the multi-KE case, clear it */
++		clear_collision(this, other);
+ 		return FALSE;
+ 	}
+ 	if (other_child->get_state(other_child) != CHILD_INSTALLED)
+ 	{
+-		DBG1(DBG_IKE, "colliding passive rekeying for CHILD_SA %s{%u} is not "
+-			 "yet complete", this->child_sa->get_name(this->child_sa),
+-			 this->child_sa->get_unique_id(this->child_sa));
+-		/* we do reference the task to check its state later */
+-		this->collision = other;
++		if (done)
++		{	/* passive task failed, clear it if necessary */
++			clear_collision(this, other);
++		}
++		else
++		{
++			DBG1(DBG_IKE, "colliding passive rekeying for CHILD_SA %s{%u} is "
++				 "not yet complete", this->child_sa->get_name(this->child_sa),
++				 this->child_sa->get_unique_id(this->child_sa));
++			/* we do reference the task to check its state later */
++			this->collision = other;
++		}
+ 		return FALSE;
+ 	}
+ 	if (this->collision && this->collision != other)
+diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.h b/src/libcharon/sa/ikev2/tasks/child_rekey.h
+index a8daed7..fef0bba 100644
+--- a/src/libcharon/sa/ikev2/tasks/child_rekey.h
++++ b/src/libcharon/sa/ikev2/tasks/child_rekey.h
+@@ -79,10 +79,11 @@ struct child_rekey_t {
+ 	 * are going on and notifies the active task by passing the passive.
+ 	 *
+ 	 * @param other		passive task
++	 * @param done		passive task is done and gets destroyed if not adopted
+ 	 * @return			whether the task was adopted and should be removed from
+ 	 *					the task manager's control
+ 	 */
+-	bool (*collide)(child_rekey_t* this, task_t *other);
++	bool (*collide)(child_rekey_t* this, task_t *other, bool done);
+ };
+ 
+ /**
+diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c
+index f624464..cb45bda 100644
+--- a/src/libcharon/sa/ikev2/tasks/ike_rekey.c
++++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c
+@@ -743,8 +743,23 @@ METHOD(ike_rekey_t, did_collide, bool,
+ 	return this->collision != NULL;
+ }
+ 
++/**
++ * Clear the colliding passive task if it did not complete successfully.
++ */
++static bool clear_collision(private_ike_rekey_t *this,
++							private_ike_rekey_t *other)
++{
++	if (this->collision == other)
++	{
++		DBG1(DBG_IKE, "colliding passive rekeying failed, ignore");
++		this->collision = NULL;
++		return TRUE;
++	}
++	return FALSE;
++}
++
+ METHOD(ike_rekey_t, collide, bool,
+-	private_ike_rekey_t* this, task_t *other)
++	private_ike_rekey_t* this, task_t *other, bool done)
+ {
+ 	DBG1(DBG_IKE, "detected %N collision with %N", task_type_names,
+ 		 TASK_IKE_REKEY, task_type_names, other->get_type(other));
+@@ -760,23 +775,30 @@ METHOD(ike_rekey_t, collide, bool,
+ 
+ 			if (!rekey->ike_init)
+ 			{
+-				DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, "
+-					 "ignore");
+-				if (this->collision == rekey)
++				if (!clear_collision(this, rekey))
+ 				{
+-					this->collision = NULL;
++					DBG1(DBG_IKE, "colliding exchange did not result in an "
++						 "IKE_SA, ignore");
+ 				}
+ 				break;
+ 			}
+-			/* we keep track of the passive exchange in any case, if not
+-			 * complete yet, this method might be called again later */
+-			this->collision = rekey;
++			/* we keep track of the passive exchange, if not complete yet, this
++			 * method might be called again later */
+ 			if (rekey->flags & IKE_REKEY_DONE)
+ 			{
++				this->collision = rekey;
+ 				this->flags |= IKE_REKEY_ADOPTED_PASSIVE;
+ 				return TRUE;
+ 			}
+-			DBG1(DBG_IKE, "colliding passive exchange is not yet complete");
++			else if (done)
++			{	/* passive task failed, clear it if necessary */
++				clear_collision(this, rekey);
++			}
++			else
++			{
++				DBG1(DBG_IKE, "colliding passive exchange is not yet complete");
++				this->collision = rekey;
++			}
+ 			break;
+ 		}
+ 		default:
+diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.h b/src/libcharon/sa/ikev2/tasks/ike_rekey.h
+index 5fab349..e68fa62 100644
+--- a/src/libcharon/sa/ikev2/tasks/ike_rekey.h
++++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.h
+@@ -54,10 +54,11 @@ struct ike_rekey_t {
+ 	 * are going on and notifies the active task by passing the passive.
+ 	 *
+ 	 * @param other		passive task
++	 * @param done		passive task is done and gets destroyed if not adopted
+ 	 * @return			whether the task was adopted and should be removed from
+ 	 *					the task manager's control
+ 	 */
+-	bool (*collide)(ike_rekey_t* this, task_t *other);
++	bool (*collide)(ike_rekey_t* this, task_t *other, bool done);
+ };
+ 
+ /**
+diff --git a/src/libcharon/tests/suites/test_child_rekey.c b/src/libcharon/tests/suites/test_child_rekey.c
+index b61f31c..ed92849 100644
+--- a/src/libcharon/tests/suites/test_child_rekey.c
++++ b/src/libcharon/tests/suites/test_child_rekey.c
+@@ -2488,6 +2488,141 @@ START_TEST(test_collision_delayed_response_multi_ke)
+ }
+ END_TEST
+ 
++/**
++ * Remove the KE payload from the IKE_FOLLOWUP_KE request
++ */
++static bool remove_ke(listener_t *listener, ike_sa_t *ike_sa,
++					  message_t *message, bool incoming, bool plain)
++{
++	if (plain && incoming &&
++		message->get_exchange_type(message) == IKE_FOLLOWUP_KE &&
++		message->get_request(message))
++	{
++		enumerator_t *enumerator = message->create_payload_enumerator(message);
++		payload_t *pld;
++
++		while (enumerator->enumerate(enumerator, &pld))
++		{
++			if (pld->get_type(pld) == PLV2_KEY_EXCHANGE)
++			{
++				message->remove_payload_at(message, enumerator);
++				pld->destroy(pld);
++				break;
++			}
++		}
++		enumerator->destroy(enumerator);
++		free(listener);
++		return FALSE;
++	}
++	return TRUE;
++}
++
++#define remove_ke_from_ike_followup_ke() ({ \
++	listener_t *_ke_listener; \
++	INIT(_ke_listener, \
++		.message = remove_ke, \
++	); \
++	exchange_test_helper->add_listener(exchange_test_helper, _ke_listener); \
++})
++
++/**
++ * This simulates an incorrect behavior by the peer.  It triggers a collision by
++ * not responding to the initial CREATE_CHILD_SA and then sends an invalid
++ * IKE_FOLLOWUP_KE (in this case the KE payload is missing).  The initiator
++ * has to correctly track and then untrack the passive rekey task.
++ *
++ *           Peer A                   Peer B
++ *            rekey ----\       /---- rekey
++ *                       \-----/----> detect collision and withhold response
++ * detect collision <---------/
++ *                  ---------------->
++ *   handle failure <---------------- send invalid additional KE
++ *     handle rekey <---------------- send withheld response
++ */
++START_TEST(test_collision_delayed_response_multi_ke_failure)
++{
++	ike_sa_t *a, *b;
++	message_t *msg;
++
++	assert_track_sas_start();
++
++	exchange_test_helper->establish_sa(exchange_test_helper,
++									   &a, &b, &multi_ke_conf);
++
++	/* make sure the responder wins the collision so it continues */
++	exchange_test_helper->nonce_first_byte = 0x00;
++	initiate_rekey(a, 1);
++	assert_ipsec_sas_installed(a, 1, 2);
++	exchange_test_helper->nonce_first_byte = 0xff;
++	initiate_rekey(b, 2);
++	assert_ipsec_sas_installed(b, 1, 2);
++
++	/* these should not get called as no SA goes down or gets rekeyed */
++	assert_hook_not_called(child_updown);
++	assert_hook_not_called(child_rekey);
++
++	/* CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } --> */
++	exchange_test_helper->nonce_first_byte = 0xff;
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++	assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
++	assert_ipsec_sas_installed(b, 1, 2);
++
++	/* <-- CREATE_CHILD_SA { N(REKEY_SA), SA, Ni, [KEi,] TSi, TSr } */
++	exchange_test_helper->nonce_first_byte = 0xff;
++	exchange_test_helper->process_message(exchange_test_helper, a, NULL);
++	assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
++	assert_ipsec_sas_installed(a, 1, 2);
++
++	/* the responder is not responding */
++	msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
++
++	/* CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } --> */
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++	assert_num_tasks(b, 0, TASK_QUEUE_PASSIVE);
++	assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
++	assert_child_sa_state(b, 2, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
++	assert_ipsec_sas_installed(b, 1, 2);
++
++	/* remove the KE payload in the IKE_FOLLOWUP_KE request */
++	remove_ke_from_ike_followup_ke();
++
++	/* <-- IKE_FOLLOWUP_KE { N(ADD_KE) } */
++	assert_no_payload(IN, PLV2_KEY_EXCHANGE);
++	assert_single_notify(OUT, INVALID_SYNTAX);
++	exchange_test_helper->process_message(exchange_test_helper, a, NULL);
++	assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
++	assert_ipsec_sas_installed(a, 1, 2);
++	assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
++
++	/* <-- CREATE_CHILD_SA { SA, Nr, [KEr,] TSi, TSr } (delayed) */
++	exchange_test_helper->process_message(exchange_test_helper, a, msg);
++	assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
++	assert_num_tasks(a, 1, TASK_QUEUE_ACTIVE);
++	assert_child_sa_state(a, 1, CHILD_REKEYING, CHILD_OUTBOUND_INSTALLED);
++	assert_ipsec_sas_installed(a, 1, 2);
++
++	/* drop the STATE_NOT_FOUND error message from the initiator */
++	msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
++	msg->destroy(msg);
++
++	/* since we explicitly forced the responder to win, it already removed
++	 * the passive task it won't accept the request */
++
++	/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
++	assert_payload(IN, PLV2_KEY_EXCHANGE);
++	assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++
++	/* child_rekey/child_updown */
++	assert_hook();
++	assert_hook();
++	assert_track_sas(2, 2);
++
++	call_ikesa(a, destroy);
++	call_ikesa(b, destroy);
++}
++END_TEST
++
+ /**
+  * In this scenario one of the peers does not notice that there is a
+  * rekey collision:
+@@ -4377,6 +4512,7 @@ Suite *child_rekey_suite_create()
+ 	tcase_add_loop_test(tc, test_collision_delayed_response, 0, 4);
+ 	tcase_add_loop_test(tc, test_collision_delayed_response_delete, 0, 4);
+ 	tcase_add_loop_test(tc, test_collision_delayed_response_multi_ke, 0, 4);
++	tcase_add_test(tc, test_collision_delayed_response_multi_ke_failure);
+ 	tcase_add_loop_test(tc, test_collision_delayed_request, 0, 6);
+ 	tcase_add_loop_test(tc, test_collision_delayed_request_more, 0, 3);
+ 	tcase_add_loop_test(tc, test_collision_delayed_request_more_delete, 0, 3);
+diff --git a/src/libcharon/tests/suites/test_ike_rekey.c b/src/libcharon/tests/suites/test_ike_rekey.c
+index c6691ac..2c41022 100644
+--- a/src/libcharon/tests/suites/test_ike_rekey.c
++++ b/src/libcharon/tests/suites/test_ike_rekey.c
+@@ -1784,6 +1784,152 @@ START_TEST(test_collision_delayed_response_multi_ke)
+ }
+ END_TEST
+ 
++/**
++ * Remove the ADDITIONAL_KEY_EXCHANGE notify payload from the IKE_FOLLOWUP_KE
++ * request
++ */
++static bool remove_notify(listener_t *listener, ike_sa_t *ike_sa,
++						  message_t *message, bool incoming, bool plain)
++{
++	if (plain && incoming &&
++		message->get_exchange_type(message) == IKE_FOLLOWUP_KE &&
++		message->get_request(message))
++	{
++		enumerator_t *enumerator = message->create_payload_enumerator(message);
++		payload_t *pld;
++
++		while (enumerator->enumerate(enumerator, &pld))
++		{	/* we only expect one notify, so just remove the first */
++			if (pld->get_type(pld) == PLV2_NOTIFY)
++			{
++				message->remove_payload_at(message, enumerator);
++				pld->destroy(pld);
++				break;
++			}
++		}
++		enumerator->destroy(enumerator);
++		free(listener);
++		return FALSE;
++	}
++	return TRUE;
++}
++
++#define remove_notify_from_ike_followup_ke() ({ \
++	listener_t *_ke_listener; \
++	INIT(_ke_listener, \
++		.message = remove_notify, \
++	); \
++	exchange_test_helper->add_listener(exchange_test_helper, _ke_listener); \
++})
++
++/**
++ * This simulates an incorrect behavior by the peer.  It triggers a collision by
++ * not responding to the initial CREATE_CHILD_SA and then sends an invalid
++ * IKE_FOLLOWUP_KE (in this case by removing the ADDITIONAL_KEY_EXCHANGE
++ * notify).  The initiator has to correctly track and then untrack the passive
++ * rekey task.
++ *
++ *           Peer A                   Peer B
++ *            rekey ----\       /---- rekey
++ *                       \-----/----> detect collision and withhold response
++ * detect collision <---------/
++ *                  ---------------->
++ *   handle failure <---------------- send invalid additional KE
++ *     handle rekey <---------------- send withheld response
++ */
++START_TEST(test_collision_delayed_response_multi_ke_failure)
++{
++	ike_sa_t *a, *b;
++	message_t *msg;
++
++	assert_track_sas_start();
++
++	exchange_test_helper->establish_sa(exchange_test_helper,
++									   &a, &b, &multi_ke_conf);
++
++	/* these should not get called as no SA goes down or gets rekeyed */
++	assert_hook_not_called(ike_updown);
++	assert_hook_not_called(ike_rekey);
++	assert_hook_not_called(child_updown);
++
++	/* make sure the responder wins the collision so it continues */
++	exchange_test_helper->nonce_first_byte = 0x00;
++	initiate_rekey(a);
++	exchange_test_helper->nonce_first_byte = 0xff;
++	initiate_rekey(b);
++
++	/* CREATE_CHILD_SA { SA, Ni, KEi } --> */
++	exchange_test_helper->nonce_first_byte = 0xff;
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++	assert_ike_sa_state(b, IKE_REKEYING);
++	assert_child_sa_count(b, 1);
++	assert_ike_sa_count(0);
++
++	/* <-- CREATE_CHILD_SA { SA, Ni, KEi } */
++	exchange_test_helper->nonce_first_byte = 0xff;
++	exchange_test_helper->process_message(exchange_test_helper, a, NULL);
++	assert_ike_sa_state(a, IKE_REKEYING);
++	assert_child_sa_count(a, 1);
++	assert_ike_sa_count(0);
++
++	/* the responder is not responding */
++	msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
++
++	/* simplify next steps by checking in original IKE_SAs */
++	charon->ike_sa_manager->checkin(charon->ike_sa_manager, a);
++	charon->ike_sa_manager->checkin(charon->ike_sa_manager, b);
++	assert_ike_sa_count(2);
++
++	/* CREATE_CHILD_SA { SA, Nr, KEr, N(ADD_KE) } --> */
++	assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++	assert_num_tasks(b, 0, TASK_QUEUE_PASSIVE);
++	assert_num_tasks(b, 1, TASK_QUEUE_ACTIVE);
++	assert_ike_sa_state(b, IKE_REKEYING);
++	assert_ike_sa_count(2);
++
++	/* remove the ADD_KE notify from the IKE_FOLLOWUP_KE request */
++	remove_notify_from_ike_followup_ke();
++
++	/* <-- IKE_FOLLOWUP_KE { KEi } */
++	assert_payload(IN, PLV2_KEY_EXCHANGE);
++	assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
++	assert_single_notify(OUT, STATE_NOT_FOUND);
++	exchange_test_helper->process_message(exchange_test_helper, a, NULL);
++	assert_ike_sa_state(a, IKE_REKEYING);
++	assert_child_sa_count(a, 1);
++	assert_ike_sa_count(2);
++
++	/* <-- CREATE_CHILD_SA { SA, Nr, KEr } (delayed) */
++	exchange_test_helper->process_message(exchange_test_helper, a, msg);
++	assert_num_tasks(a, 0, TASK_QUEUE_PASSIVE);
++	assert_num_tasks(a, 1, TASK_QUEUE_ACTIVE);
++	assert_ike_sa_state(a, IKE_REKEYING);
++	assert_child_sa_count(a, 1);
++	assert_ike_sa_count(2);
++
++	/* drop the STATE_NOT_FOUND error message from the initiator */
++	msg = exchange_test_helper->sender->dequeue(exchange_test_helper->sender);
++	msg->destroy(msg);
++
++	/* since we explicitly forced the responder to win, it already removed
++	 * the passive task it won't accept the request */
++
++	/* IKE_FOLLOWUP_KE { KEi, N(ADD_KE) } --> */
++	assert_payload(IN, PLV2_KEY_EXCHANGE);
++	assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
++	exchange_test_helper->process_message(exchange_test_helper, b, NULL);
++
++	/* ike_updown/rekey/child_updown */
++	assert_hook();
++	assert_hook();
++	assert_hook();
++	assert_track_sas(2, 2);
++
++	charon->ike_sa_manager->flush(charon->ike_sa_manager);
++}
++END_TEST
++
+ /**
+  * In this scenario one of the peers does not notice that there is a rekey
+  * collision because the other request is dropped:
+@@ -2590,6 +2736,7 @@ Suite *ike_rekey_suite_create()
+ 	tcase_add_loop_test(tc, test_collision_ke_invalid_delayed_retry, 0, 3);
+ 	tcase_add_loop_test(tc, test_collision_delayed_response, 0, 4);
+ 	tcase_add_loop_test(tc, test_collision_delayed_response_multi_ke, 0, 4);
++	tcase_add_test(tc, test_collision_delayed_response_multi_ke_failure);
+ 	tcase_add_loop_test(tc, test_collision_dropped_request, 0, 3);
+ 	tcase_add_loop_test(tc, test_collision_delayed_request, 0, 3);
+ 	tcase_add_loop_test(tc, test_collision_delayed_request_and_delete, 0, 3);


=====================================
debian/patches/0027-eap-ttls-peap-Return-auth-cfg-with-details-on-TLS-an.patch
=====================================
@@ -0,0 +1,703 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Mon, 27 Jul 2026 15:05:45 +0200
+Subject: eap-ttls/peap: Return auth-cfg with details on TLS and inner EAP
+ method
+
+This fixes several issues with binding identities to the IKE SA.
+
+If the client is authenticated with a certificate, the previous code still
+used the client's proclaimed inner EAP-Identity when starting the EAP-TNC
+method.  So that method would potentially operate on an unverified
+identity.
+
+Second, if the inner EAP method overrides the client identity (the only
+one is currently EAP-MSCHAPV2), the missing merge meant that the outer
+IKE/EAP identity could potentially be unconfirmed.
+
+For inner methods that don't override the identity (e.g. EAP-MD5), not
+propagating the inner EAP-Identity could potentially have the same
+effect.
+
+While the EAP-TTLS implementation returned the auth-cfg of the TLS
+exchange since the first referenced commit, this was mainly intended to
+enforce public key constraints.  So it didn't cover the phase 2 EAP
+methods.  For some reason EAP-PEAP did not get that method at all in that
+changeset, so we'll add that now.
+
+Additionally, the EAP-PEAP implementation now forwards the phase 2 EAP
+method type to EAP-TNC like the EAP-TTLS implementation already did,
+which allows a more informed decision on the client's identity.
+
+Fixes: 0864a31d13ff ("eap-ttls: Support EAP auth information getter in EAP-TTLS")
+Fixes: 79f2102cb442 ("implemented server side support for EAP-TTLS")
+Fixes: 2a421163bf4f ("make TNC client authentication type available to IMVs")
+Fixes: 1be296dfb2af ("implemented the PEAP tunneling protocol as an EAP plugin")
+Fixes: CVE-2026-78134
+---
+ src/libcharon/plugins/eap_peap/eap_peap.c        | 49 +++++++++++++-
+ src/libcharon/plugins/eap_peap/eap_peap_peer.c   | 20 ++++++
+ src/libcharon/plugins/eap_peap/eap_peap_peer.h   |  7 ++
+ src/libcharon/plugins/eap_peap/eap_peap_server.c | 81 +++++++++++++++++++++---
+ src/libcharon/plugins/eap_peap/eap_peap_server.h |  7 ++
+ src/libcharon/plugins/eap_ttls/eap_ttls.c        | 43 ++++++++++++-
+ src/libcharon/plugins/eap_ttls/eap_ttls_peer.c   | 19 ++++++
+ src/libcharon/plugins/eap_ttls/eap_ttls_peer.h   |  7 ++
+ src/libcharon/plugins/eap_ttls/eap_ttls_server.c | 56 ++++++++++++++--
+ src/libcharon/plugins/eap_ttls/eap_ttls_server.h |  7 ++
+ 10 files changed, 278 insertions(+), 18 deletions(-)
+
+diff --git a/src/libcharon/plugins/eap_peap/eap_peap.c b/src/libcharon/plugins/eap_peap/eap_peap.c
+index 3573cba..cd942f1 100644
+--- a/src/libcharon/plugins/eap_peap/eap_peap.c
++++ b/src/libcharon/plugins/eap_peap/eap_peap.c
+@@ -40,6 +40,25 @@ struct private_eap_peap_t {
+ 	 * TLS stack, wrapped by EAP helper
+ 	 */
+ 	tls_eap_t *tls_eap;
++
++	/**
++	 * Role
++	 */
++	bool is_server;
++
++	/**
++	 * Actual server/client implementation
++	 */
++	union {
++		tls_application_t *application;
++		eap_peap_server_t *server;
++		eap_peap_peer_t *client;
++	} impl;
++
++	/**
++	 * Cached auth data for TLS and inner EAP methods
++	 */
++	auth_cfg_t *auth;
+ };
+ 
+ /** Maximum number of EAP-PEAP messages/fragments allowed */
+@@ -113,10 +132,34 @@ METHOD(eap_method_t, is_mutual, bool,
+ 	return TRUE;
+ }
+ 
++METHOD(eap_method_t, get_auth, auth_cfg_t*,
++	private_eap_peap_t *this)
++{
++	if (!this->auth)
++	{
++		auth_cfg_t *inner;
++
++		this->auth = auth_cfg_create();
++		this->auth->merge(this->auth,
++						  this->tls_eap->get_auth(this->tls_eap), FALSE);
++		if (this->is_server)
++		{
++			inner = this->impl.server->get_auth(this->impl.server);
++		}
++		else
++		{
++			inner = this->impl.client->get_auth(this->impl.client);
++		}
++		this->auth->merge(this->auth, inner, FALSE);
++	}
++	return this->auth;
++}
++
+ METHOD(eap_method_t, destroy, void,
+ 	private_eap_peap_t *this)
+ {
+ 	this->tls_eap->destroy(this->tls_eap);
++	DESTROY_IF(this->auth);
+ 	free(this);
+ }
+ 
+@@ -135,6 +178,7 @@ static private_eap_peap_t *eap_peap_create_empty(void)
+ 				.get_type = _get_type,
+ 				.is_mutual = _is_mutual,
+ 				.get_msk = _get_msk,
++				.get_auth = _get_auth,
+ 				.get_identifier = _get_identifier,
+ 				.set_identifier = _set_identifier,
+ 				.destroy = _destroy,
+@@ -147,7 +191,7 @@ static private_eap_peap_t *eap_peap_create_empty(void)
+ /**
+  * Generic private constructor
+  */
+-static eap_peap_t *eap_peap_create(private_eap_peap_t * this,
++static eap_peap_t *eap_peap_create(private_eap_peap_t *this,
+ 								   identification_t *server,
+ 								   identification_t *peer, bool is_server,
+ 								   tls_application_t *application)
+@@ -157,6 +201,9 @@ static eap_peap_t *eap_peap_create(private_eap_peap_t * this,
+ 	bool include_length;
+ 	tls_t *tls;
+ 
++	this->is_server = is_server;
++	this->impl.application = application;
++
+ 	if (is_server && !lib->settings->get_bool(lib->settings,
+ 								"%s.plugins.eap-peap.request_peer_auth", FALSE,
+ 								lib->ns))
+diff --git a/src/libcharon/plugins/eap_peap/eap_peap_peer.c b/src/libcharon/plugins/eap_peap/eap_peap_peer.c
+index 95213a3..f6c087a 100644
+--- a/src/libcharon/plugins/eap_peap/eap_peap_peer.c
++++ b/src/libcharon/plugins/eap_peap/eap_peap_peer.c
+@@ -52,6 +52,11 @@ struct private_eap_peap_peer_t {
+ 	 */
+ 	eap_method_t *ph2_method;
+ 
++	/**
++	 * Auth data for phase 2 methods
++	 */
++	auth_cfg_t *auth;
++
+ 	/**
+      * Pending outbound EAP message
+ 	 */
+@@ -166,6 +171,12 @@ METHOD(tls_application_t, process, status_t,
+ 	switch (status)
+ 	{
+ 		case SUCCESS:
++			if (this->ph2_method->get_auth)
++			{
++				this->auth->merge(this->auth,
++								  this->ph2_method->get_auth(this->ph2_method),
++								  FALSE);
++			}
+ 			this->ph2_method->destroy(this->ph2_method);
+ 			this->ph2_method = NULL;
+ 			/* fall through to NEED_MORE */
+@@ -220,11 +231,18 @@ METHOD(tls_application_t, build, status_t,
+ 	return INVALID_STATE;
+ }
+ 
++METHOD(eap_peap_peer_t, get_auth, auth_cfg_t*,
++	private_eap_peap_peer_t *this)
++{
++	return this->auth;
++}
++
+ METHOD(tls_application_t, destroy, void,
+ 	private_eap_peap_peer_t *this)
+ {
+ 	this->server->destroy(this->server);
+ 	this->peer->destroy(this->peer);
++	this->auth->destroy(this->auth);
+ 	DESTROY_IF(this->ph2_method);
+ 	DESTROY_IF(this->out);
+ 	this->avp->destroy(this->avp);
+@@ -247,10 +265,12 @@ eap_peap_peer_t *eap_peap_peer_create(identification_t *server,
+ 				.build = _build,
+ 				.destroy = _destroy,
+ 			},
++			.get_auth = _get_auth,
+ 		},
+ 		.server = server->clone(server),
+ 		.peer = peer->clone(peer),
+ 		.ph1_method = eap_method,
++		.auth = auth_cfg_create(),
+ 		.avp = eap_peap_avp_create(FALSE),
+ 	);
+ 
+diff --git a/src/libcharon/plugins/eap_peap/eap_peap_peer.h b/src/libcharon/plugins/eap_peap/eap_peap_peer.h
+index 53c25cd..7d16957 100644
+--- a/src/libcharon/plugins/eap_peap/eap_peap_peer.h
++++ b/src/libcharon/plugins/eap_peap/eap_peap_peer.h
+@@ -38,6 +38,13 @@ struct eap_peap_peer_t {
+ 	 * Implements the TLS application data handler.
+ 	 */
+ 	tls_application_t application;
++
++	/**
++	 * Get authentication details of this EAP method and its inner method(s).
++	 *
++	 * @return				auth method, internal data
++	 */
++	auth_cfg_t *(*get_auth)(eap_peap_peer_t *this);
+ };
+ 
+ /**
+diff --git a/src/libcharon/plugins/eap_peap/eap_peap_server.c b/src/libcharon/plugins/eap_peap/eap_peap_server.c
+index 29ab9b4..388c3c6 100644
+--- a/src/libcharon/plugins/eap_peap/eap_peap_server.c
++++ b/src/libcharon/plugins/eap_peap/eap_peap_server.c
+@@ -20,6 +20,8 @@
+ #include <utils/debug.h>
+ #include <daemon.h>
+ 
++#include <sa/eap/eap_inner_method.h>
++
+ typedef struct private_eap_peap_server_t private_eap_peap_server_t;
+ 
+ /**
+@@ -77,6 +79,16 @@ struct private_eap_peap_server_t {
+ 	 */
+ 	eap_method_t *ph2_method;
+ 
++	/**
++	 * Type of the completed phase 2 EAP method
++	 */
++	eap_type_t phase2_type;
++
++	/**
++	 * Auth data for phase 2 method
++	 */
++	auth_cfg_t *auth;
++
+ 	/**
+      * Pending outbound EAP message
+ 	 */
+@@ -132,8 +144,11 @@ static status_t start_phase2_auth(private_eap_peap_server_t *this)
+ /**
+  * If configured, start EAP-TNC protocol
+  */
+-static status_t start_phase2_tnc(private_eap_peap_server_t *this)
++static status_t start_phase2_tnc(private_eap_peap_server_t *this,
++								 eap_type_t auth_type)
+ {
++	eap_inner_method_t *inner_method;
++
+ 	if (this->start_phase2_tnc && lib->settings->get_bool(lib->settings,
+ 						"%s.plugins.eap-peap.phase2_tnc", FALSE, lib->ns))
+ 	{
+@@ -145,6 +160,8 @@ static status_t start_phase2_tnc(private_eap_peap_server_t *this)
+ 			DBG1(DBG_IKE, "%N method not available", eap_type_names, EAP_TNC);
+ 			return FAILED;
+ 		}
++		inner_method = (eap_inner_method_t *)this->ph2_method;
++		inner_method->set_auth_type(inner_method, auth_type);
+ 		this->start_phase2_tnc = FALSE;
+ 
+ 		/* synchronize EAP message identifiers of inner protocol with outer */
+@@ -218,9 +235,13 @@ METHOD(tls_application_t, process, status_t,
+ 		DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [EAP/%N]",
+ 								eap_code_short_names, code);
+ 		in->destroy(in);
+-		/* if EAP_SUCCESS check if to continue phase2 with EAP-TNC */
+-		return (this->phase2_result == EAP_SUCCESS && code == EAP_SUCCESS) ?
+-			   start_phase2_tnc(this) : FAILED;
++		if (this->phase2_result == EAP_SUCCESS && code == EAP_SUCCESS)
++		{
++			/* only accept SUCCESS once after a successful inner method */
++			this->phase2_result = EAP_FAILURE;
++			return start_phase2_tnc(this, this->phase2_type);
++		}
++		return FAILED;
+ 	}
+ 
+ 	if (this->ph2_method)
+@@ -245,6 +266,10 @@ METHOD(tls_application_t, process, status_t,
+ 	if (!received_vendor && received_type == EAP_IDENTITY)
+ 	{
+ 		chunk_t eap_id;
++		bool peer_auth;
++
++		peer_auth = lib->settings->get_bool(lib->settings,
++					"%s.plugins.eap-peap.request_peer_auth", FALSE, lib->ns);
+ 
+ 		if (this->ph2_method == NULL)
+ 		{
+@@ -271,9 +296,22 @@ METHOD(tls_application_t, process, status_t,
+ 
+ 		if (this->ph2_method->get_msk(this->ph2_method, &eap_id) == SUCCESS)
+ 		{
+-			this->peer->destroy(this->peer);
+-			this->peer = identification_create_from_data(eap_id);
+-			DBG1(DBG_IKE, "received EAP identity '%Y'", this->peer);
++			identification_t *id;
++
++			id = identification_create_from_data(eap_id);
++			if (peer_auth && !id->equals(id, this->peer))
++			{
++				DBG1(DBG_IKE, "received tunneled EAP identity '%Y', keeping "
++					 "certificate-authenticated identity '%Y'", id, this->peer);
++				id->destroy(id);
++			}
++			else
++			{
++				DBG1(DBG_IKE, "received EAP identity '%Y'", id);
++				this->auth->add(this->auth, AUTH_RULE_EAP_IDENTITY, id);
++				this->peer->destroy(this->peer);
++				this->peer = id->clone(id);
++			}
+ 		}
+ 
+ 		in->destroy(in);
+@@ -281,10 +319,9 @@ METHOD(tls_application_t, process, status_t,
+ 		this->ph2_method = NULL;
+ 
+ 		/* Start Phase 2 of EAP-PEAP authentication */
+-		if (lib->settings->get_bool(lib->settings,
+-					"%s.plugins.eap-peap.request_peer_auth", FALSE, lib->ns))
++		if (peer_auth)
+ 		{
+-			return start_phase2_tnc(this);
++			return start_phase2_tnc(this, EAP_TLS);
+ 		}
+ 		else
+ 		{
+@@ -305,11 +342,26 @@ METHOD(tls_application_t, process, status_t,
+ 	switch (status)
+ 	{
+ 		case SUCCESS:
++			if (this->ph2_method->get_auth)
++			{
++				identification_t *id;
++				auth_cfg_t *auth;
++
++				auth = this->ph2_method->get_auth(this->ph2_method);
++				id = auth->get(auth, AUTH_RULE_EAP_IDENTITY);
++				if (id)
++				{
++					this->peer->destroy(this->peer);
++					this->peer = id->clone(id);
++				}
++				this->auth->merge(this->auth, auth, FALSE);
++			}
+ 			DBG1(DBG_IKE, "%N phase2 authentication of '%Y' with %N successful",
+ 							eap_type_names, EAP_PEAP, this->peer,
+ 							eap_type_names, type);
+ 			this->ph2_method->destroy(this->ph2_method);
+ 			this->ph2_method = NULL;
++			this->phase2_type = type;
+ 
+ 			/* EAP-PEAP requires the sending of an inner EAP_SUCCESS message */
+ 			this->phase2_result = EAP_SUCCESS;
+@@ -407,11 +459,18 @@ METHOD(eap_peap_server_t, set_tls, void,
+ 	this->tls = tls;
+ }
+ 
++METHOD(eap_peap_server_t, get_auth, auth_cfg_t*,
++	private_eap_peap_server_t *this)
++{
++	return this->auth;
++}
++
+ METHOD(tls_application_t, destroy, void,
+ 	private_eap_peap_server_t *this)
+ {
+ 	this->server->destroy(this->server);
+ 	this->peer->destroy(this->peer);
++	this->auth->destroy(this->auth);
+ 	DESTROY_IF(this->ph2_method);
+ 	DESTROY_IF(this->out);
+ 	this->avp->destroy(this->avp);
+@@ -435,10 +494,12 @@ eap_peap_server_t *eap_peap_server_create(identification_t *server,
+ 				.destroy = _destroy,
+ 			},
+ 			.set_tls = _set_tls,
++			.get_auth = _get_auth,
+ 		},
+ 		.server = server->clone(server),
+ 		.peer = peer->clone(peer),
+ 		.ph1_method = eap_method,
++		.auth = auth_cfg_create(),
+ 		.start_phase2 = TRUE,
+ 		.start_phase2_tnc = TRUE,
+ 		.start_phase2_id = lib->settings->get_bool(lib->settings,
+diff --git a/src/libcharon/plugins/eap_peap/eap_peap_server.h b/src/libcharon/plugins/eap_peap/eap_peap_server.h
+index 3abe88b..8080e9f 100644
+--- a/src/libcharon/plugins/eap_peap/eap_peap_server.h
++++ b/src/libcharon/plugins/eap_peap/eap_peap_server.h
+@@ -47,6 +47,13 @@ struct eap_peap_server_t {
+ 	 * @param tls		TLS connection
+ 	 */
+ 	void (*set_tls)(eap_peap_server_t *this, tls_t *tls);
++
++	/**
++	 * Get authentication details of this EAP method and its inner method(s).
++	 *
++	 * @return				auth method, internal data
++	 */
++	auth_cfg_t *(*get_auth)(eap_peap_server_t *this);
+ };
+ 
+ /**
+diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls.c b/src/libcharon/plugins/eap_ttls/eap_ttls.c
+index d8ad781..3df78bb 100644
+--- a/src/libcharon/plugins/eap_ttls/eap_ttls.c
++++ b/src/libcharon/plugins/eap_ttls/eap_ttls.c
+@@ -40,6 +40,25 @@ struct private_eap_ttls_t {
+ 	 * TLS stack, wrapped by EAP helper
+ 	 */
+ 	tls_eap_t *tls_eap;
++
++	/**
++	 * Role
++	 */
++	bool is_server;
++
++	/**
++	 * Actual server/client implementation
++	 */
++	union {
++		tls_application_t *application;
++		eap_ttls_server_t *server;
++		eap_ttls_peer_t *client;
++	} impl;
++
++	/**
++	 * Cached auth data for TLS and inner EAP methods
++	 */
++	auth_cfg_t *auth;
+ };
+ 
+ /** Maximum number of EAP-TTLS messages/fragments allowed */
+@@ -116,13 +135,31 @@ METHOD(eap_method_t, is_mutual, bool,
+ METHOD(eap_method_t, get_auth, auth_cfg_t*,
+ 	private_eap_ttls_t *this)
+ {
+-	return this->tls_eap->get_auth(this->tls_eap);
++	if (!this->auth)
++	{
++		auth_cfg_t *inner;
++
++		this->auth = auth_cfg_create();
++		this->auth->merge(this->auth,
++						  this->tls_eap->get_auth(this->tls_eap), FALSE);
++		if (this->is_server)
++		{
++			inner = this->impl.server->get_auth(this->impl.server);
++		}
++		else
++		{
++			inner = this->impl.client->get_auth(this->impl.client);
++		}
++		this->auth->merge(this->auth, inner, FALSE);
++	}
++	return this->auth;
+ }
+ 
+ METHOD(eap_method_t, destroy, void,
+ 	private_eap_ttls_t *this)
+ {
+ 	this->tls_eap->destroy(this->tls_eap);
++	DESTROY_IF(this->auth);
+ 	free(this);
+ }
+ 
+@@ -153,6 +190,10 @@ static eap_ttls_t *eap_ttls_create(identification_t *server,
+ 				.destroy = _destroy,
+ 			},
+ 		},
++		.is_server = is_server,
++		.impl = {
++			.application = application,
++		},
+ 	);
+ 	if (is_server && !lib->settings->get_bool(lib->settings,
+ 								"%s.plugins.eap-ttls.request_peer_auth", FALSE,
+diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c
+index 63126a5..f8229f5 100644
+--- a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c
++++ b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c
+@@ -54,6 +54,11 @@ struct private_eap_ttls_peer_t {
+ 	 */
+ 	eap_method_t *method;
+ 
++	/**
++	 * Auth data for phase 2 method
++	 */
++	auth_cfg_t *auth;
++
+ 	/**
+      * Pending outbound EAP message
+ 	 */
+@@ -215,6 +220,11 @@ METHOD(tls_application_t, process, status_t,
+ 	switch (status)
+ 	{
+ 		case SUCCESS:
++			if (this->method->get_auth)
++			{
++				this->auth->merge(this->auth,
++								  this->method->get_auth(this->method), FALSE);
++			}
+ 			this->method->destroy(this->method);
+ 			this->method = NULL;
+ 			/* fall through to NEED_MORE */
+@@ -275,11 +285,18 @@ METHOD(tls_application_t, build, status_t,
+ 	return INVALID_STATE;
+ }
+ 
++METHOD(eap_ttls_peer_t, get_auth, auth_cfg_t*,
++	private_eap_ttls_peer_t *this)
++{
++	return this->auth;
++}
++
+ METHOD(tls_application_t, destroy, void,
+ 	private_eap_ttls_peer_t *this)
+ {
+ 	this->server->destroy(this->server);
+ 	this->peer->destroy(this->peer);
++	this->auth->destroy(this->auth);
+ 	DESTROY_IF(this->method);
+ 	DESTROY_IF(this->out);
+ 	this->avp->destroy(this->avp);
+@@ -301,10 +318,12 @@ eap_ttls_peer_t *eap_ttls_peer_create(identification_t *server,
+ 				.build = _build,
+ 				.destroy = _destroy,
+ 			},
++			.get_auth = _get_auth,
+ 		},
+ 		.server = server->clone(server),
+ 		.peer = peer->clone(peer),
+ 		.start_phase2 = TRUE,
++		.auth = auth_cfg_create(),
+ 		.avp = eap_ttls_avp_create(),
+ 	);
+ 
+diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h
+index 0c3d90a..69a8435 100644
+--- a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h
++++ b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h
+@@ -37,6 +37,13 @@ struct eap_ttls_peer_t {
+ 	 * Implements the TLS application data handler.
+ 	 */
+ 	tls_application_t application;
++
++	/**
++	 * Get authentication details of this EAP method and its inner method(s).
++	 *
++	 * @return				auth method, internal data
++	 */
++	auth_cfg_t *(*get_auth)(eap_ttls_peer_t *this);
+ };
+ 
+ /**
+diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls_server.c b/src/libcharon/plugins/eap_ttls/eap_ttls_server.c
+index fc97f81..e1de1bf 100644
+--- a/src/libcharon/plugins/eap_ttls/eap_ttls_server.c
++++ b/src/libcharon/plugins/eap_ttls/eap_ttls_server.c
+@@ -60,6 +60,11 @@ struct private_eap_ttls_server_t {
+ 	 */
+ 	eap_method_t *method;
+ 
++	/**
++	 * Auth data for phase 2 method
++	 */
++	auth_cfg_t *auth;
++
+ 	/**
+      * Pending outbound EAP message
+ 	 */
+@@ -220,6 +225,10 @@ METHOD(tls_application_t, process, status_t,
+ 	if (!received_vendor && received_type == EAP_IDENTITY)
+ 	{
+ 		chunk_t eap_id;
++		bool peer_auth;
++
++		peer_auth = lib->settings->get_bool(lib->settings,
++					"%s.plugins.eap-ttls.request_peer_auth", FALSE, lib->ns);
+ 
+ 		if (this->method == NULL)
+ 		{
+@@ -244,9 +253,22 @@ METHOD(tls_application_t, process, status_t,
+ 
+ 		if (this->method->get_msk(this->method, &eap_id) == SUCCESS)
+ 		{
+-			this->peer->destroy(this->peer);
+-			this->peer = identification_create_from_data(eap_id);
+-			DBG1(DBG_IKE, "received EAP identity '%Y'", this->peer);
++			identification_t *id;
++
++			id = identification_create_from_data(eap_id);
++			if (peer_auth && !id->equals(id, this->peer))
++			{
++				DBG1(DBG_IKE, "received tunneled EAP identity '%Y', keeping "
++					 "certificate-authenticated identity '%Y'", id, this->peer);
++				id->destroy(id);
++			}
++			else
++			{
++				DBG1(DBG_IKE, "received EAP identity '%Y'", id);
++				this->auth->add(this->auth, AUTH_RULE_EAP_IDENTITY, id);
++				this->peer->destroy(this->peer);
++				this->peer = id->clone(id);
++			}
+ 		}
+ 
+ 		in->destroy(in);
+@@ -254,8 +276,7 @@ METHOD(tls_application_t, process, status_t,
+ 		this->method = NULL;
+ 
+ 		/* Start Phase 2 of EAP-TTLS authentication */
+-		if (lib->settings->get_bool(lib->settings,
+-					"%s.plugins.eap-ttls.request_peer_auth", FALSE, lib->ns))
++		if (peer_auth)
+ 		{
+ 			return start_phase2_tnc(this, EAP_TLS);
+ 		}
+@@ -278,6 +299,20 @@ METHOD(tls_application_t, process, status_t,
+ 	switch (status)
+ 	{
+ 		case SUCCESS:
++			if (this->method->get_auth)
++			{
++				identification_t *id;
++				auth_cfg_t *auth;
++
++				auth = this->method->get_auth(this->method);
++				id = auth->get(auth, AUTH_RULE_EAP_IDENTITY);
++				if (id)
++				{
++					this->peer->destroy(this->peer);
++					this->peer = id->clone(id);
++				}
++				this->auth->merge(this->auth, auth, FALSE);
++			}
+ 			DBG1(DBG_IKE, "%N phase2 authentication of '%Y' with %N successful",
+ 							eap_type_names, EAP_TTLS, this->peer,
+ 							eap_type_names, type);
+@@ -348,11 +383,18 @@ METHOD(tls_application_t, build, status_t,
+ 	return INVALID_STATE;
+ }
+ 
++METHOD(eap_ttls_server_t, get_auth, auth_cfg_t*,
++	private_eap_ttls_server_t *this)
++{
++	return this->auth;
++}
++
+ METHOD(tls_application_t, destroy, void,
+ 	private_eap_ttls_server_t *this)
+ {
+ 	this->server->destroy(this->server);
+ 	this->peer->destroy(this->peer);
++	this->auth->destroy(this->auth);
+ 	DESTROY_IF(this->method);
+ 	DESTROY_IF(this->out);
+ 	this->avp->destroy(this->avp);
+@@ -374,11 +416,13 @@ eap_ttls_server_t *eap_ttls_server_create(identification_t *server,
+ 				.build = _build,
+ 				.destroy = _destroy,
+ 			},
++			.get_auth = _get_auth,
+ 		},
+ 		.server = server->clone(server),
+-		.peer = peer->clone(peer),
++		.auth = auth_cfg_create(),
+ 		.start_phase2 = TRUE,
+ 		.start_phase2_tnc = TRUE,
++		.peer = peer->clone(peer),
+ 		.avp = eap_ttls_avp_create(),
+ 	);
+ 
+diff --git a/src/libcharon/plugins/eap_ttls/eap_ttls_server.h b/src/libcharon/plugins/eap_ttls/eap_ttls_server.h
+index 1e13f55..3348706 100644
+--- a/src/libcharon/plugins/eap_ttls/eap_ttls_server.h
++++ b/src/libcharon/plugins/eap_ttls/eap_ttls_server.h
+@@ -37,6 +37,13 @@ struct eap_ttls_server_t {
+ 	 * Implements the TLS application data handler.
+ 	 */
+ 	tls_application_t application;
++
++	/**
++	 * Get authentication details of this EAP method and its inner method(s).
++	 *
++	 * @return				auth method, internal data
++	 */
++	auth_cfg_t *(*get_auth)(eap_ttls_server_t *this);
+ };
+ 
+ /**


=====================================
debian/patches/0028-ikev2-Properly-reject-CREATE_CHILD_SA-requests-on-un.patch
=====================================
@@ -0,0 +1,66 @@
+From: Tobias Brunner <tobias at strongswan.org>
+Date: Mon, 27 Jul 2026 08:53:50 +0200
+Subject: ikev2: Properly reject CREATE_CHILD_SA requests on unestablished
+ IKE_SAs
+
+The previous check was not actually enforced as long as there were still
+tasks in the passive queue (it was originally added to fix an issue on
+initiators, so the passive queue was expected to be empty).  This allowed
+an unauthenticated attacker to potentially establish a usable Child SA
+if certain preconditions were met.
+
+First, it required that the initiator is authenticated with EAP so the
+authentication and the creation of the first Child SA is deferred.
+Second, the responder must either not configure an IP address pool or
+an explicit remote TS, otherwise, traffic selector negotiation fails.
+
+Note that the half-open IKE SA and the installed IPsec SA will be removed
+after the default timeout of 30 seconds.
+
+Fixes: 8503077175cd ("ikev2: Reject CREATE_CHILD_SA exchange on unestablished IKE_SAs")
+Fixes: c60c7694d2d8 ("merged tasking branch into trunk")
+Fixes: CVE-2026-78135
+---
+ src/libcharon/sa/ikev2/task_manager_v2.c | 21 +++++++++++----------
+ 1 file changed, 11 insertions(+), 10 deletions(-)
+
+diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c
+index 85aaa24..845fb9f 100644
+--- a/src/libcharon/sa/ikev2/task_manager_v2.c
++++ b/src/libcharon/sa/ikev2/task_manager_v2.c
+@@ -1134,9 +1134,18 @@ static status_t process_request(private_task_manager_t *this,
+ 	delete_payload_t *delete;
+ 	ike_sa_state_t state;
+ 
++	state = this->ike_sa->get_state(this->ike_sa);
++	if (message->get_exchange_type(message) == CREATE_CHILD_SA &&
++		(state == IKE_CREATED || state == IKE_CONNECTING))
++	{
++		DBG1(DBG_IKE, "received CREATE_CHILD_SA request for "
++			 "unestablished IKE_SA, rejected");
++		return FAILED;
++	}
++
++	/* create tasks depending on request type, if not already some queued */
+ 	if (array_count(this->passive_tasks) == 0)
+-	{	/* create tasks depending on request type, if not already some queued */
+-		state = this->ike_sa->get_state(this->ike_sa);
++	{
+ 		switch (message->get_exchange_type(message))
+ 		{
+ 			case IKE_SA_INIT:
+@@ -1177,14 +1186,6 @@ static status_t process_request(private_task_manager_t *this,
+ 			{	/* FIXME: we should prevent this on mediation connections */
+ 				bool notify_found = FALSE, ts_found = FALSE;
+ 
+-				if (state == IKE_CREATED ||
+-					state == IKE_CONNECTING)
+-				{
+-					DBG1(DBG_IKE, "received CREATE_CHILD_SA request for "
+-						 "unestablished IKE_SA, rejected");
+-					return FAILED;
+-				}
+-
+ 				enumerator = message->create_payload_enumerator(message);
+ 				while (enumerator->enumerate(enumerator, &payload))
+ 				{


=====================================
debian/patches/series
=====================================
@@ -15,3 +15,14 @@ dont-load-kernel-libipsec-plugin-by-default.patch
 0015-constraints-Case-insensitive-matching-and-reject-exc.patch
 0016-gmp-Avoid-crash-and-timing-leaks-in-PKCS-1-v1.5-decr.patch
 0017-identification-Fix-double-free-when-cloning-empty-ID.patch
+0018-openssl-Fix-undefined-memory-access-when-verifying-P.patch
+0019-openssl-Fix-memory-leaks-after-enumerating-certifica.patch
+0020-eap-aka-Only-accept-AKA-Synchronization-Failure-if-e.patch
+0021-message-Avoid-memory-leak-if-string-buffer-for-messa.patch
+0022-pkcs5-Validate-parsed-parameters-to-avoid-DoS-attack.patch
+0023-x509-Avoid-NULL-pointer-dereference-if-issuerName-is.patch
+0024-x509-Fix-memory-leaks-when-parsing-attribute-certifi.patch
+0025-x509-Prevent-infinite-loop-when-parsing-ietfAttrSynt.patch
+0026-ikev2-Prevent-use-after-free-during-collision-after-.patch
+0027-eap-ttls-peap-Return-auth-cfg-with-details-on-TLS-an.patch
+0028-ikev2-Properly-reject-CREATE_CHILD_SA-requests-on-un.patch



View it on GitLab: https://salsa.debian.org/debian/strongswan/-/compare/50df14e46f3e3224916aa9244d8bc65b0f477608...fbf0ff6eeca42df94424129bf6a2b970565f1089

-- 
View it on GitLab: https://salsa.debian.org/debian/strongswan/-/compare/50df14e46f3e3224916aa9244d8bc65b0f477608...fbf0ff6eeca42df94424129bf6a2b970565f1089
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-swan-devel/attachments/20260907/604b56ea/attachment-0001.htm>


More information about the Pkg-swan-devel mailing list