From 7926256cacd4526264107cc8198d16b452a28673 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 27 Jul 2026 15:05:45 +0200 Subject: [PATCH] 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 ++++++++++- .../plugins/eap_peap/eap_peap_peer.c | 20 +++++ .../plugins/eap_peap/eap_peap_peer.h | 7 ++ .../plugins/eap_peap/eap_peap_server.c | 81 ++++++++++++++++--- .../plugins/eap_peap/eap_peap_server.h | 7 ++ src/libcharon/plugins/eap_ttls/eap_ttls.c | 43 +++++++++- .../plugins/eap_ttls/eap_ttls_peer.c | 19 +++++ .../plugins/eap_ttls/eap_ttls_peer.h | 7 ++ .../plugins/eap_ttls/eap_ttls_server.c | 56 +++++++++++-- .../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 577d7478676c..e6c03b5e9861 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 eed40c0a7aca..d41d9eaf3bf1 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 53c25cdd6bdf..7d169574321e 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 c5d97a16a170..0deb91324c3c 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 #include +#include + typedef struct private_eap_peap_server_t private_eap_peap_server_t; /** @@ -72,6 +74,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 */ @@ -127,8 +139,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)) { @@ -140,6 +155,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 */ @@ -213,9 +230,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) @@ -240,6 +261,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) { @@ -266,9 +291,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); @@ -276,10 +314,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 { @@ -300,11 +337,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; @@ -393,11 +445,18 @@ METHOD(tls_application_t, build, status_t, return INVALID_STATE; } +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); @@ -420,10 +479,12 @@ eap_peap_server_t *eap_peap_server_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(), .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 8ec95f64b3f5..971c94a30039 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_server.h +++ b/src/libcharon/plugins/eap_peap/eap_peap_server.h @@ -38,6 +38,13 @@ struct eap_peap_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_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 d8ad781f0994..3df78bba4b58 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 4c8d33804413..5356c9cf63b9 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 */ @@ -276,11 +286,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); @@ -302,10 +319,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 0c3d90a45b7c..69a8435aefdb 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 d7279ce8c9b6..ab5b0c009a5e 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 1e13f55c4c28..3348706cf280 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); }; /** -- 2.43.0