diff --git a/src/libcharon/sa/authenticator.h b/src/libcharon/sa/authenticator.h index 0e05f7435..51aba9848 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 69e2ae0d7..a70351827 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) { - 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; } diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c index 80c0f47d3..19ecad922 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) + 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; diff --git a/src/swanctl/swanctl.opt b/src/swanctl/swanctl.opt index c71baef30..6ed5de41d 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.