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
This commit is contained in:
Tobias Brunner
2025-04-14 12:05:24 +02:00
parent 651a5b0ded
commit 2f2e4abe3c
4 changed files with 184 additions and 61 deletions
+1
View 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);
@@ -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)
{
this->eap_identity = identification_create_from_data(data);
DBG1(DBG_IKE, "received EAP identity '%Y'",
this->eap_identity);
replace_eap_identity(this);
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));
}
/* restart EAP exchange, but with real method */
/* 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->method->destroy(this->method);
this->method = NULL;
return NULL;
}
/* 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;
}
+130 -28
View 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)
if (!is_eap_cfg(cand) && !find_eap_cfg(this, &cand))
{
cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, id->clone(id));
}
id = (identification_t*)cand->get(cand, AUTH_RULE_AAA_IDENTITY);
if (id)
{
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;
+5 -3
View 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.