From: Tobias Brunner Date: Tue, 4 Mar 2025 10:14:14 +0000 (+0100) Subject: ikev2: Add support to switch peer configs based on EAP-Identities X-Git-Tag: 6.0.2dr1~28 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2f2e4abe3c5256d13195fdd0b90538ada4aa9928;p=thirdparty%2Fstrongswan.git ikev2: Add support to switch peer configs based on EAP-Identities This changes how EAP identities are used from the config. Instead of setting a statically configured identity != %any, an EAP-Identity exchange is now always initiated (and required). If the received identity doesn't match, the peer config is switched to one with a matching identity (wildcards are supported for that match). This allows switching to a config with a different EAP method or child settings based on the EAP identity. There is currently no "best" match. The configs are evaluated based on the order returned from the initial peer config lookup. References strongswan/strongswan#2702 --- diff --git a/src/libcharon/sa/authenticator.h b/src/libcharon/sa/authenticator.h index 0e05f7435f..51aba9848a 100644 --- a/src/libcharon/sa/authenticator.h +++ b/src/libcharon/sa/authenticator.h @@ -143,6 +143,7 @@ struct authenticator_t { * - SUCCESS if authentication successful * - FAILED if authentication failed * - NEED_MORE if another exchange required + * - INVALID_ARG if EAP-Identity doesn't match */ status_t (*process)(authenticator_t *this, message_t *message); diff --git a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c index 69e2ae0d79..a70351827a 100644 --- a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c @@ -171,28 +171,21 @@ static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, id = auth->get(auth, AUTH_RULE_EAP_IDENTITY); if (id) { - if (id->get_type(id) == ID_ANY) + this->method = load_method(this, EAP_IDENTITY, 0, EAP_SERVER); + if (this->method) { - this->method = load_method(this, EAP_IDENTITY, 0, EAP_SERVER); - if (this->method) + if (this->method->initiate(this->method, &out) == NEED_MORE) { - if (this->method->initiate(this->method, &out) == NEED_MORE) - { - DBG1(DBG_IKE, "initiating %N method (id 0x%02X)", - eap_type_names, EAP_IDENTITY, - this->method->get_identifier(this->method)); - return out; - } - this->method->destroy(this->method); + DBG1(DBG_IKE, "initiating %N method (id 0x%02X)", + eap_type_names, EAP_IDENTITY, + this->method->get_identifier(this->method)); + return out; } - DBG1(DBG_IKE, "EAP-Identity request configured, " - "but not supported"); - } - else - { - DBG1(DBG_IKE, "using configured EAP-Identity %Y", id); - this->eap_identity = id->clone(id); + this->method->destroy(this->method); } + DBG1(DBG_IKE, "EAP-Identity request configured, " + "but not supported"); + return eap_payload_create_code(EAP_FAILURE, 0); } } /* invoke real EAP method */ @@ -234,20 +227,29 @@ static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, } /** - * Replace the existing EAP-Identity in other auth config + * Replaces the existing EAP-Identity in other auth config and checks if it + * matches the configured identity. */ -static void replace_eap_identity(private_eap_authenticator_t *this) +static bool apply_eap_identity(private_eap_authenticator_t *this, + identification_t *eap_identity) { - identification_t *eap_identity; + identification_t *configured; auth_cfg_t *cfg; + bool match; + + DBG1(DBG_IKE, "received EAP identity '%Y'", eap_identity); + this->eap_identity = eap_identity; - eap_identity = this->eap_identity->clone(this->eap_identity); cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); - cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, eap_identity); + configured = cfg->get(cfg, AUTH_RULE_EAP_IDENTITY); + match = eap_identity->matches(eap_identity, configured) != ID_MATCH_NONE; + cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, eap_identity->clone(eap_identity)); + return match; } /** - * Handle EAP exchange as server + * Handle EAP exchange as server. Returns an EAP payload, or NULL if the EAP + * Identity doesn't match. */ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, eap_payload_t *in) @@ -300,14 +302,24 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, { chunk_t data; - if (this->method->get_msk(this->method, &data) == SUCCESS) + if (this->method->get_msk(this->method, &data) != SUCCESS) + { + DBG1(DBG_IKE, "client did not send an EAP-Identity, " + "sending %N", eap_code_names, EAP_FAILURE); + return eap_payload_create_code(EAP_FAILURE, + in->get_identifier(in)); + } + /* apply the received EAP identity and match it against config, + * return NULL if it doesn't match to possibly switch to a + * different config */ + if (!apply_eap_identity(this, + identification_create_from_data(data))) { - this->eap_identity = identification_create_from_data(data); - DBG1(DBG_IKE, "received EAP identity '%Y'", - this->eap_identity); - replace_eap_identity(this); + this->method->destroy(this->method); + this->method = NULL; + return NULL; } - /* restart EAP exchange, but with real method */ + /* ID matched, restart EAP exchange, but with real method */ this->method->destroy(this->method); return server_initiate_eap(this, FALSE); } @@ -608,6 +620,12 @@ METHOD(authenticator_t, process_server, status_t, return FAILED; } this->eap_payload = server_process_eap(this, eap_payload); + if (!this->eap_payload) + { + /* try to switch to a different config in case the EAP identity + * doesn't match */ + return INVALID_ARG; + } } return NEED_MORE; } diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c index 80c0f47d3d..19ecad922d 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_auth.c +++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c @@ -511,6 +511,121 @@ static bool update_cfg_candidates(private_ike_auth_t *this, bool strict) return this->peer_cfg != NULL; } +/** + * Check if the given auth config is for EAP. + */ +static bool is_eap_cfg(auth_cfg_t *cfg) +{ + return cfg && + ((uintptr_t)cfg->get(cfg, AUTH_RULE_EAP_TYPE) != EAP_NAK || + (uintptr_t)cfg->get(cfg, AUTH_RULE_EAP_VENDOR) != 0); +} + +/** + * Switch configurations until we find an alternative with EAP authentication. + */ +static bool find_eap_cfg(private_ike_auth_t *this, auth_cfg_t **cand) +{ + do + { + if (!is_eap_cfg(*cand)) + { + DBG1(DBG_IKE, "peer requested EAP, config unacceptable"); + } + this->peer_cfg->destroy(this->peer_cfg); + this->peer_cfg = NULL; + if (!update_cfg_candidates(this, FALSE)) + { + return FALSE; + } + *cand = get_auth_cfg(this, FALSE); + } + while (!is_eap_cfg(*cand)); + + return TRUE; +} + +/** + * Copy EAP-specific rules to another auth config. Copying the EAP-Identity is + * optional. + */ +static void copy_eap_cfg(auth_cfg_t *src, auth_cfg_t *dst, bool copy_eap_id) +{ + identification_t *id; + + /* copy over the EAP specific rules for authentication */ + dst->add(dst, AUTH_RULE_EAP_TYPE, + src->get(src, AUTH_RULE_EAP_TYPE)); + dst->add(dst, AUTH_RULE_EAP_VENDOR, + src->get(src, AUTH_RULE_EAP_VENDOR)); + id = (identification_t*)src->get(src, AUTH_RULE_AAA_IDENTITY); + if (id) + { + dst->add(dst, AUTH_RULE_AAA_IDENTITY, id->clone(id)); + } + if (copy_eap_id) + { + id = (identification_t*)src->get(src, AUTH_RULE_EAP_IDENTITY); + if (id) + { + dst->add(dst, AUTH_RULE_EAP_IDENTITY, id->clone(id)); + } + } +} + +/** + * Find an alternative EAP config that matches the EAP-Identity we received. + */ +static bool find_alternative_eap_cfg(private_ike_auth_t *this) +{ + auth_cfg_t *cfg, *cand; + identification_t *eap_id, *id; + + /* clear current auth round, but copy IKE and EAP identities we received + * from the peer */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + eap_id = cfg->get(cfg, AUTH_RULE_EAP_IDENTITY); + eap_id = eap_id->clone(eap_id); + id = cfg->get(cfg, AUTH_RULE_IDENTITY); + id = id->clone(id); + + cfg->purge(cfg, FALSE); + cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, eap_id); + cfg->add(cfg, AUTH_RULE_IDENTITY, id); + + cand = get_auth_cfg(this, FALSE); + while (TRUE) + { + /* the log messages here are similar to those in auth_cfg_t */ + id = cand->get(cand, AUTH_RULE_EAP_IDENTITY); + if (!id) + { + DBG1(DBG_CFG, "constraint check failed: no EAP identity required, " + "but '%Y' (%N) received", + eap_id, id_type_names, eap_id->get_type(eap_id)); + } + else if (!eap_id->matches(eap_id, id)) + { + DBG1(DBG_CFG, "constraint check failed: EAP identity '%Y'" + " (%N) required, not matched by '%Y' (%N)", + id, id_type_names, id->get_type(id), + eap_id, id_type_names, eap_id->get_type(eap_id)); + } + else + { + break; + } + if (!find_eap_cfg(this, &cand)) + { + return FALSE; + } + } + /* copy over the EAP-specific rules for the alternative config, except + * the identity we already received from the peer */ + copy_eap_cfg(cand, cfg, FALSE); + return TRUE; +} + /** * Currently defined PPK_ID types */ @@ -950,37 +1065,15 @@ METHOD(task_t, process_r, status_t, } } if (!message->get_payload(message, PLV2_AUTH)) - { /* before authenticating with EAP, we need a EAP config */ + { + /* before authenticating with EAP, we need an EAP config */ cand = get_auth_cfg(this, FALSE); - while (!cand || ( - (uintptr_t)cand->get(cand, AUTH_RULE_EAP_TYPE) == EAP_NAK && - (uintptr_t)cand->get(cand, AUTH_RULE_EAP_VENDOR) == 0)) - { /* peer requested EAP, but current config does not match */ - DBG1(DBG_IKE, "peer requested EAP, config unacceptable"); - this->peer_cfg->destroy(this->peer_cfg); - this->peer_cfg = NULL; - if (!update_cfg_candidates(this, FALSE)) - { - this->authentication_failed = TRUE; - return NEED_MORE; - } - cand = get_auth_cfg(this, FALSE); - } - /* copy over the EAP specific rules for authentication */ - cfg->add(cfg, AUTH_RULE_EAP_TYPE, - cand->get(cand, AUTH_RULE_EAP_TYPE)); - cfg->add(cfg, AUTH_RULE_EAP_VENDOR, - cand->get(cand, AUTH_RULE_EAP_VENDOR)); - id = (identification_t*)cand->get(cand, AUTH_RULE_EAP_IDENTITY); - if (id) - { - cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, id->clone(id)); - } - id = (identification_t*)cand->get(cand, AUTH_RULE_AAA_IDENTITY); - if (id) + if (!is_eap_cfg(cand) && !find_eap_cfg(this, &cand)) { - cfg->add(cfg, AUTH_RULE_AAA_IDENTITY, id->clone(id)); + this->authentication_failed = TRUE; + return NEED_MORE; } + copy_eap_cfg(cand, cfg, TRUE); } /* verify authentication data */ @@ -1012,6 +1105,8 @@ METHOD(task_t, process_r, status_t, this->other_auth->use_ppk(this->other_auth, this->ppk, FALSE); } } + +retry_eap_auth: switch (this->other_auth->process(this->other_auth, message)) { case SUCCESS: @@ -1024,6 +1119,13 @@ METHOD(task_t, process_r, status_t, break; } return NEED_MORE; + case INVALID_ARG: + /* EAP-Identity didn't match, try to find an alternative config */ + if (find_alternative_eap_cfg(this)) + { + goto retry_eap_auth; + } + /* fall-through */ default: this->authentication_failed = TRUE; return NEED_MORE; diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index c71baef300..6ed5de41d6 100644 --- a/src/swanctl/swanctl.opt +++ b/src/swanctl/swanctl.opt @@ -535,10 +535,12 @@ connections..remote.id = %any specifies how RDNs are matched. connections..remote.eap_id = id - Identity to use as peer identity during EAP authentication. + Use EAP-Identity method to request an identity from the client to match + against and use during EAP authentication. - Identity to use as peer identity during EAP authentication. If set to _%any_ - the EAP-Identity method will be used to ask the client for an identity. + Use EAP-Identity method to request an identity from the client to match + against and use during EAP authentication. There is currently no "best" + match, configs are matched in the order they are loaded. connections..remote.groups = Authorization group memberships to require.