diff options
Diffstat (limited to 'src/libcharon/sa/ikev2/tasks/ike_auth.c')
-rw-r--r-- | src/libcharon/sa/ikev2/tasks/ike_auth.c | 1098 |
1 files changed, 1098 insertions, 0 deletions
diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c new file mode 100644 index 000000000..183ca3440 --- /dev/null +++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2005-2009 Martin Willi + * Copyright (C) 2005 Jan Hutter + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details + */ + +#include "ike_auth.h" + +#include <string.h> + +#include <daemon.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/auth_payload.h> +#include <encoding/payloads/eap_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <sa/ikev2/authenticators/eap_authenticator.h> + +typedef struct private_ike_auth_t private_ike_auth_t; + +/** + * Private members of a ike_auth_t task. + */ +struct private_ike_auth_t { + + /** + * Public methods and task_t interface. + */ + ike_auth_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Nonce chosen by us in ike_init + */ + chunk_t my_nonce; + + /** + * Nonce chosen by peer in ike_init + */ + chunk_t other_nonce; + + /** + * IKE_SA_INIT message sent by us + */ + packet_t *my_packet; + + /** + * IKE_SA_INIT message sent by peer + */ + packet_t *other_packet; + + /** + * Reserved bytes of ID payload + */ + char reserved[3]; + + /** + * currently active authenticator, to authenticate us + */ + authenticator_t *my_auth; + + /** + * currently active authenticator, to authenticate peer + */ + authenticator_t *other_auth; + + /** + * peer_cfg candidates, ordered by priority + */ + linked_list_t *candidates; + + /** + * selected peer config (might change when using multiple authentications) + */ + peer_cfg_t *peer_cfg; + + /** + * have we planned an(other) authentication exchange? + */ + bool do_another_auth; + + /** + * has the peer announced another authentication exchange? + */ + bool expect_another_auth; + + /** + * should we send a AUTHENTICATION_FAILED notify? + */ + bool authentication_failed; + + /** + * received an INITIAL_CONTACT? + */ + bool initial_contact; +}; + +/** + * check if multiple authentication extension is enabled, configuration-wise + */ +static bool multiple_auth_enabled() +{ + return lib->settings->get_bool(lib->settings, + "charon.multiple_authentication", TRUE); +} + +/** + * collect the needed information in the IKE_SA_INIT exchange from our message + */ +static status_t collect_my_init_data(private_ike_auth_t *this, + message_t *message) +{ + nonce_payload_t *nonce; + + /* get the nonce that was generated in ike_init */ + nonce = (nonce_payload_t*)message->get_payload(message, NONCE); + if (nonce == NULL) + { + return FAILED; + } + this->my_nonce = nonce->get_nonce(nonce); + + /* pre-generate the message, keep a copy */ + if (this->ike_sa->generate_message(this->ike_sa, message, + &this->my_packet) != SUCCESS) + { + return FAILED; + } + return NEED_MORE; +} + +/** + * collect the needed information in the IKE_SA_INIT exchange from others message + */ +static status_t collect_other_init_data(private_ike_auth_t *this, + message_t *message) +{ + /* we collect the needed information in the IKE_SA_INIT exchange */ + nonce_payload_t *nonce; + + /* get the nonce that was generated in ike_init */ + nonce = (nonce_payload_t*)message->get_payload(message, NONCE); + if (nonce == NULL) + { + return FAILED; + } + this->other_nonce = nonce->get_nonce(nonce); + + /* keep a copy of the received packet */ + this->other_packet = message->get_packet(message); + return NEED_MORE; +} + +/** + * Get and store reserved bytes of id_payload, required for AUTH payload + */ +static void get_reserved_id_bytes(private_ike_auth_t *this, id_payload_t *id) +{ + u_int8_t *byte; + int i; + + for (i = 0; i < countof(this->reserved); i++) + { + byte = payload_get_field(&id->payload_interface, RESERVED_BYTE, i); + if (byte) + { + this->reserved[i] = *byte; + } + } +} + +/** + * Get the next authentication configuration + */ +static auth_cfg_t *get_auth_cfg(private_ike_auth_t *this, bool local) +{ + enumerator_t *e1, *e2; + auth_cfg_t *c1, *c2, *next = NULL; + + /* find an available config not already done */ + e1 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, local); + while (e1->enumerate(e1, &c1)) + { + bool found = FALSE; + + e2 = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, local); + while (e2->enumerate(e2, &c2)) + { + if (c2->complies(c2, c1, FALSE)) + { + found = TRUE; + break; + } + } + e2->destroy(e2); + if (!found) + { + next = c1; + break; + } + } + e1->destroy(e1); + return next; +} + +/** + * Check if we have should initiate another authentication round + */ +static bool do_another_auth(private_ike_auth_t *this) +{ + bool do_another = FALSE; + enumerator_t *done, *todo; + auth_cfg_t *done_cfg, *todo_cfg; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH)) + { + return FALSE; + } + + done = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, TRUE); + todo = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, TRUE); + while (todo->enumerate(todo, &todo_cfg)) + { + if (!done->enumerate(done, &done_cfg)) + { + done_cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + } + if (!done_cfg->complies(done_cfg, todo_cfg, FALSE)) + { + do_another = TRUE; + break; + } + } + done->destroy(done); + todo->destroy(todo); + return do_another; +} + +/** + * Get peer configuration candidates from backends + */ +static bool load_cfg_candidates(private_ike_auth_t *this) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + host_t *me, *other; + identification_t *my_id, *other_id; + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + my_id = this->ike_sa->get_my_id(this->ike_sa); + other_id = this->ike_sa->get_other_id(this->ike_sa); + + DBG1(DBG_CFG, "looking for peer configs matching %H[%Y]...%H[%Y]", + me, my_id, other, other_id); + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + me, other, my_id, other_id, IKEV2); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + peer_cfg->get_ref(peer_cfg); + if (this->peer_cfg == NULL) + { /* best match */ + this->peer_cfg = peer_cfg; + this->ike_sa->set_peer_cfg(this->ike_sa, peer_cfg); + } + else + { + this->candidates->insert_last(this->candidates, peer_cfg); + } + } + enumerator->destroy(enumerator); + if (this->peer_cfg) + { + DBG1(DBG_CFG, "selected peer config '%s'", + this->peer_cfg->get_name(this->peer_cfg)); + return TRUE; + } + DBG1(DBG_CFG, "no matching peer config found"); + return FALSE; +} + +/** + * update the current peer candidate if necessary, using candidates + */ +static bool update_cfg_candidates(private_ike_auth_t *this, bool strict) +{ + do + { + if (this->peer_cfg) + { + bool complies = TRUE; + enumerator_t *e1, *e2, *tmp; + auth_cfg_t *c1, *c2; + + e1 = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, FALSE); + e2 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, FALSE); + + if (strict) + { /* swap lists in strict mode: all configured rounds must be + * fulfilled. If !strict, we check only the rounds done so far. */ + tmp = e1; + e1 = e2; + e2 = tmp; + } + while (e1->enumerate(e1, &c1)) + { + /* check if done authentications comply to configured ones */ + if ((!e2->enumerate(e2, &c2)) || + (!strict && !c1->complies(c1, c2, TRUE)) || + (strict && !c2->complies(c2, c1, TRUE))) + { + complies = FALSE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + if (complies) + { + break; + } + DBG1(DBG_CFG, "selected peer config '%s' inacceptable", + this->peer_cfg->get_name(this->peer_cfg)); + this->peer_cfg->destroy(this->peer_cfg); + } + if (this->candidates->remove_first(this->candidates, + (void**)&this->peer_cfg) != SUCCESS) + { + DBG1(DBG_CFG, "no alternative config found"); + this->peer_cfg = NULL; + } + else + { + DBG1(DBG_CFG, "switching to peer config '%s'", + this->peer_cfg->get_name(this->peer_cfg)); + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + } + } + while (this->peer_cfg); + + return this->peer_cfg != NULL; +} + +METHOD(task_t, build_i, status_t, + private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_my_init_data(this, message); + } + + if (this->peer_cfg == NULL) + { + this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->peer_cfg->get_ref(this->peer_cfg); + } + + if (message->get_message_id(message) == 1) + { /* in the first IKE_AUTH ... */ + if (this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH)) + { /* indicate support for multiple authentication */ + message->add_notify(message, FALSE, MULTIPLE_AUTH_SUPPORTED, + chunk_empty); + } + /* indicate support for EAP-only authentication */ + message->add_notify(message, FALSE, EAP_ONLY_AUTHENTICATION, + chunk_empty); + } + + if (!this->do_another_auth && !this->my_auth) + { /* we have done our rounds */ + return NEED_MORE; + } + + /* check if an authenticator is in progress */ + if (this->my_auth == NULL) + { + identification_t *idi, *idr = NULL; + id_payload_t *id_payload; + + /* clean up authentication config from a previous round */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->purge(cfg, TRUE); + + /* add (optional) IDr */ + cfg = get_auth_cfg(this, FALSE); + if (cfg) + { + idr = cfg->get(cfg, AUTH_RULE_IDENTITY); + if (idr && !idr->contains_wildcards(idr)) + { + this->ike_sa->set_other_id(this->ike_sa, idr->clone(idr)); + id_payload = id_payload_create_from_identification( + ID_RESPONDER, idr); + message->add_payload(message, (payload_t*)id_payload); + } + } + /* add IDi */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->merge(cfg, get_auth_cfg(this, TRUE), TRUE); + idi = cfg->get(cfg, AUTH_RULE_IDENTITY); + if (!idi) + { + DBG1(DBG_CFG, "configuration misses IDi"); + return FAILED; + } + this->ike_sa->set_my_id(this->ike_sa, idi->clone(idi)); + id_payload = id_payload_create_from_identification(ID_INITIATOR, idi); + get_reserved_id_bytes(this, id_payload); + message->add_payload(message, (payload_t*)id_payload); + + if (idr && message->get_message_id(message) == 1 && + this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NO) + { + host_t *host; + + host = this->ike_sa->get_other_host(this->ike_sa); + if (!charon->ike_sa_manager->has_contact(charon->ike_sa_manager, + idi, idr, host->get_family(host))) + { + message->add_notify(message, FALSE, INITIAL_CONTACT, chunk_empty); + } + } + + /* build authentication data */ + this->my_auth = authenticator_create_builder(this->ike_sa, cfg, + this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet), + this->reserved); + if (!this->my_auth) + { + return FAILED; + } + } + switch (this->my_auth->build(this->my_auth, message)) + { + case SUCCESS: + /* authentication step complete, reset authenticator */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + break; + case NEED_MORE: + break; + default: + return FAILED; + } + + /* check for additional authentication rounds */ + if (do_another_auth(this)) + { + if (message->get_payload(message, AUTHENTICATION)) + { + message->add_notify(message, FALSE, ANOTHER_AUTH_FOLLOWS, chunk_empty); + } + } + else + { + this->do_another_auth = FALSE; + } + return NEED_MORE; +} + +METHOD(task_t, process_r, status_t, + private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg, *cand; + id_payload_t *id_payload; + identification_t *id; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + return collect_other_init_data(this, message); + } + + if (this->my_auth == NULL && this->do_another_auth) + { + /* handle (optional) IDr payload, apply proposed identity */ + id_payload = (id_payload_t*)message->get_payload(message, ID_RESPONDER); + if (id_payload) + { + id = id_payload->get_identification(id_payload); + } + else + { + id = identification_create_from_encoding(ID_ANY, chunk_empty); + } + this->ike_sa->set_my_id(this->ike_sa, id); + } + + if (!this->expect_another_auth) + { + return NEED_MORE; + } + + if (message->get_message_id(message) == 1) + { /* check for extensions in the first IKE_AUTH */ + if (message->get_notify(message, MULTIPLE_AUTH_SUPPORTED)) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_MULTIPLE_AUTH); + } + if (message->get_notify(message, EAP_ONLY_AUTHENTICATION)) + { + this->ike_sa->enable_extension(this->ike_sa, + EXT_EAP_ONLY_AUTHENTICATION); + } + } + + if (this->other_auth == NULL) + { + /* handle IDi payload */ + id_payload = (id_payload_t*)message->get_payload(message, ID_INITIATOR); + if (!id_payload) + { + DBG1(DBG_IKE, "IDi payload missing"); + return FAILED; + } + id = id_payload->get_identification(id_payload); + get_reserved_id_bytes(this, id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id)); + + if (this->peer_cfg == NULL) + { + if (!load_cfg_candidates(this)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + if (message->get_payload(message, AUTHENTICATION) == NULL) + { /* before authenticating with EAP, we need a 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 inacceptable"); + 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) + { + cfg->add(cfg, AUTH_RULE_AAA_IDENTITY, id->clone(id)); + } + } + + /* verify authentication data */ + this->other_auth = authenticator_create_verifier(this->ike_sa, + message, this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet), + this->reserved); + if (!this->other_auth) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + switch (this->other_auth->process(this->other_auth, message)) + { + case SUCCESS: + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + break; + case NEED_MORE: + if (message->get_payload(message, AUTHENTICATION)) + { /* AUTH verification successful, but another build() needed */ + break; + } + return NEED_MORE; + default: + this->authentication_failed = TRUE; + return NEED_MORE; + } + + /* If authenticated (with non-EAP) and received INITIAL_CONTACT, + * delete any existing IKE_SAs with that peer. */ + if (message->get_message_id(message) == 1 && + message->get_notify(message, INITIAL_CONTACT)) + { + this->initial_contact = TRUE; + } + + /* another auth round done, invoke authorize hook */ + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "authorization hook forbids IKE_SA, cancelling"); + this->authentication_failed = TRUE; + return NEED_MORE; + } + + /* store authentication information */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, FALSE), FALSE); + this->ike_sa->add_auth_cfg(this->ike_sa, FALSE, cfg); + + if (!update_cfg_candidates(this, FALSE)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + + if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL) + { + this->expect_another_auth = FALSE; + if (!update_cfg_candidates(this, TRUE)) + { + this->authentication_failed = TRUE; + return NEED_MORE; + } + } + return NEED_MORE; +} + +METHOD(task_t, build_r, status_t, + private_ike_auth_t *this, message_t *message) +{ + auth_cfg_t *cfg; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + if (multiple_auth_enabled()) + { + message->add_notify(message, FALSE, MULTIPLE_AUTH_SUPPORTED, + chunk_empty); + } + return collect_my_init_data(this, message); + } + + if (this->authentication_failed || this->peer_cfg == NULL) + { + goto peer_auth_failed; + } + + if (this->my_auth == NULL && this->do_another_auth) + { + identification_t *id, *id_cfg; + id_payload_t *id_payload; + + /* add IDr */ + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cfg->purge(cfg, TRUE); + cfg->merge(cfg, get_auth_cfg(this, TRUE), TRUE); + + id_cfg = cfg->get(cfg, AUTH_RULE_IDENTITY); + id = this->ike_sa->get_my_id(this->ike_sa); + if (id->get_type(id) == ID_ANY) + { /* no IDr received, apply configured ID */ + if (!id_cfg || id_cfg->contains_wildcards(id_cfg)) + { + DBG1(DBG_CFG, "IDr not configured and negotiation failed"); + goto peer_auth_failed; + } + this->ike_sa->set_my_id(this->ike_sa, id_cfg->clone(id_cfg)); + id = id_cfg; + } + else + { /* IDr received, check if it matches configuration */ + if (id_cfg && !id->matches(id, id_cfg)) + { + DBG1(DBG_CFG, "received IDr %Y, but require %Y", id, id_cfg); + goto peer_auth_failed; + } + } + + id_payload = id_payload_create_from_identification(ID_RESPONDER, id); + get_reserved_id_bytes(this, id_payload); + message->add_payload(message, (payload_t*)id_payload); + + if (this->initial_contact) + { + charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, + this->ike_sa, TRUE); + this->initial_contact = FALSE; + } + + if ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS) == AUTH_CLASS_EAP) + { /* EAP-only authentication */ + if (!this->ike_sa->supports_extension(this->ike_sa, + EXT_EAP_ONLY_AUTHENTICATION)) + { + DBG1(DBG_IKE, "configured EAP-only authentication, but peer " + "does not support it"); + goto peer_auth_failed; + } + } + else + { + /* build authentication data */ + this->my_auth = authenticator_create_builder(this->ike_sa, cfg, + this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet), + this->reserved); + if (!this->my_auth) + { + goto peer_auth_failed; + } + } + } + + if (this->other_auth) + { + switch (this->other_auth->build(this->other_auth, message)) + { + case SUCCESS: + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + break; + case NEED_MORE: + break; + default: + if (message->get_payload(message, EXTENSIBLE_AUTHENTICATION)) + { /* skip AUTHENTICATION_FAILED if we have EAP_FAILURE */ + goto peer_auth_failed_no_notify; + } + goto peer_auth_failed; + } + } + if (this->my_auth) + { + switch (this->my_auth->build(this->my_auth, message)) + { + case SUCCESS: + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), + TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + break; + case NEED_MORE: + break; + default: + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + } + + /* check for additional authentication rounds */ + if (do_another_auth(this)) + { + message->add_notify(message, FALSE, ANOTHER_AUTH_FOLLOWS, chunk_empty); + } + else + { + this->do_another_auth = FALSE; + } + if (!this->do_another_auth && !this->expect_another_auth) + { + if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, + this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy"); + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); + return FAILED; + } + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + goto peer_auth_failed; + } + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); + return SUCCESS; + } + return NEED_MORE; + +peer_auth_failed: + message->add_notify(message, TRUE, AUTHENTICATION_FAILED, + chunk_empty); +peer_auth_failed_no_notify: + charon->bus->alert(charon->bus, ALERT_PEER_AUTH_FAILED); + return FAILED; +} + +METHOD(task_t, process_i, status_t, + private_ike_auth_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *cfg; + bool mutual_eap = FALSE; + + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + if (message->get_notify(message, MULTIPLE_AUTH_SUPPORTED) && + multiple_auth_enabled()) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_MULTIPLE_AUTH); + } + return collect_other_init_data(this, message); + } + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY) + { + notify_payload_t *notify = (notify_payload_t*)payload; + notify_type_t type = notify->get_notify_type(notify); + + switch (type) + { + case NO_PROPOSAL_CHOSEN: + case SINGLE_PAIR_REQUIRED: + case NO_ADDITIONAL_SAS: + case INTERNAL_ADDRESS_FAILURE: + case FAILED_CP_REQUIRED: + case TS_UNACCEPTABLE: + case INVALID_SELECTORS: + /* these are errors, but are not critical as only the + * CHILD_SA won't get build, but IKE_SA establishes anyway */ + break; + case MOBIKE_SUPPORTED: + case ADDITIONAL_IP4_ADDRESS: + case ADDITIONAL_IP6_ADDRESS: + /* handled in ike_mobike task */ + break; + case AUTH_LIFETIME: + /* handled in ike_auth_lifetime task */ + break; + case ME_ENDPOINT: + /* handled in ike_me task */ + break; + default: + { + if (type <= 16383) + { + DBG1(DBG_IKE, "received %N notify error", + notify_type_names, type); + enumerator->destroy(enumerator); + return FAILED; + } + DBG2(DBG_IKE, "received %N notify", + notify_type_names, type); + break; + } + } + } + } + enumerator->destroy(enumerator); + + if (this->expect_another_auth) + { + if (this->other_auth == NULL) + { + id_payload_t *id_payload; + identification_t *id; + + /* handle IDr payload */ + id_payload = (id_payload_t*)message->get_payload(message, + ID_RESPONDER); + if (!id_payload) + { + DBG1(DBG_IKE, "IDr payload missing"); + goto peer_auth_failed; + } + id = id_payload->get_identification(id_payload); + get_reserved_id_bytes(this, id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id)); + + if (message->get_payload(message, AUTHENTICATION)) + { + /* verify authentication data */ + this->other_auth = authenticator_create_verifier(this->ike_sa, + message, this->other_nonce, this->my_nonce, + this->other_packet->get_data(this->other_packet), + this->my_packet->get_data(this->my_packet), + this->reserved); + if (!this->other_auth) + { + goto peer_auth_failed; + } + } + else + { + /* responder omitted AUTH payload, indicating EAP-only */ + mutual_eap = TRUE; + } + } + if (this->other_auth) + { + switch (this->other_auth->process(this->other_auth, message)) + { + case SUCCESS: + break; + case NEED_MORE: + return NEED_MORE; + default: + goto peer_auth_failed; + } + this->other_auth->destroy(this->other_auth); + this->other_auth = NULL; + } + /* another auth round done, invoke authorize hook */ + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "authorization forbids IKE_SA, cancelling"); + goto peer_auth_failed; + } + + /* store authentication information, reset authenticator */ + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, FALSE), FALSE); + this->ike_sa->add_auth_cfg(this->ike_sa, FALSE, cfg); + } + + if (this->my_auth) + { + switch (this->my_auth->process(this->my_auth, message)) + { + case SUCCESS: + cfg = auth_cfg_create(); + cfg->merge(cfg, this->ike_sa->get_auth_cfg(this->ike_sa, TRUE), + TRUE); + this->ike_sa->add_auth_cfg(this->ike_sa, TRUE, cfg); + this->my_auth->destroy(this->my_auth); + this->my_auth = NULL; + this->do_another_auth = do_another_auth(this); + break; + case NEED_MORE: + break; + default: + return FAILED; + } + } + if (mutual_eap) + { + if (!this->my_auth || !this->my_auth->is_mutual(this->my_auth)) + { + DBG1(DBG_IKE, "do not allow non-mutual EAP-only authentication"); + goto peer_auth_failed; + } + DBG1(DBG_IKE, "allow mutual EAP-only authentication"); + } + + if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL) + { + this->expect_another_auth = FALSE; + } + if (!this->expect_another_auth && !this->do_another_auth && !this->my_auth) + { + if (!update_cfg_candidates(this, TRUE)) + { + goto peer_auth_failed; + } + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, " + "cancelling"); + goto peer_auth_failed; + } + DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->ike_sa->get_other_id(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE); + return SUCCESS; + } + return NEED_MORE; + +peer_auth_failed: + charon->bus->alert(charon->bus, ALERT_PEER_AUTH_FAILED); + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_ike_auth_t *this) +{ + return TASK_IKE_AUTH; +} + +METHOD(task_t, migrate, void, + private_ike_auth_t *this, ike_sa_t *ike_sa) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + DESTROY_IF(this->my_packet); + DESTROY_IF(this->other_packet); + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->my_auth); + DESTROY_IF(this->other_auth); + this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy)); + + this->my_packet = NULL; + this->other_packet = NULL; + this->ike_sa = ike_sa; + this->peer_cfg = NULL; + this->my_auth = NULL; + this->other_auth = NULL; + this->do_another_auth = TRUE; + this->expect_another_auth = TRUE; + this->authentication_failed = FALSE; + this->candidates = linked_list_create(); +} + +METHOD(task_t, destroy, void, + private_ike_auth_t *this) +{ + chunk_free(&this->my_nonce); + chunk_free(&this->other_nonce); + DESTROY_IF(this->my_packet); + DESTROY_IF(this->other_packet); + DESTROY_IF(this->my_auth); + DESTROY_IF(this->other_auth); + DESTROY_IF(this->peer_cfg); + this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy)); + free(this); +} + +/* + * Described in header. + */ +ike_auth_t *ike_auth_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_auth_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .build = _build_r, + .process = _process_r, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .candidates = linked_list_create(), + .do_another_auth = TRUE, + .expect_another_auth = TRUE, + ); + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + return &this->public; +} |