]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
ikev2: Add support to switch peer configs based on EAP-Identities
authorTobias Brunner <tobias@strongswan.org>
Tue, 4 Mar 2025 10:14:14 +0000 (11:14 +0100)
committerTobias Brunner <tobias@strongswan.org>
Mon, 14 Apr 2025 10:05:24 +0000 (12:05 +0200)
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

src/libcharon/sa/authenticator.h
src/libcharon/sa/ikev2/authenticators/eap_authenticator.c
src/libcharon/sa/ikev2/tasks/ike_auth.c
src/swanctl/swanctl.opt

index 0e05f7435f2e64c3d57d55ab9fccb3fdb792fbcc..51aba9848a6773854faf8232fe7c298d22120292 100644 (file)
@@ -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);
 
index 69e2ae0d795780dc05064ffbf7c567cc2811c634..a70351827aa52ddc7aa94aba4eb132b661097680 100644 (file)
@@ -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;
 }
index 80c0f47d3d6496ccc55093f2c604b85f917313af..19ecad922d84ef7a220bc70211f058593f0ecc47 100644 (file)
@@ -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;
index c71baef3008986f47693e3ebab03d8125a68bc25..6ed5de41d6c829d074fb289332ae4115ecc3100a 100644 (file)
@@ -535,10 +535,12 @@ connections.<conn>.remote<suffix>.id = %any
        specifies how RDNs are matched.
 
 connections.<conn>.remote<suffix>.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.<conn>.remote<suffix>.groups =
        Authorization group memberships to require.