From e88231ad830fd70bb86bdad6c0eb0db550d6e9b8 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 4778a0977666..0bf2e55a8a5c 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap.c +++ b/src/libcharon/plugins/eap_peap/eap_peap.c @@ -38,6 +38,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 */ @@ -111,10 +130,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); } @@ -133,6 +176,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, @@ -145,7 +189,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) @@ -155,6 +199,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 2668ac4324f0..5f505a6d1796 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_peer.c +++ b/src/libcharon/plugins/eap_peap/eap_peap_peer.c @@ -51,6 +51,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 */ @@ -165,6 +170,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 */ @@ -219,11 +230,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); @@ -246,10 +264,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 196d4e2c4b9e..1e2fb9add8ad 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_peer.h +++ b/src/libcharon/plugins/eap_peap/eap_peap_peer.h @@ -37,6 +37,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 7f8348e06639..547dd6c6f54f 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_server.c +++ b/src/libcharon/plugins/eap_peap/eap_peap_server.c @@ -19,6 +19,8 @@ #include #include +#include + typedef struct private_eap_peap_server_t private_eap_peap_server_t; /** @@ -71,6 +73,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 */ @@ -126,8 +138,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)) { @@ -139,6 +154,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 */ @@ -212,9 +229,13 @@ METHOD(tls_application_t, process, status_t, DBG1(DBG_IKE, "received tunneled EAP-PEAP AVP [EAP/%N]", eap_code_short_names, code); - /* 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) @@ -239,6 +260,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) { @@ -263,9 +288,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); @@ -273,10 +311,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 { @@ -297,11 +334,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; @@ -390,11 +442,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); @@ -417,10 +476,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 4585a622ac33..1f5d5d71d841 100644 --- a/src/libcharon/plugins/eap_peap/eap_peap_server.h +++ b/src/libcharon/plugins/eap_peap/eap_peap_server.h @@ -37,6 +37,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 9987c43d4b93..e2f4c90e1c07 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls.c +++ b/src/libcharon/plugins/eap_ttls/eap_ttls.c @@ -38,6 +38,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 */ @@ -114,13 +133,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); } @@ -151,6 +188,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 be6a0812ea1f..0fcc26784672 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c +++ b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.c @@ -53,6 +53,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 */ @@ -214,6 +219,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 31fc0d9db196..b30e485332bd 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h +++ b/src/libcharon/plugins/eap_ttls/eap_ttls_peer.h @@ -36,6 +36,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 9d145ea91142..430c85fbbc3f 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls_server.c +++ b/src/libcharon/plugins/eap_ttls/eap_ttls_server.c @@ -59,6 +59,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 */ @@ -219,6 +224,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) { @@ -243,9 +252,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); @@ -253,8 +275,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); } @@ -277,6 +298,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); @@ -347,11 +382,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); @@ -373,11 +415,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 a66a813ec538..d4dd711ed635 100644 --- a/src/libcharon/plugins/eap_ttls/eap_ttls_server.h +++ b/src/libcharon/plugins/eap_ttls/eap_ttls_server.h @@ -36,6 +36,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