diff options
author | Yves-Alexis Perez <corsac@debian.org> | 2013-01-02 14:18:20 +0100 |
---|---|---|
committer | Yves-Alexis Perez <corsac@debian.org> | 2013-01-02 14:18:20 +0100 |
commit | c1343b3278cdf99533b7902744d15969f9d6fdc1 (patch) | |
tree | d5ed3dc5677a59260ec41cd39bb284d3e94c91b3 /src/libcharon/sa | |
parent | b34738ed08c2227300d554b139e2495ca5da97d6 (diff) | |
download | vyos-strongswan-c1343b3278cdf99533b7902744d15969f9d6fdc1.tar.gz vyos-strongswan-c1343b3278cdf99533b7902744d15969f9d6fdc1.zip |
Imported Upstream version 5.0.1
Diffstat (limited to 'src/libcharon/sa')
112 files changed, 16671 insertions, 3017 deletions
diff --git a/src/libcharon/sa/authenticators/authenticator.c b/src/libcharon/sa/authenticator.c index 9ffe661cc..a32b6ab12 100644 --- a/src/libcharon/sa/authenticators/authenticator.c +++ b/src/libcharon/sa/authenticator.c @@ -18,9 +18,12 @@ #include "authenticator.h" -#include <sa/authenticators/pubkey_authenticator.h> -#include <sa/authenticators/psk_authenticator.h> -#include <sa/authenticators/eap_authenticator.h> +#include <sa/ikev2/authenticators/pubkey_authenticator.h> +#include <sa/ikev2/authenticators/psk_authenticator.h> +#include <sa/ikev2/authenticators/eap_authenticator.h> +#include <sa/ikev1/authenticators/psk_v1_authenticator.h> +#include <sa/ikev1/authenticators/pubkey_v1_authenticator.h> +#include <sa/ikev1/authenticators/hybrid_authenticator.h> #include <encoding/payloads/auth_payload.h> @@ -33,7 +36,17 @@ ENUM_NEXT(auth_method_names, AUTH_ECDSA_256, AUTH_GSPM, AUTH_DSS, "ECDSA-384 signature", "ECDSA-521 signature", "secure password method"); -ENUM_END(auth_method_names, AUTH_GSPM); +ENUM_NEXT(auth_method_names, AUTH_XAUTH_INIT_PSK, AUTH_HYBRID_RESP_RSA, AUTH_GSPM, + "XAuthInitPSK", + "XAuthRespPSK", + "XAuthInitRSA", + "XauthRespRSA", + "HybridInitRSA", + "HybridRespRSA", +); +ENUM_END(auth_method_names, AUTH_HYBRID_RESP_RSA); + +#ifdef USE_IKEV2 /** * Described in header. @@ -96,3 +109,46 @@ authenticator_t *authenticator_create_verifier( } } +#endif /* USE_IKEV2 */ + +#ifdef USE_IKEV1 + +/** + * Described in header. + */ +authenticator_t *authenticator_create_v1(ike_sa_t *ike_sa, bool initiator, + auth_method_t auth_method, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload) +{ + switch (auth_method) + { + case AUTH_PSK: + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_RESP_PSK: + return (authenticator_t*)psk_v1_authenticator_create(ike_sa, + initiator, dh, dh_value, sa_payload, + id_payload, FALSE); + case AUTH_RSA: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + return (authenticator_t*)pubkey_v1_authenticator_create(ike_sa, + initiator, dh, dh_value, sa_payload, + id_payload, KEY_RSA); + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + return (authenticator_t*)pubkey_v1_authenticator_create(ike_sa, + initiator, dh, dh_value, sa_payload, + id_payload, KEY_ECDSA); + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + return (authenticator_t*)hybrid_authenticator_create(ike_sa, + initiator, dh, dh_value, sa_payload, + id_payload); + default: + return NULL; + } +} + +#endif /* USE_IKEV1 */ diff --git a/src/libcharon/sa/authenticators/authenticator.h b/src/libcharon/sa/authenticator.h index 5042e4a73..914f42d9d 100644 --- a/src/libcharon/sa/authenticators/authenticator.h +++ b/src/libcharon/sa/authenticator.h @@ -17,7 +17,7 @@ /** * @defgroup authenticator authenticator - * @{ @ingroup authenticators + * @{ @ingroup sa */ #ifndef AUTHENTICATOR_H_ @@ -34,6 +34,12 @@ typedef struct authenticator_t authenticator_t; * Method to use for authentication, as defined in IKEv2. */ enum auth_method_t { + + /** + * No authentication used. + */ + AUTH_NONE = 0, + /** * Computed as specified in section 2.15 of RFC using * an RSA private key over a PKCS#1 padded hash. @@ -73,6 +79,35 @@ enum auth_method_t { */ AUTH_GSPM = 12, + /** + * IKEv1 initiator XAUTH with PSK, outside of IANA range + */ + AUTH_XAUTH_INIT_PSK = 256, + + /** + * IKEv1 responder XAUTH with PSK, outside of IANA range + */ + AUTH_XAUTH_RESP_PSK, + + /** + * IKEv1 initiator XAUTH with RSA, outside of IANA range + */ + AUTH_XAUTH_INIT_RSA, + + /** + * IKEv1 responder XAUTH with RSA, outside of IANA range + */ + AUTH_XAUTH_RESP_RSA, + + /** + * IKEv1 initiator XAUTH, responder RSA, outside of IANA range + */ + AUTH_HYBRID_INIT_RSA, + + /** + * IKEv1 responder XAUTH, initiator RSA, outside of IANA range + */ + AUTH_HYBRID_RESP_RSA, }; /** @@ -128,7 +163,7 @@ struct authenticator_t { }; /** - * Create an authenticator to build signatures. + * Create an IKEv2 authenticator to build signatures. * * @param ike_sa associated ike_sa * @param cfg authentication configuration @@ -146,7 +181,7 @@ authenticator_t *authenticator_create_builder( char reserved[3]); /** - * Create an authenticator to verify signatures. + * Create an IKEv2 authenticator to verify signatures. * * @param ike_sa associated ike_sa * @param message message containing authentication data @@ -163,4 +198,26 @@ authenticator_t *authenticator_create_verifier( chunk_t received_init, chunk_t sent_init, char reserved[3]); +/** + * Create an IKEv1 authenticator to build and verify signatures or hash + * payloads. + * + * @note Due to the fixed ID, these authenticators can only be used in one + * direction at a time. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are the IKE_SA initiator + * @param auth_method negotiated authentication method to use + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @return authenticator, NULL if not supported + */ +authenticator_t *authenticator_create_v1(ike_sa_t *ike_sa, bool initiator, + auth_method_t auth_method, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload); + #endif /** AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 2130a5998..1245734c9 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -124,6 +124,11 @@ struct private_child_sa_t { child_sa_state_t state; /** + * TRUE if this CHILD_SA is used to install trap policies + */ + bool trap; + + /** * Specifies if UDP encapsulation is enabled (NAT traversal) */ bool encap; @@ -526,6 +531,16 @@ METHOD(child_sa_t, get_usestats, void, } } +METHOD(child_sa_t, get_mark, mark_t, + private_child_sa_t *this, bool inbound) +{ + if (inbound) + { + return this->mark_in; + } + return this->mark_out; +} + METHOD(child_sa_t, get_lifetime, time_t, private_child_sa_t *this, bool hard) { @@ -617,7 +632,14 @@ METHOD(child_sa_t, install, status_t, now = time_monotonic(NULL); if (lifetime->time.rekey) { - this->rekey_time = now + lifetime->time.rekey; + if (this->rekey_time) + { + this->rekey_time = min(this->rekey_time, now + lifetime->time.rekey); + } + else + { + this->rekey_time = now + lifetime->time.rekey; + } } if (lifetime->time.life) { @@ -757,8 +779,11 @@ METHOD(child_sa_t, add_policies, status_t, other_sa.ah.spi = this->other_spi; } - priority = this->state == CHILD_CREATED ? POLICY_PRIORITY_ROUTED - : POLICY_PRIORITY_DEFAULT; + /* if we're not in state CHILD_INSTALLING (i.e. if there is no SAD + * entry) we install a trap policy */ + this->trap = this->state == CHILD_CREATED; + priority = this->trap ? POLICY_PRIORITY_ROUTED + : POLICY_PRIORITY_DEFAULT; /* enumerate pairs of traffic selectors */ enumerator = create_policy_enumerator(this); @@ -787,16 +812,25 @@ METHOD(child_sa_t, add_policies, status_t, enumerator->destroy(enumerator); } - if (status == SUCCESS && this->state == CHILD_CREATED) - { /* switch to routed state if no SAD entry set up */ + if (status == SUCCESS && this->trap) + { set_state(this, CHILD_ROUTED); } return status; } +/** + * Callback to reinstall a virtual IP + */ +static void reinstall_vip(host_t *vip, host_t *me) +{ + hydra->kernel_interface->del_ip(hydra->kernel_interface, vip); + hydra->kernel_interface->add_ip(hydra->kernel_interface, vip, me); +} + METHOD(child_sa_t, update, status_t, - private_child_sa_t *this, host_t *me, host_t *other, host_t *vip, - bool encap) + private_child_sa_t *this, host_t *me, host_t *other, linked_list_t *vips, + bool encap) { child_sa_state_t old; bool transport_proxy_mode; @@ -902,13 +936,7 @@ METHOD(child_sa_t, update, status_t, /* we reinstall the virtual IP to handle interface roaming * correctly */ - if (vip) - { - hydra->kernel_interface->del_ip(hydra->kernel_interface, - vip); - hydra->kernel_interface->add_ip(hydra->kernel_interface, - vip, me); - } + vips->invoke_function(vips, (void*)reinstall_vip, me); /* reinstall updated policies */ install_policies_internal(this, me, other, my_ts, other_ts, @@ -960,8 +988,7 @@ METHOD(child_sa_t, destroy, void, traffic_selector_t *my_ts, *other_ts; policy_priority_t priority; - priority = this->state == CHILD_ROUTED ? POLICY_PRIORITY_ROUTED - : POLICY_PRIORITY_DEFAULT; + priority = this->trap ? POLICY_PRIORITY_ROUTED : POLICY_PRIORITY_DEFAULT; set_state(this, CHILD_DESTROYING); @@ -1038,6 +1065,7 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, .set_proposal = _set_proposal, .get_lifetime = _get_lifetime, .get_usestats = _get_usestats, + .get_mark = _get_mark, .has_encap = _has_encap, .get_ipcomp = _get_ipcomp, .set_ipcomp = _set_ipcomp, @@ -1079,6 +1107,15 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, this->reqid = rekey ? rekey : ++reqid; } + if (this->mark_in.value == MARK_REQID) + { + this->mark_in.value = this->reqid; + } + if (this->mark_out.value == MARK_REQID) + { + this->mark_out.value = this->reqid; + } + /* MIPv6 proxy transport mode sets SA endpoints to TS hosts */ if (config->get_mode(config) == MODE_TRANSPORT && config->use_proxy_mode(config)) @@ -1088,12 +1125,14 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, chunk_t addr; host_t *host; enumerator_t *enumerator; - linked_list_t *my_ts_list, *other_ts_list; + linked_list_t *my_ts_list, *other_ts_list, *list; traffic_selector_t *my_ts, *other_ts; this->mode = MODE_TRANSPORT; - my_ts_list = config->get_traffic_selectors(config, TRUE, NULL, me); + list = linked_list_create_with_items(me, NULL); + my_ts_list = config->get_traffic_selectors(config, TRUE, NULL, list); + list->destroy(list); enumerator = my_ts_list->create_enumerator(my_ts_list); if (enumerator->enumerate(enumerator, &my_ts)) { @@ -1114,7 +1153,9 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, enumerator->destroy(enumerator); my_ts_list->destroy_offset(my_ts_list, offsetof(traffic_selector_t, destroy)); - other_ts_list = config->get_traffic_selectors(config, FALSE, NULL, other); + list = linked_list_create_with_items(other, NULL); + other_ts_list = config->get_traffic_selectors(config, FALSE, NULL, list); + list->destroy(list); enumerator = other_ts_list->create_enumerator(other_ts_list); if (enumerator->enumerate(enumerator, &other_ts)) { diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index f17ef01ac..dae3f2c18 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -275,6 +275,14 @@ struct child_sa_t { u_int64_t *bytes); /** + * Get the mark used with this CHILD_SA. + * + * @param inbound TRUE to get inbound mark, FALSE for outbound + * @return mark used with this CHILD_SA + */ + mark_t (*get_mark)(child_sa_t *this, bool inbound); + + /** * Get the traffic selectors list added for one side. * * @param local TRUE for own traffic selectors, FALSE for remote @@ -338,12 +346,12 @@ struct child_sa_t { * * @param me the new local host * @param other the new remote host - * @param vip virtual IP, if any + * @param vips list of local virtual IPs * @param TRUE to use UDP encapsulation for NAT traversal * @return SUCCESS or FAILED */ status_t (*update)(child_sa_t *this, host_t *me, host_t *other, - host_t *vip, bool encap); + linked_list_t *vips, bool encap); /** * Destroys a child_sa. */ diff --git a/src/libcharon/sa/authenticators/eap/eap_manager.c b/src/libcharon/sa/eap/eap_manager.c index bc2c4a617..520c0ce56 100644 --- a/src/libcharon/sa/authenticators/eap/eap_manager.c +++ b/src/libcharon/sa/eap/eap_manager.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2008 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -104,6 +105,44 @@ METHOD(eap_manager_t, remove_method, void, this->lock->unlock(this->lock); } +/** + * filter the registered methods + */ +static bool filter_methods(uintptr_t role, eap_entry_t **entry, + eap_type_t *type, void *in, u_int32_t *vendor) +{ + if ((*entry)->role != (eap_role_t)role) + { + return FALSE; + } + if ((*entry)->vendor == 0 && + ((*entry)->type < 4 || (*entry)->type == EAP_EXPANDED || + (*entry)->type > EAP_EXPERIMENTAL)) + { /* filter invalid types */ + return FALSE; + } + if (type) + { + *type = (*entry)->type; + } + if (vendor) + { + *vendor = (*entry)->vendor; + } + return TRUE; +} + +METHOD(eap_manager_t, create_enumerator, enumerator_t*, + private_eap_manager_t *this, eap_role_t role) +{ + this->lock->read_lock(this->lock); + return enumerator_create_cleaner( + enumerator_create_filter( + this->methods->create_enumerator(this->methods), + (void*)filter_methods, (void*)(uintptr_t)role, NULL), + (void*)this->lock->unlock, this->lock); +} + METHOD(eap_manager_t, create_instance, eap_method_t*, private_eap_manager_t *this, eap_type_t type, u_int32_t vendor, eap_role_t role, identification_t *server, identification_t *peer) @@ -150,6 +189,7 @@ eap_manager_t *eap_manager_create() .public = { .add_method = _add_method, .remove_method = _remove_method, + .create_enumerator = _create_enumerator, .create_instance = _create_instance, .destroy = _destroy, }, @@ -159,4 +199,3 @@ eap_manager_t *eap_manager_create() return &this->public; } - diff --git a/src/libcharon/sa/authenticators/eap/eap_manager.h b/src/libcharon/sa/eap/eap_manager.h index 0333fb6da..e318ef57a 100644 --- a/src/libcharon/sa/authenticators/eap/eap_manager.h +++ b/src/libcharon/sa/eap/eap_manager.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2008 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -21,7 +22,7 @@ #ifndef EAP_MANAGER_H_ #define EAP_MANAGER_H_ -#include <sa/authenticators/eap/eap_method.h> +#include <sa/eap/eap_method.h> typedef struct eap_manager_t eap_manager_t; @@ -54,6 +55,17 @@ struct eap_manager_t { void (*remove_method)(eap_manager_t *this, eap_constructor_t constructor); /** + * Enumerate the registered EAP authentication methods for the given role. + * + * @note Only authentication types are enumerated (e.g. EAP-Identity is not + * even though it is registered as method with this manager). + * + * @param role EAP role of methods to enumerate + * @return enumerator over (eap_type_t type, u_int32_t vendor) + */ + enumerator_t* (*create_enumerator)(eap_manager_t *this, eap_role_t role); + + /** * Create a new EAP method instance. * * @param type type of the EAP method diff --git a/src/libcharon/sa/authenticators/eap/eap_method.c b/src/libcharon/sa/eap/eap_method.c index a05e8c59a..a05e8c59a 100644 --- a/src/libcharon/sa/authenticators/eap/eap_method.c +++ b/src/libcharon/sa/eap/eap_method.c diff --git a/src/libcharon/sa/authenticators/eap/eap_method.h b/src/libcharon/sa/eap/eap_method.h index 6242a5a6e..6242a5a6e 100644 --- a/src/libcharon/sa/authenticators/eap/eap_method.h +++ b/src/libcharon/sa/eap/eap_method.h diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 07d19381d..1d49acb52 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -28,32 +28,16 @@ #include <daemon.h> #include <utils/linked_list.h> #include <utils/lexparser.h> -#include <sa/task_manager.h> -#include <sa/tasks/ike_init.h> -#include <sa/tasks/ike_natd.h> -#include <sa/tasks/ike_mobike.h> -#include <sa/tasks/ike_auth.h> -#include <sa/tasks/ike_auth_lifetime.h> -#include <sa/tasks/ike_config.h> -#include <sa/tasks/ike_cert_pre.h> -#include <sa/tasks/ike_cert_post.h> -#include <sa/tasks/ike_rekey.h> -#include <sa/tasks/ike_reauth.h> -#include <sa/tasks/ike_delete.h> -#include <sa/tasks/ike_dpd.h> -#include <sa/tasks/ike_vendor.h> -#include <sa/tasks/child_create.h> -#include <sa/tasks/child_delete.h> -#include <sa/tasks/child_rekey.h> #include <processing/jobs/retransmit_job.h> #include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/send_dpd_job.h> #include <processing/jobs/send_keepalive_job.h> #include <processing/jobs/rekey_ike_sa_job.h> -#include <encoding/payloads/unknown_payload.h> +#include <processing/jobs/retry_initiate_job.h> +#include <sa/ikev2/tasks/ike_auth_lifetime.h> #ifdef ME -#include <sa/tasks/ike_me.h> +#include <sa/ikev2/tasks/ike_me.h> #include <processing/jobs/initiate_mediation_job.h> #endif @@ -86,6 +70,11 @@ struct private_ike_sa_t { ike_sa_id_t *ike_sa_id; /** + * IKE version of this SA. + */ + ike_version_t version; + + /** * unique numerical ID for this IKE_SA. */ u_int32_t unique_id; @@ -193,14 +182,14 @@ struct private_ike_sa_t { keymat_t *keymat; /** - * Virtual IP on local host, if any + * Virtual IPs on local host */ - host_t *my_virtual_ip; + linked_list_t *my_vips; /** - * Virtual IP on remote host, if any + * Virtual IPs on remote host */ - host_t *other_virtual_ip; + linked_list_t *other_vips; /** * List of configuration attributes (attribute_entry_t) @@ -228,6 +217,17 @@ struct private_ike_sa_t { u_int32_t keepalive_interval; /** + * interval for retries during initiation (e.g. if DNS resolution failed), + * 0 to disable (default) + */ + u_int32_t retry_initiate_interval; + + /** + * TRUE if a retry_initiate_job has been queued + */ + bool retry_initiate_queued; + + /** * Timestamps for this IKE_SA */ u_int32_t stats[STAT_MAX]; @@ -248,9 +248,9 @@ struct private_ike_sa_t { host_t *remote_host; /** - * TRUE if we are currently reauthenticating this IKE_SA + * Flush auth configs once established? */ - bool is_reauthenticating; + bool flush_auth_cfg; }; /** @@ -319,6 +319,15 @@ METHOD(ike_sa_t, get_statistic, u_int32_t, return 0; } +METHOD(ike_sa_t, set_statistic, void, + private_ike_sa_t *this, statistic_t kind, u_int32_t value) +{ + if (kind < STAT_MAX) + { + this->stats[kind] = value; + } +} + METHOD(ike_sa_t, get_my_host, host_t*, private_ike_sa_t *this) { @@ -405,6 +414,9 @@ static void flush_auth_cfgs(private_ike_sa_t *this) { auth_cfg_t *cfg; + this->my_auth->purge(this->my_auth, FALSE); + this->other_auth->purge(this->other_auth, FALSE); + while (this->my_auths->remove_last(this->my_auths, (void**)&cfg) == SUCCESS) { @@ -471,8 +483,8 @@ METHOD(ike_sa_t, send_keepalive, void, data.ptr[0] = 0xFF; data.len = 1; packet->set_data(packet, data); - DBG1(DBG_IKE, "sending keep alive"); - charon->sender->send(charon->sender, packet); + DBG1(DBG_IKE, "sending keep alive to %#H", this->other_host); + charon->sender->send_no_marker(charon->sender, packet); diff = 0; } job = send_keepalive_job_create(this->ike_sa_id); @@ -563,6 +575,7 @@ METHOD(ike_sa_t, send_dpd, status_t, { job_t *job; time_t diff, delay; + bool task_queued = FALSE; if (this->state == IKE_PASSIVE) { @@ -583,27 +596,11 @@ METHOD(ike_sa_t, send_dpd, status_t, diff = now - last_in; if (!delay || diff >= delay) { - /* to long ago, initiate dead peer detection */ - task_t *task; - ike_mobike_t *mobike; - - if (supports_extension(this, EXT_MOBIKE) && - has_condition(this, COND_NAT_HERE)) - { - /* use mobike enabled DPD to detect NAT mapping changes */ - mobike = ike_mobike_create(&this->public, TRUE); - mobike->dpd(mobike); - task = &mobike->task; - } - else - { - task = (task_t*)ike_dpd_create(TRUE); - } - diff = 0; + /* too long ago, initiate dead peer detection */ DBG1(DBG_IKE, "sending DPD request"); - - this->task_manager->queue_task(this->task_manager, task); - this->task_manager->initiate(this->task_manager); + this->task_manager->queue_dpd(this->task_manager); + task_queued = TRUE; + diff = 0; } } /* recheck in "interval" seconds */ @@ -612,6 +609,10 @@ METHOD(ike_sa_t, send_dpd, status_t, job = (job_t*)send_dpd_job_create(this->ike_sa_id); lib->scheduler->schedule_job(lib->scheduler, job, delay - diff); } + if (task_queued) + { + return this->task_manager->initiate(this->task_manager); + } return SUCCESS; } @@ -646,7 +647,7 @@ METHOD(ike_sa_t, set_state, void, /* schedule rekeying if we have a time which is smaller than * an already scheduled rekeying */ - t = this->peer_cfg->get_rekey_time(this->peer_cfg); + t = this->peer_cfg->get_rekey_time(this->peer_cfg, TRUE); if (t && (this->stats[STAT_REKEY] == 0 || (this->stats[STAT_REKEY] > t + this->stats[STAT_ESTABLISHED]))) { @@ -655,7 +656,7 @@ METHOD(ike_sa_t, set_state, void, lib->scheduler->schedule_job(lib->scheduler, job, t); DBG1(DBG_IKE, "scheduling rekeying in %ds", t); } - t = this->peer_cfg->get_reauth_time(this->peer_cfg); + t = this->peer_cfg->get_reauth_time(this->peer_cfg, TRUE); if (t && (this->stats[STAT_REAUTH] == 0 || (this->stats[STAT_REAUTH] > t + this->stats[STAT_ESTABLISHED]))) { @@ -698,7 +699,14 @@ METHOD(ike_sa_t, set_state, void, if (trigger_dpd) { - send_dpd(this); + if (supports_extension(this, EXT_DPD)) + { + send_dpd(this); + } + else + { + DBG1(DBG_IKE, "DPD not supported by peer, disabled"); + } } } @@ -716,7 +724,8 @@ METHOD(ike_sa_t, reset, void, flush_auth_cfgs(this); this->keymat->destroy(this->keymat); - this->keymat = keymat_create(this->ike_sa_id->is_initiator(this->ike_sa_id)); + this->keymat = keymat_create(this->version, + this->ike_sa_id->is_initiator(this->ike_sa_id)); this->task_manager->reset(this->task_manager, 0, 0); } @@ -727,7 +736,7 @@ METHOD(ike_sa_t, get_keymat, keymat_t*, return this->keymat; } -METHOD(ike_sa_t, set_virtual_ip, void, +METHOD(ike_sa_t, add_virtual_ip, void, private_ike_sa_t *this, bool local, host_t *ip) { if (local) @@ -736,39 +745,44 @@ METHOD(ike_sa_t, set_virtual_ip, void, if (hydra->kernel_interface->add_ip(hydra->kernel_interface, ip, this->my_host) == SUCCESS) { - if (this->my_virtual_ip) - { - DBG1(DBG_IKE, "removing old virtual IP %H", this->my_virtual_ip); - hydra->kernel_interface->del_ip(hydra->kernel_interface, - this->my_virtual_ip); - } - DESTROY_IF(this->my_virtual_ip); - this->my_virtual_ip = ip->clone(ip); + this->my_vips->insert_last(this->my_vips, ip->clone(ip)); } else { DBG1(DBG_IKE, "installing virtual IP %H failed", ip); - this->my_virtual_ip = NULL; } } else { - DESTROY_IF(this->other_virtual_ip); - this->other_virtual_ip = ip->clone(ip); + this->other_vips->insert_last(this->other_vips, ip->clone(ip)); } } -METHOD(ike_sa_t, get_virtual_ip, host_t*, + +METHOD(ike_sa_t, clear_virtual_ips, void, private_ike_sa_t *this, bool local) { - if (local) + linked_list_t *vips = local ? this->my_vips : this->other_vips; + host_t *vip; + + while (vips->remove_first(vips, (void**)&vip) == SUCCESS) { - return this->my_virtual_ip; + if (local) + { + hydra->kernel_interface->del_ip(hydra->kernel_interface, vip); + } + vip->destroy(vip); } - else +} + +METHOD(ike_sa_t, create_virtual_ip_enumerator, enumerator_t*, + private_ike_sa_t *this, bool local) +{ + if (local) { - return this->other_virtual_ip; + return this->my_vips->create_enumerator(this->my_vips); } + return this->other_vips->create_enumerator(this->other_vips); } METHOD(ike_sa_t, add_peer_address, void, @@ -837,9 +851,11 @@ METHOD(ike_sa_t, float_ports, void, private_ike_sa_t *this) { /* do not switch if we have a custom port from MOBIKE/NAT */ - if (this->my_host->get_port(this->my_host) == IKEV2_UDP_PORT) + if (this->my_host->get_port(this->my_host) == + charon->socket->get_port(charon->socket, FALSE)) { - this->my_host->set_port(this->my_host, IKEV2_NATT_PORT); + this->my_host->set_port(this->my_host, + charon->socket->get_port(charon->socket, TRUE)); } if (this->other_host->get_port(this->other_host) == IKEV2_UDP_PORT) { @@ -899,7 +915,7 @@ METHOD(ike_sa_t, update_hosts, void, while (enumerator->enumerate(enumerator, (void**)&child_sa)) { if (child_sa->update(child_sa, this->my_host, - this->other_host, this->my_virtual_ip, + this->other_host, this->my_vips, has_condition(this, COND_NAT_ANY)) == NOT_SUPPORTED) { this->public.rekey_child_sa(&this->public, @@ -914,6 +930,8 @@ METHOD(ike_sa_t, update_hosts, void, METHOD(ike_sa_t, generate_message, status_t, private_ike_sa_t *this, message_t *message, packet_t **packet) { + status_t status; + if (message->is_encoded(message)) { /* already done */ *packet = message->get_packet(message); @@ -921,44 +939,13 @@ METHOD(ike_sa_t, generate_message, status_t, } this->stats[STAT_OUTBOUND] = time_monotonic(NULL); message->set_ike_sa_id(message, this->ike_sa_id); - charon->bus->message(charon->bus, message, FALSE); - return message->generate(message, - this->keymat->get_aead(this->keymat, FALSE), packet); -} - -/** - * send a notify back to the sender - */ -static void send_notify_response(private_ike_sa_t *this, message_t *request, - notify_type_t type, chunk_t data) -{ - message_t *response; - packet_t *packet; - - response = message_create(); - response->set_exchange_type(response, request->get_exchange_type(request)); - response->set_request(response, FALSE); - response->set_message_id(response, request->get_message_id(request)); - response->add_notify(response, FALSE, type, data); - if (this->my_host->is_anyaddr(this->my_host)) - { - this->my_host->destroy(this->my_host); - this->my_host = request->get_destination(request); - this->my_host = this->my_host->clone(this->my_host); - } - if (this->other_host->is_anyaddr(this->other_host)) - { - this->other_host->destroy(this->other_host); - this->other_host = request->get_source(request); - this->other_host = this->other_host->clone(this->other_host); - } - response->set_source(response, this->my_host->clone(this->my_host)); - response->set_destination(response, this->other_host->clone(this->other_host)); - if (generate_message(this, response, &packet) == SUCCESS) + charon->bus->message(charon->bus, message, FALSE, TRUE); + status = message->generate(message, this->keymat, packet); + if (status == SUCCESS) { - charon->sender->send(charon->sender, packet); + charon->bus->message(charon->bus, message, FALSE, FALSE); } - response->destroy(response); + return status; } METHOD(ike_sa_t, set_kmaddress, void, @@ -1060,8 +1047,12 @@ static void resolve_hosts(private_ike_sa_t *this) } else { - host = host_create_from_dns(this->ike_cfg->get_other_addr(this->ike_cfg), - 0, this->ike_cfg->get_other_port(this->ike_cfg)); + char *other_addr; + u_int16_t other_port; + + other_addr = this->ike_cfg->get_other_addr(this->ike_cfg, NULL); + other_port = this->ike_cfg->get_other_port(this->ike_cfg); + host = host_create_from_dns(other_addr, 0, other_port); } if (host) { @@ -1071,10 +1062,12 @@ static void resolve_hosts(private_ike_sa_t *this) if (this->local_host) { host = this->local_host->clone(this->local_host); - host->set_port(host, IKEV2_UDP_PORT); + host->set_port(host, charon->socket->get_port(charon->socket, FALSE)); } else { + char *my_addr; + u_int16_t my_port; int family = 0; /* use same address family as for other */ @@ -1082,8 +1075,9 @@ static void resolve_hosts(private_ike_sa_t *this) { family = this->other_host->get_family(this->other_host); } - host = host_create_from_dns(this->ike_cfg->get_my_addr(this->ike_cfg), - family, this->ike_cfg->get_my_port(this->ike_cfg)); + my_addr = this->ike_cfg->get_my_addr(this->ike_cfg, NULL); + my_port = this->ike_cfg->get_my_port(this->ike_cfg); + host = host_create_from_dns(my_addr, family, my_port); if (host && host->is_anyaddr(host) && !this->other_host->is_anyaddr(this->other_host)) @@ -1097,9 +1091,7 @@ static void resolve_hosts(private_ike_sa_t *this) } else { /* fallback to address family specific %any(6), if configured */ - host = host_create_from_dns( - this->ike_cfg->get_my_addr(this->ike_cfg), - 0, this->ike_cfg->get_my_port(this->ike_cfg)); + host = host_create_from_dns(my_addr, 0, my_port); } } } @@ -1113,7 +1105,7 @@ METHOD(ike_sa_t, initiate, status_t, private_ike_sa_t *this, child_cfg_t *child_cfg, u_int32_t reqid, traffic_selector_t *tsi, traffic_selector_t *tsr) { - task_t *task; + bool defer_initiate = FALSE; if (this->state == IKE_CREATED) { @@ -1129,39 +1121,31 @@ METHOD(ike_sa_t, initiate, status_t, #endif /* ME */ ) { - child_cfg->destroy(child_cfg); - DBG1(DBG_IKE, "unable to initiate to %%any"); - charon->bus->alert(charon->bus, ALERT_PEER_ADDR_FAILED); - return DESTROY_ME; + char *addr = this->ike_cfg->get_other_addr(this->ike_cfg, NULL); + bool is_anyaddr = streq(addr, "%any") || streq(addr, "%any6"); + + if (is_anyaddr || !this->retry_initiate_interval) + { + if (is_anyaddr) + { + DBG1(DBG_IKE, "unable to initiate to %s", addr); + } + else + { + DBG1(DBG_IKE, "unable to resolve %s, initiate aborted", + addr); + } + DESTROY_IF(child_cfg); + charon->bus->alert(charon->bus, ALERT_PEER_ADDR_FAILED); + return DESTROY_ME; + } + DBG1(DBG_IKE, "unable to resolve %s, retrying in %ds", + addr, this->retry_initiate_interval); + defer_initiate = TRUE; } set_condition(this, COND_ORIGINAL_INITIATOR, TRUE); - - task = (task_t*)ike_vendor_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_init_create(&this->public, TRUE, NULL); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_natd_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_cert_pre_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_auth_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_cert_post_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_config_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_auth_lifetime_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - if (this->peer_cfg->use_mobike(this->peer_cfg)) - { - task = (task_t*)ike_mobike_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - } -#ifdef ME - task = (task_t*)ike_me_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); -#endif /* ME */ + this->task_manager->queue_ike(this->task_manager); } #ifdef ME @@ -1178,18 +1162,11 @@ METHOD(ike_sa_t, initiate, status_t, } else #endif /* ME */ + if (child_cfg) { /* normal IKE_SA with CHILD_SA */ - task = (task_t*)child_create_create(&this->public, child_cfg, FALSE, - tsi, tsr); - child_cfg->destroy(child_cfg); - if (reqid) - { - child_create_t *child_create = (child_create_t*)task; - child_create->use_reqid(child_create, reqid); - } - this->task_manager->queue_task(this->task_manager, task); - + this->task_manager->queue_child(this->task_manager, child_cfg, reqid, + tsi, tsr); #ifdef ME if (this->peer_cfg->get_mediated_by(this->peer_cfg)) { @@ -1201,135 +1178,74 @@ METHOD(ike_sa_t, initiate, status_t, #endif /* ME */ } + if (defer_initiate) + { + if (!this->retry_initiate_queued) + { + job_t *job = (job_t*)retry_initiate_job_create(this->ike_sa_id); + lib->scheduler->schedule_job(lib->scheduler, (job_t*)job, + this->retry_initiate_interval); + this->retry_initiate_queued = TRUE; + } + return SUCCESS; + } + this->retry_initiate_queued = FALSE; return this->task_manager->initiate(this->task_manager); } +METHOD(ike_sa_t, retry_initiate, status_t, + private_ike_sa_t *this) +{ + if (this->retry_initiate_queued) + { + this->retry_initiate_queued = FALSE; + return initiate(this, NULL, 0, NULL, NULL); + } + return SUCCESS; +} + METHOD(ike_sa_t, process_message, status_t, private_ike_sa_t *this, message_t *message) { status_t status; - bool is_request; - u_int8_t type = 0; if (this->state == IKE_PASSIVE) { /* do not handle messages in passive state */ return FAILED; } - - is_request = message->get_request(message); - - status = message->parse_body(message, - this->keymat->get_aead(this->keymat, TRUE)); - if (status == SUCCESS) - { /* check for unsupported critical payloads */ - enumerator_t *enumerator; - unknown_payload_t *unknown; - payload_t *payload; - - enumerator = message->create_payload_enumerator(message); - while (enumerator->enumerate(enumerator, &payload)) - { - unknown = (unknown_payload_t*)payload; - type = payload->get_type(payload); - if (!payload_is_known(type) && - unknown->is_critical(unknown)) + switch (message->get_exchange_type(message)) + { + case ID_PROT: + case AGGRESSIVE: + case IKE_SA_INIT: + case IKE_AUTH: + if (this->state != IKE_CREATED && + this->state != IKE_CONNECTING) { - DBG1(DBG_ENC, "payload type %N is not supported, " - "but its critical!", payload_type_names, type); - status = NOT_SUPPORTED; + DBG1(DBG_IKE, "ignoring %N in established IKE_SA state", + exchange_type_names, message->get_exchange_type(message)); + return FAILED; } - } - enumerator->destroy(enumerator); + break; + default: + break; } - if (status != SUCCESS) + if (message->get_major_version(message) != this->version) { - if (is_request) - { - switch (status) - { - case NOT_SUPPORTED: - DBG1(DBG_IKE, "critical unknown payloads found"); - if (is_request) - { - send_notify_response(this, message, - UNSUPPORTED_CRITICAL_PAYLOAD, - chunk_from_thing(type)); - this->task_manager->incr_mid(this->task_manager, FALSE); - } - break; - case PARSE_ERROR: - DBG1(DBG_IKE, "message parsing failed"); - if (is_request) - { - send_notify_response(this, message, - INVALID_SYNTAX, chunk_empty); - this->task_manager->incr_mid(this->task_manager, FALSE); - } - break; - case VERIFY_ERROR: - DBG1(DBG_IKE, "message verification failed"); - if (is_request) - { - send_notify_response(this, message, - INVALID_SYNTAX, chunk_empty); - this->task_manager->incr_mid(this->task_manager, FALSE); - } - break; - case FAILED: - DBG1(DBG_IKE, "integrity check failed"); - /* ignored */ - break; - case INVALID_STATE: - DBG1(DBG_IKE, "found encrypted message, but no keys available"); - default: - break; - } - } - DBG1(DBG_IKE, "%N %s with message ID %d processing failed", + DBG1(DBG_IKE, "ignoring %N IKEv%u exchange on %N SA", exchange_type_names, message->get_exchange_type(message), - message->get_request(message) ? "request" : "response", - message->get_message_id(message)); - - if (this->state == IKE_CREATED) - { /* invalid initiation attempt, close SA */ - return DESTROY_ME; - } + message->get_major_version(message), + ike_version_names, this->version); + /* TODO-IKEv1: fall back to IKEv1 if we receive an IKEv1 + * INVALID_MAJOR_VERSION on an IKEv2 SA. */ + return FAILED; } - else + status = this->task_manager->process_message(this->task_manager, message); + if (this->flush_auth_cfg && this->state == IKE_ESTABLISHED) { - /* if this IKE_SA is virgin, we check for a config */ - if (this->ike_cfg == NULL) - { - job_t *job; - host_t *me = message->get_destination(message), - *other = message->get_source(message); - this->ike_cfg = charon->backends->get_ike_cfg(charon->backends, - me, other); - if (this->ike_cfg == NULL) - { - /* no config found for these hosts, destroy */ - DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", - me, other, notify_type_names, NO_PROPOSAL_CHOSEN); - send_notify_response(this, message, - NO_PROPOSAL_CHOSEN, chunk_empty); - return DESTROY_ME; - } - /* add a timeout if peer does not establish it completely */ - job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, FALSE); - lib->scheduler->schedule_job(lib->scheduler, job, - lib->settings->get_int(lib->settings, - "charon.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT)); - } - this->stats[STAT_INBOUND] = time_monotonic(NULL); - status = this->task_manager->process_message(this->task_manager, - message); - if (message->get_exchange_type(message) == IKE_AUTH && - this->state == IKE_ESTABLISHED && - lib->settings->get_bool(lib->settings, - "charon.flush_auth_cfg", FALSE)) - { /* authentication completed */ - flush_auth_cfgs(this); - } + /* authentication completed */ + this->flush_auth_cfg = FALSE; + flush_auth_cfgs(this); } return status; } @@ -1340,6 +1256,12 @@ METHOD(ike_sa_t, get_id, ike_sa_id_t*, return this->ike_sa_id; } +METHOD(ike_sa_t, get_version, ike_version_t, + private_ike_sa_t *this) +{ + return this->version; +} + METHOD(ike_sa_t, get_my_id, identification_t*, private_ike_sa_t *this) { @@ -1373,6 +1295,10 @@ METHOD(ike_sa_t, get_other_eap_id, identification_t*, current = cfg->get(cfg, AUTH_RULE_EAP_IDENTITY); if (!current || current->get_type(current) == ID_ANY) { + current = cfg->get(cfg, AUTH_RULE_XAUTH_IDENTITY); + } + if (!current || current->get_type(current) == ID_ANY) + { current = cfg->get(cfg, AUTH_RULE_IDENTITY); } if (current && current->get_type(current) != ID_ANY) @@ -1442,30 +1368,23 @@ METHOD(ike_sa_t, remove_child_sa, void, METHOD(ike_sa_t, rekey_child_sa, status_t, private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi) { - child_rekey_t *child_rekey; - if (this->state == IKE_PASSIVE) { return INVALID_STATE; } - - child_rekey = child_rekey_create(&this->public, protocol, spi); - this->task_manager->queue_task(this->task_manager, &child_rekey->task); + this->task_manager->queue_child_rekey(this->task_manager, protocol, spi); return this->task_manager->initiate(this->task_manager); } METHOD(ike_sa_t, delete_child_sa, status_t, - private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi) + private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi, bool expired) { - child_delete_t *child_delete; - if (this->state == IKE_PASSIVE) { return INVALID_STATE; } - - child_delete = child_delete_create(&this->public, protocol, spi); - this->task_manager->queue_task(this->task_manager, &child_delete->task); + this->task_manager->queue_child_delete(this->task_manager, + protocol, spi, expired); return this->task_manager->initiate(this->task_manager); } @@ -1495,14 +1414,17 @@ METHOD(ike_sa_t, destroy_child_sa, status_t, METHOD(ike_sa_t, delete_, status_t, private_ike_sa_t *this) { - ike_delete_t *ike_delete; - switch (this->state) { - case IKE_ESTABLISHED: case IKE_REKEYING: - ike_delete = ike_delete_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, &ike_delete->task); + if (this->version == IKEV1) + { /* SA has been reauthenticated, delete */ + charon->bus->ike_updown(charon->bus, &this->public, FALSE); + break; + } + /* FALL */ + case IKE_ESTABLISHED: + this->task_manager->queue_ike_delete(this->task_manager); return this->task_manager->initiate(this->task_manager); case IKE_CREATED: DBG1(DBG_IKE, "deleting unestablished IKE_SA"); @@ -1521,23 +1443,17 @@ METHOD(ike_sa_t, delete_, status_t, METHOD(ike_sa_t, rekey, status_t, private_ike_sa_t *this) { - ike_rekey_t *ike_rekey; - if (this->state == IKE_PASSIVE) { return INVALID_STATE; } - ike_rekey = ike_rekey_create(&this->public, TRUE); - - this->task_manager->queue_task(this->task_manager, &ike_rekey->task); + this->task_manager->queue_ike_rekey(this->task_manager); return this->task_manager->initiate(this->task_manager); } METHOD(ike_sa_t, reauth, status_t, private_ike_sa_t *this) { - task_t *task; - if (this->state == IKE_PASSIVE) { return INVALID_STATE; @@ -1548,7 +1464,8 @@ METHOD(ike_sa_t, reauth, status_t, if (!has_condition(this, COND_ORIGINAL_INITIATOR)) { DBG1(DBG_IKE, "initiator did not reauthenticate as requested"); - if (this->other_virtual_ip != NULL || + if (this->other_vips->get_count(this->other_vips) != 0 || + has_condition(this, COND_XAUTH_AUTHENTICATED) || has_condition(this, COND_EAP_AUTHENTICATED) #ifdef ME /* as mediation server we too cannot reauth the IKE_SA */ @@ -1575,10 +1492,8 @@ METHOD(ike_sa_t, reauth, status_t, DBG0(DBG_IKE, "reauthenticating IKE_SA %s[%d]", get_name(this), this->unique_id); } - this->is_reauthenticating = TRUE; - task = (task_t*)ike_reauth_create(&this->public); - this->task_manager->queue_task(this->task_manager, task); - + set_condition(this, COND_REAUTHENTICATING, TRUE); + this->task_manager->queue_ike_reauth(this->task_manager); return this->task_manager->initiate(this->task_manager); } @@ -1594,7 +1509,7 @@ METHOD(ike_sa_t, reestablish, status_t, bool restart = FALSE; status_t status = FAILED; - if (this->is_reauthenticating) + if (has_condition(this, COND_REAUTHENTICATING)) { /* only reauthenticate if we have children */ if (this->child_sas->get_count(this->child_sas) == 0 #ifdef ME @@ -1653,7 +1568,7 @@ METHOD(ike_sa_t, reestablish, status_t, /* check if we are able to reestablish this IKE_SA */ if (!has_condition(this, COND_ORIGINAL_INITIATOR) && - (this->other_virtual_ip != NULL || + (this->other_vips->get_count(this->other_vips) != 0 || has_condition(this, COND_EAP_AUTHENTICATED) #ifdef ME || this->is_mediation_server @@ -1664,18 +1579,24 @@ METHOD(ike_sa_t, reestablish, status_t, return FAILED; } - new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, TRUE); + new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + this->version, TRUE); + if (!new) + { + return FAILED; + } new->set_peer_cfg(new, this->peer_cfg); host = this->other_host; new->set_other_host(new, host->clone(host)); host = this->my_host; new->set_my_host(new, host->clone(host)); /* if we already have a virtual IP, we reuse it */ - host = this->my_virtual_ip; - if (host) + enumerator = this->my_vips->create_enumerator(this->my_vips); + while (enumerator->enumerate(enumerator, &host)) { - new->set_virtual_ip(new, TRUE, host); + new->add_virtual_ip(new, TRUE, host); } + enumerator->destroy(enumerator); #ifdef ME if (this->peer_cfg->is_mediation(this->peer_cfg)) @@ -1688,7 +1609,7 @@ METHOD(ike_sa_t, reestablish, status_t, enumerator = this->child_sas->create_enumerator(this->child_sas); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { - if (this->is_reauthenticating) + if (has_condition(this, COND_REAUTHENTICATING)) { switch (child_sa->get_state(child_sa)) { @@ -1744,6 +1665,7 @@ METHOD(ike_sa_t, reestablish, status_t, } else { + charon->bus->ike_reestablish(charon->bus, &this->public, new); charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); status = SUCCESS; } @@ -1751,40 +1673,6 @@ METHOD(ike_sa_t, reestablish, status_t, return status; } -/** - * Requeue the IKE_SA_INIT tasks for initiation, if required - */ -static void requeue_init_tasks(private_ike_sa_t *this) -{ - enumerator_t *enumerator; - bool has_init = FALSE; - task_t *task; - - /* if we have advanced to IKE_AUTH, the IKE_INIT and related tasks - * have already completed. Recreate them if necessary. */ - enumerator = this->task_manager->create_task_enumerator( - this->task_manager, TASK_QUEUE_QUEUED); - while (enumerator->enumerate(enumerator, &task)) - { - if (task->get_type(task) == IKE_INIT) - { - has_init = TRUE; - break; - } - } - enumerator->destroy(enumerator); - - if (!has_init) - { - task = (task_t*)ike_vendor_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_natd_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_init_create(&this->public, TRUE, NULL); - this->task_manager->queue_task(this->task_manager, task); - } -} - METHOD(ike_sa_t, retransmit, status_t, private_ike_sa_t *this, u_int32_t message_id) { @@ -1800,7 +1688,7 @@ METHOD(ike_sa_t, retransmit, status_t, { case IKE_CONNECTING: { - /* retry IKE_SA_INIT if we have multiple keyingtries */ + /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */ u_int32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg); this->keyingtry++; if (tries == 0 || tries > this->keyingtry) @@ -1809,7 +1697,7 @@ METHOD(ike_sa_t, retransmit, status_t, this->keyingtry + 1, tries); reset(this); resolve_hosts(this); - requeue_init_tasks(this); + this->task_manager->queue_ike(this->task_manager); return this->task_manager->initiate(this->task_manager); } DBG1(DBG_IKE, "establishing IKE_SA failed, peer not responding"); @@ -1817,7 +1705,7 @@ METHOD(ike_sa_t, retransmit, status_t, } case IKE_DELETING: DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding"); - if (this->is_reauthenticating) + if (has_condition(this, COND_REAUTHENTICATING)) { DBG1(DBG_IKE, "delete during reauthentication failed, " "trying to reestablish IKE_SA anyway"); @@ -1831,6 +1719,10 @@ METHOD(ike_sa_t, retransmit, status_t, reestablish(this); break; } + if (this->state != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, &this->public, FALSE); + } return DESTROY_ME; } return SUCCESS; @@ -1840,7 +1732,6 @@ METHOD(ike_sa_t, set_auth_lifetime, status_t, private_ike_sa_t *this, u_int32_t lifetime) { u_int32_t diff, hard, soft, now; - ike_auth_lifetime_t *task; bool send_update; diff = this->peer_cfg->get_over_time(this->peer_cfg); @@ -1850,9 +1741,9 @@ METHOD(ike_sa_t, set_auth_lifetime, status_t, /* check if we have to send an AUTH_LIFETIME to enforce the new lifetime. * We send the notify in IKE_AUTH if not yet ESTABLISHED. */ - send_update = this->state == IKE_ESTABLISHED && + send_update = this->state == IKE_ESTABLISHED && this->version == IKEV2 && !has_condition(this, COND_ORIGINAL_INITIATOR) && - (this->other_virtual_ip != NULL || + (this->other_vips->get_count(this->other_vips) != 0 || has_condition(this, COND_EAP_AUTHENTICATED)); if (lifetime < diff) @@ -1890,12 +1781,16 @@ METHOD(ike_sa_t, set_auth_lifetime, status_t, /* give at least some seconds to reauthenticate */ this->stats[STAT_DELETE] = max(hard, now + 10); +#ifdef USE_IKEV2 if (send_update) { + ike_auth_lifetime_t *task; + task = ike_auth_lifetime_create(&this->public, TRUE); this->task_manager->queue_task(this->task_manager, &task->task); return this->task_manager->initiate(this->task_manager); } +#endif return SUCCESS; } @@ -1953,8 +1848,6 @@ static bool is_any_path_valid(private_ike_sa_t *this) METHOD(ike_sa_t, roam, status_t, private_ike_sa_t *this, bool address) { - ike_mobike_t *mobike; - switch (this->state) { case IKE_CREATED: @@ -1976,10 +1869,7 @@ METHOD(ike_sa_t, roam, status_t, if (supports_extension(this, EXT_MOBIKE) && address) { /* if any addresses changed, send an updated list */ DBG1(DBG_IKE, "sending address list update using MOBIKE"); - mobike = ike_mobike_create(&this->public, TRUE); - mobike->addresses(mobike); - this->task_manager->queue_task(this->task_manager, - (task_t*)mobike); + this->task_manager->queue_mobike(this->task_manager, FALSE, TRUE); return this->task_manager->initiate(this->task_manager); } return SUCCESS; @@ -2007,9 +1897,7 @@ METHOD(ike_sa_t, roam, status_t, { DBG1(DBG_IKE, "requesting address change using MOBIKE"); } - mobike = ike_mobike_create(&this->public, TRUE); - mobike->roam(mobike, address); - this->task_manager->queue_task(this->task_manager, (task_t*)mobike); + this->task_manager->queue_mobike(this->task_manager, TRUE, address); return this->task_manager->initiate(this->task_manager); } @@ -2044,6 +1932,18 @@ METHOD(ike_sa_t, create_task_enumerator, enumerator_t*, return this->task_manager->create_task_enumerator(this->task_manager, queue); } +METHOD(ike_sa_t, flush_queue, void, + private_ike_sa_t *this, task_queue_t queue) +{ + this->task_manager->flush_queue(this->task_manager, queue); +} + +METHOD(ike_sa_t, queue_task, void, + private_ike_sa_t *this, task_t *task) +{ + this->task_manager->queue_task(this->task_manager, task); +} + METHOD(ike_sa_t, inherit, void, private_ike_sa_t *this, ike_sa_t *other_public) { @@ -2052,6 +1952,7 @@ METHOD(ike_sa_t, inherit, void, attribute_entry_t *entry; enumerator_t *enumerator; auth_cfg_t *cfg; + host_t *vip; /* apply hosts and ids */ this->my_host->destroy(this->my_host); @@ -2063,16 +1964,15 @@ METHOD(ike_sa_t, inherit, void, this->my_id = other->my_id->clone(other->my_id); this->other_id = other->other_id->clone(other->other_id); - /* apply virtual assigned IPs... */ - if (other->my_virtual_ip) + /* apply assigned virtual IPs... */ + while (this->my_vips->remove_last(this->my_vips, (void**)&vip) == SUCCESS) { - this->my_virtual_ip = other->my_virtual_ip; - other->my_virtual_ip = NULL; + other->my_vips->insert_first(other->my_vips, vip); } - if (other->other_virtual_ip) + while (this->other_vips->remove_last(this->other_vips, + (void**)&vip) == SUCCESS) { - this->other_virtual_ip = other->other_virtual_ip; - other->other_virtual_ip = NULL; + other->other_vips->insert_first(other->other_vips, vip); } /* authentication information */ @@ -2147,11 +2047,12 @@ METHOD(ike_sa_t, destroy, void, private_ike_sa_t *this) { attribute_entry_t *entry; + host_t *vip; charon->bus->set_sa(charon->bus, &this->public); set_state(this, IKE_DESTROYING); - this->task_manager->destroy(this->task_manager); + DESTROY_IF(this->task_manager); /* remove attributes first, as we pass the IKE_SA to the handler */ while (this->attributes->remove_last(this->attributes, @@ -2169,24 +2070,31 @@ METHOD(ike_sa_t, destroy, void, /* unset SA after here to avoid usage by the listeners */ charon->bus->set_sa(charon->bus, NULL); - this->keymat->destroy(this->keymat); + DESTROY_IF(this->keymat); - if (this->my_virtual_ip) + while (this->my_vips->remove_last(this->my_vips, (void**)&vip) == SUCCESS) { - hydra->kernel_interface->del_ip(hydra->kernel_interface, - this->my_virtual_ip); - this->my_virtual_ip->destroy(this->my_virtual_ip); + hydra->kernel_interface->del_ip(hydra->kernel_interface, vip); + vip->destroy(vip); } - if (this->other_virtual_ip) + this->my_vips->destroy(this->my_vips); + while (this->other_vips->remove_last(this->other_vips, + (void**)&vip) == SUCCESS) { - if (this->peer_cfg && this->peer_cfg->get_pool(this->peer_cfg)) + if (this->peer_cfg) { - hydra->attributes->release_address(hydra->attributes, - this->peer_cfg->get_pool(this->peer_cfg), - this->other_virtual_ip, get_other_eap_id(this)); + linked_list_t *pools; + identification_t *id; + + id = get_other_eap_id(this); + pools = linked_list_create_from_enumerator( + this->peer_cfg->create_pool_enumerator(this->peer_cfg)); + hydra->attributes->release_address(hydra->attributes, pools, vip, id); + pools->destroy(pools); } - this->other_virtual_ip->destroy(this->other_virtual_ip); + vip->destroy(vip); } + this->other_vips->destroy(this->other_vips); this->peer_addresses->destroy_offset(this->peer_addresses, offsetof(host_t, destroy)); #ifdef ME @@ -2224,19 +2132,32 @@ METHOD(ike_sa_t, destroy, void, /* * Described in header. */ -ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) +ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, + ike_version_t version) { private_ike_sa_t *this; static u_int32_t unique_id = 0; + if (version == IKE_ANY) + { /* prefer IKEv2 if protocol not specified */ +#ifdef USE_IKEV2 + version = IKEV2; +#else + version = IKEV1; +#endif + } + INIT(this, .public = { + .get_version = _get_version, .get_state = _get_state, .set_state = _set_state, .get_name = _get_name, .get_statistic = _get_statistic, + .set_statistic = _set_statistic, .process_message = _process_message, .initiate = _initiate, + .retry_initiate = _retry_initiate, .get_ike_cfg = _get_ike_cfg, .set_ike_cfg = _set_ike_cfg, .get_peer_cfg = _get_peer_cfg, @@ -2292,11 +2213,14 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) .generate_message = _generate_message, .reset = _reset, .get_unique_id = _get_unique_id, - .set_virtual_ip = _set_virtual_ip, - .get_virtual_ip = _get_virtual_ip, + .add_virtual_ip = _add_virtual_ip, + .clear_virtual_ips = _clear_virtual_ips, + .create_virtual_ip_enumerator = _create_virtual_ip_enumerator, .add_configuration_attribute = _add_configuration_attribute, .set_kmaddress = _set_kmaddress, .create_task_enumerator = _create_task_enumerator, + .flush_queue = _flush_queue, + .queue_task = _queue_task, #ifdef ME .act_as_mediation_server = _act_as_mediation_server, .get_server_reflexive_host = _get_server_reflexive_host, @@ -2310,12 +2234,13 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) #endif /* ME */ }, .ike_sa_id = ike_sa_id->clone(ike_sa_id), + .version = version, .child_sas = linked_list_create(), .my_host = host_create_any(AF_INET), .other_host = host_create_any(AF_INET), .my_id = identification_create_from_encoding(ID_ANY, chunk_empty), .other_id = identification_create_from_encoding(ID_ANY, chunk_empty), - .keymat = keymat_create(ike_sa_id->is_initiator(ike_sa_id)), + .keymat = keymat_create(version, initiator), .state = IKE_CREATED, .stats[STAT_INBOUND] = time_monotonic(NULL), .stats[STAT_OUTBOUND] = time_monotonic(NULL), @@ -2325,12 +2250,31 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) .other_auths = linked_list_create(), .unique_id = ++unique_id, .peer_addresses = linked_list_create(), + .my_vips = linked_list_create(), + .other_vips = linked_list_create(), .attributes = linked_list_create(), .keepalive_interval = lib->settings->get_time(lib->settings, - "charon.keep_alive", KEEPALIVE_INTERVAL), + "%s.keep_alive", KEEPALIVE_INTERVAL, charon->name), + .retry_initiate_interval = lib->settings->get_time(lib->settings, + "%s.retry_initiate_interval", 0, charon->name), + .flush_auth_cfg = lib->settings->get_bool(lib->settings, + "%s.flush_auth_cfg", FALSE, charon->name), ); + + if (version == IKEV2) + { /* always supported with IKEv2 */ + enable_extension(this, EXT_DPD); + } + this->task_manager = task_manager_create(&this->public); - this->my_host->set_port(this->my_host, IKEV2_UDP_PORT); + this->my_host->set_port(this->my_host, + charon->socket->get_port(charon->socket, FALSE)); + if (!this->task_manager || !this->keymat) + { + DBG1(DBG_IKE, "IKE version %d not supported", this->version); + destroy(this); + return NULL; + } return &this->public; } diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index 537565e89..af741c799 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -37,11 +37,13 @@ typedef struct ike_sa_t ike_sa_t; #include <encoding/payloads/configuration_attribute.h> #include <sa/ike_sa_id.h> #include <sa/child_sa.h> +#include <sa/task.h> #include <sa/task_manager.h> #include <sa/keymat.h> #include <config/peer_cfg.h> #include <config/ike_cfg.h> #include <credentials/auth_cfg.h> +#include <utils/packet.h> /** * Timeout in seconds after that a half open IKE_SA gets deleted. @@ -69,7 +71,7 @@ typedef struct ike_sa_t ike_sa_t; enum ike_extension_t { /** - * peer supports NAT traversal as specified in RFC4306 + * peer supports NAT traversal as specified in RFC4306 or RFC3947 */ EXT_NATT = (1<<0), @@ -102,6 +104,21 @@ enum ike_extension_t { * peer is probably a Windows 7 RAS client */ EXT_MS_WINDOWS = (1<<6), + + /** + * peer supports XAuth authentication, draft-ietf-ipsec-isakmp-xauth-06 + */ + EXT_XAUTH = (1<<7), + + /** + * peer supports DPD detection, RFC 3706 (or IKEv2) + */ + EXT_DPD = (1<<8), + + /** + * peer supports Cisco Unity configuration attributes + */ + EXT_CISCO_UNITY = (1<<9), }; /** @@ -148,6 +165,21 @@ enum ike_condition_t { * IKE_SA is stale, the peer is currently unreachable (MOBIKE) */ COND_STALE = (1<<7), + + /** + * Initial contact received + */ + COND_INIT_CONTACT_SEEN = (1<<8), + + /** + * Peer has been authenticated using XAuth + */ + COND_XAUTH_AUTHENTICATED = (1<<9), + + /** + * This IKE_SA is currently being reauthenticated + */ + COND_REAUTHENTICATING = (1<<10), }; /** @@ -270,6 +302,11 @@ struct ike_sa_t { ike_sa_id_t* (*get_id) (ike_sa_t *this); /** + * Gets the IKE version of the SA + */ + ike_version_t (*get_version)(ike_sa_t *this); + + /** * Get the numerical ID uniquely defining this IKE_SA. * * @return unique ID @@ -288,7 +325,7 @@ struct ike_sa_t { * * @param state state to set for the IKE_SA */ - void (*set_state) (ike_sa_t *this, ike_sa_state_t ike_sa); + void (*set_state) (ike_sa_t *this, ike_sa_state_t state); /** * Get the name of the connection this IKE_SA uses. @@ -306,6 +343,14 @@ struct ike_sa_t { u_int32_t (*get_statistic)(ike_sa_t *this, statistic_t kind); /** + * Set statistic value of the IKE_SA. + * + * @param kind kind of value to update + * @param value value as integer + */ + void (*set_statistic)(ike_sa_t *this, statistic_t kind, u_int32_t value); + + /** * Get the own host address. * * @return host address @@ -661,6 +706,15 @@ struct ike_sa_t { traffic_selector_t *tsr); /** + * Retry initiation of this IKE_SA after it got deferred previously. + * + * @return + * - SUCCESS if initiation deferred or started + * - DESTROY_ME if initiation failed + */ + status_t (*retry_initiate) (ike_sa_t *this); + + /** * Initiates the deletion of an IKE_SA. * * Sends a delete message to the remote peer and waits for @@ -821,11 +875,13 @@ struct ike_sa_t { * * @param protocol protocol of the SA * @param spi inbound SPI of the CHILD_SA + * @param expired TRUE if CHILD_SA is expired * @return * - NOT_FOUND, if IKE_SA has no such CHILD_SA * - SUCCESS, if delete message sent */ - status_t (*delete_child_sa) (ike_sa_t *this, protocol_id_t protocol, u_int32_t spi); + status_t (*delete_child_sa)(ike_sa_t *this, protocol_id_t protocol, + u_int32_t spi, bool expired); /** * Destroy a CHILD SA with the specified protocol/SPI. @@ -880,7 +936,7 @@ struct ike_sa_t { status_t (*set_auth_lifetime)(ike_sa_t *this, u_int32_t lifetime); /** - * Set the virtual IP to use for this IKE_SA and its children. + * Add a virtual IP to use for this IKE_SA and its children. * * The virtual IP is assigned per IKE_SA, not per CHILD_SA. It has the same * lifetime as the IKE_SA. @@ -888,15 +944,22 @@ struct ike_sa_t { * @param local TRUE to set local address, FALSE for remote * @param ip IP to set as virtual IP */ - void (*set_virtual_ip) (ike_sa_t *this, bool local, host_t *ip); + void (*add_virtual_ip) (ike_sa_t *this, bool local, host_t *ip); /** - * Get the virtual IP configured. + * Clear all virtual IPs stored on this IKE_SA. + * + * @param local TRUE to clear local addresses, FALSE for remote + */ + void (*clear_virtual_ips) (ike_sa_t *this, bool local); + + /** + * Create an enumerator over virtual IPs. * * @param local TRUE to get local virtual IP, FALSE for remote - * @return host_t *virtual IP + * @return enumerator over host_t* */ - host_t* (*get_virtual_ip) (ike_sa_t *this, bool local); + enumerator_t* (*create_virtual_ip_enumerator) (ike_sa_t *this, bool local); /** * Register a configuration attribute to the IKE_SA. @@ -933,6 +996,20 @@ struct ike_sa_t { enumerator_t* (*create_task_enumerator)(ike_sa_t *this, task_queue_t queue); /** + * Flush a task queue, cancelling all tasks in it. + * + * @param queue queue type to flush + */ + void (*flush_queue)(ike_sa_t *this, task_queue_t queue); + + /** + * Queue a task for initiaton to the task manager. + * + * @param task task to queue + */ + void (*queue_task)(ike_sa_t *this, task_t *task); + + /** * Inherit all attributes of other to this after rekeying. * * When rekeying is completed, all CHILD_SAs, the virtual IP and all @@ -955,11 +1032,14 @@ struct ike_sa_t { }; /** - * Creates an ike_sa_t object with a specific ID. + * Creates an ike_sa_t object with a specific ID and IKE version. * - * @param ike_sa_id ike_sa_id_t object to associate with new IKE_SA + * @param ike_sa_id ike_sa_id_t to associate with new IKE_SA/ISAKMP_SA + * @param initiator TRUE to create this IKE_SA as initiator + * @param version IKE version of this SA * @return ike_sa_t object */ -ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id); +ike_sa_t *ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, + ike_version_t version); #endif /** IKE_SA_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa_id.c b/src/libcharon/sa/ike_sa_id.c index bea4c2124..0f0f1ab63 100644 --- a/src/libcharon/sa/ike_sa_id.c +++ b/src/libcharon/sa/ike_sa_id.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -30,13 +31,18 @@ struct private_ike_sa_id_t { */ ike_sa_id_t public; + /** + * Major IKE version of IKE_SA. + */ + u_int8_t ike_version; + /** - * SPI of Initiator. + * SPI of initiator. */ u_int64_t initiator_spi; /** - * SPI of Responder. + * SPI of responder. */ u_int64_t responder_spi; @@ -46,6 +52,12 @@ struct private_ike_sa_id_t { bool is_initiator_flag; }; +METHOD(ike_sa_id_t, get_ike_version, u_int8_t, + private_ike_sa_id_t *this) +{ + return this->ike_version; +} + METHOD(ike_sa_id_t, set_responder_spi, void, private_ike_sa_id_t *this, u_int64_t responder_spi) { @@ -77,23 +89,15 @@ METHOD(ike_sa_id_t, equals, bool, { return FALSE; } - if ((this->is_initiator_flag == other->is_initiator_flag) && - (this->initiator_spi == other->initiator_spi) && - (this->responder_spi == other->responder_spi)) - { - /* private_ike_sa_id's are equal */ - return TRUE; - } - else - { - /* private_ike_sa_id's are not equal */ - return FALSE; - } + return this->ike_version == other->ike_version && + this->initiator_spi == other->initiator_spi && + this->responder_spi == other->responder_spi; } METHOD(ike_sa_id_t, replace_values, void, private_ike_sa_id_t *this, private_ike_sa_id_t *other) { + this->ike_version = other->ike_version; this->initiator_spi = other->initiator_spi; this->responder_spi = other->responder_spi; this->is_initiator_flag = other->is_initiator_flag; @@ -108,22 +112,15 @@ METHOD(ike_sa_id_t, is_initiator, bool, METHOD(ike_sa_id_t, switch_initiator, bool, private_ike_sa_id_t *this) { - if (this->is_initiator_flag) - { - this->is_initiator_flag = FALSE; - } - else - { - this->is_initiator_flag = TRUE; - } + this->is_initiator_flag = !this->is_initiator_flag; return this->is_initiator_flag; } METHOD(ike_sa_id_t, clone_, ike_sa_id_t*, private_ike_sa_id_t *this) { - return ike_sa_id_create(this->initiator_spi, this->responder_spi, - this->is_initiator_flag); + return ike_sa_id_create(this->ike_version, this->initiator_spi, + this->responder_spi, this->is_initiator_flag); } METHOD(ike_sa_id_t, destroy, void, @@ -135,13 +132,14 @@ METHOD(ike_sa_id_t, destroy, void, /* * Described in header. */ -ike_sa_id_t * ike_sa_id_create(u_int64_t initiator_spi, u_int64_t responder_spi, - bool is_initiator_flag) +ike_sa_id_t * ike_sa_id_create(u_int8_t ike_version, u_int64_t initiator_spi, + u_int64_t responder_spi, bool is_initiator_flag) { private_ike_sa_id_t *this; INIT(this, .public = { + .get_ike_version = _get_ike_version, .set_responder_spi = _set_responder_spi, .set_initiator_spi = _set_initiator_spi, .get_responder_spi = _get_responder_spi, @@ -153,6 +151,7 @@ ike_sa_id_t * ike_sa_id_create(u_int64_t initiator_spi, u_int64_t responder_spi, .clone = _clone_, .destroy = _destroy, }, + .ike_version = ike_version, .initiator_spi = initiator_spi, .responder_spi = responder_spi, .is_initiator_flag = is_initiator_flag, diff --git a/src/libcharon/sa/ike_sa_id.h b/src/libcharon/sa/ike_sa_id.h index fb55359bc..227683d1c 100644 --- a/src/libcharon/sa/ike_sa_id.h +++ b/src/libcharon/sa/ike_sa_id.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -29,13 +30,20 @@ typedef struct ike_sa_id_t ike_sa_id_t; /** * An object of type ike_sa_id_t is used to identify an IKE_SA. * - * An IKE_SA is identified by its initiator and responder spi's. - * Additionally it contains the role of the actual running IKEv2 daemon - * for the specific IKE_SA (original initiator or responder). + * An IKE_SA is identified by its initiator and responder SPIs. + * Additionally, it contains the major IKE version of the IKE_SA and, for IKEv2, + * the role of the daemon (original initiator or responder). */ struct ike_sa_id_t { /** + * Get the major IKE version of this IKE_SA. + * + * @return IKE version + */ + u_int8_t (*get_ike_version) (ike_sa_id_t *this); + + /** * Set the SPI of the responder. * * This function is called when a request or reply of a IKE_SA_INIT is received. @@ -68,10 +76,12 @@ struct ike_sa_id_t { /** * Check if two ike_sa_id_t objects are equal. * - * Two ike_sa_id_t objects are equal if both SPI values and the role matches. + * Two ike_sa_id_t objects are equal if version and both SPI values match. + * The role is not compared. * * @param other ike_sa_id_t object to check if equal - * @return TRUE if given ike_sa_id_t are equal, FALSE otherwise + * @return TRUE if given ike_sa_id_t are equal, + * FALSE otherwise */ bool (*equals) (ike_sa_id_t *this, ike_sa_id_t *other); @@ -93,9 +103,9 @@ struct ike_sa_id_t { bool (*is_initiator) (ike_sa_id_t *this); /** - * Switche the original initiator flag. + * Switch the original initiator flag. * - * @return TRUE if we are the original initiator after switch, FALSE otherwise + * @return new value if initiator flag. */ bool (*switch_initiator) (ike_sa_id_t *this); @@ -113,14 +123,15 @@ struct ike_sa_id_t { }; /** - * Creates an ike_sa_id_t object with specific SPI's and defined role. + * Creates an ike_sa_id_t object. * + * @param ike_version major IKE version * @param initiator_spi initiators SPI * @param responder_spi responders SPI * @param is_initiaor TRUE if we are the original initiator * @return ike_sa_id_t object */ -ike_sa_id_t * ike_sa_id_create(u_int64_t initiator_spi, u_int64_t responder_spi, - bool is_initiaor); +ike_sa_id_t * ike_sa_id_create(u_int8_t ike_version, u_int64_t initiator_spi, + u_int64_t responder_spi, bool is_initiaor); #endif /** IKE_SA_ID_H_ @}*/ diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c index 731ae6007..a396235c2 100644 --- a/src/libcharon/sa/ike_sa_manager.c +++ b/src/libcharon/sa/ike_sa_manager.c @@ -1,7 +1,7 @@ /* * Copyright (C) 2005-2011 Martin Willi * Copyright (C) 2011 revosec AG - * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2008-2012 Tobias Brunner * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil * @@ -157,18 +157,6 @@ static entry_t *entry_create() } /** - * Function that matches entry_t objects by initiator SPI and the hash of the - * IKE_SA_INIT message. - */ -static bool entry_match_by_hash(entry_t *entry, ike_sa_id_t *id, chunk_t *hash) -{ - return id->get_responder_spi(id) == 0 && - id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) && - id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id) && - chunk_equals(*hash, entry->init_hash); -} - -/** * Function that matches entry_t objects by ike_sa_id_t. */ static bool entry_match_by_id(entry_t *entry, ike_sa_id_t *id) @@ -179,7 +167,6 @@ static bool entry_match_by_id(entry_t *entry, ike_sa_id_t *id) } if ((id->get_responder_spi(id) == 0 || entry->ike_sa_id->get_responder_spi(entry->ike_sa_id) == 0) && - id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id) && id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id)) { /* this is TRUE for IKE_SAs that we initiated but have not yet received a response */ @@ -201,8 +188,19 @@ static bool entry_match_by_sa(entry_t *entry, ike_sa_t *ike_sa) */ static u_int ike_sa_id_hash(ike_sa_id_t *ike_sa_id) { - /* we always use initiator spi as key */ - return ike_sa_id->get_initiator_spi(ike_sa_id); + /* IKEv2 does not mandate random SPIs (RFC 5996, 2.6), they just have to be + * locally unique, so we use our randomly allocated SPI whether we are + * initiator or responder to ensure a good distribution. The latter is not + * possible for IKEv1 as we don't know whether we are original initiator or + * not (based on the IKE header). But as RFC 2408, section 2.5.3 proposes + * SPIs (Cookies) to be allocated near random (we allocate them randomly + * anyway) it seems safe to always use the initiator SPI. */ + if (ike_sa_id->get_ike_version(ike_sa_id) == IKEV1_MAJOR_VERSION || + ike_sa_id->is_initiator(ike_sa_id)) + { + return ike_sa_id->get_initiator_spi(ike_sa_id); + } + return ike_sa_id->get_responder_spi(ike_sa_id); } typedef struct half_open_t half_open_t; @@ -227,14 +225,6 @@ static void half_open_destroy(half_open_t *this) free(this); } -/** - * Function that matches half_open_t objects by the given IP address chunk. - */ -static bool half_open_match(half_open_t *half_open, chunk_t *addr) -{ - return chunk_equals(*addr, half_open->other); -} - typedef struct connected_peers_t connected_peers_t; struct connected_peers_t { @@ -262,15 +252,25 @@ static void connected_peers_destroy(connected_peers_t *this) /** * Function that matches connected_peers_t objects by the given ids. */ -static bool connected_peers_match(connected_peers_t *connected_peers, +static inline bool connected_peers_match(connected_peers_t *connected_peers, identification_t *my_id, identification_t *other_id, - uintptr_t family) + int family) { return my_id->equals(my_id, connected_peers->my_id) && other_id->equals(other_id, connected_peers->other_id) && - family == connected_peers->family; + (!family || family == connected_peers->family); } +typedef struct init_hash_t init_hash_t; + +struct init_hash_t { + /** hash of IKE_SA_INIT or initial phase1 message (data is not cloned) */ + chunk_t hash; + + /** our SPI allocated for the IKE_SA based on this message */ + u_int64_t our_spi; +}; + typedef struct segment_t segment_t; /** @@ -298,6 +298,20 @@ struct shareable_segment_t { u_int count; }; +typedef struct table_item_t table_item_t; + +/** + * Instead of using linked_list_t for each bucket we store the data in our own + * list to save memory. + */ +struct table_item_t { + /** data of this item */ + void *value; + + /** next item in the overflow list */ + table_item_t *next; +}; + typedef struct private_ike_sa_manager_t private_ike_sa_manager_t; /** @@ -312,7 +326,7 @@ struct private_ike_sa_manager_t { /** * Hash table with entries for the ike_sa_t objects. */ - linked_list_t **ike_sa_table; + table_item_t **ike_sa_table; /** * The size of the hash table. @@ -342,7 +356,7 @@ struct private_ike_sa_manager_t { /** * Hash table with half_open_t objects. */ - linked_list_t **half_open_table; + table_item_t **half_open_table; /** * Segments of the "half-open" hash table. @@ -352,7 +366,7 @@ struct private_ike_sa_manager_t { /** * Hash table with connected_peers_t objects. */ - linked_list_t **connected_peers_table; + table_item_t **connected_peers_table; /** * Segments of the "connected peers" hash table. @@ -360,6 +374,16 @@ struct private_ike_sa_manager_t { shareable_segment_t *connected_peers_segments; /** + * Hash table with init_hash_t objects. + */ + table_item_t **init_hashes_table; + + /** + * Segments of the "hashes" hash table. + */ + segment_t *init_hashes_segments; + + /** * RNG to get random SPIs for our side */ rng_t *rng; @@ -379,10 +403,10 @@ struct private_ike_sa_manager_t { * Acquire a lock to access the segment of the table row with the given index. * It also works with the segment index directly. */ -static void lock_single_segment(private_ike_sa_manager_t *this, u_int index) +static inline void lock_single_segment(private_ike_sa_manager_t *this, + u_int index) { mutex_t *lock = this->segments[index & this->segment_mask].mutex; - lock->lock(lock); } @@ -390,10 +414,10 @@ static void lock_single_segment(private_ike_sa_manager_t *this, u_int index) * Release the lock required to access the segment of the table row with the given index. * It also works with the segment index directly. */ -static void unlock_single_segment(private_ike_sa_manager_t *this, u_int index) +static inline void unlock_single_segment(private_ike_sa_manager_t *this, + u_int index) { mutex_t *lock = this->segments[index & this->segment_mask].mutex; - lock->unlock(lock); } @@ -456,9 +480,14 @@ struct private_enumerator_t { u_int row; /** - * enumerator for the current table row + * current table item + */ + table_item_t *current; + + /** + * previous table item */ - enumerator_t *current; + table_item_t *prev; }; METHOD(enumerator_t, enumerate, bool, @@ -473,33 +502,23 @@ METHOD(enumerator_t, enumerate, bool, { while (this->row < this->manager->table_size) { + this->prev = this->current; if (this->current) { - entry_t *item; - - if (this->current->enumerate(this->current, &item)) - { - *entry = this->entry = item; - *segment = this->segment; - return TRUE; - } - this->current->destroy(this->current); - this->current = NULL; - unlock_single_segment(this->manager, this->segment); + this->current = this->current->next; } else { - linked_list_t *list; - lock_single_segment(this->manager, this->segment); - if ((list = this->manager->ike_sa_table[this->row]) != NULL && - list->get_count(list)) - { - this->current = list->create_enumerator(list); - continue; - } - unlock_single_segment(this->manager, this->segment); + this->current = this->manager->ike_sa_table[this->row]; } + if (this->current) + { + *entry = this->entry = this->current->value; + *segment = this->segment; + return TRUE; + } + unlock_single_segment(this->manager, this->segment); this->row += this->manager->segment_count; } this->segment++; @@ -517,7 +536,6 @@ METHOD(enumerator_t, enumerator_destroy, void, } if (this->current) { - this->current->destroy(this->current); unlock_single_segment(this->manager, this->segment); } free(this); @@ -546,19 +564,23 @@ static enumerator_t* create_table_enumerator(private_ike_sa_manager_t *this) */ static u_int put_entry(private_ike_sa_manager_t *this, entry_t *entry) { - linked_list_t *list; + table_item_t *current, *item; u_int row, segment; + INIT(item, + .value = entry, + ); + row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask; segment = row & this->segment_mask; lock_single_segment(this, segment); - list = this->ike_sa_table[row]; - if (!list) - { - list = this->ike_sa_table[row] = linked_list_create(); + current = this->ike_sa_table[row]; + if (current) + { /* insert at the front of current bucket */ + item->next = current; } - list->insert_last(list, entry); + this->ike_sa_table[row] = item; this->segments[segment].count++; return segment; } @@ -569,28 +591,30 @@ static u_int put_entry(private_ike_sa_manager_t *this, entry_t *entry) */ static void remove_entry(private_ike_sa_manager_t *this, entry_t *entry) { - linked_list_t *list; + table_item_t *item, *prev = NULL; u_int row, segment; row = ike_sa_id_hash(entry->ike_sa_id) & this->table_mask; segment = row & this->segment_mask; - list = this->ike_sa_table[row]; - if (list) + item = this->ike_sa_table[row]; + while (item) { - entry_t *current; - enumerator_t *enumerator; - - enumerator = list->create_enumerator(list); - while (enumerator->enumerate(enumerator, ¤t)) + if (item->value == entry) { - if (current == entry) + if (prev) { - list->remove_at(list, enumerator); - this->segments[segment].count--; - break; + prev->next = item->next; } + else + { + this->ike_sa_table[row] = item->next; + } + this->segments[segment].count--; + free(item); + break; } - enumerator->destroy(enumerator); + prev = item; + item = item->next; } } @@ -602,9 +626,21 @@ static void remove_entry_at(private_enumerator_t *this) this->entry = NULL; if (this->current) { - linked_list_t *list = this->manager->ike_sa_table[this->row]; - list->remove_at(list, this->current); + table_item_t *current = this->current; + this->manager->segments[this->segment].count--; + this->current = this->prev; + + if (this->prev) + { + this->prev->next = current->next; + } + else + { + this->manager->ike_sa_table[this->row] = current->next; + unlock_single_segment(this->manager, this->segment); + } + free(current); } } @@ -614,26 +650,26 @@ static void remove_entry_at(private_enumerator_t *this) */ static status_t get_entry_by_match_function(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, entry_t **entry, u_int *segment, - linked_list_match_t match, void *p1, void *p2) + linked_list_match_t match, void *param) { - entry_t *current; - linked_list_t *list; + table_item_t *item; u_int row, seg; row = ike_sa_id_hash(ike_sa_id) & this->table_mask; seg = row & this->segment_mask; lock_single_segment(this, seg); - list = this->ike_sa_table[row]; - if (list) + item = this->ike_sa_table[row]; + while (item) { - if (list->find_first(list, match, (void**)¤t, p1, p2) == SUCCESS) + if (match(item->value, param)) { - *entry = current; + *entry = item->value; *segment = seg; /* the locked segment has to be unlocked by the caller */ return SUCCESS; } + item = item->next; } unlock_single_segment(this, seg); return NOT_FOUND; @@ -647,18 +683,7 @@ static status_t get_entry_by_id(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, entry_t **entry, u_int *segment) { return get_entry_by_match_function(this, ike_sa_id, entry, segment, - (linked_list_match_t)entry_match_by_id, ike_sa_id, NULL); -} - -/** - * Find an entry by initiator SPI and IKE_SA_INIT hash. - * Note: On SUCCESS, the caller has to unlock the segment. - */ -static status_t get_entry_by_hash(private_ike_sa_manager_t *this, - ike_sa_id_t *ike_sa_id, chunk_t hash, entry_t **entry, u_int *segment) -{ - return get_entry_by_match_function(this, ike_sa_id, entry, segment, - (linked_list_match_t)entry_match_by_hash, ike_sa_id, &hash); + (linked_list_match_t)entry_match_by_id, ike_sa_id); } /** @@ -669,7 +694,7 @@ static status_t get_entry_by_sa(private_ike_sa_manager_t *this, ike_sa_id_t *ike_sa_id, ike_sa_t *ike_sa, entry_t **entry, u_int *segment) { return get_entry_by_match_function(this, ike_sa_id, entry, segment, - (linked_list_match_t)entry_match_by_sa, ike_sa, NULL); + (linked_list_match_t)entry_match_by_sa, ike_sa); } /** @@ -707,44 +732,43 @@ static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry, */ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry) { - half_open_t *half_open = NULL; - linked_list_t *list; - chunk_t addr; + table_item_t *item; u_int row, segment; rwlock_t *lock; + half_open_t *half_open; + chunk_t addr; addr = entry->other->get_address(entry->other); row = chunk_hash(addr) & this->table_mask; segment = row & this->segment_mask; lock = this->half_open_segments[segment].lock; lock->write_lock(lock); - list = this->half_open_table[row]; - if (list) + item = this->half_open_table[row]; + while (item) { - half_open_t *current; + half_open = item->value; - if (list->find_first(list, (linked_list_match_t)half_open_match, - (void**)¤t, &addr) == SUCCESS) + if (chunk_equals(addr, half_open->other)) { - half_open = current; half_open->count++; - this->half_open_segments[segment].count++; + break; } - } - else - { - list = this->half_open_table[row] = linked_list_create(); + item = item->next; } - if (!half_open) + if (!item) { INIT(half_open, .other = chunk_clone(addr), .count = 1, ); - list->insert_last(list, half_open); - this->half_open_segments[segment].count++; + INIT(item, + .value = half_open, + .next = this->half_open_table[row], + ); + this->half_open_table[row] = item; } + this->half_open_segments[segment].count++; lock->unlock(lock); } @@ -753,37 +777,41 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry) */ static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry) { - linked_list_t *list; - chunk_t addr; + table_item_t *item, *prev = NULL; u_int row, segment; rwlock_t *lock; + chunk_t addr; addr = entry->other->get_address(entry->other); row = chunk_hash(addr) & this->table_mask; segment = row & this->segment_mask; lock = this->half_open_segments[segment].lock; lock->write_lock(lock); - list = this->half_open_table[row]; - if (list) + item = this->half_open_table[row]; + while (item) { - half_open_t *current; - enumerator_t *enumerator; + half_open_t *half_open = item->value; - enumerator = list->create_enumerator(list); - while (enumerator->enumerate(enumerator, ¤t)) + if (chunk_equals(addr, half_open->other)) { - if (half_open_match(current, &addr)) + if (--half_open->count == 0) { - if (--current->count == 0) + if (prev) { - list->remove_at(list, enumerator); - half_open_destroy(current); + prev->next = item->next; } - this->half_open_segments[segment].count--; - break; + else + { + this->half_open_table[row] = item->next; + } + half_open_destroy(half_open); + free(item); } + this->half_open_segments[segment].count--; + break; } - enumerator->destroy(enumerator); + prev = item; + item = item->next; } lock->unlock(lock); } @@ -793,28 +821,28 @@ static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry) */ static void put_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) { - connected_peers_t *connected_peers = NULL; - chunk_t my_id, other_id; - linked_list_t *list; + table_item_t *item; u_int row, segment; rwlock_t *lock; + connected_peers_t *connected_peers; + chunk_t my_id, other_id; + int family; my_id = entry->my_id->get_encoding(entry->my_id); other_id = entry->other_id->get_encoding(entry->other_id); + family = entry->other->get_family(entry->other); row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask; segment = row & this->segment_mask; lock = this->connected_peers_segments[segment].lock; lock->write_lock(lock); - list = this->connected_peers_table[row]; - if (list) + item = this->connected_peers_table[row]; + while (item) { - connected_peers_t *current; + connected_peers = item->value; - if (list->find_first(list, (linked_list_match_t)connected_peers_match, - (void**)¤t, entry->my_id, entry->other_id, - (uintptr_t)entry->other->get_family(entry->other)) == SUCCESS) + if (connected_peers_match(connected_peers, entry->my_id, + entry->other_id, family)) { - connected_peers = current; if (connected_peers->sas->find_first(connected_peers->sas, (linked_list_match_t)entry->ike_sa_id->equals, NULL, entry->ike_sa_id) == SUCCESS) @@ -822,22 +850,24 @@ static void put_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) lock->unlock(lock); return; } + break; } - } - else - { - list = this->connected_peers_table[row] = linked_list_create(); + item = item->next; } - if (!connected_peers) + if (!item) { INIT(connected_peers, .my_id = entry->my_id->clone(entry->my_id), .other_id = entry->other_id->clone(entry->other_id), - .family = entry->other->get_family(entry->other), + .family = family, .sas = linked_list_create(), ); - list->insert_last(list, connected_peers); + INIT(item, + .value = connected_peers, + .next = this->connected_peers_table[row], + ); + this->connected_peers_table[row] = item; } connected_peers->sas->insert_last(connected_peers->sas, entry->ike_sa_id->clone(entry->ike_sa_id)); @@ -850,54 +880,61 @@ static void put_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) */ static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entry) { - chunk_t my_id, other_id; - linked_list_t *list; + table_item_t *item, *prev = NULL; u_int row, segment; rwlock_t *lock; + chunk_t my_id, other_id; + int family; my_id = entry->my_id->get_encoding(entry->my_id); other_id = entry->other_id->get_encoding(entry->other_id); + family = entry->other->get_family(entry->other); + row = chunk_hash_inc(other_id, chunk_hash(my_id)) & this->table_mask; segment = row & this->segment_mask; lock = this->connected_peers_segments[segment].lock; lock->write_lock(lock); - list = this->connected_peers_table[row]; - if (list) + item = this->connected_peers_table[row]; + while (item) { - connected_peers_t *current; - enumerator_t *enumerator; + connected_peers_t *current = item->value; - enumerator = list->create_enumerator(list); - while (enumerator->enumerate(enumerator, ¤t)) + if (connected_peers_match(current, entry->my_id, entry->other_id, + family)) { - if (connected_peers_match(current, entry->my_id, entry->other_id, - (uintptr_t)entry->other->get_family(entry->other))) - { - ike_sa_id_t *ike_sa_id; - enumerator_t *inner; + enumerator_t *enumerator; + ike_sa_id_t *ike_sa_id; - inner = current->sas->create_enumerator(current->sas); - while (inner->enumerate(inner, &ike_sa_id)) + enumerator = current->sas->create_enumerator(current->sas); + while (enumerator->enumerate(enumerator, &ike_sa_id)) + { + if (ike_sa_id->equals(ike_sa_id, entry->ike_sa_id)) { - if (ike_sa_id->equals(ike_sa_id, entry->ike_sa_id)) - { - current->sas->remove_at(current->sas, inner); - ike_sa_id->destroy(ike_sa_id); - this->connected_peers_segments[segment].count--; - break; - } + current->sas->remove_at(current->sas, enumerator); + ike_sa_id->destroy(ike_sa_id); + this->connected_peers_segments[segment].count--; + break; } - inner->destroy(inner); - if (current->sas->get_count(current->sas) == 0) + } + enumerator->destroy(enumerator); + if (current->sas->get_count(current->sas) == 0) + { + if (prev) { - list->remove_at(list, enumerator); - connected_peers_destroy(current); + prev->next = item->next; } - break; + else + { + this->connected_peers_table[row] = item->next; + } + connected_peers_destroy(current); + free(item); } + break; } - enumerator->destroy(enumerator); + prev = item; + item = item->next; } lock->unlock(lock); } @@ -907,13 +944,143 @@ static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entr */ static u_int64_t get_spi(private_ike_sa_manager_t *this) { - u_int64_t spi = 0; + u_int64_t spi; + + if (this->rng && + this->rng->get_bytes(this->rng, sizeof(spi), (u_int8_t*)&spi)) + { + return spi; + } + return 0; +} + +/** + * Calculate the hash of the initial IKE message. Memory for the hash is + * allocated on success. + * + * @returns TRUE on success + */ +static bool get_init_hash(private_ike_sa_manager_t *this, message_t *message, + chunk_t *hash) +{ + if (!this->hasher) + { /* this might be the case when flush() has been called */ + return FALSE; + } + if (message->get_exchange_type(message) == ID_PROT) + { /* include the source for Main Mode as the hash will be the same if + * SPIs are reused by two initiators that use the same proposal */ + host_t *src = message->get_source(message); + + if (!this->hasher->allocate_hash(this->hasher, + src->get_address(src), NULL)) + { + return FALSE; + } + } + return this->hasher->allocate_hash(this->hasher, + message->get_packet_data(message), hash); +} + +/** + * Check if we already have created an IKE_SA based on the initial IKE message + * with the given hash. + * If not the hash is stored, the hash data is not(!) cloned. + * + * Also, the local SPI is returned. In case of a retransmit this is already + * stored together with the hash, otherwise it is newly allocated and should + * be used to create the IKE_SA. + * + * @returns ALREADY_DONE if the message with the given hash has been seen before + * NOT_FOUND if the message hash was not found + * FAILED if the SPI allocation failed + */ +static status_t check_and_put_init_hash(private_ike_sa_manager_t *this, + chunk_t init_hash, u_int64_t *our_spi) +{ + table_item_t *item; + u_int row, segment; + mutex_t *mutex; + init_hash_t *init; + u_int64_t spi; - if (this->rng) + row = chunk_hash(init_hash) & this->table_mask; + segment = row & this->segment_mask; + mutex = this->init_hashes_segments[segment].mutex; + mutex->lock(mutex); + item = this->init_hashes_table[row]; + while (item) { - this->rng->get_bytes(this->rng, sizeof(spi), (u_int8_t*)&spi); + init_hash_t *current = item->value; + + if (chunk_equals(init_hash, current->hash)) + { + *our_spi = current->our_spi; + mutex->unlock(mutex); + return ALREADY_DONE; + } + item = item->next; + } + + spi = get_spi(this); + if (!spi) + { + return FAILED; } - return spi; + + INIT(init, + .hash = { + .len = init_hash.len, + .ptr = init_hash.ptr, + }, + .our_spi = spi, + ); + INIT(item, + .value = init, + .next = this->init_hashes_table[row], + ); + this->init_hashes_table[row] = item; + *our_spi = init->our_spi; + mutex->unlock(mutex); + return NOT_FOUND; +} + +/** + * Remove the hash of an initial IKE message from the cache. + */ +static void remove_init_hash(private_ike_sa_manager_t *this, chunk_t init_hash) +{ + table_item_t *item, *prev = NULL; + u_int row, segment; + mutex_t *mutex; + + row = chunk_hash(init_hash) & this->table_mask; + segment = row & this->segment_mask; + mutex = this->init_hashes_segments[segment].mutex; + mutex->lock(mutex); + item = this->init_hashes_table[row]; + while (item) + { + init_hash_t *current = item->value; + + if (chunk_equals(init_hash, current->hash)) + { + if (prev) + { + prev->next = item->next; + } + else + { + this->init_hashes_table[row] = item->next; + } + free(current); + free(item); + break; + } + prev = item; + item = item->next; + } + mutex->unlock(mutex); } METHOD(ike_sa_manager_t, checkout, ike_sa_t*, @@ -941,25 +1108,38 @@ METHOD(ike_sa_manager_t, checkout, ike_sa_t*, } METHOD(ike_sa_manager_t, checkout_new, ike_sa_t*, - private_ike_sa_manager_t* this, bool initiator) + private_ike_sa_manager_t* this, ike_version_t version, bool initiator) { ike_sa_id_t *ike_sa_id; ike_sa_t *ike_sa; + u_int8_t ike_version; + u_int64_t spi; + + ike_version = version == IKEV1 ? IKEV1_MAJOR_VERSION : IKEV2_MAJOR_VERSION; + + spi = get_spi(this); + if (!spi) + { + DBG1(DBG_MGR, "failed to allocate SPI for new IKE_SA"); + return NULL; + } if (initiator) { - ike_sa_id = ike_sa_id_create(get_spi(this), 0, TRUE); + ike_sa_id = ike_sa_id_create(ike_version, spi, 0, TRUE); } else { - ike_sa_id = ike_sa_id_create(0, get_spi(this), FALSE); + ike_sa_id = ike_sa_id_create(ike_version, 0, spi, FALSE); } - ike_sa = ike_sa_create(ike_sa_id); + ike_sa = ike_sa_create(ike_sa_id, initiator, version); ike_sa_id->destroy(ike_sa_id); - DBG2(DBG_MGR, "created IKE_SA %s[%u]", ike_sa->get_name(ike_sa), - ike_sa->get_unique_id(ike_sa)); - + if (ike_sa) + { + DBG2(DBG_MGR, "created IKE_SA %s[%u]", ike_sa->get_name(ike_sa), + ike_sa->get_unique_id(ike_sa)); + } return ike_sa; } @@ -970,94 +1150,118 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*, entry_t *entry; ike_sa_t *ike_sa = NULL; ike_sa_id_t *id; + ike_version_t ike_version; + bool is_init = FALSE; id = message->get_ike_sa_id(message); + /* clone the IKE_SA ID so we can modify the initiator flag */ id = id->clone(id); id->switch_initiator(id); DBG2(DBG_MGR, "checkout IKE_SA by message"); - if (message->get_request(message) && - message->get_exchange_type(message) == IKE_SA_INIT && - this->hasher) + if (id->get_responder_spi(id) == 0) { - /* IKE_SA_INIT request. Check for an IKE_SA with such a message hash. */ - chunk_t data, hash; - - data = message->get_packet_data(message); - this->hasher->allocate_hash(this->hasher, data, &hash); - chunk_free(&data); - - if (get_entry_by_hash(this, id, hash, &entry, &segment) == SUCCESS) + if (message->get_major_version(message) == IKEV2_MAJOR_VERSION) { - if (entry->message_id == 0) + if (message->get_exchange_type(message) == IKE_SA_INIT && + message->get_request(message)) { - unlock_single_segment(this, segment); - chunk_free(&hash); - id->destroy(id); - DBG1(DBG_MGR, "ignoring IKE_SA_INIT, already processing"); - return NULL; + ike_version = IKEV2; + is_init = TRUE; } - else if (wait_for_entry(this, entry, segment)) + } + else + { + if (message->get_exchange_type(message) == ID_PROT || + message->get_exchange_type(message) == AGGRESSIVE) { - entry->checked_out = TRUE; - entry->message_id = message->get_message_id(message); - ike_sa = entry->ike_sa; - DBG2(DBG_MGR, "IKE_SA %s[%u] checked out by hash", - ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa)); + ike_version = IKEV1; + is_init = TRUE; + if (id->is_initiator(id)) + { /* not set in IKEv1, switch back before applying to new SA */ + id->switch_initiator(id); + } } - unlock_single_segment(this, segment); + } + } + + if (is_init) + { + u_int64_t our_spi; + chunk_t hash; + + if (!get_init_hash(this, message, &hash)) + { + DBG1(DBG_MGR, "ignoring message, failed to hash message"); + id->destroy(id); + return NULL; } - if (ike_sa == NULL) + /* ensure this is not a retransmit of an already handled init message */ + switch (check_and_put_init_hash(this, hash, &our_spi)) { - if (id->get_responder_spi(id) == 0 && - message->get_exchange_type(message) == IKE_SA_INIT) - { - /* no IKE_SA found, create a new one */ - id->set_responder_spi(id, get_spi(this)); - entry = entry_create(); - entry->ike_sa = ike_sa_create(id); - entry->ike_sa_id = id->clone(id); + case NOT_FOUND: + { /* we've not seen this packet yet, create a new IKE_SA */ + id->set_responder_spi(id, our_spi); + ike_sa = ike_sa_create(id, FALSE, ike_version); + if (ike_sa) + { + entry = entry_create(); + entry->ike_sa = ike_sa; + entry->ike_sa_id = id->clone(id); - segment = put_entry(this, entry); - entry->checked_out = TRUE; - unlock_single_segment(this, segment); + segment = put_entry(this, entry); + entry->checked_out = TRUE; + unlock_single_segment(this, segment); - entry->message_id = message->get_message_id(message); - entry->init_hash = hash; - ike_sa = entry->ike_sa; + entry->message_id = message->get_message_id(message); + entry->init_hash = hash; - DBG2(DBG_MGR, "created IKE_SA %s[%u]", - ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa)); + DBG2(DBG_MGR, "created IKE_SA %s[%u]", + ike_sa->get_name(ike_sa), + ike_sa->get_unique_id(ike_sa)); + } + else + { + remove_init_hash(this, hash); + chunk_free(&hash); + DBG1(DBG_MGR, "ignoring message, no such IKE_SA"); + } + id->destroy(id); + charon->bus->set_sa(charon->bus, ike_sa); + return ike_sa; } - else - { + case FAILED: + { /* we failed to allocate an SPI */ chunk_free(&hash); - DBG1(DBG_MGR, "ignoring message, no such IKE_SA"); + id->destroy(id); + DBG1(DBG_MGR, "ignoring message, failed to allocate SPI"); + return NULL; } + case ALREADY_DONE: + default: + break; } - else - { - chunk_free(&hash); - } - id->destroy(id); - charon->bus->set_sa(charon->bus, ike_sa); - return ike_sa; + /* it looks like we already handled this init message to some degree */ + id->set_responder_spi(id, our_spi); + chunk_free(&hash); } if (get_entry_by_id(this, id, &entry, &segment) == SUCCESS) { - /* only check out if we are not processing this request */ + /* only check out in IKEv2 if we are not already processing it */ if (message->get_request(message) && message->get_message_id(message) == entry->message_id) { - DBG1(DBG_MGR, "ignoring request with ID %d, already processing", + DBG1(DBG_MGR, "ignoring request with ID %u, already processing", entry->message_id); } else if (wait_for_entry(this, entry, segment)) { - ike_sa_id_t *ike_id = entry->ike_sa->get_id(entry->ike_sa); + ike_sa_id_t *ike_id; + + ike_id = entry->ike_sa->get_id(entry->ike_sa); entry->checked_out = TRUE; entry->message_id = message->get_message_id(message); if (ike_id->get_responder_spi(ike_id) == 0) @@ -1089,7 +1293,7 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*, if (!this->reuse_ikesa) { /* IKE_SA reuse disable by config */ - ike_sa = checkout_new(this, TRUE); + ike_sa = checkout_new(this, peer_cfg->get_ike_version(peer_cfg), TRUE); charon->bus->set_sa(charon->bus, ike_sa); return ike_sa; } @@ -1125,7 +1329,7 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*, if (!ike_sa) { /* no IKE_SA using such a config, hand out a new */ - ike_sa = checkout_new(this, TRUE); + ike_sa = checkout_new(this, peer_cfg->get_ike_version(peer_cfg), TRUE); } charon->bus->set_sa(charon->bus, ike_sa); return ike_sa; @@ -1244,6 +1448,7 @@ static bool enumerator_filter_wait(private_ike_sa_manager_t *this, if (wait_for_entry(this, *in, *segment)) { *out = (*in)->ike_sa; + charon->bus->set_sa(charon->bus, *out); return TRUE; } return FALSE; @@ -1260,17 +1465,26 @@ static bool enumerator_filter_skip(private_ike_sa_manager_t *this, !(*in)->checked_out) { *out = (*in)->ike_sa; + charon->bus->set_sa(charon->bus, *out); return TRUE; } return FALSE; } +/** + * Reset threads SA after enumeration + */ +static void reset_sa(void *data) +{ + charon->bus->set_sa(charon->bus, NULL); +} + METHOD(ike_sa_manager_t, create_enumerator, enumerator_t*, private_ike_sa_manager_t* this, bool wait) { return enumerator_create_filter(create_table_enumerator(this), wait ? (void*)enumerator_filter_wait : (void*)enumerator_filter_skip, - this, NULL); + this, reset_sa); } METHOD(ike_sa_manager_t, checkin, void, @@ -1290,7 +1504,7 @@ METHOD(ike_sa_manager_t, checkin, void, ike_sa_id = ike_sa->get_id(ike_sa); my_id = ike_sa->get_my_id(ike_sa); - other_id = ike_sa->get_other_id(ike_sa); + other_id = ike_sa->get_other_eap_id(ike_sa); other = ike_sa->get_other_host(ike_sa); DBG2(DBG_MGR, "checkin IKE_SA %s[%u]", ike_sa->get_name(ike_sa), @@ -1340,9 +1554,21 @@ METHOD(ike_sa_manager_t, checkin, void, } /* apply identities for duplicate test */ - if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED && + if ((ike_sa->get_state(ike_sa) == IKE_ESTABLISHED || + ike_sa->get_state(ike_sa) == IKE_PASSIVE) && entry->my_id == NULL && entry->other_id == NULL) { + if (ike_sa->get_version(ike_sa) == IKEV1) + { + /* If authenticated and received INITIAL_CONTACT, + * delete any existing IKE_SAs with that peer. */ + if (ike_sa->has_condition(ike_sa, COND_INIT_CONTACT_SEEN)) + { + this->public.check_uniqueness(&this->public, ike_sa, TRUE); + ike_sa->set_condition(ike_sa, COND_INIT_CONTACT_SEEN, FALSE); + } + } + entry->my_id = my_id->clone(my_id); entry->other_id = other_id->clone(other_id); if (!entry->other) @@ -1376,6 +1602,16 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void, if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS) { + if (entry->driveout_waiting_threads && entry->driveout_new_threads) + { /* it looks like flush() has been called and the SA is being deleted + * anyway, just check it in */ + DBG2(DBG_MGR, "ignored check-in and destroy of IKE_SA during shutdown"); + entry->checked_out = FALSE; + entry->condvar->broadcast(entry->condvar); + unlock_single_segment(this, segment); + return; + } + /* drive out waiting threads, as we are in hurry */ entry->driveout_waiting_threads = TRUE; /* mark it, so no new threads can get this entry */ @@ -1399,6 +1635,10 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void, { remove_connected_peers(this, entry); } + if (entry->init_hash.ptr) + { + remove_init_hash(this, entry->init_hash); + } entry_destroy(entry); @@ -1412,65 +1652,81 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void, charon->bus->set_sa(charon->bus, NULL); } -METHOD(ike_sa_manager_t, check_uniqueness, bool, - private_ike_sa_manager_t *this, ike_sa_t *ike_sa, bool force_replace) +/** + * Cleanup function for create_id_enumerator + */ +static void id_enumerator_cleanup(linked_list_t *ids) { - bool cancel = FALSE; - peer_cfg_t *peer_cfg; - unique_policy_t policy; - linked_list_t *list, *duplicate_ids = NULL; - enumerator_t *enumerator; - ike_sa_id_t *duplicate_id = NULL; - identification_t *me, *other; + ids->destroy_offset(ids, offsetof(ike_sa_id_t, destroy)); +} + +METHOD(ike_sa_manager_t, create_id_enumerator, enumerator_t*, + private_ike_sa_manager_t *this, identification_t *me, + identification_t *other, int family) +{ + table_item_t *item; u_int row, segment; rwlock_t *lock; - - peer_cfg = ike_sa->get_peer_cfg(ike_sa); - policy = peer_cfg->get_unique_policy(peer_cfg); - if (policy == UNIQUE_NO && !force_replace) - { - return FALSE; - } - - me = ike_sa->get_my_id(ike_sa); - other = ike_sa->get_other_id(ike_sa); + linked_list_t *ids = NULL; row = chunk_hash_inc(other->get_encoding(other), chunk_hash(me->get_encoding(me))) & this->table_mask; segment = row & this->segment_mask; - lock = this->connected_peers_segments[segment & this->segment_mask].lock; + lock = this->connected_peers_segments[segment].lock; lock->read_lock(lock); - list = this->connected_peers_table[row]; - if (list) + item = this->connected_peers_table[row]; + while (item) { - connected_peers_t *current; - host_t *other_host; + connected_peers_t *current = item->value; - other_host = ike_sa->get_other_host(ike_sa); - if (list->find_first(list, (linked_list_match_t)connected_peers_match, - (void**)¤t, me, other, - (uintptr_t)other_host->get_family(other_host)) == SUCCESS) + if (connected_peers_match(current, me, other, family)) { - /* clone the list, so we can release the lock */ - duplicate_ids = current->sas->clone_offset(current->sas, - offsetof(ike_sa_id_t, clone)); + ids = current->sas->clone_offset(current->sas, + offsetof(ike_sa_id_t, clone)); + break; } + item = item->next; } lock->unlock(lock); - if (!duplicate_ids) + if (!ids) + { + return enumerator_create_empty(); + } + return enumerator_create_cleaner(ids->create_enumerator(ids), + (void*)id_enumerator_cleanup, ids); +} + +METHOD(ike_sa_manager_t, check_uniqueness, bool, + private_ike_sa_manager_t *this, ike_sa_t *ike_sa, bool force_replace) +{ + bool cancel = FALSE; + peer_cfg_t *peer_cfg; + unique_policy_t policy; + enumerator_t *enumerator; + ike_sa_id_t *id = NULL; + identification_t *me, *other; + host_t *other_host; + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + policy = peer_cfg->get_unique_policy(peer_cfg); + if (policy == UNIQUE_NEVER || (policy == UNIQUE_NO && !force_replace)) { return FALSE; } + me = ike_sa->get_my_id(ike_sa); + other = ike_sa->get_other_eap_id(ike_sa); + other_host = ike_sa->get_other_host(ike_sa); - enumerator = duplicate_ids->create_enumerator(duplicate_ids); - while (enumerator->enumerate(enumerator, &duplicate_id)) + enumerator = create_id_enumerator(this, me, other, + other_host->get_family(other_host)); + while (enumerator->enumerate(enumerator, &id)) { status_t status = SUCCESS; ike_sa_t *duplicate; - duplicate = checkout(this, duplicate_id); + duplicate = checkout(this, id); if (!duplicate) { continue; @@ -1520,7 +1776,6 @@ METHOD(ike_sa_manager_t, check_uniqueness, bool, } } enumerator->destroy(enumerator); - duplicate_ids->destroy_offset(duplicate_ids, offsetof(ike_sa_id_t, destroy)); /* reset thread's current IKE_SA after checkin */ charon->bus->set_sa(charon->bus, ike_sa); return cancel; @@ -1530,7 +1785,7 @@ METHOD(ike_sa_manager_t, has_contact, bool, private_ike_sa_manager_t *this, identification_t *me, identification_t *other, int family) { - linked_list_t *list; + table_item_t *item; u_int row, segment; rwlock_t *lock; bool found = FALSE; @@ -1538,16 +1793,17 @@ METHOD(ike_sa_manager_t, has_contact, bool, row = chunk_hash_inc(other->get_encoding(other), chunk_hash(me->get_encoding(me))) & this->table_mask; segment = row & this->segment_mask; - lock = this->connected_peers_segments[segment & this->segment_mask].lock; + lock = this->connected_peers_segments[segment].lock; lock->read_lock(lock); - list = this->connected_peers_table[row]; - if (list) + item = this->connected_peers_table[row]; + while (item) { - if (list->find_first(list, (linked_list_match_t)connected_peers_match, - NULL, me, other, family) == SUCCESS) + if (connected_peers_match(item->value, me, other, family)) { found = TRUE; + break; } + item = item->next; } lock->unlock(lock); @@ -1573,8 +1829,8 @@ METHOD(ike_sa_manager_t, get_count, u_int, METHOD(ike_sa_manager_t, get_half_open_count, u_int, private_ike_sa_manager_t *this, host_t *ip) { - linked_list_t *list; - u_int segment, row; + table_item_t *item; + u_int row, segment; rwlock_t *lock; chunk_t addr; u_int count = 0; @@ -1584,17 +1840,19 @@ METHOD(ike_sa_manager_t, get_half_open_count, u_int, addr = ip->get_address(ip); row = chunk_hash(addr) & this->table_mask; segment = row & this->segment_mask; - lock = this->half_open_segments[segment & this->segment_mask].lock; + lock = this->half_open_segments[segment].lock; lock->read_lock(lock); - if ((list = this->half_open_table[row]) != NULL) + item = this->half_open_table[row]; + while (item) { - half_open_t *current; + half_open_t *half_open = item->value; - if (list->find_first(list, (linked_list_match_t)half_open_match, - (void**)¤t, &addr) == SUCCESS) + if (chunk_equals(addr, half_open->other)) { - count = current->count; + count = half_open->count; + break; } + item = item->next; } lock->unlock(lock); } @@ -1602,7 +1860,7 @@ METHOD(ike_sa_manager_t, get_half_open_count, u_int, { for (segment = 0; segment < this->segment_count; segment++) { - lock = this->half_open_segments[segment & this->segment_mask].lock; + lock = this->half_open_segments[segment].lock; lock->read_lock(lock); count += this->half_open_segments[segment].count; lock->unlock(lock); @@ -1651,16 +1909,18 @@ METHOD(ike_sa_manager_t, flush, void, while (enumerator->enumerate(enumerator, &entry, &segment)) { charon->bus->set_sa(charon->bus, entry->ike_sa); - /* as the delete never gets processed, fire down events */ - switch (entry->ike_sa->get_state(entry->ike_sa)) - { - case IKE_ESTABLISHED: - case IKE_REKEYING: - case IKE_DELETING: - charon->bus->ike_updown(charon->bus, entry->ike_sa, FALSE); - break; - default: - break; + if (entry->ike_sa->get_version(entry->ike_sa) == IKEV2) + { /* as the delete never gets processed, fire down events */ + switch (entry->ike_sa->get_state(entry->ike_sa)) + { + case IKE_ESTABLISHED: + case IKE_REKEYING: + case IKE_DELETING: + charon->bus->ike_updown(charon->bus, entry->ike_sa, FALSE); + break; + default: + break; + } } entry->ike_sa->delete(entry->ike_sa); } @@ -1680,6 +1940,10 @@ METHOD(ike_sa_manager_t, flush, void, { remove_connected_peers(this, entry); } + if (entry->init_hash.ptr) + { + remove_init_hash(this, entry->init_hash); + } remove_entry_at((private_enumerator_t*)enumerator); entry_destroy(entry); } @@ -1698,24 +1962,22 @@ METHOD(ike_sa_manager_t, destroy, void, { u_int i; - for (i = 0; i < this->table_size; i++) - { - DESTROY_IF(this->ike_sa_table[i]); - DESTROY_IF(this->half_open_table[i]); - DESTROY_IF(this->connected_peers_table[i]); - } + /* these are already cleared in flush() above */ free(this->ike_sa_table); free(this->half_open_table); free(this->connected_peers_table); + free(this->init_hashes_table); for (i = 0; i < this->segment_count; i++) { this->segments[i].mutex->destroy(this->segments[i].mutex); this->half_open_segments[i].lock->destroy(this->half_open_segments[i].lock); this->connected_peers_segments[i].lock->destroy(this->connected_peers_segments[i].lock); + this->init_hashes_segments[i].mutex->destroy(this->init_hashes_segments[i].mutex); } free(this->segments); free(this->half_open_segments); free(this->connected_peers_segments); + free(this->init_hashes_segments); free(this); } @@ -1757,6 +2019,7 @@ ike_sa_manager_t *ike_sa_manager_create() .check_uniqueness = _check_uniqueness, .has_contact = _has_contact, .create_enumerator = _create_enumerator, + .create_id_enumerator = _create_id_enumerator, .checkin = _checkin, .checkin_and_destroy = _checkin_and_destroy, .get_count = _get_count, @@ -1782,17 +2045,19 @@ ike_sa_manager_t *ike_sa_manager_create() return NULL; } - this->table_size = get_nearest_powerof2(lib->settings->get_int(lib->settings, - "charon.ikesa_table_size", DEFAULT_HASHTABLE_SIZE)); + this->table_size = get_nearest_powerof2(lib->settings->get_int( + lib->settings, "%s.ikesa_table_size", + DEFAULT_HASHTABLE_SIZE, charon->name)); this->table_size = max(1, min(this->table_size, MAX_HASHTABLE_SIZE)); this->table_mask = this->table_size - 1; - this->segment_count = get_nearest_powerof2(lib->settings->get_int(lib->settings, - "charon.ikesa_table_segments", DEFAULT_SEGMENT_COUNT)); + this->segment_count = get_nearest_powerof2(lib->settings->get_int( + lib->settings, "%s.ikesa_table_segments", + DEFAULT_SEGMENT_COUNT, charon->name)); this->segment_count = max(1, min(this->segment_count, this->table_size)); this->segment_mask = this->segment_count - 1; - this->ike_sa_table = calloc(this->table_size, sizeof(linked_list_t*)); + this->ike_sa_table = calloc(this->table_size, sizeof(table_item_t*)); this->segments = (segment_t*)calloc(this->segment_count, sizeof(segment_t)); for (i = 0; i < this->segment_count; i++) { @@ -1801,7 +2066,7 @@ ike_sa_manager_t *ike_sa_manager_create() } /* we use the same table parameters for the table to track half-open SAs */ - this->half_open_table = calloc(this->table_size, sizeof(linked_list_t*)); + this->half_open_table = calloc(this->table_size, sizeof(table_item_t*)); this->half_open_segments = calloc(this->segment_count, sizeof(shareable_segment_t)); for (i = 0; i < this->segment_count; i++) { @@ -1810,7 +2075,7 @@ ike_sa_manager_t *ike_sa_manager_create() } /* also for the hash table used for duplicate tests */ - this->connected_peers_table = calloc(this->table_size, sizeof(linked_list_t*)); + this->connected_peers_table = calloc(this->table_size, sizeof(table_item_t*)); this->connected_peers_segments = calloc(this->segment_count, sizeof(shareable_segment_t)); for (i = 0; i < this->segment_count; i++) { @@ -1818,7 +2083,16 @@ ike_sa_manager_t *ike_sa_manager_create() this->connected_peers_segments[i].count = 0; } + /* and again for the table of hashes of seen initial IKE messages */ + this->init_hashes_table = calloc(this->table_size, sizeof(table_item_t*)); + this->init_hashes_segments = calloc(this->segment_count, sizeof(segment_t)); + for (i = 0; i < this->segment_count; i++) + { + this->init_hashes_segments[i].mutex = mutex_create(MUTEX_TYPE_RECURSIVE); + this->init_hashes_segments[i].count = 0; + } + this->reuse_ikesa = lib->settings->get_bool(lib->settings, - "charon.reuse_ikesa", TRUE); + "%s.reuse_ikesa", TRUE, charon->name); return &this->public; } diff --git a/src/libcharon/sa/ike_sa_manager.h b/src/libcharon/sa/ike_sa_manager.h index 5e542e7df..a68ae7763 100644 --- a/src/libcharon/sa/ike_sa_manager.h +++ b/src/libcharon/sa/ike_sa_manager.h @@ -52,10 +52,12 @@ struct ike_sa_manager_t { /** * Create and check out a new IKE_SA. * + * @param version IKE version of this SA * @param initiator TRUE for initiator, FALSE otherwise * @returns created and checked out IKE_SA */ - ike_sa_t* (*checkout_new) (ike_sa_manager_t* this, bool initiator); + ike_sa_t* (*checkout_new) (ike_sa_manager_t* this, ike_version_t version, + bool initiator); /** * Checkout an IKE_SA by a message. @@ -168,6 +170,20 @@ struct ike_sa_manager_t { enumerator_t *(*create_enumerator) (ike_sa_manager_t* this, bool wait); /** + * Create an enumerator over ike_sa_id_t*, matching peer identities. + * + * The remote peer is identified by its XAuth or EAP identity, if available. + * + * @param me local peer identity to match + * @param other remote peer identity to match + * @param family address family to match, 0 for any + * @return enumerator over ike_sa_id_t* + */ + enumerator_t* (*create_id_enumerator)(ike_sa_manager_t *this, + identification_t *me, identification_t *other, + int family); + + /** * Checkin the SA after usage. * * If the IKE_SA is not registered in the manager, a new entry is created. diff --git a/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c new file mode 100644 index 000000000..689f5f376 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "hybrid_authenticator.h" + +#include <daemon.h> +#include <sa/ikev1/authenticators/psk_v1_authenticator.h> + +typedef struct private_hybrid_authenticator_t private_hybrid_authenticator_t; + +/** + * Private data of an hybrid_authenticator_t object. + */ +struct private_hybrid_authenticator_t { + + /** + * Public authenticator_t interface. + */ + hybrid_authenticator_t public; + + /** + * Public key authenticator + */ + authenticator_t *sig; + + /** + * HASH payload authenticator without credentials + */ + authenticator_t *hash; +}; + +METHOD(authenticator_t, build_i, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->hash->build(this->hash, message); +} + +METHOD(authenticator_t, process_r, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->hash->process(this->hash, message); +} + +METHOD(authenticator_t, build_r, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->sig->build(this->sig, message); +} + +METHOD(authenticator_t, process_i, status_t, + private_hybrid_authenticator_t *this, message_t *message) +{ + return this->sig->process(this->sig, message); +} + +METHOD(authenticator_t, destroy, void, + private_hybrid_authenticator_t *this) +{ + DESTROY_IF(this->hash); + DESTROY_IF(this->sig); + free(this); +} + +/* + * Described in header. + */ +hybrid_authenticator_t *hybrid_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload) +{ + private_hybrid_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .hash = (authenticator_t*)psk_v1_authenticator_create(ike_sa, initiator, + dh, dh_value, sa_payload, id_payload, TRUE), + .sig = authenticator_create_v1(ike_sa, initiator, AUTH_RSA, dh, + dh_value, sa_payload, chunk_clone(id_payload)), + ); + if (!this->sig || !this->hash) + { + destroy(this); + return NULL; + } + if (initiator) + { + this->public.authenticator.build = _build_i; + this->public.authenticator.process = _process_i; + } + else + { + this->public.authenticator.build = _build_r; + this->public.authenticator.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h new file mode 100644 index 000000000..69e596959 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/hybrid_authenticator.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup hybrid_authenticator hybrid_authenticator + * @{ @ingroup authenticators_v1 + */ + +#ifndef HYBRID_AUTHENTICATOR_H_ +#define HYBRID_AUTHENTICATOR_H_ + +typedef struct hybrid_authenticator_t hybrid_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using IKEv1 hybrid authentication. + */ +struct hybrid_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build hybrid signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are the IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @return hybrid authenticator + */ +hybrid_authenticator_t *hybrid_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload); + +#endif /** HYBRID_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c new file mode 100644 index 000000000..ee15408c7 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "psk_v1_authenticator.h" + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_psk_v1_authenticator_t private_psk_v1_authenticator_t; + +/** + * Private data of an psk_v1_authenticator_t object. + */ +struct private_psk_v1_authenticator_t { + + /** + * Public authenticator_t interface. + */ + psk_v1_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiator + */ + bool initiator; + + /** + * DH key exchange + */ + diffie_hellman_t *dh; + + /** + * Others DH public value + */ + chunk_t dh_value; + + /** + * Encoded SA payload, without fixed header + */ + chunk_t sa_payload; + + /** + * Encoded ID payload, without fixed header + */ + chunk_t id_payload; + + /** + * Used for Hybrid authentication to build hash without PSK? + */ + bool hybrid; +}; + +METHOD(authenticator_t, build, status_t, + private_psk_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *hash_payload; + keymat_v1_t *keymat; + chunk_t hash, dh; + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_hash(keymat, this->initiator, dh, this->dh_value, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload, &hash)) + { + free(dh.ptr); + return FAILED; + } + free(dh.ptr); + + hash_payload = hash_payload_create(HASH_V1); + hash_payload->set_hash(hash_payload, hash); + message->add_payload(message, &hash_payload->payload_interface); + free(hash.ptr); + + return SUCCESS; +} + +METHOD(authenticator_t, process, status_t, + private_psk_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *hash_payload; + keymat_v1_t *keymat; + chunk_t hash, dh; + auth_cfg_t *auth; + + hash_payload = (hash_payload_t*)message->get_payload(message, HASH_V1); + if (!hash_payload) + { + DBG1(DBG_IKE, "HASH payload missing in message"); + return FAILED; + } + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_hash(keymat, !this->initiator, this->dh_value, dh, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload, &hash)) + { + free(dh.ptr); + return FAILED; + } + free(dh.ptr); + if (chunk_equals(hash, hash_payload->get_hash(hash_payload))) + { + free(hash.ptr); + if (!this->hybrid) + { + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK); + } + return SUCCESS; + } + free(hash.ptr); + DBG1(DBG_IKE, "calculated HASH does not match HASH payload"); + return FAILED; +} + +METHOD(authenticator_t, destroy, void, + private_psk_v1_authenticator_t *this) +{ + chunk_free(&this->id_payload); + free(this); +} + +/* + * Described in header. + */ +psk_v1_authenticator_t *psk_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload, bool hybrid) +{ + private_psk_v1_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .build = _build, + .process = _process, + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .dh = dh, + .dh_value = dh_value, + .sa_payload = sa_payload, + .id_payload = id_payload, + .hybrid = hybrid, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h new file mode 100644 index 000000000..cc9e18ba1 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup psk_v1_authenticator psk_v1_authenticator + * @{ @ingroup authenticators_v1 + */ + +#ifndef PSK_V1_AUTHENTICATOR_H_ +#define PSK_V1_AUTHENTICATOR_H_ + +typedef struct psk_v1_authenticator_t psk_v1_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using pre-shared keys for IKEv1. + */ +struct psk_v1_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build PSK signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are the IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @param hybrid TRUE if used for hybrid authentication without PSK + * @return PSK authenticator + */ +psk_v1_authenticator_t *psk_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload, bool hybrid); + +#endif /** PSK_V1_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c new file mode 100644 index 000000000..d81c77f0d --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "pubkey_v1_authenticator.h" + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_pubkey_v1_authenticator_t private_pubkey_v1_authenticator_t; + +/** + * Private data of an pubkey_v1_authenticator_t object. + */ +struct private_pubkey_v1_authenticator_t { + + /** + * Public authenticator_t interface. + */ + pubkey_v1_authenticator_t public; + + /** + * Assigned IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiator + */ + bool initiator; + + /** + * DH key exchange + */ + diffie_hellman_t *dh; + + /** + * Others DH public value + */ + chunk_t dh_value; + + /** + * Encoded SA payload, without fixed header + */ + chunk_t sa_payload; + + /** + * Encoded ID payload, without fixed header + */ + chunk_t id_payload; + + /** + * Key type to use + */ + key_type_t type; +}; + +METHOD(authenticator_t, build, status_t, + private_pubkey_v1_authenticator_t *this, message_t *message) +{ + hash_payload_t *sig_payload; + chunk_t hash, sig, dh; + keymat_v1_t *keymat; + status_t status; + private_key_t *private; + identification_t *id; + auth_cfg_t *auth; + signature_scheme_t scheme = SIGN_RSA_EMSA_PKCS1_NULL; + + if (this->type == KEY_ECDSA) + { + scheme = SIGN_ECDSA_WITH_NULL; + } + + id = this->ike_sa->get_my_id(this->ike_sa); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + private = lib->credmgr->get_private(lib->credmgr, this->type, id, auth); + if (!private) + { + DBG1(DBG_IKE, "no %N private key found for '%Y'", + key_type_names, this->type, id); + return NOT_FOUND; + } + + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_hash(keymat, this->initiator, dh, this->dh_value, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload, &hash)) + { + private->destroy(private); + free(dh.ptr); + return FAILED; + } + free(dh.ptr); + + if (private->sign(private, scheme, hash, &sig)) + { + sig_payload = hash_payload_create(SIGNATURE_V1); + sig_payload->set_hash(sig_payload, sig); + free(sig.ptr); + message->add_payload(message, &sig_payload->payload_interface); + status = SUCCESS; + DBG1(DBG_IKE, "authentication of '%Y' (myself) successful", id); + } + else + { + DBG1(DBG_IKE, "authentication of '%Y' (myself) failed", id); + status = FAILED; + } + private->destroy(private); + free(hash.ptr); + + return status; +} + +METHOD(authenticator_t, process, status_t, + private_pubkey_v1_authenticator_t *this, message_t *message) +{ + chunk_t hash, sig, dh; + keymat_v1_t *keymat; + public_key_t *public; + hash_payload_t *sig_payload; + auth_cfg_t *auth, *current_auth; + enumerator_t *enumerator; + status_t status = NOT_FOUND; + identification_t *id; + signature_scheme_t scheme = SIGN_RSA_EMSA_PKCS1_NULL; + + if (this->type == KEY_ECDSA) + { + scheme = SIGN_ECDSA_WITH_NULL; + } + + sig_payload = (hash_payload_t*)message->get_payload(message, SIGNATURE_V1); + if (!sig_payload) + { + DBG1(DBG_IKE, "SIG payload missing in message"); + return FAILED; + } + + id = this->ike_sa->get_other_id(this->ike_sa); + this->dh->get_my_public_value(this->dh, &dh); + keymat = (keymat_v1_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_hash(keymat, !this->initiator, this->dh_value, dh, + this->ike_sa->get_id(this->ike_sa), this->sa_payload, + this->id_payload, &hash)) + { + free(dh.ptr); + return FAILED; + } + free(dh.ptr); + + sig = sig_payload->get_hash(sig_payload); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, this->type, + id, auth); + while (enumerator->enumerate(enumerator, &public, ¤t_auth)) + { + if (public->verify(public, scheme, hash, sig)) + { + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", + id, key_type_names, this->type); + status = SUCCESS; + auth->merge(auth, current_auth, FALSE); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + break; + } + else + { + DBG1(DBG_IKE, "signature validation failed, looking for another key"); + status = FAILED; + } + } + enumerator->destroy(enumerator); + free(hash.ptr); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "no trusted %N public key found for '%Y'", + key_type_names, this->type, id); + } + return status; +} + +METHOD(authenticator_t, destroy, void, + private_pubkey_v1_authenticator_t *this) +{ + chunk_free(&this->id_payload); + free(this); +} + +/* + * Described in header. + */ +pubkey_v1_authenticator_t *pubkey_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload, key_type_t type) +{ + private_pubkey_v1_authenticator_t *this; + + INIT(this, + .public = { + .authenticator = { + .build = _build, + .process = _process, + .is_mutual = (void*)return_false, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .dh = dh, + .dh_value = dh_value, + .sa_payload = sa_payload, + .id_payload = id_payload, + .type = type, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h new file mode 100644 index 000000000..385664cf3 --- /dev/null +++ b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup pubkey_v1_authenticator pubkey_v1_authenticator + * @{ @ingroup authenticators_v1 + */ + +#ifndef PUBKEY_V1_AUTHENTICATOR_H_ +#define PUBKEY_V1_AUTHENTICATOR_H_ + +typedef struct pubkey_v1_authenticator_t pubkey_v1_authenticator_t; + +#include <sa/authenticator.h> + +/** + * Implementation of authenticator_t using public keys for IKEv1. + */ +struct pubkey_v1_authenticator_t { + + /** + * Implemented authenticator_t interface. + */ + authenticator_t authenticator; +}; + +/** + * Create an authenticator to build and verify public key signatures. + * + * @param ike_sa associated IKE_SA + * @param initiator TRUE if we are IKE_SA initiator + * @param dh diffie hellman key exchange + * @param dh_value others public diffie hellman value + * @param sa_payload generated SA payload data, without payload header + * @param id_payload encoded ID payload of peer to authenticate or verify + * without payload header (gets owned) + * @param type key type to use, KEY_RSA or KEY_ECDSA + * @return pubkey authenticator + */ +pubkey_v1_authenticator_t *pubkey_v1_authenticator_create(ike_sa_t *ike_sa, + bool initiator, diffie_hellman_t *dh, + chunk_t dh_value, chunk_t sa_payload, + chunk_t id_payload, key_type_t type); + +#endif /** PUBKEY_V1_AUTHENTICATOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/keymat_v1.c b/src/libcharon/sa/ikev1/keymat_v1.c new file mode 100644 index 000000000..cff344a34 --- /dev/null +++ b/src/libcharon/sa/ikev1/keymat_v1.c @@ -0,0 +1,1157 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * 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 "keymat_v1.h" + +#include <daemon.h> +#include <encoding/generator.h> +#include <encoding/payloads/nonce_payload.h> +#include <utils/linked_list.h> + +typedef struct private_keymat_v1_t private_keymat_v1_t; + +/** + * Max. number of IVs to track. + */ +#define MAX_IV 3 + +/** + * Max. number of Quick Modes to track. + */ +#define MAX_QM 2 + +/** + * Data stored for IVs + */ +typedef struct { + /** message ID */ + u_int32_t mid; + /** current IV */ + chunk_t iv; + /** last block of encrypted message */ + chunk_t last_block; +} iv_data_t; + +/** + * Private data of an keymat_t object. + */ +struct private_keymat_v1_t { + + /** + * Public keymat_v1_t interface. + */ + keymat_v1_t public; + + /** + * IKE_SA Role, initiator or responder + */ + bool initiator; + + /** + * General purpose PRF + */ + prf_t *prf; + + /** + * PRF to create Phase 1 HASH payloads + */ + prf_t *prf_auth; + + /** + * Crypter wrapped in an aead_t interface + */ + aead_t *aead; + + /** + * Hasher used for IV generation (and other things like e.g. NAT-T) + */ + hasher_t *hasher; + + /** + * Key used for authentication during main mode + */ + chunk_t skeyid; + + /** + * Key to derive key material from for non-ISAKMP SAs, rekeying + */ + chunk_t skeyid_d; + + /** + * Key used for authentication after main mode + */ + chunk_t skeyid_a; + + /** + * Phase 1 IV + */ + iv_data_t phase1_iv; + + /** + * Keep track of IVs for exchanges after phase 1. We store only a limited + * number of IVs in an MRU sort of way. Stores iv_data_t objects. + */ + linked_list_t *ivs; + + /** + * Keep track of Nonces during Quick Mode exchanges. Only a limited number + * of QMs are tracked at the same time. Stores qm_data_t objects. + */ + linked_list_t *qms; +}; + + +/** + * Destroy an iv_data_t object. + */ +static void iv_data_destroy(iv_data_t *this) +{ + chunk_free(&this->last_block); + chunk_free(&this->iv); + free(this); +} + +/** + * Data stored for Quick Mode exchanges + */ +typedef struct { + /** message ID */ + u_int32_t mid; + /** Ni_b (Nonce from first message) */ + chunk_t n_i; + /** Nr_b (Nonce from second message) */ + chunk_t n_r; +} qm_data_t; + +/** + * Destroy a qm_data_t object. + */ +static void qm_data_destroy(qm_data_t *this) +{ + chunk_free(&this->n_i); + chunk_free(&this->n_r); + free(this); +} + +/** + * Constants used in key derivation. + */ +static const chunk_t octet_0 = chunk_from_chars(0x00); +static const chunk_t octet_1 = chunk_from_chars(0x01); +static const chunk_t octet_2 = chunk_from_chars(0x02); + +/** + * Simple aead_t implementation without support for authentication. + */ +typedef struct { + /** implements aead_t interface */ + aead_t aead; + /** crypter to be used */ + crypter_t *crypter; +} private_aead_t; + + +METHOD(aead_t, encrypt, bool, + private_aead_t *this, chunk_t plain, chunk_t assoc, chunk_t iv, + chunk_t *encrypted) +{ + return this->crypter->encrypt(this->crypter, plain, iv, encrypted); +} + +METHOD(aead_t, decrypt, bool, + private_aead_t *this, chunk_t encrypted, chunk_t assoc, chunk_t iv, + chunk_t *plain) +{ + return this->crypter->decrypt(this->crypter, encrypted, iv, plain); +} + +METHOD(aead_t, get_block_size, size_t, + private_aead_t *this) +{ + return this->crypter->get_block_size(this->crypter); +} + +METHOD(aead_t, get_icv_size, size_t, + private_aead_t *this) +{ + return 0; +} + +METHOD(aead_t, get_iv_size, size_t, + private_aead_t *this) +{ + /* in order to create the messages properly we return 0 here */ + return 0; +} + +METHOD(aead_t, get_key_size, size_t, + private_aead_t *this) +{ + return this->crypter->get_key_size(this->crypter); +} + +METHOD(aead_t, set_key, bool, + private_aead_t *this, chunk_t key) +{ + return this->crypter->set_key(this->crypter, key); +} + +METHOD(aead_t, aead_destroy, void, + private_aead_t *this) +{ + this->crypter->destroy(this->crypter); + free(this); +} + +/** + * Expand SKEYID_e according to Appendix B in RFC 2409. + * TODO-IKEv1: verify keys (e.g. for weak keys, see Appendix B) + */ +static bool expand_skeyid_e(chunk_t skeyid_e, size_t key_size, prf_t *prf, + chunk_t *ka) +{ + size_t block_size; + chunk_t seed; + int i; + + if (skeyid_e.len >= key_size) + { /* no expansion required, reduce to key_size */ + skeyid_e.len = key_size; + *ka = skeyid_e; + return TRUE; + } + block_size = prf->get_block_size(prf); + *ka = chunk_alloc((key_size / block_size + 1) * block_size); + ka->len = key_size; + + /* Ka = K1 | K2 | ..., K1 = prf(SKEYID_e, 0), K2 = prf(SKEYID_e, K1) ... */ + if (!prf->set_key(prf, skeyid_e)) + { + chunk_clear(ka); + chunk_clear(&skeyid_e); + return FALSE; + } + seed = octet_0; + for (i = 0; i < key_size; i += block_size) + { + if (!prf->get_bytes(prf, seed, ka->ptr + i)) + { + chunk_clear(ka); + chunk_clear(&skeyid_e); + return FALSE; + } + seed = chunk_create(ka->ptr + i, block_size); + } + chunk_clear(&skeyid_e); + return TRUE; +} + +/** + * Create a simple implementation of the aead_t interface which only encrypts + * or decrypts data. + */ +static aead_t *create_aead(proposal_t *proposal, prf_t *prf, chunk_t skeyid_e) +{ + private_aead_t *this; + u_int16_t alg, key_size; + crypter_t *crypter; + chunk_t ka; + + if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, + &key_size)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, ENCRYPTION_ALGORITHM); + return NULL; + } + crypter = lib->crypto->create_crypter(lib->crypto, alg, key_size / 8); + if (!crypter) + { + DBG1(DBG_IKE, "%N %N (key size %d) not supported!", + transform_type_names, ENCRYPTION_ALGORITHM, + encryption_algorithm_names, alg, key_size); + return NULL; + } + key_size = crypter->get_key_size(crypter); + if (!expand_skeyid_e(skeyid_e, crypter->get_key_size(crypter), prf, &ka)) + { + return NULL; + } + DBG4(DBG_IKE, "encryption key Ka %B", &ka); + if (!crypter->set_key(crypter, ka)) + { + chunk_clear(&ka); + return NULL; + } + chunk_clear(&ka); + + INIT(this, + .aead = { + .encrypt = _encrypt, + .decrypt = _decrypt, + .get_block_size = _get_block_size, + .get_icv_size = _get_icv_size, + .get_iv_size = _get_iv_size, + .get_key_size = _get_key_size, + .set_key = _set_key, + .destroy = _aead_destroy, + }, + .crypter = crypter, + ); + return &this->aead; +} + +/** + * Converts integrity algorithm to PRF algorithm + */ +static u_int16_t auth_to_prf(u_int16_t alg) +{ + switch (alg) + { + case AUTH_HMAC_SHA1_96: + return PRF_HMAC_SHA1; + case AUTH_HMAC_SHA2_256_128: + return PRF_HMAC_SHA2_256; + case AUTH_HMAC_SHA2_384_192: + return PRF_HMAC_SHA2_384; + case AUTH_HMAC_SHA2_512_256: + return PRF_HMAC_SHA2_512; + case AUTH_HMAC_MD5_96: + return PRF_HMAC_MD5; + case AUTH_AES_XCBC_96: + return PRF_AES128_XCBC; + default: + return PRF_UNDEFINED; + } +} + +/** + * Converts integrity algorithm to hash algorithm + */ +static u_int16_t auth_to_hash(u_int16_t alg) +{ + switch (alg) + { + case AUTH_HMAC_SHA1_96: + return HASH_SHA1; + case AUTH_HMAC_SHA2_256_128: + return HASH_SHA256; + case AUTH_HMAC_SHA2_384_192: + return HASH_SHA384; + case AUTH_HMAC_SHA2_512_256: + return HASH_SHA512; + case AUTH_HMAC_MD5_96: + return HASH_MD5; + default: + return HASH_UNKNOWN; + } +} + +/** + * Adjust the key length for PRF algorithms that expect a fixed key length. + */ +static void adjust_keylen(u_int16_t alg, chunk_t *key) +{ + switch (alg) + { + case PRF_AES128_XCBC: + /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does + * not and therefore fixed key semantics apply to XCBC for key + * derivation. */ + key->len = min(key->len, 16); + break; + default: + /* all other algorithms use variable key length */ + break; + } +} + +METHOD(keymat_v1_t, derive_ike_keys, bool, + private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh, + chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, + auth_method_t auth, shared_key_t *shared_key) +{ + chunk_t g_xy, g_xi, g_xr, dh_me, spi_i, spi_r, nonces, data, skeyid_e; + chunk_t skeyid; + u_int16_t alg; + + spi_i = chunk_alloca(sizeof(u_int64_t)); + spi_r = chunk_alloca(sizeof(u_int64_t)); + + if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) + { /* no PRF negotiated, use HMAC version of integrity algorithm instead */ + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL) + || (alg = auth_to_prf(alg)) == PRF_UNDEFINED) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, PSEUDO_RANDOM_FUNCTION); + return FALSE; + } + } + this->prf = lib->crypto->create_prf(lib->crypto, alg); + if (!this->prf) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + if (this->prf->get_block_size(this->prf) < + this->prf->get_key_size(this->prf)) + { /* TODO-IKEv1: support PRF output expansion (RFC 2409, Appendix B) */ + DBG1(DBG_IKE, "expansion of %N %N output not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + + if (dh->get_shared_secret(dh, &g_xy) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &g_xy); + + *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); + *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); + nonces = chunk_cata("cc", nonce_i, nonce_r); + + switch (auth) + { + case AUTH_PSK: + case AUTH_XAUTH_INIT_PSK: + { /* SKEYID = prf(pre-shared-key, Ni_b | Nr_b) */ + chunk_t psk; + if (!shared_key) + { + chunk_clear(&g_xy); + return FALSE; + } + psk = shared_key->get_key(shared_key); + adjust_keylen(alg, &psk); + if (!this->prf->set_key(this->prf, psk) || + !this->prf->allocate_bytes(this->prf, nonces, &skeyid)) + { + chunk_clear(&g_xy); + return FALSE; + } + break; + } + case AUTH_RSA: + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + { + if (!this->prf->set_key(this->prf, nonces) || + !this->prf->allocate_bytes(this->prf, g_xy, &skeyid)) + { + chunk_clear(&g_xy); + return FALSE; + } + break; + } + default: + /* TODO-IKEv1: implement key derivation for other schemes */ + /* authentication class not supported */ + chunk_clear(&g_xy); + return FALSE; + } + adjust_keylen(alg, &skeyid); + DBG4(DBG_IKE, "SKEYID %B", &skeyid); + + /* SKEYID_d = prf(SKEYID, g^xy | CKY-I | CKY-R | 0) */ + data = chunk_cat("cccc", g_xy, spi_i, spi_r, octet_0); + if (!this->prf->set_key(this->prf, skeyid) || + !this->prf->allocate_bytes(this->prf, data, &this->skeyid_d)) + { + chunk_clear(&g_xy); + chunk_clear(&data); + return FALSE; + } + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_d %B", &this->skeyid_d); + + /* SKEYID_a = prf(SKEYID, SKEYID_d | g^xy | CKY-I | CKY-R | 1) */ + data = chunk_cat("ccccc", this->skeyid_d, g_xy, spi_i, spi_r, octet_1); + if (!this->prf->allocate_bytes(this->prf, data, &this->skeyid_a)) + { + chunk_clear(&g_xy); + chunk_clear(&data); + return FALSE; + } + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_a %B", &this->skeyid_a); + + /* SKEYID_e = prf(SKEYID, SKEYID_a | g^xy | CKY-I | CKY-R | 2) */ + data = chunk_cat("ccccc", this->skeyid_a, g_xy, spi_i, spi_r, octet_2); + if (!this->prf->allocate_bytes(this->prf, data, &skeyid_e)) + { + chunk_clear(&g_xy); + chunk_clear(&data); + return FALSE; + } + chunk_clear(&data); + DBG4(DBG_IKE, "SKEYID_e %B", &skeyid_e); + + chunk_clear(&g_xy); + + switch (auth) + { + case AUTH_ECDSA_256: + alg = PRF_HMAC_SHA2_256; + break; + case AUTH_ECDSA_384: + alg = PRF_HMAC_SHA2_384; + break; + case AUTH_ECDSA_521: + alg = PRF_HMAC_SHA2_512; + break; + default: + /* use proposal algorithm */ + break; + } + this->prf_auth = lib->crypto->create_prf(lib->crypto, alg); + if (!this->prf_auth) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + chunk_clear(&skeyid); + return FALSE; + } + if (!this->prf_auth->set_key(this->prf_auth, skeyid)) + { + chunk_clear(&skeyid); + return FALSE; + } + chunk_clear(&skeyid); + + this->aead = create_aead(proposal, this->prf, skeyid_e); + if (!this->aead) + { + return FALSE; + } + if (!this->hasher && !this->public.create_hasher(&this->public, proposal)) + { + return FALSE; + } + + dh->get_my_public_value(dh, &dh_me); + g_xi = this->initiator ? dh_me : dh_other; + g_xr = this->initiator ? dh_other : dh_me; + + /* initial IV = hash(g^xi | g^xr) */ + data = chunk_cata("cc", g_xi, g_xr); + chunk_free(&dh_me); + if (!this->hasher->allocate_hash(this->hasher, data, &this->phase1_iv.iv)) + { + return FALSE; + } + if (this->phase1_iv.iv.len > this->aead->get_block_size(this->aead)) + { + this->phase1_iv.iv.len = this->aead->get_block_size(this->aead); + } + DBG4(DBG_IKE, "initial IV %B", &this->phase1_iv.iv); + + return TRUE; +} + +METHOD(keymat_v1_t, derive_child_keys, bool, + private_keymat_v1_t *this, proposal_t *proposal, diffie_hellman_t *dh, + u_int32_t spi_i, u_int32_t spi_r, chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, chunk_t *encr_r, chunk_t *integ_r) +{ + u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; + u_int8_t protocol; + prf_plus_t *prf_plus; + chunk_t seed, secret = chunk_empty; + bool success = FALSE; + + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, + &enc_alg, &enc_size)) + { + DBG2(DBG_CHD, " using %N for encryption", + encryption_algorithm_names, enc_alg); + + if (!enc_size) + { + enc_size = keymat_get_keylen_encr(enc_alg); + } + if (enc_alg != ENCR_NULL && !enc_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + encryption_algorithm_names, enc_alg); + return FALSE; + } + /* to bytes */ + enc_size /= 8; + + /* CCM/GCM/CTR/GMAC needs additional bytes */ + switch (enc_alg) + { + case ENCR_AES_CCM_ICV8: + case ENCR_AES_CCM_ICV12: + case ENCR_AES_CCM_ICV16: + case ENCR_CAMELLIA_CCM_ICV8: + case ENCR_CAMELLIA_CCM_ICV12: + case ENCR_CAMELLIA_CCM_ICV16: + enc_size += 3; + break; + case ENCR_AES_GCM_ICV8: + case ENCR_AES_GCM_ICV12: + case ENCR_AES_GCM_ICV16: + case ENCR_AES_CTR: + case ENCR_NULL_AUTH_AES_GMAC: + enc_size += 4; + break; + default: + break; + } + } + + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &int_alg, &int_size)) + { + DBG2(DBG_CHD, " using %N for integrity", + integrity_algorithm_names, int_alg); + + if (!int_size) + { + int_size = keymat_get_keylen_integ(int_alg); + } + if (!int_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + integrity_algorithm_names, int_alg); + return FALSE; + } + /* to bytes */ + int_size /= 8; + } + + /* KEYMAT = prf+(SKEYID_d, [ g(qm)^xy | ] protocol | SPI | Ni_b | Nr_b) */ + if (!this->prf->set_key(this->prf, this->skeyid_d)) + { + return FALSE; + } + protocol = proposal->get_protocol(proposal); + if (dh) + { + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_CHD, "DH secret %B", &secret); + } + + *encr_r = *integ_r = *encr_i = *integ_i = chunk_empty; + seed = chunk_cata("ccccc", secret, chunk_from_thing(protocol), + chunk_from_thing(spi_r), nonce_i, nonce_r); + DBG4(DBG_CHD, "initiator SA seed %B", &seed); + + prf_plus = prf_plus_create(this->prf, FALSE, seed); + if (!prf_plus || + !prf_plus->allocate_bytes(prf_plus, enc_size, encr_i) || + !prf_plus->allocate_bytes(prf_plus, int_size, integ_i)) + { + goto failure; + } + + seed = chunk_cata("ccccc", secret, chunk_from_thing(protocol), + chunk_from_thing(spi_i), nonce_i, nonce_r); + DBG4(DBG_CHD, "responder SA seed %B", &seed); + prf_plus->destroy(prf_plus); + prf_plus = prf_plus_create(this->prf, FALSE, seed); + if (!prf_plus || + !prf_plus->allocate_bytes(prf_plus, enc_size, encr_r) || + !prf_plus->allocate_bytes(prf_plus, int_size, integ_r)) + { + goto failure; + } + + if (enc_size) + { + DBG4(DBG_CHD, "encryption initiator key %B", encr_i); + DBG4(DBG_CHD, "encryption responder key %B", encr_r); + } + if (int_size) + { + DBG4(DBG_CHD, "integrity initiator key %B", integ_i); + DBG4(DBG_CHD, "integrity responder key %B", integ_r); + } + success = TRUE; + +failure: + if (!success) + { + chunk_clear(encr_i); + chunk_clear(integ_i); + chunk_clear(encr_r); + chunk_clear(integ_r); + } + DESTROY_IF(prf_plus); + chunk_clear(&secret); + + return success; +} + +METHOD(keymat_v1_t, create_hasher, bool, + private_keymat_v1_t *this, proposal_t *proposal) +{ + u_int16_t alg; + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL) || + (alg = auth_to_hash(alg)) == HASH_UNKNOWN) + { + DBG1(DBG_IKE, "no %N selected", transform_type_names, HASH_ALGORITHM); + return FALSE; + } + this->hasher = lib->crypto->create_hasher(lib->crypto, alg); + if (!this->hasher) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, HASH_ALGORITHM, + hash_algorithm_names, alg); + return FALSE; + } + return TRUE; +} + +METHOD(keymat_v1_t, get_hasher, hasher_t*, + private_keymat_v1_t *this) +{ + return this->hasher; +} + +METHOD(keymat_v1_t, get_hash, bool, + private_keymat_v1_t *this, bool initiator, chunk_t dh, chunk_t dh_other, + ike_sa_id_t *ike_sa_id, chunk_t sa_i, chunk_t id, chunk_t *hash) +{ + chunk_t data; + u_int64_t spi, spi_other; + + /* HASH_I = prf(SKEYID, g^xi | g^xr | CKY-I | CKY-R | SAi_b | IDii_b ) + * HASH_R = prf(SKEYID, g^xr | g^xi | CKY-R | CKY-I | SAi_b | IDir_b ) + */ + if (initiator) + { + spi = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_other = ike_sa_id->get_responder_spi(ike_sa_id); + } + else + { + spi_other = ike_sa_id->get_initiator_spi(ike_sa_id); + spi = ike_sa_id->get_responder_spi(ike_sa_id); + } + data = chunk_cat("cccccc", dh, dh_other, + chunk_from_thing(spi), chunk_from_thing(spi_other), + sa_i, id); + + DBG3(DBG_IKE, "HASH_%c data %B", initiator ? 'I' : 'R', &data); + + if (!this->prf_auth->allocate_bytes(this->prf_auth, data, hash)) + { + free(data.ptr); + return FALSE; + } + + DBG3(DBG_IKE, "HASH_%c %B", initiator ? 'I' : 'R', hash); + + free(data.ptr); + return TRUE; +} + +/** + * Get the nonce value found in the given message. + * Returns FALSE if none is found. + */ +static bool get_nonce(message_t *message, chunk_t *n) +{ + nonce_payload_t *nonce; + nonce = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (nonce) + { + *n = nonce->get_nonce(nonce); + return TRUE; + } + return FALSE; +} + +/** + * Generate the message data in order to generate the hashes. + */ +static chunk_t get_message_data(message_t *message, generator_t *generator) +{ + payload_t *payload, *next; + enumerator_t *enumerator; + u_int32_t *lenpos; + + if (message->is_encoded(message)) + { /* inbound, although the message is generated, we cannot access the + * cleartext message data, so generate it anyway */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == HASH_V1) + { + continue; + } + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + else + { + /* outbound, generate the payloads (there is no HASH payload yet) */ + enumerator = message->create_payload_enumerator(message); + if (enumerator->enumerate(enumerator, &payload)) + { + while (enumerator->enumerate(enumerator, &next)) + { + payload->set_next_type(payload, next->get_type(next)); + generator->generate_payload(generator, payload); + payload = next; + } + payload->set_next_type(payload, NO_PAYLOAD); + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + return generator->get_chunk(generator, &lenpos); +} + +/** + * Try to find data about a Quick Mode with the given message ID, + * if none is found, state is generated. + */ +static qm_data_t *lookup_quick_mode(private_keymat_v1_t *this, u_int32_t mid) +{ + enumerator_t *enumerator; + qm_data_t *qm, *found = NULL; + + enumerator = this->qms->create_enumerator(this->qms); + while (enumerator->enumerate(enumerator, &qm)) + { + if (qm->mid == mid) + { /* state gets moved to the front of the list */ + this->qms->remove_at(this->qms, enumerator); + found = qm; + break; + } + } + enumerator->destroy(enumerator); + if (!found) + { + INIT(found, + .mid = mid, + ); + } + this->qms->insert_first(this->qms, found); + /* remove least recently used state if maximum reached */ + if (this->qms->get_count(this->qms) > MAX_QM && + this->qms->remove_last(this->qms, (void**)&qm) == SUCCESS) + { + qm_data_destroy(qm); + } + return found; +} + +METHOD(keymat_v1_t, get_hash_phase2, bool, + private_keymat_v1_t *this, message_t *message, chunk_t *hash) +{ + u_int32_t mid, mid_n; + chunk_t data = chunk_empty; + bool add_message = TRUE; + char *name = "Hash"; + + if (!this->prf) + { /* no keys derived yet */ + return FALSE; + } + + mid = message->get_message_id(message); + mid_n = htonl(mid); + + /* Hashes are simple for most exchanges in Phase 2: + * Hash = prf(SKEYID_a, M-ID | Complete message after HASH payload) + * For Quick Mode there are three hashes: + * Hash(1) = same as above + * Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH payload) + * Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) + * So, for Quick Mode we keep track of the nonce values. + */ + switch (message->get_exchange_type(message)) + { + case QUICK_MODE: + { + qm_data_t *qm = lookup_quick_mode(this, mid); + if (!qm->n_i.ptr) + { /* Hash(1) = prf(SKEYID_a, M-ID | Message after HASH payload) */ + name = "Hash(1)"; + if (!get_nonce(message, &qm->n_i)) + { + return FALSE; + } + data = chunk_from_thing(mid_n); + } + else if (!qm->n_r.ptr) + { /* Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH) */ + name = "Hash(2)"; + if (!get_nonce(message, &qm->n_r)) + { + return FALSE; + } + data = chunk_cata("cc", chunk_from_thing(mid_n), qm->n_i); + } + else + { /* Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) */ + name = "Hash(3)"; + data = chunk_cata("cccc", octet_0, chunk_from_thing(mid_n), + qm->n_i, qm->n_r); + add_message = FALSE; + /* we don't need the state anymore */ + this->qms->remove(this->qms, qm, NULL); + qm_data_destroy(qm); + } + break; + } + case TRANSACTION: + case INFORMATIONAL_V1: + /* Hash = prf(SKEYID_a, M-ID | Message after HASH payload) */ + data = chunk_from_thing(mid_n); + break; + default: + return FALSE; + } + if (!this->prf->set_key(this->prf, this->skeyid_a)) + { + return FALSE; + } + if (add_message) + { + generator_t *generator; + chunk_t msg; + + generator = generator_create_no_dbg(); + msg = get_message_data(message, generator); + if (!this->prf->allocate_bytes(this->prf, data, NULL) || + !this->prf->allocate_bytes(this->prf, msg, hash)) + { + generator->destroy(generator); + return FALSE; + } + generator->destroy(generator); + } + else + { + if (!this->prf->allocate_bytes(this->prf, data, hash)) + { + return FALSE; + } + } + DBG3(DBG_IKE, "%s %B", name, hash); + return TRUE; +} + +/** + * Generate an IV + */ +static bool generate_iv(private_keymat_v1_t *this, iv_data_t *iv) +{ + if (iv->mid == 0 || iv->iv.ptr) + { /* use last block of previous encrypted message */ + chunk_free(&iv->iv); + iv->iv = iv->last_block; + iv->last_block = chunk_empty; + } + else + { + /* initial phase 2 IV = hash(last_phase1_block | mid) */ + u_int32_t net;; + chunk_t data; + + net = htonl(iv->mid); + data = chunk_cata("cc", this->phase1_iv.iv, chunk_from_thing(net)); + if (!this->hasher->allocate_hash(this->hasher, data, &iv->iv)) + { + return FALSE; + } + if (iv->iv.len > this->aead->get_block_size(this->aead)) + { + iv->iv.len = this->aead->get_block_size(this->aead); + } + } + DBG4(DBG_IKE, "next IV for MID %u %B", iv->mid, &iv->iv); + return TRUE; +} + +/** + * Try to find an IV for the given message ID, if not found, generate it. + */ +static iv_data_t *lookup_iv(private_keymat_v1_t *this, u_int32_t mid) +{ + enumerator_t *enumerator; + iv_data_t *iv, *found = NULL; + + if (mid == 0) + { + return &this->phase1_iv; + } + + enumerator = this->ivs->create_enumerator(this->ivs); + while (enumerator->enumerate(enumerator, &iv)) + { + if (iv->mid == mid) + { /* IV gets moved to the front of the list */ + this->ivs->remove_at(this->ivs, enumerator); + found = iv; + break; + } + } + enumerator->destroy(enumerator); + if (!found) + { + INIT(found, + .mid = mid, + ); + if (!generate_iv(this, found)) + { + iv_data_destroy(found); + return NULL; + } + } + this->ivs->insert_first(this->ivs, found); + /* remove least recently used IV if maximum reached */ + if (this->ivs->get_count(this->ivs) > MAX_IV && + this->ivs->remove_last(this->ivs, (void**)&iv) == SUCCESS) + { + iv_data_destroy(iv); + } + return found; +} + +METHOD(keymat_v1_t, get_iv, bool, + private_keymat_v1_t *this, u_int32_t mid, chunk_t *out) +{ + iv_data_t *iv; + + iv = lookup_iv(this, mid); + if (iv) + { + *out = iv->iv; + return TRUE; + } + return FALSE; +} + +METHOD(keymat_v1_t, update_iv, bool, + private_keymat_v1_t *this, u_int32_t mid, chunk_t last_block) +{ + iv_data_t *iv = lookup_iv(this, mid); + if (iv) + { /* update last block */ + chunk_free(&iv->last_block); + iv->last_block = chunk_clone(last_block); + return TRUE; + } + return FALSE; +} + +METHOD(keymat_v1_t, confirm_iv, bool, + private_keymat_v1_t *this, u_int32_t mid) +{ + iv_data_t *iv = lookup_iv(this, mid); + if (iv) + { + return generate_iv(this, iv); + } + return FALSE; +} + +METHOD(keymat_t, get_version, ike_version_t, + private_keymat_v1_t *this) +{ + return IKEV1; +} + +METHOD(keymat_t, create_dh, diffie_hellman_t*, + private_keymat_v1_t *this, diffie_hellman_group_t group) +{ + return lib->crypto->create_dh(lib->crypto, group); +} + +METHOD(keymat_t, create_nonce_gen, nonce_gen_t*, + private_keymat_v1_t *this) +{ + return lib->crypto->create_nonce_gen(lib->crypto); +} + +METHOD(keymat_t, get_aead, aead_t*, + private_keymat_v1_t *this, bool in) +{ + return this->aead; +} + +METHOD(keymat_t, destroy, void, + private_keymat_v1_t *this) +{ + DESTROY_IF(this->prf); + DESTROY_IF(this->prf_auth); + DESTROY_IF(this->aead); + DESTROY_IF(this->hasher); + chunk_clear(&this->skeyid_d); + chunk_clear(&this->skeyid_a); + chunk_free(&this->phase1_iv.iv); + chunk_free(&this->phase1_iv.last_block); + this->ivs->destroy_function(this->ivs, (void*)iv_data_destroy); + this->qms->destroy_function(this->qms, (void*)qm_data_destroy); + free(this); +} + +/** + * See header + */ +keymat_v1_t *keymat_v1_create(bool initiator) +{ + private_keymat_v1_t *this; + + INIT(this, + .public = { + .keymat = { + .get_version = _get_version, + .create_dh = _create_dh, + .create_nonce_gen = _create_nonce_gen, + .get_aead = _get_aead, + .destroy = _destroy, + }, + .derive_ike_keys = _derive_ike_keys, + .derive_child_keys = _derive_child_keys, + .create_hasher = _create_hasher, + .get_hasher = _get_hasher, + .get_hash = _get_hash, + .get_hash_phase2 = _get_hash_phase2, + .get_iv = _get_iv, + .update_iv = _update_iv, + .confirm_iv = _confirm_iv, + }, + .ivs = linked_list_create(), + .qms = linked_list_create(), + .initiator = initiator, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/keymat_v1.h b/src/libcharon/sa/ikev1/keymat_v1.h new file mode 100644 index 000000000..cc9f3b339 --- /dev/null +++ b/src/libcharon/sa/ikev1/keymat_v1.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * 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. + */ + +/** + * @defgroup keymat_v1 keymat_v1 + * @{ @ingroup ikev1 + */ + +#ifndef KEYMAT_V1_H_ +#define KEYMAT_V1_H_ + +#include <sa/keymat.h> +#include <sa/authenticator.h> + +typedef struct keymat_v1_t keymat_v1_t; + +/** + * Derivation and management of sensitive keying material, IKEv1 variant. + */ +struct keymat_v1_t { + + /** + * Implements keymat_t. + */ + keymat_t keymat; + + /** + * Derive keys for the IKE_SA. + * + * These keys are not handed out, but are used by the associated signers, + * crypters and authentication functions. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh() + * @param dh_other public DH value from other peer + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param id IKE_SA identifier + * @param auth authentication method + * @param shared_key PSK in case of AUTH_CLASS_PSK, NULL otherwise + * @return TRUE on success + */ + bool (*derive_ike_keys)(keymat_v1_t *this, proposal_t *proposal, + diffie_hellman_t *dh, chunk_t dh_other, + chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, + auth_method_t auth, shared_key_t *shared_key); + + /** + * Derive keys for the CHILD_SA. + * + * @param proposal selected algorithms + * @param dh diffie hellman key, NULL if none used + * @param spi_i SPI chosen by initiatior + * @param spi_r SPI chosen by responder + * @param nonce_i quick mode initiator nonce + * @param nonce_r quick mode responder nonce + * @param encr_i allocated initiators encryption key + * @param integ_i allocated initiators integrity key + * @param encr_r allocated responders encryption key + * @param integ_r allocated responders integrity key + */ + bool (*derive_child_keys)(keymat_v1_t *this, proposal_t *proposal, + diffie_hellman_t *dh, u_int32_t spi_i, u_int32_t spi_r, + chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r); + + /** + * Create the negotiated hasher. + * + * @param proposal selected algorithms + * @return TRUE, if creation was successful + */ + bool (*create_hasher)(keymat_v1_t *this, proposal_t *proposal); + + /** + * Get the negotiated hasher. + * + * @return allocated hasher or NULL + */ + hasher_t *(*get_hasher)(keymat_v1_t *this); + + /** + * Get HASH data for authentication. + * + * @param initiatior TRUE to create HASH_I, FALSE for HASH_R + * @param dh public DH value of peer to create HASH for + * @param dh_other others public DH value + * @param ike_sa_id IKE_SA identifier + * @param sa_i encoded SA payload of initiator + * @param id encoded IDii payload for HASH_I (IDir for HASH_R) + * @param hash chunk receiving allocated HASH data + * @return TRUE if hash allocated successfully + */ + bool (*get_hash)(keymat_v1_t *this, bool initiator, + chunk_t dh, chunk_t dh_other, ike_sa_id_t *ike_sa_id, + chunk_t sa_i, chunk_t id, chunk_t *hash); + + /** + * Get HASH data for integrity/authentication in Phase 2 exchanges. + * + * @param message message to generate the HASH data for + * @param hash chunk receiving allocated hash data + * @return TRUE if hash allocated successfully + */ + bool (*get_hash_phase2)(keymat_v1_t *this, message_t *message, chunk_t *hash); + + /** + * Returns the IV for a message with the given message ID. + * + * The return chunk contains internal data and is valid until the next + * get_iv/udpate_iv/confirm_iv call. + * + * @param mid message ID + * @param iv chunk receiving IV, internal data + * @return TRUE if IV allocated successfully + */ + bool (*get_iv)(keymat_v1_t *this, u_int32_t mid, chunk_t *iv); + + /** + * Updates the IV for the next message with the given message ID. + * + * A call of confirm_iv() is required in order to actually make the IV + * available. This is needed for the inbound case where we store the last + * block of the encrypted message but want to update the IV only after + * verification of the decrypted message. + * + * @param mid message ID + * @param last_block last block of encrypted message (gets cloned) + * @return TRUE if IV updated successfully + */ + bool (*update_iv)(keymat_v1_t *this, u_int32_t mid, chunk_t last_block); + + /** + * Confirms the updated IV for the given message ID. + * + * To actually make the new IV available via get_iv this method has to + * be called after update_iv. + * + * @param mid message ID + * @return TRUE if IV confirmed successfully + */ + bool (*confirm_iv)(keymat_v1_t *this, u_int32_t mid); +}; + +/** + * Create a keymat instance. + * + * @param initiator TRUE if we are the initiator + * @return keymat instance + */ +keymat_v1_t *keymat_v1_create(bool initiator); + +#endif /** KEYMAT_V1_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/phase1.c b/src/libcharon/sa/ikev1/phase1.c new file mode 100644 index 000000000..4096141ec --- /dev/null +++ b/src/libcharon/sa/ikev1/phase1.c @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * + * 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 "phase1.h" + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <utils/linked_list.h> + +typedef struct private_phase1_t private_phase1_t; + +/** + * Private data of an phase1_t object. + */ +struct private_phase1_t { + + /** + * Public phase1_t interface. + */ + phase1_t public; + + /** + * IKE_SA we negotiate + */ + ike_sa_t *ike_sa; + + /** + * Currently selected peer config + */ + peer_cfg_t *peer_cfg; + + /** + * Other possible peer config candidates + */ + linked_list_t *candidates; + + /** + * Acting as initiator + */ + bool initiator; + + /** + * Extracted SA payload bytes + */ + chunk_t sa_payload; + + /** + * DH exchange + */ + diffie_hellman_t *dh; + + /** + * Keymat derivation (from SA) + */ + keymat_v1_t *keymat; + + /** + * Received public DH value from peer + */ + chunk_t dh_value; + + /** + * Initiators nonce + */ + chunk_t nonce_i; + + /** + * Responder nonce + */ + chunk_t nonce_r; +}; + +/** + * Get the first authentcation config from peer config + */ +static auth_cfg_t *get_auth_cfg(peer_cfg_t *peer_cfg, bool local) +{ + enumerator_t *enumerator; + auth_cfg_t *cfg = NULL; + + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local); + enumerator->enumerate(enumerator, &cfg); + enumerator->destroy(enumerator); + return cfg; +} + +/** + * Lookup a shared secret for this IKE_SA + */ +static shared_key_t *lookup_shared_key(private_phase1_t *this, + peer_cfg_t *peer_cfg) +{ + host_t *me, *other; + identification_t *my_id, *other_id; + shared_key_t *shared_key = NULL; + auth_cfg_t *my_auth, *other_auth; + enumerator_t *enumerator; + + /* try to get a PSK for IP addresses */ + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + my_id = identification_create_from_sockaddr(me->get_sockaddr(me)); + other_id = identification_create_from_sockaddr(other->get_sockaddr(other)); + if (my_id && other_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, SHARED_IKE, + my_id, other_id); + } + DESTROY_IF(my_id); + DESTROY_IF(other_id); + if (shared_key) + { + return shared_key; + } + + if (peer_cfg) + { /* as initiator or aggressive responder, use identities */ + my_auth = get_auth_cfg(peer_cfg, TRUE); + other_auth = get_auth_cfg(peer_cfg, FALSE); + if (my_auth && other_auth) + { + my_id = my_auth->get(my_auth, AUTH_RULE_IDENTITY); + if (peer_cfg->use_aggressive(peer_cfg)) + { + other_id = this->ike_sa->get_other_id(this->ike_sa); + } + else + { + other_id = other_auth->get(other_auth, AUTH_RULE_IDENTITY); + } + if (my_id && other_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, SHARED_IKE, + my_id, other_id); + if (!shared_key) + { + DBG1(DBG_IKE, "no shared key found for '%Y'[%H] - '%Y'[%H]", + my_id, me, other_id, other); + } + } + } + return shared_key; + } + /* as responder, we try to find a config by IP */ + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + me, other, NULL, NULL, IKEV1); + while (enumerator->enumerate(enumerator, &peer_cfg)) + { + my_auth = get_auth_cfg(peer_cfg, TRUE); + other_auth = get_auth_cfg(peer_cfg, FALSE); + if (my_auth && other_auth) + { + my_id = my_auth->get(my_auth, AUTH_RULE_IDENTITY); + other_id = other_auth->get(other_auth, AUTH_RULE_IDENTITY); + if (my_id) + { + shared_key = lib->credmgr->get_shared(lib->credmgr, SHARED_IKE, + my_id, other_id); + if (shared_key) + { + break; + } + else + { + DBG1(DBG_IKE, "no shared key found for '%Y'[%H] - '%Y'[%H]", + my_id, me, other_id, other); + } + } + } + } + enumerator->destroy(enumerator); + if (!peer_cfg) + { + DBG1(DBG_IKE, "no shared key found for %H - %H", me, other); + } + return shared_key; +} + +METHOD(phase1_t, create_hasher, bool, + private_phase1_t *this) +{ + return this->keymat->create_hasher(this->keymat, + this->ike_sa->get_proposal(this->ike_sa)); +} + +METHOD(phase1_t, create_dh, bool, + private_phase1_t *this, diffie_hellman_group_t group) +{ + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, group); + return this->dh != NULL; +} + +METHOD(phase1_t, derive_keys, bool, + private_phase1_t *this, peer_cfg_t *peer_cfg, auth_method_t method) +{ + shared_key_t *shared_key = NULL; + + switch (method) + { + case AUTH_PSK: + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_RESP_PSK: + shared_key = lookup_shared_key(this, peer_cfg); + if (!shared_key) + { + return FALSE; + } + break; + default: + break; + } + + if (!this->keymat->derive_ike_keys(this->keymat, + this->ike_sa->get_proposal(this->ike_sa), + this->dh, this->dh_value, this->nonce_i, this->nonce_r, + this->ike_sa->get_id(this->ike_sa), method, shared_key)) + { + DESTROY_IF(shared_key); + DBG1(DBG_IKE, "key derivation for %N failed", auth_method_names, method); + return FALSE; + } + charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, this->dh_value, + this->nonce_i, this->nonce_r, NULL, shared_key); + DESTROY_IF(shared_key); + return TRUE; +} + +/** + * Check if a peer skipped authentication by using Hybrid authentication + */ +static bool skipped_auth(private_phase1_t *this, + auth_method_t method, bool local) +{ + bool initiator; + + initiator = local == this->initiator; + if (initiator && method == AUTH_HYBRID_INIT_RSA) + { + return TRUE; + } + if (!initiator && method == AUTH_HYBRID_RESP_RSA) + { + return TRUE; + } + return FALSE; +} + +/** + * Check if remote authentication constraints fulfilled + */ +static bool check_constraints(private_phase1_t *this, auth_method_t method) +{ + identification_t *id; + auth_cfg_t *auth, *cfg; + peer_cfg_t *peer_cfg; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + /* auth identity to comply */ + id = this->ike_sa->get_other_id(this->ike_sa); + auth->add(auth, AUTH_RULE_IDENTITY, id->clone(id)); + if (skipped_auth(this, method, FALSE)) + { + return TRUE; + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + cfg = get_auth_cfg(peer_cfg, FALSE); + return cfg && auth->complies(auth, cfg, TRUE); +} + +/** + * Save authentication information after authentication succeeded + */ +static void save_auth_cfg(private_phase1_t *this, + auth_method_t method, bool local) +{ + auth_cfg_t *auth; + + if (skipped_auth(this, method, local)) + { + return; + } + auth = auth_cfg_create(); + /* for local config, we _copy_ entires from the config, as it contains + * certificates we must send later. */ + auth->merge(auth, this->ike_sa->get_auth_cfg(this->ike_sa, local), local); + this->ike_sa->add_auth_cfg(this->ike_sa, local, auth); +} + +/** + * Create an authenticator instance + */ +static authenticator_t* create_authenticator(private_phase1_t *this, + auth_method_t method, chunk_t id) +{ + authenticator_t *authenticator; + + authenticator = authenticator_create_v1(this->ike_sa, this->initiator, + method, this->dh, this->dh_value, this->sa_payload, id); + if (!authenticator) + { + DBG1(DBG_IKE, "negotiated authentication method %N not supported", + auth_method_names, method); + } + return authenticator; +} + +METHOD(phase1_t, verify_auth, bool, + private_phase1_t *this, auth_method_t method, message_t *message, + chunk_t id_data) +{ + authenticator_t *authenticator; + status_t status; + + authenticator = create_authenticator(this, method, id_data); + if (authenticator) + { + status = authenticator->process(authenticator, message); + authenticator->destroy(authenticator); + if (status == SUCCESS && check_constraints(this, method)) + { + save_auth_cfg(this, method, FALSE); + return TRUE; + } + } + return FALSE; +} + +METHOD(phase1_t, build_auth, bool, + private_phase1_t *this, auth_method_t method, message_t *message, + chunk_t id_data) +{ + authenticator_t *authenticator; + status_t status; + + authenticator = create_authenticator(this, method, id_data); + if (authenticator) + { + status = authenticator->build(authenticator, message); + authenticator->destroy(authenticator); + if (status == SUCCESS) + { + save_auth_cfg(this, method, TRUE); + return TRUE; + } + } + return FALSE; +} + +/** + * Get the two auth classes from local or remote config + */ +static void get_auth_class(peer_cfg_t *peer_cfg, bool local, + auth_class_t *c1, auth_class_t *c2) +{ + enumerator_t *enumerator; + auth_cfg_t *auth; + + *c1 = *c2 = AUTH_CLASS_ANY; + + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local); + while (enumerator->enumerate(enumerator, &auth)) + { + if (*c1 == AUTH_CLASS_ANY) + { + *c1 = (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS); + } + else + { + *c2 = (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS); + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Select an auth method to use by checking what key we have + */ +static auth_method_t get_pubkey_method(private_phase1_t *this, auth_cfg_t *auth) +{ + auth_method_t method = AUTH_NONE; + identification_t *id; + private_key_t *private; + + if (auth) + { + id = (identification_t*)auth->get(auth, AUTH_RULE_IDENTITY); + if (id) + { + private = lib->credmgr->get_private(lib->credmgr, KEY_ANY, id, NULL); + if (private) + { + switch (private->get_type(private)) + { + case KEY_RSA: + method = AUTH_RSA; + break; + case KEY_ECDSA: + switch (private->get_keysize(private)) + { + case 256: + method = AUTH_ECDSA_256; + break; + case 384: + method = AUTH_ECDSA_384; + break; + case 521: + method = AUTH_ECDSA_521; + break; + default: + DBG1(DBG_IKE, "%d bit ECDSA private key size not " + "supported", private->get_keysize(private)); + break; + } + break; + default: + DBG1(DBG_IKE, "private key of type %N not supported", + key_type_names, private->get_type(private)); + break; + } + private->destroy(private); + } + else + { + DBG1(DBG_IKE, "no private key found for '%Y'", id); + } + } + } + return method; +} + +/** + * Calculate authentication method from a peer config + */ +static auth_method_t calc_auth_method(private_phase1_t *this, + peer_cfg_t *peer_cfg) +{ + auth_class_t i1, i2, r1, r2; + + get_auth_class(peer_cfg, this->initiator, &i1, &i2); + get_auth_class(peer_cfg, !this->initiator, &r1, &r2); + + if (i1 == AUTH_CLASS_PUBKEY && r1 == AUTH_CLASS_PUBKEY) + { + if (i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + /* for any pubkey method, return RSA */ + return AUTH_RSA; + } + if (i2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_INIT_RSA; + } + if (r2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_RESP_RSA; + } + } + if (i1 == AUTH_CLASS_PSK && r1 == AUTH_CLASS_PSK) + { + if (i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + return AUTH_PSK; + } + if (i2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_INIT_PSK; + } + if (r2 == AUTH_CLASS_XAUTH) + { + return AUTH_XAUTH_RESP_PSK; + } + } + if (i1 == AUTH_CLASS_XAUTH && r1 == AUTH_CLASS_PUBKEY && + i2 == AUTH_CLASS_ANY && r2 == AUTH_CLASS_ANY) + { + return AUTH_HYBRID_INIT_RSA; + } + return AUTH_NONE; +} + +METHOD(phase1_t, get_auth_method, auth_method_t, + private_phase1_t *this, peer_cfg_t *peer_cfg) +{ + auth_method_t method; + + method = calc_auth_method(this, peer_cfg); + if (method == AUTH_RSA) + { + return get_pubkey_method(this, get_auth_cfg(peer_cfg, TRUE)); + } + return method; +} + +/** + * Check if a peer config can be used with a given auth method + */ +static bool check_auth_method(private_phase1_t *this, peer_cfg_t *peer_cfg, + auth_method_t given) +{ + auth_method_t method; + + method = calc_auth_method(this, peer_cfg); + switch (given) + { + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + return method == AUTH_RSA; + default: + return method == given; + } +} + +METHOD(phase1_t, select_config, peer_cfg_t*, + private_phase1_t *this, auth_method_t method, bool aggressive, + identification_t *id) +{ + enumerator_t *enumerator; + peer_cfg_t *current; + host_t *me, *other; + + if (this->peer_cfg) + { /* try to find an alternative config */ + if (this->candidates->remove_first(this->candidates, + (void**)¤t) != SUCCESS) + { + DBG1(DBG_CFG, "no alternative config found"); + return NULL; + } + DBG1(DBG_CFG, "switching to peer config '%s'", + current->get_name(current)); + return current; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + DBG1(DBG_CFG, "looking for %N peer configs matching %H...%H[%Y]", + auth_method_names, method, me, other, id); + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + me, other, NULL, id, IKEV1); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (check_auth_method(this, current, method) && + current->use_aggressive(current) == aggressive) + { + current->get_ref(current); + if (!this->peer_cfg) + { + this->peer_cfg = current; + } + else + { + this->candidates->insert_last(this->candidates, current); + } + } + } + enumerator->destroy(enumerator); + + if (this->peer_cfg) + { + DBG1(DBG_CFG, "selected peer config \"%s\"", + this->peer_cfg->get_name(this->peer_cfg)); + return this->peer_cfg->get_ref(this->peer_cfg); + } + DBG1(DBG_IKE, "no peer config found"); + return NULL; +} + +METHOD(phase1_t, get_id, identification_t*, + private_phase1_t *this, peer_cfg_t *peer_cfg, bool local) +{ + identification_t *id = NULL; + auth_cfg_t *auth; + + auth = get_auth_cfg(peer_cfg, local); + if (auth) + { + id = auth->get(auth, AUTH_RULE_IDENTITY); + if (local && (!id || id->get_type(id) == ID_ANY)) + { /* no ID configured, use local IP address */ + host_t *me; + + me = this->ike_sa->get_my_host(this->ike_sa); + if (!me->is_anyaddr(me)) + { + id = identification_create_from_sockaddr(me->get_sockaddr(me)); + auth->add(auth, AUTH_RULE_IDENTITY, id); + } + } + } + return id; +} + +METHOD(phase1_t, has_virtual_ip, bool, + private_phase1_t *this, peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + bool found = FALSE; + host_t *host; + + enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg); + found = enumerator->enumerate(enumerator, &host); + enumerator->destroy(enumerator); + + return found; +} + +METHOD(phase1_t, has_pool, bool, + private_phase1_t *this, peer_cfg_t *peer_cfg) +{ + enumerator_t *enumerator; + bool found = FALSE; + char *pool; + + enumerator = peer_cfg->create_pool_enumerator(peer_cfg); + found = enumerator->enumerate(enumerator, &pool); + enumerator->destroy(enumerator); + + return found; +} + +METHOD(phase1_t, save_sa_payload, bool, + private_phase1_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload, *sa = NULL; + chunk_t data; + size_t offset = IKE_HEADER_LENGTH; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa = payload; + break; + } + else + { + offset += payload->get_length(payload); + } + } + enumerator->destroy(enumerator); + + data = message->get_packet_data(message); + if (sa && data.len >= offset + sa->get_length(sa)) + { + /* Get SA payload without 4 byte fixed header */ + data = chunk_skip(data, offset); + data.len = sa->get_length(sa); + data = chunk_skip(data, 4); + this->sa_payload = chunk_clone(data); + return TRUE; + } + DBG1(DBG_IKE, "unable to extract SA payload encoding"); + return FALSE; +} + +METHOD(phase1_t, add_nonce_ke, bool, + private_phase1_t *this, message_t *message) +{ + nonce_payload_t *nonce_payload; + ke_payload_t *ke_payload; + nonce_gen_t *nonceg; + chunk_t nonce; + + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, this->dh); + message->add_payload(message, &ke_payload->payload_interface); + + nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); + if (!nonceg) + { + DBG1(DBG_IKE, "no nonce generator found to create nonce"); + return FALSE; + } + if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, &nonce)) + { + DBG1(DBG_IKE, "nonce allocation failed"); + nonceg->destroy(nonceg); + return FALSE; + } + nonceg->destroy(nonceg); + + nonce_payload = nonce_payload_create(NONCE_V1); + nonce_payload->set_nonce(nonce_payload, nonce); + message->add_payload(message, &nonce_payload->payload_interface); + + if (this->initiator) + { + this->nonce_i = nonce; + } + else + { + this->nonce_r = nonce; + } + return TRUE; +} + +METHOD(phase1_t, get_nonce_ke, bool, + private_phase1_t *this, message_t *message) +{ + nonce_payload_t *nonce_payload; + ke_payload_t *ke_payload; + + ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1); + if (!ke_payload) + { + DBG1(DBG_IKE, "KE payload missing in message"); + return FALSE; + } + this->dh_value = chunk_clone(ke_payload->get_key_exchange_data(ke_payload)); + this->dh->set_other_public_value(this->dh, this->dh_value); + + nonce_payload = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (!nonce_payload) + { + DBG1(DBG_IKE, "NONCE payload missing in message"); + return FALSE; + } + + if (this->initiator) + { + this->nonce_r = nonce_payload->get_nonce(nonce_payload); + } + else + { + this->nonce_i = nonce_payload->get_nonce(nonce_payload); + } + return TRUE; +} + +METHOD(phase1_t, destroy, void, + private_phase1_t *this) +{ + DESTROY_IF(this->peer_cfg); + this->candidates->destroy_offset(this->candidates, + offsetof(peer_cfg_t, destroy)); + chunk_free(&this->sa_payload); + DESTROY_IF(this->dh); + free(this->dh_value.ptr); + free(this->nonce_i.ptr); + free(this->nonce_r.ptr); + free(this); +} + +/** + * See header + */ +phase1_t *phase1_create(ike_sa_t *ike_sa, bool initiator) +{ + private_phase1_t *this; + + INIT(this, + .public = { + .create_hasher = _create_hasher, + .create_dh = _create_dh, + .derive_keys = _derive_keys, + .get_auth_method = _get_auth_method, + .get_id = _get_id, + .select_config = _select_config, + .has_virtual_ip = _has_virtual_ip, + .has_pool = _has_pool, + .verify_auth = _verify_auth, + .build_auth = _build_auth, + .save_sa_payload = _save_sa_payload, + .add_nonce_ke = _add_nonce_ke, + .get_nonce_ke = _get_nonce_ke, + .destroy = _destroy, + }, + .candidates = linked_list_create(), + .ike_sa = ike_sa, + .initiator = initiator, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/phase1.h b/src/libcharon/sa/ikev1/phase1.h new file mode 100644 index 000000000..eaf8908e7 --- /dev/null +++ b/src/libcharon/sa/ikev1/phase1.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * + * 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. + */ + +/** + * @defgroup phase1 phase1 + * @{ @ingroup ikev1 + */ + +#ifndef PHASE1_H_ +#define PHASE1_H_ + +typedef struct phase1_t phase1_t; + +#include <sa/ike_sa.h> +#include <crypto/diffie_hellman.h> + +/** + * Common phase 1 helper for main and aggressive mode. + */ +struct phase1_t { + + /** + * Create keymat hasher. + * + * @return TRUE if hasher created + */ + bool (*create_hasher)(phase1_t *this); + + /** + * Create DH object using SA keymat. + * + * @param group negotiated DH group + * @return TRUE if group supported + */ + bool (*create_dh)(phase1_t *this, diffie_hellman_group_t group); + + /** + * Derive key material. + * + * @param peer_cfg peer config to look up shared key for, or NULL + * @param method negotiated authenticated method + * @return TRUE if successful + */ + bool (*derive_keys)(phase1_t *this, peer_cfg_t *peer_cfg, + auth_method_t method); + /** + * Verify a HASH or SIG payload in message. + * + * @param method negotiated auth method + * @param message message containing HASH or SIG payload + * @param id_data encoded identity, including protocol/port fields + * @return TRUE if verified successfully + */ + bool (*verify_auth)(phase1_t *this, auth_method_t method, + message_t *message, chunk_t id_data); + + /** + * Build a HASH or SIG payload and add it to message. + * + * @param method negotiated auth method + * @param message message to add payload to + * @param id_data encoded identity, including protocol/port fields + * @return TRUE if built successfully + */ + bool (*build_auth)(phase1_t *this, auth_method_t method, + message_t *message, chunk_t id_data); + + /** + * Get the IKEv1 authentication method defined by peer config. + * + * @param peer_cfg peer config to get auth method from + * @return auth method, or AUTH_NONE + */ + auth_method_t (*get_auth_method)(phase1_t *this, peer_cfg_t *peer_cfg); + + /** + * Select a peer config as responder. + * + * If called after the first successful call the next alternative config + * is returned, if any. + * + * @param method used authentication method + * @param aggressive TRUE to get an aggressive mode config + * @param id initiator identity + * @return selected peer config, NULL if none found + */ + peer_cfg_t* (*select_config)(phase1_t *this, auth_method_t method, + bool aggressive, identification_t *id); + + /** + * Get configured identity from peer config. + * + * @param peer_cfg peer config to get identity from + * @param local TRUE to get own identity, FALSE for remote + * @return identity, pointing to internal config data + */ + identification_t* (*get_id)(phase1_t *this, peer_cfg_t *peer_cfg, bool local); + + /** + * Check if peer config has virtual IPs pool assigned. + * + * @param peer_cfg peer_config to check + * @return TRUE if peer config contains at least one pool + */ + bool (*has_pool)(phase1_t *this, peer_cfg_t *peer_cfg); + + /** + * Check if peer config has virtual IPs to request + * + * @param peer_cfg peer_config to check + * @return TRUE if peer config contains at least one virtual IP + */ + bool (*has_virtual_ip)(phase1_t *this, peer_cfg_t *peer_cfg); + + /** + * Extract and store SA payload bytes from encoded message. + * + * @param message message to extract SA payload bytes from + * @return TRUE if SA payload found + */ + bool (*save_sa_payload)(phase1_t *this, message_t *message); + + /** + * Add Nonce and KE payload to message. + * + * @param message message to add payloads + * @return TRUE if payloads added successfully + */ + bool (*add_nonce_ke)(phase1_t *this, message_t *message); + + /** + * Extract Nonce and KE payload from message. + * + * @param message message to get payloads from + * @return TRUE if payloads extracted successfully + */ + bool (*get_nonce_ke)(phase1_t *this, message_t *message); + + /** + * Destroy a phase1_t. + */ + void (*destroy)(phase1_t *this); +}; + +/** + * Create a phase1 instance. + * + * @param ike_sa IKE_SA to set up + * @param initiator TRUE if initiating actively + * @return Phase 1 helper + */ +phase1_t *phase1_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** PHASE1_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c new file mode 100644 index 000000000..fd0ad235a --- /dev/null +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -0,0 +1,1714 @@ +/* + * Copyright (C) 2007-2011 Tobias Brunner + * Copyright (C) 2007-2011 Martin Willi + * 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 "task_manager_v1.h" + +#include <math.h> + +#include <daemon.h> +#include <sa/ikev1/tasks/main_mode.h> +#include <sa/ikev1/tasks/aggressive_mode.h> +#include <sa/ikev1/tasks/quick_mode.h> +#include <sa/ikev1/tasks/quick_delete.h> +#include <sa/ikev1/tasks/xauth.h> +#include <sa/ikev1/tasks/mode_config.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/isakmp_natd.h> +#include <sa/ikev1/tasks/isakmp_vendor.h> +#include <sa/ikev1/tasks/isakmp_cert_pre.h> +#include <sa/ikev1/tasks/isakmp_cert_post.h> +#include <sa/ikev1/tasks/isakmp_delete.h> +#include <sa/ikev1/tasks/isakmp_dpd.h> + +#include <processing/jobs/retransmit_job.h> +#include <processing/jobs/delete_ike_sa_job.h> +#include <processing/jobs/dpd_timeout_job.h> + +/** + * Number of old messages hashes we keep for retransmission. + * + * In Main Mode, we must ignore messages from a previous message pair if + * we already continued to the next. Otherwise a late retransmission + * could be considered as a reply to the newer request. + */ +#define MAX_OLD_HASHES 2 + +/** + * First sequence number of responding packets. + * + * To distinguish retransmission jobs for initiating and responding packets, + * we split up the sequence counter and use the upper half for responding. + */ +#define RESPONDING_SEQ INT_MAX + +typedef struct exchange_t exchange_t; + +/** + * An exchange in the air, used do detect and handle retransmission + */ +struct exchange_t { + + /** + * Message ID used for this transaction + */ + u_int32_t mid; + + /** + * generated packet for retransmission + */ + packet_t *packet; +}; + +typedef struct private_task_manager_t private_task_manager_t; + +/** + * private data of the task manager + */ +struct private_task_manager_t { + + /** + * public functions + */ + task_manager_v1_t public; + + /** + * associated IKE_SA we are serving + */ + ike_sa_t *ike_sa; + + /** + * RNG to create message IDs + */ + rng_t *rng; + + /** + * Exchange we are currently handling as responder + */ + struct { + /** + * Message ID of the last response + */ + u_int32_t mid; + + /** + * Hash of a previously received message + */ + u_int32_t hash; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * Sequence number of the last sent message + */ + u_int32_t seqnr; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + } responding; + + /** + * Exchange we are currently handling as initiator + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * Hashes of old responses we can ignore + */ + u_int32_t old_hashes[MAX_OLD_HASHES]; + + /** + * Position in old hash array + */ + int old_hash_pos; + + /** + * Sequence number of the last sent message + */ + u_int32_t seqnr; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * type of the initated exchange + */ + exchange_type_t type; + + } initiating; + + /** + * List of queued tasks not yet in action + */ + linked_list_t *queued_tasks; + + /** + * List of active tasks, initiated by ourselve + */ + linked_list_t *active_tasks; + + /** + * List of tasks initiated by peer + */ + linked_list_t *passive_tasks; + + /** + * Queued messages not yet ready to process + */ + message_t *queued; + + /** + * Number of times we retransmit messages before giving up + */ + u_int retransmit_tries; + + /** + * Retransmission timeout + */ + double retransmit_timeout; + + /** + * Base to calculate retransmission timeout + */ + double retransmit_base; + + /** + * Sequence number for sending DPD requests + */ + u_int32_t dpd_send; + + /** + * Sequence number for received DPD requests + */ + u_int32_t dpd_recv; +}; + +METHOD(task_manager_t, flush_queue, void, + private_task_manager_t *this, task_queue_t queue) +{ + linked_list_t *list; + task_t *task; + + if (this->queued) + { + this->queued->destroy(this->queued); + this->queued = NULL; + } + switch (queue) + { + case TASK_QUEUE_ACTIVE: + list = this->active_tasks; + /* cancel pending retransmits */ + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + DESTROY_IF(this->initiating.packet); + this->initiating.packet = NULL; + break; + case TASK_QUEUE_PASSIVE: + list = this->passive_tasks; + break; + case TASK_QUEUE_QUEUED: + list = this->queued_tasks; + break; + default: + return; + } + while (list->remove_last(list, (void**)&task) == SUCCESS) + { + task->destroy(task); + } +} + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + flush_queue(this, TASK_QUEUE_QUEUED); + flush_queue(this, TASK_QUEUE_PASSIVE); + flush_queue(this, TASK_QUEUE_ACTIVE); +} + +/** + * move a task of a specific type from the queue to the active list + */ +static bool activate_task(private_task_manager_t *this, task_type_t type) +{ + enumerator_t *enumerator; + task_t *task; + bool found = FALSE; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + if (task->get_type(task) == type) + { + DBG2(DBG_IKE, " activating %N task", task_type_names, type); + this->queued_tasks->remove_at(this->queued_tasks, enumerator); + this->active_tasks->insert_last(this->active_tasks, task); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +/** + * Retransmit a packet, either as initiator or as responder + */ +static status_t retransmit_packet(private_task_manager_t *this, u_int32_t seqnr, + u_int mid, u_int retransmitted, packet_t *packet) +{ + u_int32_t t; + + if (retransmitted > this->retransmit_tries) + { + DBG1(DBG_IKE, "giving up after %u retransmits", retransmitted - 1); + return DESTROY_ME; + } + t = (u_int32_t)(this->retransmit_timeout * 1000.0 * + pow(this->retransmit_base, retransmitted)); + if (retransmitted) + { + DBG1(DBG_IKE, "sending retransmit %u of %s message ID %u, seq %u", + retransmitted, seqnr < RESPONDING_SEQ ? "request" : "response", + mid, seqnr < RESPONDING_SEQ ? seqnr : seqnr - RESPONDING_SEQ); + } + charon->sender->send(charon->sender, packet->clone(packet)); + lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*) + retransmit_job_create(seqnr, this->ike_sa->get_id(this->ike_sa)), t); + return NEED_MORE; +} + +METHOD(task_manager_t, retransmit, status_t, + private_task_manager_t *this, u_int32_t seqnr) +{ + status_t status = SUCCESS; + + if (seqnr == this->initiating.seqnr && this->initiating.packet) + { + status = retransmit_packet(this, seqnr, this->initiating.mid, + this->initiating.retransmitted, this->initiating.packet); + if (status == NEED_MORE) + { + this->initiating.retransmitted++; + status = SUCCESS; + } + } + if (seqnr == this->responding.seqnr && this->responding.packet) + { + status = retransmit_packet(this, seqnr, this->responding.mid, + this->responding.retransmitted, this->responding.packet); + if (status == NEED_MORE) + { + this->responding.retransmitted++; + status = SUCCESS; + } + } + return status; +} + +/** + * Check if we have to wait for a mode config before starting a quick mode + */ +static bool mode_config_expected(private_task_manager_t *this) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + char *pool; + host_t *host; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_pool_enumerator(peer_cfg); + if (!enumerator->enumerate(enumerator, &pool)) + { /* no pool configured */ + enumerator->destroy(enumerator); + return FALSE; + } + enumerator->destroy(enumerator); + + enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, + FALSE); + if (!enumerator->enumerate(enumerator, &host)) + { /* have a pool, but no VIP assigned yet */ + enumerator->destroy(enumerator); + return TRUE; + } + enumerator->destroy(enumerator); + } + return FALSE; +} + +METHOD(task_manager_t, initiate, status_t, + private_task_manager_t *this) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + status_t status; + exchange_type_t exchange = EXCHANGE_TYPE_UNDEFINED; + bool new_mid = FALSE, expect_response = FALSE, cancelled = FALSE, keep = FALSE; + + if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED && + this->initiating.type != INFORMATIONAL_V1) + { + DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress", + exchange_type_names, this->initiating.type); + /* do not initiate if we already have a message in the air */ + return SUCCESS; + } + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { + DBG2(DBG_IKE, "activating new tasks"); + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CREATED: + activate_task(this, TASK_ISAKMP_VENDOR); + activate_task(this, TASK_ISAKMP_CERT_PRE); + if (activate_task(this, TASK_MAIN_MODE)) + { + exchange = ID_PROT; + } + else if (activate_task(this, TASK_AGGRESSIVE_MODE)) + { + exchange = AGGRESSIVE; + } + activate_task(this, TASK_ISAKMP_CERT_POST); + activate_task(this, TASK_ISAKMP_NATD); + break; + case IKE_CONNECTING: + if (activate_task(this, TASK_ISAKMP_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_XAUTH)) + { + exchange = TRANSACTION; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_INFORMATIONAL)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, TASK_MODE_CONFIG)) + { + exchange = TRANSACTION; + new_mid = TRUE; + break; + } + if (!mode_config_expected(this) && + activate_task(this, TASK_QUICK_MODE)) + { + exchange = QUICK_MODE; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_INFORMATIONAL)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_QUICK_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_ISAKMP_DELETE)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + if (activate_task(this, TASK_ISAKMP_DPD)) + { + exchange = INFORMATIONAL_V1; + new_mid = TRUE; + break; + } + break; + default: + break; + } + } + else + { + DBG2(DBG_IKE, "reinitiating already active tasks"); + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); + switch (task->get_type(task)) + { + case TASK_MAIN_MODE: + exchange = ID_PROT; + break; + case TASK_AGGRESSIVE_MODE: + exchange = AGGRESSIVE; + break; + case TASK_QUICK_MODE: + exchange = QUICK_MODE; + break; + case TASK_XAUTH: + exchange = TRANSACTION; + new_mid = TRUE; + break; + default: + continue; + } + break; + } + enumerator->destroy(enumerator); + } + + if (exchange == EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + + if (new_mid) + { + if (!this->rng->get_bytes(this->rng, sizeof(this->initiating.mid), + (void*)&this->initiating.mid)) + { + DBG1(DBG_IKE, "failed to allocate message ID, destroying IKE_SA"); + flush(this); + return DESTROY_ME; + } + } + message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + message->set_message_id(message, this->initiating.mid); + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_exchange_type(message, exchange); + this->initiating.type = exchange; + this->initiating.retransmitted = 0; + + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + if (task->get_type(task) == TASK_AGGRESSIVE_MODE || + task->get_type(task) == TASK_QUICK_MODE) + { /* last message of three message exchange */ + keep = TRUE; + } + task->destroy(task); + continue; + case NEED_MORE: + expect_response = TRUE; + /* processed, but task needs another exchange */ + continue; + case ALREADY_DONE: + cancelled = TRUE; + break; + case FAILED: + default: + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + enumerator->destroy(enumerator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + if (this->active_tasks->get_count(this->active_tasks) == 0 && + (exchange == QUICK_MODE || exchange == AGGRESSIVE)) + { /* tasks completed, no exchange active anymore */ + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + } + if (cancelled) + { + message->destroy(message); + return initiate(this); + } + + DESTROY_IF(this->initiating.packet); + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + message->destroy(message); + flush(this); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + this->initiating.seqnr++; + if (expect_response) + { + message->destroy(message); + return retransmit(this, this->initiating.seqnr); + } + if (keep) + { /* keep the packet for retransmission, the responder might request it */ + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + } + else + { + charon->sender->send(charon->sender, this->initiating.packet); + this->initiating.packet = NULL; + } + message->destroy(message); + + if (exchange == INFORMATIONAL_V1) + { + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CONNECTING: + /* close after sending an INFORMATIONAL when unestablished */ + return FAILED; + case IKE_DELETING: + /* close after sending a DELETE */ + return DESTROY_ME; + default: + break; + } + } + return initiate(this); +} + +/** + * build a response depending on the "passive" task list + */ +static status_t build_response(private_task_manager_t *this, message_t *request) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + bool delete = FALSE, cancelled = FALSE, expect_request = FALSE; + status_t status; + + me = request->get_destination(request); + other = request->get_source(request); + + message = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + message->set_exchange_type(message, request->get_exchange_type(request)); + /* send response along the path the request came in */ + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_message_id(message, request->get_message_id(request)); + message->set_request(message, FALSE); + + this->responding.mid = request->get_message_id(request); + this->responding.retransmitted = 0; + this->responding.seqnr++; + + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + /* processed, but task needs another exchange */ + if (task->get_type(task) == TASK_QUICK_MODE || + task->get_type(task) == TASK_AGGRESSIVE_MODE) + { /* we rely on initiator retransmission, except for + * three-message exchanges */ + expect_request = TRUE; + } + continue; + case ALREADY_DONE: + cancelled = TRUE; + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* destroy IKE_SA, but SEND response first */ + delete = TRUE; + break; + } + break; + } + enumerator->destroy(enumerator); + + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + if (cancelled) + { + message->destroy(message); + return initiate(this); + } + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->responding.packet); + message->destroy(message); + if (status != SUCCESS) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + if (expect_request && !delete) + { + return retransmit(this, this->responding.seqnr); + } + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + if (delete) + { + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * Send a notify in a separate INFORMATIONAL exchange back to the sender. + * The notify protocol_id is set to ISAKMP + */ +static void send_notify(private_task_manager_t *this, message_t *request, + notify_type_t type) +{ + message_t *response; + packet_t *packet; + host_t *me, *other; + u_int32_t mid; + + if (request->get_exchange_type(request) == INFORMATIONAL_V1) + { /* don't respond to INFORMATIONAL requests to avoid a notify war */ + DBG1(DBG_IKE, "ignore malformed INFORMATIONAL request"); + return; + } + if (!this->rng->get_bytes(this->rng, sizeof(mid), (void*)&mid)) + { + DBG1(DBG_IKE, "failed to allocate message ID"); + return; + } + response = message_create(IKEV1_MAJOR_VERSION, IKEV1_MINOR_VERSION); + response->set_exchange_type(response, INFORMATIONAL_V1); + response->set_request(response, TRUE); + response->set_message_id(response, mid); + response->add_payload(response, (payload_t*) + notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, type)); + + me = this->ike_sa->get_my_host(this->ike_sa); + if (me->is_anyaddr(me)) + { + me = request->get_destination(request); + this->ike_sa->set_my_host(this->ike_sa, me->clone(me)); + } + other = this->ike_sa->get_other_host(this->ike_sa); + if (other->is_anyaddr(other)) + { + other = request->get_source(request); + this->ike_sa->set_other_host(this->ike_sa, other->clone(other)); + } + response->set_source(response, me->clone(me)); + response->set_destination(response, other->clone(other)); + if (this->ike_sa->generate_message(this->ike_sa, response, + &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet); + } + response->destroy(response); +} + +/** + * Process a DPD request/response + */ +static bool process_dpd(private_task_manager_t *this, message_t *message) +{ + notify_payload_t *notify; + notify_type_t type; + u_int32_t seq; + chunk_t data; + + type = DPD_R_U_THERE; + notify = message->get_notify(message, type); + if (!notify) + { + type = DPD_R_U_THERE_ACK; + notify = message->get_notify(message, type); + } + if (!notify) + { + return FALSE; + } + data = notify->get_notification_data(notify); + if (data.len != 4) + { + return FALSE; + } + seq = untoh32(data.ptr); + + if (type == DPD_R_U_THERE) + { + if (this->dpd_recv == 0 || seq == this->dpd_recv) + { /* check sequence validity */ + this->dpd_recv = seq + 1; + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + } + /* but respond anyway */ + this->ike_sa->queue_task(this->ike_sa, + &isakmp_dpd_create(this->ike_sa, DPD_R_U_THERE_ACK, seq)->task); + } + else /* DPD_R_U_THERE_ACK */ + { + if (seq == this->dpd_send - 1) + { + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + } + else + { + DBG1(DBG_IKE, "received invalid DPD sequence number %u " + "(expected %u), ignored", seq, this->dpd_send - 1); + } + } + return TRUE; +} + +/** + * handle an incoming request message + */ +static status_t process_request(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + task_t *task = NULL; + bool send_response = FALSE, dpd = FALSE; + + if (message->get_exchange_type(message) == INFORMATIONAL_V1 || + this->passive_tasks->get_count(this->passive_tasks) == 0) + { /* create tasks depending on request type, if not already some queued */ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + task = (task_t *)isakmp_vendor_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_pre_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)main_mode_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_post_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)isakmp_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case AGGRESSIVE: + task = (task_t *)isakmp_vendor_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_pre_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)aggressive_mode_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)isakmp_cert_post_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t *)isakmp_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case QUICK_MODE: + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { + DBG1(DBG_IKE, "received quick mode request for " + "unestablished IKE_SA, ignored"); + return FAILED; + } + task = (task_t *)quick_mode_create(this->ike_sa, NULL, + NULL, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + case INFORMATIONAL_V1: + if (process_dpd(this, message)) + { + dpd = TRUE; + } + else + { + task = (task_t *)informational_create(this->ike_sa, NULL); + this->passive_tasks->insert_first(this->passive_tasks, task); + } + break; + case TRANSACTION: + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + task = (task_t *)mode_config_create(this->ike_sa, FALSE); + } + else + { + task = (task_t *)xauth_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + default: + return FAILED; + } + } + if (dpd) + { + return initiate(this); + } + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, time_monotonic(NULL)); + + /* let the tasks process the message */ + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + /* processed, but task needs at least another call to build() */ + send_response = TRUE; + continue; + case ALREADY_DONE: + send_response = FALSE; + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + if (send_response) + { + if (build_response(this, message) != SUCCESS) + { + return DESTROY_ME; + } + } + else + { /* We don't send a response, so don't retransmit one if we get + * the same message again. */ + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + } + if (this->passive_tasks->get_count(this->passive_tasks) == 0 && + this->queued_tasks->get_count(this->queued_tasks) > 0) + { + /* passive tasks completed, check if an active task has been queued, + * such as XAUTH or modeconfig push */ + return initiate(this); + } + return SUCCESS; +} + +/** + * handle an incoming response message + */ +static status_t process_response(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + message_t *queued; + status_t status; + task_t *task; + + if (message->get_exchange_type(message) != this->initiating.type) + { + DBG1(DBG_IKE, "received %N response, but expected %N", + exchange_type_names, message->get_exchange_type(message), + exchange_type_names, this->initiating.type); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + task->destroy(task); + continue; + case NEED_MORE: + /* processed, but task needs another exchange */ + continue; + case ALREADY_DONE: + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + break; + } + enumerator->destroy(enumerator); + + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + DESTROY_IF(this->initiating.packet); + this->initiating.packet = NULL; + + if (this->queued && this->active_tasks->get_count(this->active_tasks) == 0) + { + queued = this->queued; + this->queued = NULL; + status = this->public.task_manager.process_message( + &this->public.task_manager, queued); + queued->destroy(queued); + if (status == DESTROY_ME) + { + return status; + } + } + + return initiate(this); +} + +/** + * Parse the given message and verify that it is valid. + */ +static status_t parse_message(private_task_manager_t *this, message_t *msg) +{ + status_t status; + + status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa)); + + if (status != SUCCESS) + { + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "unsupported exchange type"); + send_notify(this, msg, INVALID_EXCHANGE_TYPE); + break; + case PARSE_ERROR: + DBG1(DBG_IKE, "message parsing failed"); + send_notify(this, msg, PAYLOAD_MALFORMED); + break; + case VERIFY_ERROR: + DBG1(DBG_IKE, "message verification failed"); + send_notify(this, msg, PAYLOAD_MALFORMED); + break; + case FAILED: + DBG1(DBG_IKE, "integrity check failed"); + send_notify(this, msg, INVALID_HASH_INFORMATION); + break; + case INVALID_STATE: + DBG1(DBG_IKE, "found encrypted message, but no keys available"); + send_notify(this, msg, PAYLOAD_MALFORMED); + default: + break; + } + DBG1(DBG_IKE, "%N %s with message ID %u processing failed", + exchange_type_names, msg->get_exchange_type(msg), + msg->get_request(msg) ? "request" : "response", + msg->get_message_id(msg)); + + if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED) + { /* invalid initiation attempt, close SA */ + return DESTROY_ME; + } + } + return status; +} + +METHOD(task_manager_t, process_message, status_t, + private_task_manager_t *this, message_t *msg) +{ + u_int32_t hash, mid, i; + host_t *me, *other; + status_t status; + + /* TODO-IKEv1: update hosts more selectively */ + me = msg->get_destination(msg); + other = msg->get_source(msg); + mid = msg->get_message_id(msg); + hash = chunk_hash(msg->get_packet_data(msg)); + for (i = 0; i < MAX_OLD_HASHES; i++) + { + if (this->initiating.old_hashes[i] == hash) + { + if (this->initiating.packet && + i == (this->initiating.old_hash_pos % MAX_OLD_HASHES) && + (msg->get_exchange_type(msg) == QUICK_MODE || + msg->get_exchange_type(msg) == AGGRESSIVE)) + { + DBG1(DBG_IKE, "received retransmit of response with ID %u, " + "resending last request", mid); + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + return SUCCESS; + } + DBG1(DBG_IKE, "received retransmit of response with ID %u, " + "but next request already sent", mid); + return SUCCESS; + } + } + + if ((mid && mid == this->initiating.mid) || + (this->initiating.mid == 0 && + msg->get_exchange_type(msg) == this->initiating.type && + this->active_tasks->get_count(this->active_tasks))) + { + msg->set_request(msg, FALSE); + charon->bus->message(charon->bus, msg, TRUE, FALSE); + status = parse_message(this, msg); + if (status != SUCCESS) + { + return status; + } + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); + charon->bus->message(charon->bus, msg, TRUE, TRUE); + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->initiating.old_hashes[(++this->initiating.old_hash_pos) % + MAX_OLD_HASHES] = hash; + } + else + { + if (hash == this->responding.hash) + { + if (this->responding.packet) + { + DBG1(DBG_IKE, "received retransmit of request with ID %u, " + "retransmitting response", mid); + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + } + else if (this->initiating.packet && + this->initiating.type == INFORMATIONAL_V1) + { + DBG1(DBG_IKE, "received retransmit of DPD request, " + "retransmitting response"); + charon->sender->send(charon->sender, + this->initiating.packet->clone(this->initiating.packet)); + } + else + { + DBG1(DBG_IKE, "received retransmit of request with ID %u, " + "but no response to retransmit", mid); + } + return SUCCESS; + } + if (msg->get_exchange_type(msg) == TRANSACTION && + this->active_tasks->get_count(this->active_tasks)) + { /* main mode not yet complete, queue XAuth/Mode config tasks */ + if (this->queued) + { + DBG1(DBG_IKE, "ignoring additional %N request, queue full", + exchange_type_names, TRANSACTION); + return SUCCESS; + } + this->queued = message_create_from_packet(msg->get_packet(msg)); + if (this->queued->parse_header(this->queued) != SUCCESS) + { + this->queued->destroy(this->queued); + this->queued = NULL; + return FAILED; + } + DBG1(DBG_IKE, "queueing %N request as tasks still active", + exchange_type_names, TRANSACTION); + return SUCCESS; + } + + msg->set_request(msg, TRUE); + charon->bus->message(charon->bus, msg, TRUE, FALSE); + status = parse_message(this, msg); + if (status != SUCCESS) + { + return status; + } + /* if this IKE_SA is virgin, we check for a config */ + if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL) + { + ike_sa_id_t *ike_sa_id; + ike_cfg_t *ike_cfg; + job_t *job; + + ike_cfg = charon->backends->get_ike_cfg(charon->backends, me, other); + if (ike_cfg == NULL) + { + /* no config found for these hosts, destroy */ + DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", + me, other, notify_type_names, NO_PROPOSAL_CHOSEN); + send_notify(this, msg, NO_PROPOSAL_CHOSEN); + return DESTROY_ME; + } + this->ike_sa->set_ike_cfg(this->ike_sa, ike_cfg); + ike_cfg->destroy(ike_cfg); + /* add a timeout if peer does not establish it completely */ + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + job = (job_t*)delete_ike_sa_job_create(ike_sa_id, FALSE); + lib->scheduler->schedule_job(lib->scheduler, job, + lib->settings->get_int(lib->settings, + "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT, + charon->name)); + } + this->ike_sa->update_hosts(this->ike_sa, me, other, TRUE); + charon->bus->message(charon->bus, msg, TRUE, TRUE); + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.hash = hash; + } + return SUCCESS; +} + +METHOD(task_manager_t, queue_task, void, + private_task_manager_t *this, task_t *task) +{ + DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); + this->queued_tasks->insert_last(this->queued_tasks, task); +} + +/** + * Check if a given task has been queued already + */ +static bool has_queued(private_task_manager_t *this, task_type_t type) +{ + enumerator_t *enumerator; + bool found = FALSE; + task_t *task; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + if (task->get_type(task) == type) + { + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +METHOD(task_manager_t, queue_ike, void, + private_task_manager_t *this) +{ + peer_cfg_t *peer_cfg; + + if (!has_queued(this, TASK_ISAKMP_VENDOR)) + { + queue_task(this, (task_t*)isakmp_vendor_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_ISAKMP_CERT_PRE)) + { + queue_task(this, (task_t*)isakmp_cert_pre_create(this->ike_sa, TRUE)); + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg->use_aggressive(peer_cfg)) + { + if (!has_queued(this, TASK_AGGRESSIVE_MODE)) + { + queue_task(this, (task_t*)aggressive_mode_create(this->ike_sa, TRUE)); + } + } + else + { + if (!has_queued(this, TASK_MAIN_MODE)) + { + queue_task(this, (task_t*)main_mode_create(this->ike_sa, TRUE)); + } + } + if (!has_queued(this, TASK_ISAKMP_CERT_POST)) + { + queue_task(this, (task_t*)isakmp_cert_post_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_ISAKMP_NATD)) + { + queue_task(this, (task_t*)isakmp_natd_create(this->ike_sa, TRUE)); + } +} + +METHOD(task_manager_t, queue_ike_reauth, void, + private_task_manager_t *this) +{ + enumerator_t *enumerator; + child_sa_t *child_sa; + ike_sa_t *new; + host_t *host; + + new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + this->ike_sa->get_version(this->ike_sa), TRUE); + if (!new) + { /* shouldn't happen */ + return; + } + + new->set_peer_cfg(new, this->ike_sa->get_peer_cfg(this->ike_sa)); + host = this->ike_sa->get_other_host(this->ike_sa); + new->set_other_host(new, host->clone(host)); + host = this->ike_sa->get_my_host(this->ike_sa); + new->set_my_host(new, host->clone(host)); + enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, TRUE); + while (enumerator->enumerate(enumerator, &host)) + { + new->add_virtual_ip(new, TRUE, host); + } + enumerator->destroy(enumerator); + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (enumerator->enumerate(enumerator, &child_sa)) + { + this->ike_sa->remove_child_sa(this->ike_sa, enumerator); + new->add_child_sa(new, child_sa); + } + enumerator->destroy(enumerator); + + if (!new->get_child_count(new)) + { /* check if a Quick Mode task is queued (UNITY_LOAD_BALANCE case) */ + task_t *task; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + if (task->get_type(task) == TASK_QUICK_MODE) + { + this->queued_tasks->remove_at(this->queued_tasks, enumerator); + task->migrate(task, new); + new->queue_task(new, task); + } + } + enumerator->destroy(enumerator); + } + + if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + { + charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + } + else + { + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new); + DBG1(DBG_IKE, "reauthenticating IKE_SA failed"); + } + charon->bus->set_sa(charon->bus, this->ike_sa); +} + +METHOD(task_manager_t, queue_ike_rekey, void, + private_task_manager_t *this) +{ + queue_ike_reauth(this); +} + +METHOD(task_manager_t, queue_ike_delete, void, + private_task_manager_t *this) +{ + enumerator_t *enumerator; + child_sa_t *child_sa; + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (enumerator->enumerate(enumerator, &child_sa)) + { + queue_task(this, (task_t*) + quick_delete_create(this->ike_sa, child_sa->get_protocol(child_sa), + child_sa->get_spi(child_sa, TRUE), FALSE, FALSE)); + } + enumerator->destroy(enumerator); + + queue_task(this, (task_t*)isakmp_delete_create(this->ike_sa, TRUE)); +} + +METHOD(task_manager_t, queue_mobike, void, + private_task_manager_t *this, bool roam, bool address) +{ + /* Not supported in IKEv1 */ +} + +METHOD(task_manager_t, queue_child, void, + private_task_manager_t *this, child_cfg_t *cfg, u_int32_t reqid, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + quick_mode_t *task; + + task = quick_mode_create(this->ike_sa, cfg, tsi, tsr); + task->use_reqid(task, reqid); + + queue_task(this, &task->task); +} + +/** + * Check if two CHILD_SAs have the same traffic selector + */ +static bool have_equal_ts(child_sa_t *a, child_sa_t *b, bool local) +{ + linked_list_t *list; + traffic_selector_t *ts_a, *ts_b; + + list = a->get_traffic_selectors(a, local); + if (list->get_first(list, (void**)&ts_a) == SUCCESS) + { + list = b->get_traffic_selectors(b, local); + if (list->get_first(list, (void**)&ts_b) == SUCCESS) + { + return ts_a->equals(ts_a, ts_b); + } + } + return FALSE; +} + +/** + * Check if a CHILD_SA is redundant and we should delete instead of rekey + */ +static bool is_redundant(private_task_manager_t *this, child_sa_t *child_sa) +{ + enumerator_t *enumerator; + child_sa_t *current; + bool redundant = FALSE; + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (current->get_state(current) == CHILD_INSTALLED && + streq(current->get_name(current), child_sa->get_name(child_sa)) && + have_equal_ts(current, child_sa, TRUE) && + have_equal_ts(current, child_sa, FALSE) && + current->get_lifetime(current, FALSE) > + child_sa->get_lifetime(child_sa, FALSE)) + { + DBG1(DBG_IKE, "deleting redundant CHILD_SA %s{%d}", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa)); + redundant = TRUE; + break; + } + } + enumerator->destroy(enumerator); + + return redundant; +} + +/** + * Get the first traffic selector of a CHILD_SA, local or remote + */ +static traffic_selector_t* get_first_ts(child_sa_t *child_sa, bool local) +{ + traffic_selector_t *ts = NULL; + linked_list_t *list; + + list = child_sa->get_traffic_selectors(child_sa, local); + if (list->get_first(list, (void**)&ts) == SUCCESS) + { + return ts; + } + return NULL; +} + +METHOD(task_manager_t, queue_child_rekey, void, + private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi) +{ + child_sa_t *child_sa; + child_cfg_t *cfg; + quick_mode_t *task; + + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, TRUE); + if (!child_sa) + { + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, FALSE); + } + if (child_sa && child_sa->get_state(child_sa) == CHILD_INSTALLED) + { + if (is_redundant(this, child_sa)) + { + queue_task(this, (task_t*)quick_delete_create(this->ike_sa, + protocol, spi, FALSE, FALSE)); + } + else + { + child_sa->set_state(child_sa, CHILD_REKEYING); + cfg = child_sa->get_config(child_sa); + task = quick_mode_create(this->ike_sa, cfg->get_ref(cfg), + get_first_ts(child_sa, TRUE), get_first_ts(child_sa, FALSE)); + task->use_reqid(task, child_sa->get_reqid(child_sa)); + task->rekey(task, child_sa->get_spi(child_sa, TRUE)); + + queue_task(this, &task->task); + } + } +} + +METHOD(task_manager_t, queue_child_delete, void, + private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi, + bool expired) +{ + queue_task(this, (task_t*)quick_delete_create(this->ike_sa, protocol, + spi, FALSE, expired)); +} + +METHOD(task_manager_t, queue_dpd, void, + private_task_manager_t *this) +{ + peer_cfg_t *peer_cfg; + u_int32_t t, retransmit; + + queue_task(this, (task_t*)isakmp_dpd_create(this->ike_sa, DPD_R_U_THERE, + this->dpd_send++)); + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + + /* compute timeout in milliseconds */ + t = 1000 * peer_cfg->get_dpd_timeout(peer_cfg); + if (t == 0) + { + /* use the same timeout as a retransmitting IKE message would have */ + for (retransmit = 0; retransmit <= this->retransmit_tries; retransmit++) + { + t += (u_int32_t)(this->retransmit_timeout * 1000.0 * + pow(this->retransmit_base, retransmit)); + } + } + + /* schedule DPD timeout job */ + lib->scheduler->schedule_job_ms(lib->scheduler, + (job_t*)dpd_timeout_job_create(this->ike_sa->get_id(this->ike_sa)), t); +} + +METHOD(task_manager_t, adopt_tasks, void, + private_task_manager_t *this, task_manager_t *other_public) +{ + private_task_manager_t *other = (private_task_manager_t*)other_public; + task_t *task; + + /* move queued tasks from other to this */ + while (other->queued_tasks->remove_last(other->queued_tasks, + (void**)&task) == SUCCESS) + { + DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +METHOD(task_manager_t, busy, bool, + private_task_manager_t *this) +{ + return (this->active_tasks->get_count(this->active_tasks) > 0); +} + +METHOD(task_manager_t, incr_mid, void, + private_task_manager_t *this, bool initiate) +{ +} + +METHOD(task_manager_t, reset, void, + private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) +{ + enumerator_t *enumerator; + task_t *task; + + /* reset message counters and retransmit packets */ + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + this->responding.packet = NULL; + this->responding.seqnr = RESPONDING_SEQ; + this->responding.retransmitted = 0; + this->initiating.packet = NULL; + this->initiating.mid = 0; + this->initiating.seqnr = 0; + this->initiating.retransmitted = 0; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + if (initiate != UINT_MAX) + { + this->dpd_send = initiate; + } + if (respond != UINT_MAX) + { + this->dpd_recv = respond; + } + + /* reset queued tasks */ + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + task->migrate(task, this->ike_sa); + } + enumerator->destroy(enumerator); + + /* reset active tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +METHOD(task_manager_t, create_task_enumerator, enumerator_t*, + private_task_manager_t *this, task_queue_t queue) +{ + switch (queue) + { + case TASK_QUEUE_ACTIVE: + return this->active_tasks->create_enumerator(this->active_tasks); + case TASK_QUEUE_PASSIVE: + return this->passive_tasks->create_enumerator(this->passive_tasks); + case TASK_QUEUE_QUEUED: + return this->queued_tasks->create_enumerator(this->queued_tasks); + default: + return enumerator_create_empty(); + } +} + +METHOD(task_manager_t, destroy, void, + private_task_manager_t *this) +{ + flush(this); + + this->active_tasks->destroy(this->active_tasks); + this->queued_tasks->destroy(this->queued_tasks); + this->passive_tasks->destroy(this->passive_tasks); + + DESTROY_IF(this->queued); + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + DESTROY_IF(this->rng); + free(this); +} + +/* + * see header file + */ +task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa) +{ + private_task_manager_t *this; + + INIT(this, + .public = { + .task_manager = { + .process_message = _process_message, + .queue_task = _queue_task, + .queue_ike = _queue_ike, + .queue_ike_rekey = _queue_ike_rekey, + .queue_ike_reauth = _queue_ike_reauth, + .queue_ike_delete = _queue_ike_delete, + .queue_mobike = _queue_mobike, + .queue_child = _queue_child, + .queue_child_rekey = _queue_child_rekey, + .queue_child_delete = _queue_child_delete, + .queue_dpd = _queue_dpd, + .initiate = _initiate, + .retransmit = _retransmit, + .incr_mid = _incr_mid, + .reset = _reset, + .adopt_tasks = _adopt_tasks, + .busy = _busy, + .create_task_enumerator = _create_task_enumerator, + .flush_queue = _flush_queue, + .destroy = _destroy, + }, + }, + .initiating = { + .type = EXCHANGE_TYPE_UNDEFINED, + }, + .responding = { + .seqnr = RESPONDING_SEQ, + }, + .ike_sa = ike_sa, + .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK), + .queued_tasks = linked_list_create(), + .active_tasks = linked_list_create(), + .passive_tasks = linked_list_create(), + .retransmit_tries = lib->settings->get_int(lib->settings, + "%s.retransmit_tries", RETRANSMIT_TRIES, charon->name), + .retransmit_timeout = lib->settings->get_double(lib->settings, + "%s.retransmit_timeout", RETRANSMIT_TIMEOUT, charon->name), + .retransmit_base = lib->settings->get_double(lib->settings, + "%s.retransmit_base", RETRANSMIT_BASE, charon->name), + ); + + if (!this->rng) + { + DBG1(DBG_IKE, "no RNG found, unable to create IKE_SA"); + destroy(this); + return NULL; + } + if (!this->rng->get_bytes(this->rng, sizeof(this->dpd_send), + (void*)&this->dpd_send)) + { + DBG1(DBG_IKE, "failed to allocate message ID, unable to create IKE_SA"); + destroy(this); + return NULL; + } + this->dpd_send &= 0x7FFFFFFF; + + return &this->public; +} + diff --git a/src/libcharon/sa/ikev1/task_manager_v1.h b/src/libcharon/sa/ikev1/task_manager_v1.h new file mode 100644 index 000000000..61e409bbe --- /dev/null +++ b/src/libcharon/sa/ikev1/task_manager_v1.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup task_manager_v1 task_manager_v1 + * @{ @ingroup ikev1 + */ + +#ifndef TASK_MANAGER_V1_H_ +#define TASK_MANAGER_V1_H_ + +typedef struct task_manager_v1_t task_manager_v1_t; + +#include <sa/task_manager.h> + +/** + * Task manager, IKEv1 variant. + */ +struct task_manager_v1_t { + + /** + * Implements task_manager_t. + */ + task_manager_t task_manager; +}; + +/** + * Create an instance of the task manager. + * + * @param ike_sa IKE_SA to manage. + */ +task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa); + +#endif /** TASK_MANAGER_V1_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/aggressive_mode.c b/src/libcharon/sa/ikev1/tasks/aggressive_mode.c new file mode 100644 index 000000000..954dea880 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/aggressive_mode.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * + * 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 "aggressive_mode.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/ikev1/phase1.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/hash_payload.h> +#include <sa/ikev1/tasks/xauth.h> +#include <sa/ikev1/tasks/mode_config.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/isakmp_delete.h> +#include <processing/jobs/adopt_children_job.h> + +typedef struct private_aggressive_mode_t private_aggressive_mode_t; + +/** + * Private members of a aggressive_mode_t task. + */ +struct private_aggressive_mode_t { + + /** + * Public methods and task_t interface. + */ + aggressive_mode_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Common phase 1 helper class + */ + phase1_t *ph1; + + /** + * IKE config to establish + */ + ike_cfg_t *ike_cfg; + + /** + * Peer config to use + */ + peer_cfg_t *peer_cfg; + + /** + * selected IKE proposal + */ + proposal_t *proposal; + + /** + * Negotiated SA lifetime + */ + u_int32_t lifetime; + + /** + * Negotiated authentication method + */ + auth_method_t method; + + /** + * Encoded ID payload, without fixed header + */ + chunk_t id_data; + + /** states of aggressive mode */ + enum { + AM_INIT, + AM_AUTH, + } state; +}; + +/** + * Set IKE_SA to established state + */ +static bool establish(private_aggressive_mode_t *this) +{ + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + return FALSE; + } + + 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 TRUE; +} + +/** + * Check for notify errors, return TRUE if error found + */ +static bool has_notify_errors(private_aggressive_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool err = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY_V1) + { + notify_payload_t *notify; + notify_type_t type; + + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + if (type < 16384) + { + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + err = TRUE; + } + else + { + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + } + } + } + enumerator->destroy(enumerator); + + return err; +} + +/** + * Queue a task sending a notify in an INFORMATIONAL exchange + */ +static status_t send_notify(private_aggressive_mode_t *this, notify_type_t type) +{ + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + u_int64_t spi_i, spi_r; + chunk_t spi; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, type); + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + spi = chunk_cata("cc", chunk_from_thing(spi_i), chunk_from_thing(spi_r)); + notify->set_spi_data(notify, spi); + + this->ike_sa->queue_task(this->ike_sa, + (task_t*)informational_create(this->ike_sa, notify)); + /* cancel all active/passive tasks in favour of informational */ + this->ike_sa->flush_queue(this->ike_sa, + this->initiator ? TASK_QUEUE_ACTIVE : TASK_QUEUE_PASSIVE); + return ALREADY_DONE; +} + +/** + * Queue a delete task if authentication failed as initiator + */ +static status_t send_delete(private_aggressive_mode_t *this) +{ + this->ike_sa->queue_task(this->ike_sa, + (task_t*)isakmp_delete_create(this->ike_sa, TRUE)); + /* cancel all active tasks in favour of informational */ + this->ike_sa->flush_queue(this->ike_sa, + this->initiator ? TASK_QUEUE_ACTIVE : TASK_QUEUE_PASSIVE); + return ALREADY_DONE; +} + +METHOD(task_t, build_i, status_t, + private_aggressive_mode_t *this, message_t *message) +{ + switch (this->state) + { + case AM_INIT: + { + sa_payload_t *sa_payload; + id_payload_t *id_payload; + linked_list_t *proposals; + identification_t *id; + packet_t *packet; + u_int16_t group; + + DBG0(DBG_IKE, "initiating Aggressive Mode IKE_SA %s[%d] to %H", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->peer_cfg->get_ref(this->peer_cfg); + + this->method = this->ph1->get_auth_method(this->ph1, this->peer_cfg); + if (this->method == AUTH_NONE) + { + DBG1(DBG_CFG, "configuration uses unsupported authentication"); + return FAILED; + } + this->lifetime = this->peer_cfg->get_reauth_time(this->peer_cfg, + FALSE); + if (!this->lifetime) + { /* fall back to rekey time of no rekey time configured */ + this->lifetime = this->peer_cfg->get_rekey_time(this->peer_cfg, + FALSE); + } + this->lifetime += this->peer_cfg->get_over_time(this->peer_cfg); + proposals = this->ike_cfg->get_proposals(this->ike_cfg); + sa_payload = sa_payload_create_from_proposals_v1(proposals, + this->lifetime, 0, this->method, MODE_NONE, FALSE, 0); + proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); + + message->add_payload(message, &sa_payload->payload_interface); + + group = this->ike_cfg->get_dh_group(this->ike_cfg); + if (group == MODP_NONE) + { + DBG1(DBG_IKE, "DH group selection failed"); + return FAILED; + } + if (!this->ph1->create_dh(this->ph1, group)) + { + DBG1(DBG_IKE, "DH group %N not supported", + diffie_hellman_group_names, group); + return FAILED; + } + if (!this->ph1->add_nonce_ke(this->ph1, message)) + { + return FAILED; + } + id = this->ph1->get_id(this->ph1, this->peer_cfg, TRUE); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return FAILED; + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + id_payload = id_payload_create_from_identification(ID_V1, id); + this->id_data = id_payload->get_encoded(id_payload); + message->add_payload(message, &id_payload->payload_interface); + + /* pregenerate message to store SA payload */ + if (this->ike_sa->generate_message(this->ike_sa, message, + &packet) != SUCCESS) + { + DBG1(DBG_IKE, "pregenerating SA payload failed"); + return FAILED; + } + packet->destroy(packet); + if (!this->ph1->save_sa_payload(this->ph1, message)) + { + DBG1(DBG_IKE, "SA payload invalid"); + return FAILED; + } + this->state = AM_AUTH; + return NEED_MORE; + } + case AM_AUTH: + { + if (!this->ph1->build_auth(this->ph1, this->method, message, + this->id_data)) + { + this->id_data = chunk_empty; + return send_notify(this, AUTHENTICATION_FAILED); + } + this->id_data = chunk_empty; + + switch (this->method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + /* wait for XAUTH request */ + break; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + this->ike_sa->queue_task(this->ike_sa, + (task_t*)xauth_create(this->ike_sa, TRUE)); + return SUCCESS; + default: + if (charon->ike_sa_manager->check_uniqueness( + charon->ike_sa_manager, this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling Aggressive Mode due to " + "uniqueness policy"); + return send_notify(this, AUTHENTICATION_FAILED); + } + if (!establish(this)) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + break; + } + if (this->ph1->has_virtual_ip(this->ph1, this->peer_cfg)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)mode_config_create(this->ike_sa, TRUE)); + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_aggressive_mode_t *this, message_t *message) +{ + switch (this->state) + { + case AM_INIT: + { + sa_payload_t *sa_payload; + id_payload_t *id_payload; + identification_t *id; + linked_list_t *list; + u_int16_t group; + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "%H is initiating a Aggressive Mode IKE_SA", + message->get_source(message)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->ike_sa->update_hosts(this->ike_sa, + message->get_destination(message), + message->get_source(message), TRUE); + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "SA payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!this->ph1->save_sa_payload(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + this->method = sa_payload->get_auth_method(sa_payload); + this->lifetime = sa_payload->get_lifetime(sa_payload); + + switch (this->method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_RESP_PSK: + case AUTH_PSK: + if (!lib->settings->get_bool(lib->settings, "%s.i_dont_care" + "_about_security_and_use_aggressive_mode_psk", + FALSE, charon->name)) + { + DBG1(DBG_IKE, "Aggressive Mode PSK disabled for " + "security reasons"); + return send_notify(this, AUTHENTICATION_FAILED); + } + break; + default: + break; + } + + if (!this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + DBG1(DBG_IKE, "DH group selection failed"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->create_dh(this->ph1, group)) + { + DBG1(DBG_IKE, "negotiated DH group not supported"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->get_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDii payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + id = id_payload->get_identification(id_payload); + this->id_data = id_payload->get_encoded(id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + this->peer_cfg = this->ph1->select_config(this->ph1, + this->method, TRUE, id); + if (!this->peer_cfg) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + + this->state = AM_AUTH; + if (has_notify_errors(this, message)) + { + return FAILED; + } + return NEED_MORE; + } + case AM_AUTH: + { + while (TRUE) + { + if (this->ph1->verify_auth(this->ph1, this->method, message, + this->id_data)) + { + break; + } + this->peer_cfg->destroy(this->peer_cfg); + this->peer_cfg = this->ph1->select_config(this->ph1, + this->method, TRUE, NULL); + if (!this->peer_cfg) + { + this->id_data = chunk_empty; + return send_delete(this); + } + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + } + this->id_data = chunk_empty; + + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "Aggressive Mode authorization hook forbids " + "IKE_SA, cancelling"); + return send_delete(this); + } + + switch (this->method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + this->ike_sa->queue_task(this->ike_sa, + (task_t*)xauth_create(this->ike_sa, TRUE)); + return SUCCESS; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + /* wait for XAUTH request */ + break; + default: + if (charon->ike_sa_manager->check_uniqueness( + charon->ike_sa_manager, this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling Aggressive Mode due to " + "uniqueness policy"); + return send_delete(this); + } + if (!establish(this)) + { + return send_delete(this); + } + lib->processor->queue_job(lib->processor, (job_t*) + adopt_children_job_create( + this->ike_sa->get_id(this->ike_sa))); + break; + } + if (!this->ph1->has_pool(this->ph1, this->peer_cfg) && + this->ph1->has_virtual_ip(this->ph1, this->peer_cfg)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)mode_config_create(this->ike_sa, TRUE)); + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_aggressive_mode_t *this, message_t *message) +{ + if (this->state == AM_AUTH) + { + sa_payload_t *sa_payload; + id_payload_t *id_payload; + identification_t *id; + + sa_payload = sa_payload_create_from_proposal_v1(this->proposal, + this->lifetime, 0, this->method, MODE_NONE, FALSE, 0); + message->add_payload(message, &sa_payload->payload_interface); + + if (!this->ph1->add_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->create_hasher(this->ph1)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + if (!this->ph1->derive_keys(this->ph1, this->peer_cfg, this->method)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + + id = this->ph1->get_id(this->ph1, this->peer_cfg, TRUE); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return send_notify(this, INVALID_ID_INFORMATION); + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + + id_payload = id_payload_create_from_identification(ID_V1, id); + message->add_payload(message, &id_payload->payload_interface); + + if (!this->ph1->build_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + return NEED_MORE; + } + return FAILED; +} + +METHOD(task_t, process_i, status_t, + private_aggressive_mode_t *this, message_t *message) +{ + if (this->state == AM_AUTH) + { + auth_method_t method; + sa_payload_t *sa_payload; + id_payload_t *id_payload; + identification_t *id, *cid; + linked_list_t *list; + u_int32_t lifetime; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "SA payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + list = sa_payload->get_proposals(sa_payload); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, FALSE); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + lifetime = sa_payload->get_lifetime(sa_payload); + if (lifetime != this->lifetime) + { + DBG1(DBG_IKE, "received lifetime %us does not match configured " + "lifetime %us", lifetime, this->lifetime); + } + this->lifetime = lifetime; + method = sa_payload->get_auth_method(sa_payload); + if (method != this->method) + { + DBG1(DBG_IKE, "received %N authentication, but configured %N, " + "continue with configured", auth_method_names, method, + auth_method_names, this->method); + } + if (!this->ph1->get_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!this->ph1->create_hasher(this->ph1)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDir payload missing"); + return send_delete(this); + } + id = id_payload->get_identification(id_payload); + cid = this->ph1->get_id(this->ph1, this->peer_cfg, FALSE); + if (cid && !id->matches(id, cid)) + { + DBG1(DBG_IKE, "IDir '%Y' does not match to '%Y'", id, cid); + id->destroy(id); + return send_notify(this, INVALID_ID_INFORMATION); + } + this->ike_sa->set_other_id(this->ike_sa, id); + + if (!this->ph1->derive_keys(this->ph1, this->peer_cfg, this->method)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->verify_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "Aggressive Mode authorization hook forbids IKE_SA, " + "cancelling"); + return send_notify(this, AUTHENTICATION_FAILED); + } + + return NEED_MORE; + } + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_aggressive_mode_t *this) +{ + return TASK_AGGRESSIVE_MODE; +} + +METHOD(task_t, migrate, void, + private_aggressive_mode_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + this->ph1->destroy(this->ph1); + chunk_free(&this->id_data); + + this->ike_sa = ike_sa; + this->state = AM_INIT; + this->peer_cfg = NULL; + this->proposal = NULL; + this->ph1 = phase1_create(ike_sa, this->initiator); +} + +METHOD(task_t, destroy, void, + private_aggressive_mode_t *this) +{ + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + this->ph1->destroy(this->ph1); + chunk_free(&this->id_data); + free(this); +} + +/* + * Described in header. + */ +aggressive_mode_t *aggressive_mode_create(ike_sa_t *ike_sa, bool initiator) +{ + private_aggressive_mode_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .ph1 = phase1_create(ike_sa, initiator), + .initiator = initiator, + .state = AM_INIT, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/aggressive_mode.h b/src/libcharon/sa/ikev1/tasks/aggressive_mode.h new file mode 100644 index 000000000..d0666f41c --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/aggressive_mode.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * + * 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. + */ + +/** + * @defgroup aggressive_mode aggressive_mode + * @{ @ingroup tasks_v1 + */ + +#ifndef AGGRESSIVE_MODE_H_ +#define AGGRESSIVE_MODE_H_ + +typedef struct aggressive_mode_t aggressive_mode_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 aggressive mode, establishes an IKE_SA without identity protection. + */ +struct aggressive_mode_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new AGGRESSIVE_MODE task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task initiated locally + * @return task to handle by the task_manager + */ +aggressive_mode_t *aggressive_mode_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** AGGRESSIVE_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/informational.c b/src/libcharon/sa/ikev1/tasks/informational.c new file mode 100644 index 000000000..bda1d2afb --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/informational.c @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "informational.h" + +#include <daemon.h> +#include <sa/ikev1/tasks/isakmp_delete.h> +#include <sa/ikev1/tasks/quick_delete.h> + +#include <encoding/payloads/delete_payload.h> + +typedef struct private_informational_t private_informational_t; + +/** + * Private members of a informational_t task. + */ +struct private_informational_t { + + /** + * Public methods and task_t interface. + */ + informational_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Notify payload to send + */ + notify_payload_t *notify; + + /** + * Delete subtask + */ + task_t *del; +}; + +/** + * Cancel active quick mode after receiving an error + */ +static void cancel_quick_mode(private_informational_t *this) +{ + enumerator_t *enumerator; + task_t *task; + + enumerator = this->ike_sa->create_task_enumerator(this->ike_sa, + TASK_QUEUE_ACTIVE); + while (enumerator->enumerate(enumerator, &task)) + { + if (task->get_type(task) == TASK_QUICK_MODE) + { + this->ike_sa->flush_queue(this->ike_sa, TASK_QUEUE_ACTIVE); + break; + } + } + enumerator->destroy(enumerator); +} + +METHOD(task_t, build_i, status_t, + private_informational_t *this, message_t *message) +{ + message->add_payload(message, &this->notify->payload_interface); + this->notify = NULL; + return SUCCESS; +} + +METHOD(task_t, process_r, status_t, + private_informational_t *this, message_t *message) +{ + enumerator_t *enumerator; + delete_payload_t *delete; + notify_payload_t *notify; + notify_type_t type; + payload_t *payload; + status_t status = SUCCESS; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY_V1: + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + + if (type == INITIAL_CONTACT_IKEV1) + { + this->ike_sa->set_condition(this->ike_sa, + COND_INIT_CONTACT_SEEN, TRUE); + } + else if (type == UNITY_LOAD_BALANCE) + { + host_t *redirect, *me; + chunk_t data; + + data = notify->get_notification_data(notify); + redirect = host_create_from_chunk(AF_INET, data, + IKEV2_UDP_PORT); + if (redirect) + { /* treat the redirect as reauthentication */ + DBG1(DBG_IKE, "received %N notify. redirected to %H", + notify_type_names, type, redirect); + /* Cisco boxes reject the first message from 4500 */ + me = this->ike_sa->get_my_host(this->ike_sa); + me->set_port(me, charon->socket->get_port( + charon->socket, FALSE)); + this->ike_sa->set_other_host(this->ike_sa, redirect); + this->ike_sa->reauth(this->ike_sa); + enumerator->destroy(enumerator); + return DESTROY_ME; + } + else + { + DBG1(DBG_IKE, "received %N notify, invalid address"); + } + } + else if (type < 16384) + { + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + if (this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING) + { /* only critical during main mode */ + status = FAILED; + } + switch (type) + { + case INVALID_ID_INFORMATION: + case NO_PROPOSAL_CHOSEN: + cancel_quick_mode(this); + break; + default: + break; + } + break; + } + else + { + DBG1(DBG_IKE, "received %N notify", + notify_type_names, type); + } + continue; + case DELETE_V1: + if (!this->del) + { + delete = (delete_payload_t*)payload; + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + this->del = (task_t*)isakmp_delete_create(this->ike_sa, + FALSE); + } + else + { + this->del = (task_t*)quick_delete_create(this->ike_sa, + PROTO_NONE, 0, FALSE, FALSE); + } + } + break; + default: + continue; + } + break; + } + enumerator->destroy(enumerator); + + if (this->del && status == SUCCESS) + { + return this->del->process(this->del, message); + } + return status; +} + +METHOD(task_t, build_r, status_t, + private_informational_t *this, message_t *message) +{ + if (this->del) + { + return this->del->build(this->del, message); + } + return FAILED; +} + +METHOD(task_t, process_i, status_t, + private_informational_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_informational_t *this) +{ + return TASK_INFORMATIONAL; +} + +METHOD(task_t, migrate, void, + private_informational_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_informational_t *this) +{ + DESTROY_IF(this->notify); + DESTROY_IF(this->del); + free(this); +} + +/* + * Described in header. + */ +informational_t *informational_create(ike_sa_t *ike_sa, notify_payload_t *notify) +{ + private_informational_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .notify = notify, + ); + + if (notify) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/informational.h b/src/libcharon/sa/ikev1/tasks/informational.h new file mode 100644 index 000000000..52938ffbc --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/informational.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup informational informational + * @{ @ingroup tasks_v1 + */ + +#ifndef INFORMATIONAL_H_ +#define INFORMATIONAL_H_ + +typedef struct informational_t informational_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> +#include <encoding/payloads/notify_payload.h> + +/** + * IKEv1 informational exchange, negotiates errors. + */ +struct informational_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new informational task. + * + * @param ike_sa IKE_SA this task works for + * @param notify notify to send as initiator, NULL if responder + * @return task to handle by the task_manager + */ +informational_t *informational_create(ike_sa_t *ike_sa, notify_payload_t *notify); + +#endif /** INFORMATIONAL_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c new file mode 100644 index 000000000..edad3b2fa --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.c @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "isakmp_cert_post.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/certreq_payload.h> +#include <encoding/payloads/auth_payload.h> +#include <encoding/payloads/sa_payload.h> +#include <credentials/certificates/x509.h> + + +typedef struct private_isakmp_cert_post_t private_isakmp_cert_post_t; + +/** + * Private members of a isakmp_cert_post_t task. + */ +struct private_isakmp_cert_post_t { + + /** + * Public methods and task_t interface. + */ + isakmp_cert_post_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * States of ike cert pre + */ + enum { + CR_SA, + CR_KE, + CR_AUTH, + } state; +}; + +/** + * Check if we actually use certificates for authentication + */ +static bool use_certs(private_isakmp_cert_post_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool use = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + + switch (sa_payload->get_auth_method(sa_payload)) + { + case AUTH_RSA: + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + use = TRUE; + break; + default: + break; + } + break; + } + } + enumerator->destroy(enumerator); + + return use; +} + +/** + * Add certificates to message + */ +static void build_certs(private_isakmp_cert_post_t *this, message_t *message) +{ + peer_cfg_t *peer_cfg; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (!peer_cfg) + { + return; + } + + switch (peer_cfg->get_cert_policy(peer_cfg)) + { + case CERT_NEVER_SEND: + break; + case CERT_SEND_IF_ASKED: + if (!this->ike_sa->has_condition(this->ike_sa, COND_CERTREQ_SEEN)) + { + break; + } + /* FALL */ + case CERT_ALWAYS_SEND: + { + cert_payload_t *payload; + enumerator_t *enumerator; + certificate_t *cert; + auth_rule_t type; + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + cert = auth->get(auth, AUTH_RULE_SUBJECT_CERT); + if (!cert) + { + break; + } + payload = cert_payload_create_from_cert(CERTIFICATE_V1, cert); + if (!payload) + { + break; + } + DBG1(DBG_IKE, "sending end entity cert \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*)payload); + + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &type, &cert)) + { + if (type == AUTH_RULE_IM_CERT) + { + payload = cert_payload_create_from_cert(CERTIFICATE_V1, cert); + if (payload) + { + DBG1(DBG_IKE, "sending issuer cert \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*)payload); + } + } + } + enumerator->destroy(enumerator); + } + } +} + +METHOD(task_t, build_i, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + if (this->state == CR_AUTH) + { + build_certs(this, message); + return SUCCESS; + } + return NEED_MORE; + case AGGRESSIVE: + if (this->state == CR_AUTH) + { + build_certs(this, message); + return SUCCESS; + } + return NEED_MORE; + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_KE: + return NEED_MORE; + case CR_AUTH: + return NEED_MORE; + default: + return FAILED; + } + } + case AGGRESSIVE: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + switch (this->state) + { + case CR_SA: + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + build_certs(this, message); + return SUCCESS; + } + case AGGRESSIVE: + switch (this->state) + { + case CR_SA: + build_certs(this, message); + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_isakmp_cert_post_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + break; + } + case AGGRESSIVE: + { + if (this->state == CR_SA) + { + if (!use_certs(this, message)) + { + return SUCCESS; + } + this->state = CR_AUTH; + return NEED_MORE; + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_cert_post_t *this) +{ + return TASK_ISAKMP_CERT_POST; +} + +METHOD(task_t, migrate, void, + private_isakmp_cert_post_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->state = CR_SA; +} + +METHOD(task_t, destroy, void, + private_isakmp_cert_post_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_cert_post_t *isakmp_cert_post_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_cert_post_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .state = CR_SA, + ); + if (initiator) + { + this->public.task.process = _process_i; + this->public.task.build = _build_i; + } + else + { + this->public.task.process = _process_r; + this->public.task.build = _build_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h new file mode 100644 index 000000000..3a155cb68 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_post.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup isakmp_cert_post isakmp_cert_post + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_CERT_POST_H_ +#define ISAKMP_CERT_POST_H_ + +typedef struct isakmp_cert_post_t isakmp_cert_post_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * ISAKMP_CERT_POST, IKEv1 certificate processing after authentication. + */ +struct isakmp_cert_post_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new isakmp_cert_post task. + * + * The initiator parameter means the original initiator, not the initiator + * of the certificate request. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_cert_post task to handle by the task_manager + */ +isakmp_cert_post_t *isakmp_cert_post_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_CERT_POST_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c new file mode 100644 index 000000000..d48484f09 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.c @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "isakmp_cert_pre.h" + +#include <daemon.h> +#include <sa/ike_sa.h> +#include <encoding/payloads/cert_payload.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/certreq_payload.h> +#include <credentials/certificates/x509.h> + + +typedef struct private_isakmp_cert_pre_t private_isakmp_cert_pre_t; + +/** + * Private members of a isakmp_cert_pre_t task. + */ +struct private_isakmp_cert_pre_t { + + /** + * Public methods and task_t interface. + */ + isakmp_cert_pre_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Send certificate requests? + */ + bool send_req; + + /** next message we expect */ + enum { + CR_SA, + CR_KE, + CR_AUTH, + } state; +}; + +/** + * Find the CA certificate for a given certreq payload + */ +static certificate_t* find_certificate(private_isakmp_cert_pre_t *this, + certreq_payload_t *certreq) +{ + identification_t *id; + certificate_t *cert; + + if (certreq->get_cert_type(certreq) != CERT_X509) + { + DBG1(DBG_IKE, "%N CERTREQ not supported - ignored", + certificate_type_names, certreq->get_cert_type(certreq)); + return NULL; + } + id = certreq->get_dn(certreq); + if (!id) + { + DBG1(DBG_IKE, "ignoring certificate request without data", + certificate_type_names, certreq->get_cert_type(certreq)); + return NULL; + } + cert = lib->credmgr->get_cert(lib->credmgr, CERT_X509, KEY_ANY, id, TRUE); + if (cert) + { + DBG1(DBG_IKE, "received cert request for '%Y'", + cert->get_subject(cert)); + } + else + { + DBG1(DBG_IKE, "received cert request for unknown ca '%Y'", id); + } + id->destroy(id); + + return cert; +} + +/** + * read certificate requests + */ +static void process_certreqs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case CERTIFICATE_REQUEST_V1: + { + certificate_t *cert; + + this->ike_sa->set_condition(this->ike_sa, + COND_CERTREQ_SEEN, TRUE); + cert = find_certificate(this, (certreq_payload_t*)payload); + if (cert) + { + auth->add(auth, AUTH_RULE_CA_CERT, cert); + } + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Import receuved certificates + */ +static void process_certs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + auth_cfg_t *auth; + bool first = TRUE; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == CERTIFICATE_V1) + { + cert_payload_t *cert_payload; + cert_encoding_t encoding; + certificate_t *cert; + + cert_payload = (cert_payload_t*)payload; + encoding = cert_payload->get_cert_encoding(cert_payload); + + switch (encoding) + { + case ENC_X509_SIGNATURE: + { + cert = cert_payload->get_cert(cert_payload); + if (cert) + { + if (first) + { /* the first is an end entity certificate */ + DBG1(DBG_IKE, "received end entity cert \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_SUBJECT_CERT, cert); + first = FALSE; + } + else + { + DBG1(DBG_IKE, "received issuer cert \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_IM_CERT, cert); + } + } + break; + } + case ENC_CRL: + cert = cert_payload->get_cert(cert_payload); + if (cert) + { + DBG1(DBG_IKE, "received CRL \"%Y\"", + cert->get_subject(cert)); + auth->add(auth, AUTH_HELPER_REVOCATION_CERT, cert); + } + break; + case ENC_PKCS7_WRAPPED_X509: + case ENC_PGP: + case ENC_DNS_SIGNED_KEY: + case ENC_KERBEROS_TOKEN: + case ENC_ARL: + case ENC_SPKI: + case ENC_X509_ATTRIBUTE: + case ENC_RAW_RSA_KEY: + case ENC_X509_HASH_AND_URL_BUNDLE: + case ENC_OCSP_CONTENT: + default: + DBG1(DBG_ENC, "certificate encoding %N not supported", + cert_encoding_names, encoding); + } + } + } + enumerator->destroy(enumerator); +} + +/** + * Add the subject of a CA certificate a message + */ +static void add_certreq(private_isakmp_cert_pre_t *this, message_t *message, + certificate_t *cert) +{ + if (cert->get_type(cert) == CERT_X509) + { + x509_t *x509 = (x509_t*)cert; + + if (x509->get_flags(x509) & X509_CA) + { + DBG1(DBG_IKE, "sending cert request for \"%Y\"", + cert->get_subject(cert)); + message->add_payload(message, (payload_t*) + certreq_payload_create_dn(cert->get_subject(cert))); + } + } +} + +/** + * Add auth_cfg's CA certificates to the certificate request + */ +static void add_certreqs(private_isakmp_cert_pre_t *this, + auth_cfg_t *auth, message_t *message) +{ + enumerator_t *enumerator; + auth_rule_t type; + void *value; + + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &type, &value)) + { + switch (type) + { + case AUTH_RULE_CA_CERT: + add_certreq(this, message, (certificate_t*)value); + break; + default: + break; + } + } + enumerator->destroy(enumerator); +} + +/** + * Build certificate requests + */ +static void build_certreqs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + certificate_t *cert; + auth_cfg_t *auth; + + ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + if (!ike_cfg->send_certreq(ike_cfg)) + { + return; + } + /* check if we require a specific CA for that peer */ + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, FALSE); + if (enumerator->enumerate(enumerator, &auth)) + { + add_certreqs(this, auth, message); + } + enumerator->destroy(enumerator); + } + if (!message->get_payload(message, CERTIFICATE_REQUEST_V1)) + { + /* otherwise add all trusted CA certificates */ + enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, + CERT_ANY, KEY_ANY, NULL, TRUE); + while (enumerator->enumerate(enumerator, &cert)) + { + add_certreq(this, message, cert); + } + enumerator->destroy(enumerator); + } +} + +/** + * Check if we actually use certificates for authentication + */ +static bool use_certs(private_isakmp_cert_pre_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool use = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == SECURITY_ASSOCIATION_V1) + { + sa_payload_t *sa_payload = (sa_payload_t*)payload; + + switch (sa_payload->get_auth_method(sa_payload)) + { + case AUTH_HYBRID_INIT_RSA: + case AUTH_HYBRID_RESP_RSA: + if (!this->initiator) + { + this->send_req = FALSE; + } + /* FALL */ + case AUTH_RSA: + case AUTH_ECDSA_256: + case AUTH_ECDSA_384: + case AUTH_ECDSA_521: + case AUTH_XAUTH_INIT_RSA: + case AUTH_XAUTH_RESP_RSA: + use = TRUE; + break; + default: + break; + } + break; + } + } + enumerator->destroy(enumerator); + + return use; +} + +/** + * Check if we should send a certificate request + */ +static bool send_certreq(private_isakmp_cert_pre_t *this) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + auth_cfg_t *auth; + bool req = FALSE; + auth_class_t class; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, FALSE); + if (enumerator->enumerate(enumerator, &auth)) + { + class = (intptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS); + if (class == AUTH_CLASS_PUBKEY) + { + req = TRUE; + } + } + enumerator->destroy(enumerator); + } + return req; +} + +METHOD(task_t, build_i, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + if (this->state == CR_AUTH) + { + build_certreqs(this, message); + } + return NEED_MORE; + case AGGRESSIVE: + if (this->state == CR_SA) + { + if (send_certreq(this)) + { + build_certreqs(this, message); + } + } + return NEED_MORE; + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + return NEED_MORE; + case CR_KE: + process_certreqs(this, message); + return NEED_MORE; + case CR_AUTH: + process_certreqs(this, message); + process_certs(this, message); + return SUCCESS; + default: + return FAILED; + } + } + case AGGRESSIVE: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + process_certreqs(this, message); + return NEED_MORE; + case CR_AUTH: + process_certs(this, message); + return SUCCESS; + default: + return FAILED; + } + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + switch (this->state) + { + case CR_SA: + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + if (this->send_req) + { + build_certreqs(this, message); + } + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return NEED_MORE; + default: + return FAILED; + } + case AGGRESSIVE: + switch (this->state) + { + case CR_SA: + if (this->send_req) + { + build_certreqs(this, message); + } + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + return SUCCESS; + default: + return FAILED; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_isakmp_cert_pre_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { + switch (this->state) + { + case CR_SA: + if (!use_certs(this, message)) + { + return SUCCESS; + } + this->state = CR_KE; + return NEED_MORE; + case CR_KE: + process_certreqs(this, message); + this->state = CR_AUTH; + return NEED_MORE; + case CR_AUTH: + process_certs(this, message); + return SUCCESS; + default: + return FAILED; + } + break; + } + case AGGRESSIVE: + { + if (!use_certs(this, message)) + { + return SUCCESS; + } + process_certreqs(this, message); + process_certs(this, message); + this->state = CR_AUTH; + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_cert_pre_t *this) +{ + return TASK_ISAKMP_CERT_PRE; +} + +METHOD(task_t, migrate, void, + private_isakmp_cert_pre_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->state = CR_SA; + this->send_req = TRUE; +} + +METHOD(task_t, destroy, void, + private_isakmp_cert_pre_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_cert_pre_t *isakmp_cert_pre_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_cert_pre_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiator = initiator, + .state = CR_SA, + .send_req = TRUE, + ); + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h new file mode 100644 index 000000000..8e1a94b97 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_cert_pre.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup isakmp_cert_pre isakmp_cert_pre + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_CERT_PRE_H_ +#define ISAKMP_CERT_PRE_H_ + +typedef struct isakmp_cert_pre_t isakmp_cert_pre_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * ISAKMP_CERT_PRE task, IKEv1 certificate processing before authentication. + */ +struct isakmp_cert_pre_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ISAKMP_CERT_PRE task. + * + * The initiator parameter means the original initiator, not the initiator + * of the certificate request. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_cert_pre task to handle by the task_manager + */ +isakmp_cert_pre_t *isakmp_cert_pre_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_CERT_PRE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_delete.c b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c new file mode 100644 index 000000000..0640d13b1 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "isakmp_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + +typedef struct private_isakmp_delete_t private_isakmp_delete_t; + +/** + * Private members of a isakmp_delete_t task. + */ +struct private_isakmp_delete_t { + + /** + * Public methods and task_t interface. + */ + isakmp_delete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; +}; + +METHOD(task_t, build_i, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + delete_payload_t *delete_payload; + ike_sa_id_t *id; + + DBG0(DBG_IKE, "deleting IKE_SA %s[%d] 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)); + + delete_payload = delete_payload_create(DELETE_V1, PROTO_IKE); + id = this->ike_sa->get_id(this->ike_sa); + delete_payload->set_ike_spi(delete_payload, id->get_initiator_spi(id), + id->get_responder_spi(id)); + message->add_payload(message, (payload_t*)delete_payload); + + DBG1(DBG_IKE, "sending DELETE for IKE_SA %s[%d]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa)); + + this->ike_sa->set_state(this->ike_sa, IKE_DELETING); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + DBG1(DBG_IKE, "received DELETE for IKE_SA %s[%d]", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa)); + DBG0(DBG_IKE, "deleting IKE_SA %s[%d] 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_DELETING); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; +} + +METHOD(task_t, build_r, status_t, + private_isakmp_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_delete_t *this) +{ + return TASK_ISAKMP_DELETE; +} + +METHOD(task_t, migrate, void, + private_isakmp_delete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_isakmp_delete_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_delete_t *isakmp_delete_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_delete_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_delete.h b/src/libcharon/sa/ikev1/tasks/isakmp_delete.h new file mode 100644 index 000000000..1a7a62207 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_delete.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup isakmp_delete isakmp_delete + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_DELETE_H_ +#define ISAKMP_DELETE_H_ + +typedef struct isakmp_delete_t isakmp_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type ISAKMP_DELETE, delete an IKEv1 IKE_SA. + */ +struct isakmp_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new isakmp_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if we initiate the delete + * @return isakmp_delete task to handle by the task_manager + */ +isakmp_delete_t *isakmp_delete_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_dpd.c b/src/libcharon/sa/ikev1/tasks/isakmp_dpd.c new file mode 100644 index 000000000..a3395a043 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_dpd.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "isakmp_dpd.h" + +#include <daemon.h> +#include <encoding/payloads/notify_payload.h> + +typedef struct private_isakmp_dpd_t private_isakmp_dpd_t; + +/** + * Private members of a isakmp_dpd_t task. + */ +struct private_isakmp_dpd_t { + + /** + * Public methods and task_t interface. + */ + isakmp_dpd_t public; + + /** + * Sequence number. + */ + u_int32_t seqnr; + + /** + * DPD notify type + */ + notify_type_t type; + + /** + * IKE SA we are serving. + */ + ike_sa_t *ike_sa; +}; + +METHOD(task_t, build, status_t, + private_isakmp_dpd_t *this, message_t *message) +{ + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + u_int64_t spi_i, spi_r; + u_int32_t seqnr; + chunk_t spi; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, this->type); + seqnr = htonl(this->seqnr); + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + spi = chunk_cata("cc", chunk_from_thing(spi_i), chunk_from_thing(spi_r)); + + notify->set_spi_data(notify, spi); + notify->set_notification_data(notify, chunk_from_thing(seqnr)); + + message->add_payload(message, (payload_t*)notify); + + return SUCCESS; +} + +METHOD(task_t, process, status_t, + private_isakmp_dpd_t *this, message_t *message) +{ + /* done in task manager */ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_dpd_t *this) +{ + return TASK_ISAKMP_DPD; +} + +METHOD(task_t, migrate, void, + private_isakmp_dpd_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_isakmp_dpd_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_dpd_t *isakmp_dpd_create(ike_sa_t *ike_sa, notify_type_t type, + u_int32_t seqnr) +{ + private_isakmp_dpd_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .build = _build, + .process = _process, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .seqnr = seqnr, + .type = type, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_dpd.h b/src/libcharon/sa/ikev1/tasks/isakmp_dpd.h new file mode 100644 index 000000000..06a0175eb --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_dpd.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * + * 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. + */ + +/** + * @defgroup isakmp_dpd isakmp_dpd + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_DPD_H_ +#define ISAKMP_DPD_H_ + +typedef struct isakmp_dpd_t isakmp_dpd_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 dead peer detection task. + */ +struct isakmp_dpd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ISAKMP_DPD task. + * + * @param ike_sa associated IKE_SA + * @param type DPD notify to use, DPD_R_U_THERE | DPD_R_U_THERE_ACK + * @param seqnr DPD sequence number to use/expect + * @return ISAKMP_DPD task to handle by the task_manager + */ +isakmp_dpd_t *isakmp_dpd_create(ike_sa_t *ike_sa, notify_type_t type, + u_int32_t seqnr); + +#endif /** ISAKMP_DPD_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_natd.c b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c new file mode 100644 index 000000000..50bf1612d --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_natd.c @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2006-2011 Tobias Brunner, + * Copyright (C) 2006-2007 Martin Willi + * Copyright (C) 2006 Daniel Roethlisberger + * 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 "isakmp_natd.h" + +#include <string.h> + +#include <hydra.h> +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <config/peer_cfg.h> +#include <crypto/hashers/hasher.h> +#include <encoding/payloads/hash_payload.h> + +typedef struct private_isakmp_natd_t private_isakmp_natd_t; + +/** + * Private members of a ike_natt_t task. + */ +struct private_isakmp_natd_t { + + /** + * Public interface. + */ + isakmp_natd_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Keymat derivation (from SA) + */ + keymat_v1_t *keymat; + + /** + * Did we process any NAT detection payloads for a source address? + */ + bool src_seen; + + /** + * Did we process any NAT detection payloads for a destination address? + */ + bool dst_seen; + + /** + * Have we found a matching source address NAT hash? + */ + bool src_matched; + + /** + * Have we found a matching destination address NAT hash? + */ + bool dst_matched; +}; + +/** + * Build NAT detection hash for a host. + */ +static chunk_t generate_natd_hash(private_isakmp_natd_t *this, + ike_sa_id_t *ike_sa_id, host_t *host) +{ + hasher_t *hasher; + chunk_t natd_chunk, natd_hash; + u_int64_t spi_i, spi_r; + u_int16_t port; + + hasher = this->keymat->get_hasher(this->keymat); + if (!hasher) + { + DBG1(DBG_IKE, "no hasher available to build NAT-D payload"); + return chunk_empty; + } + + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + port = htons(host->get_port(host)); + + /* natd_hash = HASH(CKY-I | CKY-R | IP | Port) */ + natd_chunk = chunk_cata("cccc", chunk_from_thing(spi_i), + chunk_from_thing(spi_r), host->get_address(host), + chunk_from_thing(port)); + if (!hasher->allocate_hash(hasher, natd_chunk, &natd_hash)) + { + DBG1(DBG_IKE, "creating NAT-D payload hash failed"); + return chunk_empty; + } + DBG3(DBG_IKE, "natd_chunk %B", &natd_chunk); + DBG3(DBG_IKE, "natd_hash %B", &natd_hash); + + return natd_hash; +} + +/** + * Build a faked NAT-D payload to enforce UDP encapsulation. + */ +static chunk_t generate_natd_hash_faked(private_isakmp_natd_t *this) +{ + hasher_t *hasher; + chunk_t chunk; + rng_t *rng; + + hasher = this->keymat->get_hasher(this->keymat); + if (!hasher) + { + DBG1(DBG_IKE, "no hasher available to build NAT-D payload"); + return chunk_empty; + } + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng || + !rng->allocate_bytes(rng, hasher->get_hash_size(hasher), &chunk)) + { + DBG1(DBG_IKE, "unable to get random bytes for NAT-D fake"); + DESTROY_IF(rng); + return chunk_empty; + } + rng->destroy(rng); + return chunk; +} + +/** + * Build a NAT-D payload. + */ +static hash_payload_t *build_natd_payload(private_isakmp_natd_t *this, bool src, + host_t *host) +{ + hash_payload_t *payload; + ike_cfg_t *config; + chunk_t hash; + + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (src && config->force_encap(config)) + { + hash = generate_natd_hash_faked(this); + } + else + { + ike_sa_id_t *ike_sa_id = this->ike_sa->get_id(this->ike_sa); + hash = generate_natd_hash(this, ike_sa_id, host); + } + if (!hash.len) + { + return NULL; + } + payload = hash_payload_create(NAT_D_V1); + payload->set_hash(payload, hash); + chunk_free(&hash); + return payload; +} + +/** + * Add NAT-D payloads to the message. + */ +static void add_natd_payloads(private_isakmp_natd_t *this, message_t *message) +{ + hash_payload_t *payload; + host_t *host; + + /* destination has to be added first */ + host = message->get_destination(message); + payload = build_natd_payload(this, FALSE, host); + if (payload) + { + message->add_payload(message, (payload_t*)payload); + } + + /* source is added second, compared with IKEv2 we always know the source, + * as these payloads are added in the second Phase 1 exchange or the + * response to the first */ + host = message->get_source(message); + payload = build_natd_payload(this, TRUE, host); + if (payload) + { + message->add_payload(message, (payload_t*)payload); + } +} + +/** + * Read NAT-D payloads from message and evaluate them. + */ +static void process_payloads(private_isakmp_natd_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + hash_payload_t *hash_payload; + chunk_t hash, src_hash, dst_hash; + ike_sa_id_t *ike_sa_id; + host_t *me, *other; + ike_cfg_t *config; + + /* precompute hashes for incoming NAT-D comparison */ + ike_sa_id = message->get_ike_sa_id(message); + me = message->get_destination(message); + other = message->get_source(message); + dst_hash = generate_natd_hash(this, ike_sa_id, me); + src_hash = generate_natd_hash(this, ike_sa_id, other); + + DBG3(DBG_IKE, "precalculated src_hash %B", &src_hash); + DBG3(DBG_IKE, "precalculated dst_hash %B", &dst_hash); + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) != NAT_D_V1) + { + continue; + } + hash_payload = (hash_payload_t*)payload; + if (!this->dst_seen) + { /* the first NAT-D payload contains the destination hash */ + this->dst_seen = TRUE; + hash = hash_payload->get_hash(hash_payload); + DBG3(DBG_IKE, "received dst_hash %B", &hash); + if (chunk_equals(hash, dst_hash)) + { + this->dst_matched = TRUE; + } + continue; + } + /* the other NAT-D payloads contain source hashes */ + this->src_seen = TRUE; + if (!this->src_matched) + { + hash = hash_payload->get_hash(hash_payload); + DBG3(DBG_IKE, "received src_hash %B", &hash); + if (chunk_equals(hash, src_hash)) + { + this->src_matched = TRUE; + } + } + } + enumerator->destroy(enumerator); + + chunk_free(&src_hash); + chunk_free(&dst_hash); + + if (this->src_seen && this->dst_seen) + { + this->ike_sa->set_condition(this->ike_sa, COND_NAT_HERE, + !this->dst_matched); + this->ike_sa->set_condition(this->ike_sa, COND_NAT_THERE, + !this->src_matched); + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (this->dst_matched && this->src_matched && + config->force_encap(config)) + { + this->ike_sa->set_condition(this->ike_sa, COND_NAT_FAKE, TRUE); + } + } +} + +METHOD(task_t, build_i, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + switch (message->get_exchange_type(message)) + { + case AGGRESSIVE: + { /* add NAT-D payloads to the second request, already processed + * those by the responder contained in the first response */ + result = SUCCESS; + /* fall */ + } + case ID_PROT: + { /* add NAT-D payloads to the second request, need to process + * those by the responder contained in the second response */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + add_natd_payloads(this, message); + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_NATT)) + { /* we didn't receive VIDs inidcating support for NAT-T */ + return SUCCESS; + } + + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { /* process NAT-D payloads in the second response, added them in the + * second request already, so we're done afterwards */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + result = SUCCESS; + /* fall */ + } + case AGGRESSIVE: + { /* process NAT-D payloads in the first response, add them in the + * following second request */ + process_payloads(this, message); + + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + this->ike_sa->float_ports(this->ike_sa); + } + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, process_r, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + status_t result = NEED_MORE; + + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_NATT)) + { /* we didn't receive VIDs indicating NAT-T support */ + return SUCCESS; + } + + switch (message->get_exchange_type(message)) + { + case AGGRESSIVE: + { /* proccess NAT-D payloads in the second request, already added ours + * in the first response */ + result = SUCCESS; + /* fall */ + } + case ID_PROT: + { /* process NAT-D payloads in the second request, need to add ours + * to the second response */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + process_payloads(this, message); + return result; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, build_r, status_t, + private_isakmp_natd_t *this, message_t *message) +{ + switch (message->get_exchange_type(message)) + { + case ID_PROT: + { /* add NAT-D payloads to second response, already processed those + * contained in the second request */ + if (message->get_payload(message, SECURITY_ASSOCIATION_V1)) + { /* wait for the second exchange */ + return NEED_MORE; + } + add_natd_payloads(this, message); + return SUCCESS; + } + case AGGRESSIVE: + { /* add NAT-D payloads to the first response, process those contained + * in the following second request */ + add_natd_payloads(this, message); + return NEED_MORE; + } + default: + break; + } + return SUCCESS; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_natd_t *this) +{ + return TASK_ISAKMP_NATD; +} + +METHOD(task_t, migrate, void, + private_isakmp_natd_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa); + this->src_seen = FALSE; + this->dst_seen = FALSE; + this->src_matched = FALSE; + this->dst_matched = FALSE; +} + +METHOD(task_t, destroy, void, + private_isakmp_natd_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +isakmp_natd_t *isakmp_natd_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_natd_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + .initiator = initiator, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_natd.h b/src/libcharon/sa/ikev1/tasks/isakmp_natd.h new file mode 100644 index 000000000..63947fc73 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_natd.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * 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. + */ + +/** + * @defgroup isakmp_natd isakmp_natd + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_NATD_H_ +#define ISAKMP_NATD_H_ + +typedef struct isakmp_natd_t isakmp_natd_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type ISAKMP_NATD, detects NAT situation in IKEv1 Phase 1. + */ +struct isakmp_natd_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ISAKMP_NATD task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + * @return isakmp_natd task to handle by the task_manager + */ +isakmp_natd_t *isakmp_natd_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_NATD_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c new file mode 100644 index 000000000..4fd0ef39b --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.c @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2009 Martin Willi + * 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 "isakmp_vendor.h" + +#include <daemon.h> +#include <encoding/payloads/vendor_id_payload.h> + +typedef struct private_isakmp_vendor_t private_isakmp_vendor_t; + +/** + * Private data of an isakmp_vendor_t object. + */ +struct private_isakmp_vendor_t { + + /** + * Public isakmp_vendor_t interface. + */ + isakmp_vendor_t public; + + /** + * Associated IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * Are we the inititator of this task + */ + bool initiator; +}; + +/** + * IKEv1 Vendor ID database + */ +static struct { + /* Description */ + char *desc; + /* extension flag negotiated with vendor ID, if any */ + ike_extension_t extension; + /* send yourself? */ + bool send; + /* length of vendor ID string */ + int len; + /* vendor ID string */ + char *id; +} vendor_ids[] = { + + /* strongSwan MD5("strongSwan") */ + { "strongSwan", EXT_STRONGSWAN, FALSE, 16, + "\x88\x2f\xe5\x6d\x6f\xd2\x0d\xbc\x22\x51\x61\x3b\x2e\xbe\x5b\xeb"}, + + /* XAuth, MD5("draft-ietf-ipsra-isakmp-xauth-06.txt") */ + { "XAuth", EXT_XAUTH, TRUE, 8, + "\x09\x00\x26\x89\xdf\xd6\xb7\x12"}, + + /* NAT-Traversal, MD5("RFC 3947") */ + { "NAT-T (RFC 3947)", EXT_NATT, TRUE, 16, + "\x4a\x13\x1c\x81\x07\x03\x58\x45\x5c\x57\x28\xf2\x0e\x95\x45\x2f"}, + + /* Dead peer detection, RFC 3706 */ + { "DPD", EXT_DPD, TRUE, 16, + "\xaf\xca\xd7\x13\x68\xa1\xf1\xc9\x6b\x86\x96\xfc\x77\x57\x01\x00"}, + + { "draft-stenberg-ipsec-nat-traversal-01", 0, FALSE, 16, + "\x27\xba\xb5\xdc\x01\xea\x07\x60\xea\x4e\x31\x90\xac\x27\xc0\xd0"}, + + { "draft-stenberg-ipsec-nat-traversal-02", 0, FALSE, 16, + "\x61\x05\xc4\x22\xe7\x68\x47\xe4\x3f\x96\x84\x80\x12\x92\xae\xcd"}, + + { "draft-ietf-ipsec-nat-t-ike", 0, FALSE, 16, + "\x4d\xf3\x79\x28\xe9\xfc\x4f\xd1\xb3\x26\x21\x70\xd5\x15\xc6\x62"}, + + { "draft-ietf-ipsec-nat-t-ike-00", 0, FALSE, 16, + "\x44\x85\x15\x2d\x18\xb6\xbb\xcd\x0b\xe8\xa8\x46\x95\x79\xdd\xcc"}, + + { "draft-ietf-ipsec-nat-t-ike-02", 0, FALSE, 16, + "\xcd\x60\x46\x43\x35\xdf\x21\xf8\x7c\xfd\xb2\xfc\x68\xb6\xa4\x48"}, + + { "draft-ietf-ipsec-nat-t-ike-02\\n", 0, FALSE, 16, + "\x90\xcb\x80\x91\x3e\xbb\x69\x6e\x08\x63\x81\xb5\xec\x42\x7b\x1f"}, + + { "draft-ietf-ipsec-nat-t-ike-03", 0, FALSE, 16, + "\x7d\x94\x19\xa6\x53\x10\xca\x6f\x2c\x17\x9d\x92\x15\x52\x9d\x56"}, + + { "draft-ietf-ipsec-nat-t-ike-04", 0, FALSE, 16, + "\x99\x09\xb6\x4e\xed\x93\x7c\x65\x73\xde\x52\xac\xe9\x52\xfa\x6b"}, + + { "draft-ietf-ipsec-nat-t-ike-05", 0, FALSE, 16, + "\x80\xd0\xbb\x3d\xef\x54\x56\x5e\xe8\x46\x45\xd4\xc8\x5c\xe3\xee"}, + + { "draft-ietf-ipsec-nat-t-ike-06", 0, FALSE, 16, + "\x4d\x1e\x0e\x13\x6d\xea\xfa\x34\xc4\xf3\xea\x9f\x02\xec\x72\x85"}, + + { "draft-ietf-ipsec-nat-t-ike-07", 0, FALSE, 16, + "\x43\x9b\x59\xf8\xba\x67\x6c\x4c\x77\x37\xae\x22\xea\xb8\xf5\x82"}, + + { "draft-ietf-ipsec-nat-t-ike-08", 0, FALSE, 16, + "\x8f\x8d\x83\x82\x6d\x24\x6b\x6f\xc7\xa8\xa6\xa4\x28\xc1\x1d\xe8"}, + + { "Cisco Unity", EXT_CISCO_UNITY, FALSE, 16, + "\x12\xf5\xf2\x8c\x45\x71\x68\xa9\x70\x2d\x9f\xe2\x74\xcc\x01\x00"}, +}; + +METHOD(task_t, build, status_t, + private_isakmp_vendor_t *this, message_t *message) +{ + vendor_id_payload_t *vid_payload; + bool strongswan, cisco_unity; + int i; + + strongswan = lib->settings->get_bool(lib->settings, + "%s.send_vendor_id", FALSE, charon->name); + cisco_unity = lib->settings->get_bool(lib->settings, + "%s.cisco_unity", FALSE, charon->name); + for (i = 0; i < countof(vendor_ids); i++) + { + if (vendor_ids[i].send || + (vendor_ids[i].extension == EXT_STRONGSWAN && strongswan) || + (vendor_ids[i].extension == EXT_CISCO_UNITY && cisco_unity)) + { + vid_payload = vendor_id_payload_create_data(VENDOR_ID_V1, + chunk_clone(chunk_create(vendor_ids[i].id, vendor_ids[i].len))); + message->add_payload(message, &vid_payload->payload_interface); + } + } + return this->initiator ? NEED_MORE : SUCCESS; +} + +METHOD(task_t, process, status_t, + private_isakmp_vendor_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + int i; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == VENDOR_ID_V1) + { + vendor_id_payload_t *vid; + bool found = FALSE; + chunk_t data; + + vid = (vendor_id_payload_t*)payload; + data = vid->get_data(vid); + + for (i = 0; i < countof(vendor_ids); i++) + { + if (chunk_equals(data, chunk_create(vendor_ids[i].id, + vendor_ids[i].len))) + { + DBG1(DBG_IKE, "received %s vendor ID", vendor_ids[i].desc); + if (vendor_ids[i].extension) + { + this->ike_sa->enable_extension(this->ike_sa, + vendor_ids[i].extension); + } + found = TRUE; + } + } + if (!found) + { + DBG1(DBG_ENC, "received unknown vendor ID: %#B", &data); + } + } + } + enumerator->destroy(enumerator); + + return this->initiator ? SUCCESS : NEED_MORE; +} + +METHOD(task_t, migrate, void, + private_isakmp_vendor_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, get_type, task_type_t, + private_isakmp_vendor_t *this) +{ + return TASK_ISAKMP_VENDOR; +} + +METHOD(task_t, destroy, void, + private_isakmp_vendor_t *this) +{ + free(this); +} + +/** + * See header + */ +isakmp_vendor_t *isakmp_vendor_create(ike_sa_t *ike_sa, bool initiator) +{ + private_isakmp_vendor_t *this; + + INIT(this, + .public = { + .task = { + .build = _build, + .process = _process, + .migrate = _migrate, + .get_type = _get_type, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h new file mode 100644 index 000000000..91891085b --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/isakmp_vendor.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup isakmp_vendor isakmp_vendor + * @{ @ingroup tasks_v1 + */ + +#ifndef ISAKMP_VENDOR_H_ +#define ISAKMP_VENDOR_H_ + +typedef struct isakmp_vendor_t isakmp_vendor_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Vendor ID processing task for IKEv1. + */ +struct isakmp_vendor_t { + + /** + * Implements task interface. + */ + task_t task; +}; + +/** + * Create a isakmp_vendor instance. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task is the original initiator + */ +isakmp_vendor_t *isakmp_vendor_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** ISAKMP_VENDOR_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/main_mode.c b/src/libcharon/sa/ikev1/tasks/main_mode.c new file mode 100644 index 000000000..9ccf9abf5 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/main_mode.c @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2011-2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "main_mode.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/ikev1/phase1.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/hash_payload.h> +#include <sa/ikev1/tasks/xauth.h> +#include <sa/ikev1/tasks/mode_config.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/isakmp_delete.h> +#include <processing/jobs/adopt_children_job.h> + +typedef struct private_main_mode_t private_main_mode_t; + +/** + * Private members of a main_mode_t task. + */ +struct private_main_mode_t { + + /** + * Public methods and task_t interface. + */ + main_mode_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Common phase 1 helper class + */ + phase1_t *ph1; + + /** + * IKE config to establish + */ + ike_cfg_t *ike_cfg; + + /** + * Peer config to use + */ + peer_cfg_t *peer_cfg; + + /** + * selected IKE proposal + */ + proposal_t *proposal; + + /** + * Negotiated SA lifetime + */ + u_int32_t lifetime; + + /** + * Negotiated authentication method + */ + auth_method_t method; + + /** states of main mode */ + enum { + MM_INIT, + MM_SA, + MM_KE, + MM_AUTH, + } state; +}; + +/** + * Set IKE_SA to established state + */ +static bool establish(private_main_mode_t *this) +{ + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + return FALSE; + } + + 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 TRUE; +} + +/** + * Check for notify errors, return TRUE if error found + */ +static bool has_notify_errors(private_main_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool err = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY_V1) + { + notify_payload_t *notify; + notify_type_t type; + + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + if (type < 16384) + { + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + err = TRUE; + } + else if (type == INITIAL_CONTACT_IKEV1) + { + if (!this->initiator && this->state == MM_AUTH) + { + /* If authenticated and received INITIAL_CONTACT, + * delete any existing IKE_SAs with that peer. + * The delete takes place when the SA is checked in due + * to other id not known until the 3rd message.*/ + this->ike_sa->set_condition(this->ike_sa, + COND_INIT_CONTACT_SEEN, TRUE); + } + } + else + { + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + } + } + } + enumerator->destroy(enumerator); + + return err; +} + +/** + * Queue a task sending a notify in an INFORMATIONAL exchange + */ +static status_t send_notify(private_main_mode_t *this, notify_type_t type) +{ + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + u_int64_t spi_i, spi_r; + chunk_t spi; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_IKE, type); + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); + spi_r = ike_sa_id->get_responder_spi(ike_sa_id); + spi = chunk_cata("cc", chunk_from_thing(spi_i), chunk_from_thing(spi_r)); + notify->set_spi_data(notify, spi); + + this->ike_sa->queue_task(this->ike_sa, + (task_t*)informational_create(this->ike_sa, notify)); + /* cancel all active/passive tasks in favour of informational */ + this->ike_sa->flush_queue(this->ike_sa, + this->initiator ? TASK_QUEUE_ACTIVE : TASK_QUEUE_PASSIVE); + return ALREADY_DONE; +} + +/** + * Queue a delete task if authentication failed as initiator + */ +static status_t send_delete(private_main_mode_t *this) +{ + this->ike_sa->queue_task(this->ike_sa, + (task_t*)isakmp_delete_create(this->ike_sa, TRUE)); + /* cancel all active tasks in favour of informational */ + this->ike_sa->flush_queue(this->ike_sa, + this->initiator ? TASK_QUEUE_ACTIVE : TASK_QUEUE_PASSIVE); + return ALREADY_DONE; +} + +METHOD(task_t, build_i, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *proposals; + packet_t *packet; + + DBG0(DBG_IKE, "initiating Main Mode IKE_SA %s[%d] to %H", + this->ike_sa->get_name(this->ike_sa), + this->ike_sa->get_unique_id(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + this->peer_cfg->get_ref(this->peer_cfg); + + this->method = this->ph1->get_auth_method(this->ph1, this->peer_cfg); + if (this->method == AUTH_NONE) + { + DBG1(DBG_CFG, "configuration uses unsupported authentication"); + return FAILED; + } + this->lifetime = this->peer_cfg->get_reauth_time(this->peer_cfg, + FALSE); + if (!this->lifetime) + { /* fall back to rekey time of no rekey time configured */ + this->lifetime = this->peer_cfg->get_rekey_time(this->peer_cfg, + FALSE); + } + this->lifetime += this->peer_cfg->get_over_time(this->peer_cfg); + proposals = this->ike_cfg->get_proposals(this->ike_cfg); + sa_payload = sa_payload_create_from_proposals_v1(proposals, + this->lifetime, 0, this->method, MODE_NONE, FALSE, 0); + proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); + + message->add_payload(message, &sa_payload->payload_interface); + + /* pregenerate message to store SA payload */ + if (this->ike_sa->generate_message(this->ike_sa, message, + &packet) != SUCCESS) + { + DBG1(DBG_IKE, "pregenerating SA payload failed"); + return FAILED; + } + packet->destroy(packet); + if (!this->ph1->save_sa_payload(this->ph1, message)) + { + return FAILED; + } + + this->state = MM_SA; + return NEED_MORE; + } + case MM_SA: + { + u_int16_t group; + + if (!this->ph1->create_hasher(this->ph1)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + if (!this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + DBG1(DBG_IKE, "DH group selection failed"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + if (!this->ph1->create_dh(this->ph1, group)) + { + DBG1(DBG_IKE, "negotiated DH group not supported"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->add_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + this->state = MM_KE; + return NEED_MORE; + } + case MM_KE: + { + id_payload_t *id_payload; + identification_t *id; + + id = this->ph1->get_id(this->ph1, this->peer_cfg, TRUE); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return send_notify(this, INVALID_ID_INFORMATION); + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + id_payload = id_payload_create_from_identification(ID_V1, id); + message->add_payload(message, &id_payload->payload_interface); + + if (!this->ph1->build_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + + this->state = MM_AUTH; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_r, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_INIT: + { + linked_list_t *list; + sa_payload_t *sa_payload; + bool private; + + this->ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa); + DBG0(DBG_IKE, "%H is initiating a Main Mode IKE_SA", + message->get_source(message)); + this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); + + this->ike_sa->update_hosts(this->ike_sa, + message->get_destination(message), + message->get_source(message), TRUE); + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "SA payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!this->ph1->save_sa_payload(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + list = sa_payload->get_proposals(sa_payload); + private = this->ike_sa->supports_extension(this->ike_sa, + EXT_STRONGSWAN); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, private); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + this->method = sa_payload->get_auth_method(sa_payload); + this->lifetime = sa_payload->get_lifetime(sa_payload); + + this->state = MM_SA; + return NEED_MORE; + } + case MM_SA: + { + u_int16_t group; + + if (!this->ph1->create_hasher(this->ph1)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + DBG1(DBG_IKE, "DH group selection failed"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->create_dh(this->ph1, group)) + { + DBG1(DBG_IKE, "negotiated DH group not supported"); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->get_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + this->state = MM_KE; + return NEED_MORE; + } + case MM_KE: + { + id_payload_t *id_payload; + identification_t *id; + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDii payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + id = id_payload->get_identification(id_payload); + this->ike_sa->set_other_id(this->ike_sa, id); + + while (TRUE) + { + DESTROY_IF(this->peer_cfg); + this->peer_cfg = this->ph1->select_config(this->ph1, + this->method, FALSE, id); + if (!this->peer_cfg) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg); + + if (this->ph1->verify_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + break; + } + } + + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "Main Mode authorization hook forbids IKE_SA, " + "cancelling"); + return send_notify(this, AUTHENTICATION_FAILED); + } + + this->state = MM_AUTH; + if (has_notify_errors(this, message)) + { + return FAILED; + } + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_SA: + { + sa_payload_t *sa_payload; + + sa_payload = sa_payload_create_from_proposal_v1(this->proposal, + this->lifetime, 0, this->method, MODE_NONE, FALSE, 0); + message->add_payload(message, &sa_payload->payload_interface); + + return NEED_MORE; + } + case MM_KE: + { + if (!this->ph1->add_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!this->ph1->derive_keys(this->ph1, this->peer_cfg, this->method)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + return NEED_MORE; + } + case MM_AUTH: + { + id_payload_t *id_payload; + identification_t *id; + + id = this->ph1->get_id(this->ph1, this->peer_cfg, TRUE); + if (!id) + { + DBG1(DBG_CFG, "own identity not known"); + return send_notify(this, INVALID_ID_INFORMATION); + } + this->ike_sa->set_my_id(this->ike_sa, id->clone(id)); + + id_payload = id_payload_create_from_identification(ID_V1, id); + message->add_payload(message, &id_payload->payload_interface); + + if (!this->ph1->build_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + + switch (this->method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + this->ike_sa->queue_task(this->ike_sa, + (task_t*)xauth_create(this->ike_sa, TRUE)); + return SUCCESS; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + /* wait for XAUTH request */ + break; + default: + if (charon->ike_sa_manager->check_uniqueness( + charon->ike_sa_manager, this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling Main Mode due to uniqueness " + "policy"); + return send_notify(this, AUTHENTICATION_FAILED); + } + if (!establish(this)) + { + return send_notify(this, AUTHENTICATION_FAILED); + } + lib->processor->queue_job(lib->processor, (job_t*) + adopt_children_job_create( + this->ike_sa->get_id(this->ike_sa))); + break; + } + if (!this->ph1->has_pool(this->ph1, this->peer_cfg) && + this->ph1->has_virtual_ip(this->ph1, this->peer_cfg)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)mode_config_create(this->ike_sa, TRUE)); + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_main_mode_t *this, message_t *message) +{ + switch (this->state) + { + case MM_SA: + { + linked_list_t *list; + sa_payload_t *sa_payload; + auth_method_t method; + u_int32_t lifetime; + bool private; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "SA payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + list = sa_payload->get_proposals(sa_payload); + private = this->ike_sa->supports_extension(this->ike_sa, + EXT_STRONGSWAN); + this->proposal = this->ike_cfg->select_proposal(this->ike_cfg, + list, private); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->ike_sa->set_proposal(this->ike_sa, this->proposal); + + lifetime = sa_payload->get_lifetime(sa_payload); + if (lifetime != this->lifetime) + { + DBG1(DBG_IKE, "received lifetime %us does not match configured " + "lifetime %us", lifetime, this->lifetime); + } + this->lifetime = lifetime; + method = sa_payload->get_auth_method(sa_payload); + if (method != this->method) + { + DBG1(DBG_IKE, "received %N authentication, but configured %N, " + "continue with configured", auth_method_names, method, + auth_method_names, this->method); + } + return NEED_MORE; + } + case MM_KE: + { + if (!this->ph1->get_nonce_ke(this->ph1, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!this->ph1->derive_keys(this->ph1, this->peer_cfg, this->method)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + return NEED_MORE; + } + case MM_AUTH: + { + id_payload_t *id_payload; + identification_t *id, *cid; + + id_payload = (id_payload_t*)message->get_payload(message, ID_V1); + if (!id_payload) + { + DBG1(DBG_IKE, "IDir payload missing"); + return send_delete(this); + } + id = id_payload->get_identification(id_payload); + cid = this->ph1->get_id(this->ph1, this->peer_cfg, FALSE); + if (cid && !id->matches(id, cid)) + { + DBG1(DBG_IKE, "IDir '%Y' does not match to '%Y'", id, cid); + id->destroy(id); + return send_delete(this); + } + this->ike_sa->set_other_id(this->ike_sa, id); + + if (!this->ph1->verify_auth(this->ph1, this->method, message, + id_payload->get_encoded(id_payload))) + { + return send_delete(this); + } + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "Main Mode authorization hook forbids IKE_SA, " + "cancelling"); + return send_delete(this); + } + + switch (this->method) + { + case AUTH_XAUTH_INIT_PSK: + case AUTH_XAUTH_INIT_RSA: + case AUTH_HYBRID_INIT_RSA: + /* wait for XAUTH request */ + break; + case AUTH_XAUTH_RESP_PSK: + case AUTH_XAUTH_RESP_RSA: + case AUTH_HYBRID_RESP_RSA: + this->ike_sa->queue_task(this->ike_sa, + (task_t*)xauth_create(this->ike_sa, TRUE)); + return SUCCESS; + default: + if (charon->ike_sa_manager->check_uniqueness( + charon->ike_sa_manager, this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling Main Mode due to uniqueness " + "policy"); + return send_delete(this); + } + if (!establish(this)) + { + return send_delete(this); + } + break; + } + if (this->ph1->has_virtual_ip(this->ph1, this->peer_cfg)) + { + this->ike_sa->queue_task(this->ike_sa, + (task_t*)mode_config_create(this->ike_sa, TRUE)); + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_main_mode_t *this) +{ + return TASK_MAIN_MODE; +} + +METHOD(task_t, migrate, void, + private_main_mode_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + this->ph1->destroy(this->ph1); + + this->ike_sa = ike_sa; + this->state = MM_INIT; + this->peer_cfg = NULL; + this->proposal = NULL; + this->ph1 = phase1_create(ike_sa, this->initiator); +} + +METHOD(task_t, destroy, void, + private_main_mode_t *this) +{ + DESTROY_IF(this->peer_cfg); + DESTROY_IF(this->proposal); + this->ph1->destroy(this->ph1); + free(this); +} + +/* + * Described in header. + */ +main_mode_t *main_mode_create(ike_sa_t *ike_sa, bool initiator) +{ + private_main_mode_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .ph1 = phase1_create(ike_sa, initiator), + .initiator = initiator, + .state = MM_INIT, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/main_mode.h b/src/libcharon/sa/ikev1/tasks/main_mode.h new file mode 100644 index 000000000..141701f75 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/main_mode.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup main_mode main_mode + * @{ @ingroup tasks_v1 + */ + +#ifndef MAIN_MODE_H_ +#define MAIN_MODE_H_ + +typedef struct main_mode_t main_mode_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 main mode, establishes a mainmode including authentication. + */ +struct main_mode_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new main_mode task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if task initiated locally + * @return task to handle by the task_manager + */ +main_mode_t *main_mode_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** MAIN_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/mode_config.c b/src/libcharon/sa/ikev1/tasks/mode_config.c new file mode 100644 index 000000000..ce897727a --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/mode_config.c @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "mode_config.h" + +#include <daemon.h> +#include <hydra.h> +#include <encoding/payloads/cp_payload.h> + +typedef struct private_mode_config_t private_mode_config_t; + +/** + * Private members of a mode_config_t task. + */ +struct private_mode_config_t { + + /** + * Public methods and task_t interface. + */ + mode_config_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Received list of virtual IPs, host_t* + */ + linked_list_t *vips; + + /** + * list of attributes requested and its handler, entry_t + */ + linked_list_t *requested; + + /** + * Identifier to include in response + */ + u_int16_t identifier; +}; + +/** + * Entry for a requested attribute and the requesting handler + */ +typedef struct { + /** attribute requested */ + configuration_attribute_type_t type; + /** handler requesting this attribute */ + attribute_handler_t *handler; +} entry_t; + +/** + * build INTERNAL_IPV4/6_ADDRESS attribute from virtual ip + */ +static configuration_attribute_t *build_vip(host_t *vip) +{ + configuration_attribute_type_t type; + chunk_t chunk, prefix; + + if (vip->get_family(vip) == AF_INET) + { + type = INTERNAL_IP4_ADDRESS; + if (vip->is_anyaddr(vip)) + { + chunk = chunk_empty; + } + else + { + chunk = vip->get_address(vip); + } + } + else + { + type = INTERNAL_IP6_ADDRESS; + if (vip->is_anyaddr(vip)) + { + chunk = chunk_empty; + } + else + { + prefix = chunk_alloca(1); + *prefix.ptr = 64; + chunk = vip->get_address(vip); + chunk = chunk_cata("cc", chunk, prefix); + } + } + return configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, chunk); +} + +/** + * Handle a received attribute as initiator + */ +static void handle_attribute(private_mode_config_t *this, + configuration_attribute_t *ca) +{ + attribute_handler_t *handler = NULL; + enumerator_t *enumerator; + entry_t *entry; + + /* find the handler which requested this attribute */ + enumerator = this->requested->create_enumerator(this->requested); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->type == ca->get_type(ca)) + { + handler = entry->handler; + this->requested->remove_at(this->requested, enumerator); + free(entry); + break; + } + } + enumerator->destroy(enumerator); + + /* and pass it to the handle function */ + handler = hydra->attributes->handle(hydra->attributes, + this->ike_sa->get_other_id(this->ike_sa), handler, + ca->get_type(ca), ca->get_chunk(ca)); + if (handler) + { + this->ike_sa->add_configuration_attribute(this->ike_sa, + handler, ca->get_type(ca), ca->get_chunk(ca)); + } +} + +/** + * process a single configuration attribute + */ +static void process_attribute(private_mode_config_t *this, + configuration_attribute_t *ca) +{ + host_t *ip; + chunk_t addr; + int family = AF_INET6; + + switch (ca->get_type(ca)) + { + case INTERNAL_IP4_ADDRESS: + family = AF_INET; + /* fall */ + case INTERNAL_IP6_ADDRESS: + { + addr = ca->get_chunk(ca); + if (addr.len == 0) + { + ip = host_create_any(family); + } + else + { + /* skip prefix byte in IPv6 payload*/ + if (family == AF_INET6) + { + addr.len--; + } + ip = host_create_from_chunk(family, addr, 0); + } + if (ip) + { + this->vips->insert_last(this->vips, ip); + } + break; + } + default: + { + if (this->initiator) + { + handle_attribute(this, ca); + } + } + } +} + +/** + * Scan for configuration payloads and attributes + */ +static void process_payloads(private_mode_config_t *this, message_t *message) +{ + enumerator_t *enumerator, *attributes; + payload_t *payload; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == CONFIGURATION_V1) + { + cp_payload_t *cp = (cp_payload_t*)payload; + configuration_attribute_t *ca; + + switch (cp->get_type(cp)) + { + case CFG_REQUEST: + this->identifier = cp->get_identifier(cp); + /* FALL */ + case CFG_REPLY: + attributes = cp->create_attribute_enumerator(cp); + while (attributes->enumerate(attributes, &ca)) + { + DBG2(DBG_IKE, "processing %N attribute", + configuration_attribute_type_names, ca->get_type(ca)); + process_attribute(this, ca); + } + attributes->destroy(attributes); + break; + default: + DBG1(DBG_IKE, "ignoring %N config payload", + config_type_names, cp->get_type(cp)); + break; + } + } + } + enumerator->destroy(enumerator); +} + +METHOD(task_t, build_i, status_t, + private_mode_config_t *this, message_t *message) +{ + cp_payload_t *cp; + enumerator_t *enumerator; + attribute_handler_t *handler; + peer_cfg_t *config; + configuration_attribute_type_t type; + chunk_t data; + linked_list_t *vips; + host_t *host; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REQUEST); + + vips = linked_list_create(); + + /* reuse virtual IP if we already have one */ + enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, TRUE); + while (enumerator->enumerate(enumerator, &host)) + { + vips->insert_last(vips, host); + } + enumerator->destroy(enumerator); + + if (vips->get_count(vips) == 0) + { + config = this->ike_sa->get_peer_cfg(this->ike_sa); + enumerator = config->create_virtual_ip_enumerator(config); + while (enumerator->enumerate(enumerator, &host)) + { + vips->insert_last(vips, host); + } + enumerator->destroy(enumerator); + } + + if (vips->get_count(vips)) + { + enumerator = vips->create_enumerator(vips); + while (enumerator->enumerate(enumerator, &host)) + { + cp->add_attribute(cp, build_vip(host)); + } + enumerator->destroy(enumerator); + } + + enumerator = hydra->attributes->create_initiator_enumerator( + hydra->attributes, + this->ike_sa->get_other_id(this->ike_sa), vips); + while (enumerator->enumerate(enumerator, &handler, &type, &data)) + { + entry_t *entry; + + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + cp->add_attribute(cp, + configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, data)); + INIT(entry, + .type = type, + .handler = handler, + ); + this->requested->insert_last(this->requested, entry); + } + enumerator->destroy(enumerator); + + vips->destroy(vips); + + message->add_payload(message, (payload_t*)cp); + + return NEED_MORE; +} + +METHOD(task_t, process_r, status_t, + private_mode_config_t *this, message_t *message) +{ + process_payloads(this, message); + return NEED_MORE; +} + +METHOD(task_t, build_r, status_t, + private_mode_config_t *this, message_t *message) +{ + enumerator_t *enumerator; + configuration_attribute_type_t type; + chunk_t value; + cp_payload_t *cp; + peer_cfg_t *config; + identification_t *id; + linked_list_t *vips, *pools; + host_t *requested; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REPLY); + + id = this->ike_sa->get_other_eap_id(this->ike_sa); + config = this->ike_sa->get_peer_cfg(this->ike_sa); + vips = linked_list_create(); + pools = linked_list_create_from_enumerator( + config->create_pool_enumerator(config)); + + this->ike_sa->clear_virtual_ips(this->ike_sa, FALSE); + + enumerator = this->vips->create_enumerator(this->vips); + while (enumerator->enumerate(enumerator, &requested)) + { + host_t *found = NULL; + + /* query all pools until we get an address */ + DBG1(DBG_IKE, "peer requested virtual IP %H", requested); + + found = hydra->attributes->acquire_address(hydra->attributes, + pools, id, requested); + if (found) + { + DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", found, id); + this->ike_sa->add_virtual_ip(this->ike_sa, FALSE, found); + cp->add_attribute(cp, build_vip(found)); + vips->insert_last(vips, found); + } + else + { + DBG1(DBG_IKE, "no virtual IP found for %H requested by '%Y'", + requested, id); + } + } + enumerator->destroy(enumerator); + + /* query registered providers for additional attributes to include */ + enumerator = hydra->attributes->create_responder_enumerator( + hydra->attributes, pools, id, vips); + while (enumerator->enumerate(enumerator, &type, &value)) + { + DBG2(DBG_IKE, "building %N attribute", + configuration_attribute_type_names, type); + cp->add_attribute(cp, + configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE_V1, + type, value)); + } + enumerator->destroy(enumerator); + vips->destroy_offset(vips, offsetof(host_t, destroy)); + pools->destroy(pools); + + cp->set_identifier(cp, this->identifier); + message->add_payload(message, (payload_t*)cp); + + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_mode_config_t *this, message_t *message) +{ + enumerator_t *enumerator; + host_t *host; + + process_payloads(this, message); + + this->ike_sa->clear_virtual_ips(this->ike_sa, TRUE); + + enumerator = this->vips->create_enumerator(this->vips); + while (enumerator->enumerate(enumerator, &host)) + { + if (!host->is_anyaddr(host)) + { + this->ike_sa->add_virtual_ip(this->ike_sa, TRUE, host); + } + } + enumerator->destroy(enumerator); + + return SUCCESS; +} + +METHOD(task_t, get_type, task_type_t, + private_mode_config_t *this) +{ + return TASK_MODE_CONFIG; +} + +METHOD(task_t, migrate, void, + private_mode_config_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; + this->vips->destroy_offset(this->vips, offsetof(host_t, destroy)); + this->vips = linked_list_create(); + this->requested->destroy_function(this->requested, free); + this->requested = linked_list_create(); +} + +METHOD(task_t, destroy, void, + private_mode_config_t *this) +{ + this->vips->destroy_offset(this->vips, offsetof(host_t, destroy)); + this->requested->destroy_function(this->requested, free); + free(this); +} + +/* + * Described in header. + */ +mode_config_t *mode_config_create(ike_sa_t *ike_sa, bool initiator) +{ + private_mode_config_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + .requested = linked_list_create(), + .vips = linked_list_create(), + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/mode_config.h b/src/libcharon/sa/ikev1/tasks/mode_config.h new file mode 100644 index 000000000..462bee374 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/mode_config.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup mode_config mode_config + * @{ @ingroup tasks_v1 + */ + +#ifndef MODE_CONFIG_H_ +#define MODE_CONFIG_H_ + +typedef struct mode_config_t mode_config_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type TASK_MODE_COFNIG, IKEv1 configuration attribute exchange. + */ +struct mode_config_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new mode_config task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator + * @return mode_config task to handle by the task_manager + */ +mode_config_t *mode_config_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** MODE_CONFIG_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c new file mode 100644 index 000000000..db48bc58e --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "quick_delete.h" + +#include <daemon.h> +#include <encoding/payloads/delete_payload.h> + +typedef struct private_quick_delete_t private_quick_delete_t; + +/** + * Private members of a quick_delete_t task. + */ +struct private_quick_delete_t { + + /** + * Public methods and task_t interface. + */ + quick_delete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Protocol of CHILD_SA to delete + */ + protocol_id_t protocol; + + /** + * Inbound SPI of CHILD_SA to delete + */ + u_int32_t spi; + + /** + * Send delete even if SA does not exist + */ + bool force; + + /** + * SA already expired? + */ + bool expired; +}; + +/** + * Delete the specified CHILD_SA, if found + */ +static bool delete_child(private_quick_delete_t *this, + protocol_id_t protocol, u_int32_t spi) +{ + u_int64_t bytes_in, bytes_out; + child_sa_t *child_sa; + bool rekeyed; + + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, TRUE); + if (!child_sa) + { /* fallback and check for outbound SA */ + child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol, spi, FALSE); + if (!child_sa) + { + return FALSE; + } + this->spi = spi = child_sa->get_spi(child_sa, TRUE); + } + + rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYING; + child_sa->set_state(child_sa, CHILD_DELETING); + + if (this->expired) + { + DBG0(DBG_IKE, "closing expired CHILD_SA %s{%d} " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), + ntohl(child_sa->get_spi(child_sa, FALSE)), + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + } + else + { + child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in); + child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out); + + DBG0(DBG_IKE, "closing CHILD_SA %s{%d} with SPIs " + "%.8x_i (%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, + ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + } + + if (!rekeyed) + { + charon->bus->child_updown(charon->bus, child_sa, FALSE); + } + + this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); + + /* TODO-IKEv1: handle close action? */ + + return TRUE; +} + +METHOD(task_t, build_i, status_t, + private_quick_delete_t *this, message_t *message) +{ + if (delete_child(this, this->protocol, this->spi) || this->force) + { + delete_payload_t *delete_payload; + + DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, this->protocol, ntohl(this->spi)); + + delete_payload = delete_payload_create(DELETE_V1, PROTO_ESP); + delete_payload->add_spi(delete_payload, this->spi); + message->add_payload(message, &delete_payload->payload_interface); + + return SUCCESS; + } + this->ike_sa->flush_queue(this->ike_sa, TASK_QUEUE_ACTIVE); + return ALREADY_DONE; +} + +METHOD(task_t, process_i, status_t, + private_quick_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_quick_delete_t *this, message_t *message) +{ + enumerator_t *payloads, *spis; + payload_t *payload; + delete_payload_t *delete_payload; + protocol_id_t protocol; + u_int32_t spi; + + payloads = message->create_payload_enumerator(message); + while (payloads->enumerate(payloads, &payload)) + { + if (payload->get_type(payload) == DELETE_V1) + { + delete_payload = (delete_payload_t*)payload; + protocol = delete_payload->get_protocol_id(delete_payload); + if (protocol != PROTO_ESP && protocol != PROTO_AH) + { + continue; + } + spis = delete_payload->create_spi_enumerator(delete_payload); + while (spis->enumerate(spis, &spi)) + { + DBG1(DBG_IKE, "received DELETE for %N CHILD_SA with SPI %.8x", + protocol_id_names, protocol, ntohl(spi)); + if (!delete_child(this, protocol, spi)) + { + DBG1(DBG_IKE, "CHILD_SA not found, ignored"); + continue; + } + } + spis->destroy(spis); + } + } + payloads->destroy(payloads); + + return SUCCESS; +} + +METHOD(task_t, build_r, status_t, + private_quick_delete_t *this, message_t *message) +{ + return FAILED; +} + +METHOD(task_t, get_type, task_type_t, + private_quick_delete_t *this) +{ + return TASK_QUICK_DELETE; +} + +METHOD(task_t, migrate, void, + private_quick_delete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_quick_delete_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +quick_delete_t *quick_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi, bool force, bool expired) +{ + private_quick_delete_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .protocol = protocol, + .spi = spi, + .force = force, + .expired = expired, + ); + + if (protocol != PROTO_NONE) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.h b/src/libcharon/sa/ikev1/tasks/quick_delete.h new file mode 100644 index 000000000..4df30c8fe --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup quick_delete quick_delete + * @{ @ingroup tasks_v1 + */ + +#ifndef QUICK_DELETE_H_ +#define QUICK_DELETE_H_ + +typedef struct quick_delete_t quick_delete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> +#include <sa/child_sa.h> + +/** + * Task of type QUICK_DELETE, delete an IKEv1 quick mode SA. + */ +struct quick_delete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new quick_delete task. + * + * @param ike_sa IKE_SA this task works for + * @param protocol protocol of CHILD_SA to delete, PROTO_NONE as responder + * @param spi inbound SPI of CHILD_SA to delete + * @param force send delete even if SA does not exist + * @param expired TRUE if SA already expired + * @return quick_delete task to handle by the task_manager + */ +quick_delete_t *quick_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, + u_int32_t spi, bool force, bool expired); + +#endif /** QUICK_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c new file mode 100644 index 000000000..82a7238c3 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -0,0 +1,1273 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "quick_mode.h" + +#include <string.h> + +#include <daemon.h> +#include <sa/ikev1/keymat_v1.h> +#include <encoding/payloads/sa_payload.h> +#include <encoding/payloads/nonce_payload.h> +#include <encoding/payloads/ke_payload.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/payload.h> +#include <sa/ikev1/tasks/informational.h> +#include <sa/ikev1/tasks/quick_delete.h> +#include <processing/jobs/inactivity_job.h> + +typedef struct private_quick_mode_t private_quick_mode_t; + +/** + * Private members of a quick_mode_t task. + */ +struct private_quick_mode_t { + + /** + * Public methods and task_t interface. + */ + quick_mode_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * TRUE if we are initiating quick mode + */ + bool initiator; + + /** + * Traffic selector of initiator + */ + traffic_selector_t *tsi; + + /** + * Traffic selector of responder + */ + traffic_selector_t *tsr; + + /** + * Initiators nonce + */ + chunk_t nonce_i; + + /** + * Responder nonce + */ + chunk_t nonce_r; + + /** + * Initiators ESP SPI + */ + u_int32_t spi_i; + + /** + * Responder ESP SPI + */ + u_int32_t spi_r; + + /** + * Initiators IPComp CPI + */ + u_int16_t cpi_i; + + /** + * Responders IPComp CPI + */ + u_int16_t cpi_r; + + /** + * selected CHILD_SA proposal + */ + proposal_t *proposal; + + /** + * Config of CHILD_SA to establish + */ + child_cfg_t *config; + + /** + * CHILD_SA we are about to establish + */ + child_sa_t *child_sa; + + /** + * IKEv1 keymat + */ + keymat_v1_t *keymat; + + /** + * DH exchange, when PFS is in use + */ + diffie_hellman_t *dh; + + /** + * Negotiated lifetime of new SA + */ + u_int32_t lifetime; + + /** + * Negotaited lifebytes of new SA + */ + u_int64_t lifebytes; + + /** + * Reqid to use, 0 for auto-allocate + */ + u_int32_t reqid; + + /** + * SPI of SA we rekey + */ + u_int32_t rekey; + + /** + * Negotiated mode, tunnel or transport + */ + ipsec_mode_t mode; + + /** + * Use UDP encapsulation + */ + bool udp; + + /** states of quick mode */ + enum { + QM_INIT, + QM_NEGOTIATED, + } state; +}; + +/** + * Schedule inactivity timeout for CHILD_SA with reqid, if enabled + */ +static void schedule_inactivity_timeout(private_quick_mode_t *this) +{ + u_int32_t timeout; + bool close_ike; + + timeout = this->config->get_inactivity(this->config); + if (timeout) + { + close_ike = lib->settings->get_bool(lib->settings, + "%s.inactivity_close_ike", FALSE, charon->name); + lib->scheduler->schedule_job(lib->scheduler, (job_t*) + inactivity_job_create(this->child_sa->get_reqid(this->child_sa), + timeout, close_ike), timeout); + } +} + +/** + * Check if we have a an address pool configured + */ +static bool have_pool(ike_sa_t *ike_sa) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + char *pool; + bool found = FALSE; + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_pool_enumerator(peer_cfg); + if (enumerator->enumerate(enumerator, &pool)) + { + found = TRUE; + } + enumerator->destroy(enumerator); + } + return found; +} + +/** + * Get hosts to use for dynamic traffic selectors + */ +static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local) +{ + enumerator_t *enumerator; + linked_list_t *list; + host_t *host; + + list = linked_list_create(); + enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local); + while (enumerator->enumerate(enumerator, &host)) + { + list->insert_last(list, host); + } + enumerator->destroy(enumerator); + + if (list->get_count(list) == 0) + { /* no virtual IPs assigned */ + if (local) + { + host = ike_sa->get_my_host(ike_sa); + list->insert_last(list, host); + } + else if (!have_pool(ike_sa)) + { /* use host only if we don't have a pool configured */ + host = ike_sa->get_other_host(ike_sa); + list->insert_last(list, host); + } + } + return list; +} + +/** + * Install negotiated CHILD_SA + */ +static bool install(private_quick_mode_t *this) +{ + status_t status, status_i, status_o; + chunk_t encr_i, encr_r, integ_i, integ_r; + linked_list_t *tsi, *tsr; + child_sa_t *old = NULL; + + this->child_sa->set_proposal(this->child_sa, this->proposal); + this->child_sa->set_state(this->child_sa, CHILD_INSTALLING); + this->child_sa->set_mode(this->child_sa, this->mode); + + if (this->cpi_i && this->cpi_r) + { /* DEFLATE is the only transform we currently support */ + this->child_sa->set_ipcomp(this->child_sa, IPCOMP_DEFLATE); + } + else + { + this->cpi_i = this->cpi_r = 0; + } + + this->child_sa->set_protocol(this->child_sa, + this->proposal->get_protocol(this->proposal)); + + status_i = status_o = FAILED; + encr_i = encr_r = integ_i = integ_r = chunk_empty; + tsi = linked_list_create_with_items(this->tsi->clone(this->tsi), NULL); + tsr = linked_list_create_with_items(this->tsr->clone(this->tsr), NULL); + if (this->initiator) + { + charon->bus->narrow(charon->bus, this->child_sa, + NARROW_INITIATOR_POST_AUTH, tsi, tsr); + } + else + { + charon->bus->narrow(charon->bus, this->child_sa, + NARROW_RESPONDER_POST, tsr, tsi); + } + if (tsi->get_count(tsi) == 0 || tsr->get_count(tsr) == 0) + { + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + DBG1(DBG_IKE, "no acceptable traffic selectors found"); + return FALSE; + } + + if (this->keymat->derive_child_keys(this->keymat, this->proposal, this->dh, + this->spi_i, this->spi_r, this->nonce_i, this->nonce_r, + &encr_i, &integ_i, &encr_r, &integ_r)) + { + if (this->initiator) + { + status_i = this->child_sa->install(this->child_sa, encr_r, integ_r, + this->spi_i, this->cpi_i, TRUE, FALSE, tsi, tsr); + status_o = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->spi_r, this->cpi_r, FALSE, FALSE, tsi, tsr); + } + else + { + status_i = this->child_sa->install(this->child_sa, encr_i, integ_i, + this->spi_r, this->cpi_r, TRUE, FALSE, tsr, tsi); + status_o = this->child_sa->install(this->child_sa, encr_r, integ_r, + this->spi_i, this->cpi_i, FALSE, FALSE, tsr, tsi); + } + } + chunk_clear(&integ_i); + chunk_clear(&integ_r); + chunk_clear(&encr_i); + chunk_clear(&encr_r); + + if (status_i != SUCCESS || status_o != SUCCESS) + { + DBG1(DBG_IKE, "unable to install %s%s%sIPsec SA (SAD) in kernel", + (status_i != SUCCESS) ? "inbound " : "", + (status_i != SUCCESS && status_o != SUCCESS) ? "and ": "", + (status_o != SUCCESS) ? "outbound " : ""); + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + return FALSE; + } + + if (this->initiator) + { + status = this->child_sa->add_policies(this->child_sa, tsi, tsr); + } + else + { + status = this->child_sa->add_policies(this->child_sa, tsr, tsi); + } + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + if (status != SUCCESS) + { + DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel"); + return FALSE; + } + + charon->bus->child_keys(charon->bus, this->child_sa, this->initiator, + this->dh, this->nonce_i, this->nonce_r); + + /* add to IKE_SA, and remove from task */ + this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + this->ike_sa->add_child_sa(this->ike_sa, this->child_sa); + + DBG0(DBG_IKE, "CHILD_SA %s{%d} established " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + this->child_sa->get_name(this->child_sa), + this->child_sa->get_reqid(this->child_sa), + ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), + ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), + this->child_sa->get_traffic_selectors(this->child_sa, TRUE), + this->child_sa->get_traffic_selectors(this->child_sa, FALSE)); + + if (this->rekey) + { + old = this->ike_sa->get_child_sa(this->ike_sa, + this->proposal->get_protocol(this->proposal), + this->rekey, TRUE); + } + if (old) + { + charon->bus->child_rekey(charon->bus, old, this->child_sa); + } + else + { + charon->bus->child_updown(charon->bus, this->child_sa, TRUE); + } + if (!this->rekey) + { + schedule_inactivity_timeout(this); + } + this->child_sa = NULL; + return TRUE; +} + +/** + * Generate and add NONCE + */ +static bool add_nonce(private_quick_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + nonce_gen_t *nonceg; + + nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); + if (!nonceg) + { + DBG1(DBG_IKE, "no nonce generator found to create nonce"); + return FALSE; + } + if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, nonce)) + { + DBG1(DBG_IKE, "nonce allocation failed"); + nonceg->destroy(nonceg); + return FALSE; + } + nonceg->destroy(nonceg); + + nonce_payload = nonce_payload_create(NONCE_V1); + nonce_payload->set_nonce(nonce_payload, *nonce); + message->add_payload(message, &nonce_payload->payload_interface); + + return TRUE; +} + +/** + * Extract nonce from NONCE payload + */ +static bool get_nonce(private_quick_mode_t *this, chunk_t *nonce, + message_t *message) +{ + nonce_payload_t *nonce_payload; + + nonce_payload = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (!nonce_payload) + { + DBG1(DBG_IKE, "NONCE payload missing in message"); + return FALSE; + } + *nonce = nonce_payload->get_nonce(nonce_payload); + + return TRUE; +} + +/** + * Add KE payload to message + */ +static void add_ke(private_quick_mode_t *this, message_t *message) +{ + ke_payload_t *ke_payload; + + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, this->dh); + message->add_payload(message, &ke_payload->payload_interface); +} + +/** + * Get DH value from a KE payload + */ +static bool get_ke(private_quick_mode_t *this, message_t *message) +{ + ke_payload_t *ke_payload; + + ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1); + if (!ke_payload) + { + DBG1(DBG_IKE, "KE payload missing"); + return FALSE; + } + this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload)); + return TRUE; +} + +/** + * Select a traffic selector from configuration + */ +static traffic_selector_t* select_ts(private_quick_mode_t *this, bool local, + linked_list_t *supplied) +{ + traffic_selector_t *ts; + linked_list_t *list, *hosts; + + hosts = get_dynamic_hosts(this->ike_sa, local); + list = this->config->get_traffic_selectors(this->config, + local, supplied, hosts); + hosts->destroy(hosts); + if (list->get_first(list, (void**)&ts) == SUCCESS) + { + ts = ts->clone(ts); + } + else + { + DBG1(DBG_IKE, "%s traffic selector missing in configuration", + local ? "local" : "local"); + ts = NULL; + } + list->destroy_offset(list, offsetof(traffic_selector_t, destroy)); + return ts; +} + +/** + * Add selected traffic selectors to message + */ +static void add_ts(private_quick_mode_t *this, message_t *message) +{ + id_payload_t *id_payload; + host_t *hsi, *hsr; + + if (this->initiator) + { + hsi = this->ike_sa->get_my_host(this->ike_sa); + hsr = this->ike_sa->get_other_host(this->ike_sa); + } + else + { + hsr = this->ike_sa->get_my_host(this->ike_sa); + hsi = this->ike_sa->get_other_host(this->ike_sa); + } + /* add ID payload only if negotiating non host2host tunnels */ + if (!this->tsi->is_host(this->tsi, hsi) || + !this->tsr->is_host(this->tsr, hsr) || + this->tsi->get_protocol(this->tsi) || + this->tsr->get_protocol(this->tsr) || + this->tsi->get_from_port(this->tsi) || + this->tsr->get_from_port(this->tsr) || + this->tsi->get_to_port(this->tsi) != 65535 || + this->tsr->get_to_port(this->tsr) != 65535) + { + id_payload = id_payload_create_from_ts(this->tsi); + message->add_payload(message, &id_payload->payload_interface); + id_payload = id_payload_create_from_ts(this->tsr); + message->add_payload(message, &id_payload->payload_interface); + } +} + +/** + * Get traffic selectors from received message + */ +static bool get_ts(private_quick_mode_t *this, message_t *message) +{ + traffic_selector_t *tsi = NULL, *tsr = NULL; + enumerator_t *enumerator; + id_payload_t *id_payload; + payload_t *payload; + host_t *hsi, *hsr; + bool first = TRUE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == ID_V1) + { + id_payload = (id_payload_t*)payload; + + if (first) + { + tsi = id_payload->get_ts(id_payload); + first = FALSE; + } + else + { + tsr = id_payload->get_ts(id_payload); + break; + } + } + } + enumerator->destroy(enumerator); + + /* create host2host selectors if ID payloads missing */ + if (this->initiator) + { + hsi = this->ike_sa->get_my_host(this->ike_sa); + hsr = this->ike_sa->get_other_host(this->ike_sa); + } + else + { + hsr = this->ike_sa->get_my_host(this->ike_sa); + hsi = this->ike_sa->get_other_host(this->ike_sa); + } + if (!tsi) + { + tsi = traffic_selector_create_from_subnet(hsi->clone(hsi), + hsi->get_family(hsi) == AF_INET ? 32 : 128, 0, 0); + } + if (!tsr) + { + tsr = traffic_selector_create_from_subnet(hsr->clone(hsr), + hsr->get_family(hsr) == AF_INET ? 32 : 128, 0, 0); + } + if (!this->initiator && this->mode == MODE_TRANSPORT && this->udp && + (!tsi->is_host(tsi, hsi) || !tsr->is_host(tsr, hsr))) + { /* change TS in case of a NAT in transport mode */ + DBG2(DBG_IKE, "changing received traffic selectors %R=== %R due to NAT", + tsi, tsr); + tsi->set_address(tsi, hsi); + tsr->set_address(tsr, hsr); + } + + if (this->initiator) + { + /* check if peer selection valid */ + if (!tsr->is_contained_in(tsr, this->tsr) || + !tsi->is_contained_in(tsi, this->tsi)) + { + DBG1(DBG_IKE, "peer selected invalid traffic selectors: ", + "%R for %R, %R for %R", tsi, this->tsi, tsr, this->tsr); + tsi->destroy(tsi); + tsr->destroy(tsr); + return FALSE; + } + this->tsi->destroy(this->tsi); + this->tsr->destroy(this->tsr); + this->tsi = tsi; + this->tsr = tsr; + } + else + { + this->tsi = tsi; + this->tsr = tsr; + } + return TRUE; +} + +/** + * Add NAT-OA payloads + */ +static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message) +{ + identification_t *id; + id_payload_t *nat_oa; + host_t *src, *dst; + + src = message->get_source(message); + dst = message->get_destination(message); + + src = this->initiator ? src : dst; + dst = this->initiator ? dst : src; + + /* first NAT-OA is the initiator's address */ + id = identification_create_from_sockaddr(src->get_sockaddr(src)); + nat_oa = id_payload_create_from_identification(NAT_OA_V1, id); + message->add_payload(message, (payload_t*)nat_oa); + id->destroy(id); + + /* second NAT-OA is that of the responder */ + id = identification_create_from_sockaddr(dst->get_sockaddr(dst)); + nat_oa = id_payload_create_from_identification(NAT_OA_V1, id); + message->add_payload(message, (payload_t*)nat_oa); + id->destroy(id); +} + +/** + * Look up lifetimes + */ +static void get_lifetimes(private_quick_mode_t *this) +{ + lifetime_cfg_t *lft; + + lft = this->config->get_lifetime(this->config); + if (lft->time.life) + { + this->lifetime = lft->time.life; + } + else if (lft->bytes.life) + { + this->lifebytes = lft->bytes.life; + } + free(lft); +} + +/** + * Check and apply lifetimes + */ +static void apply_lifetimes(private_quick_mode_t *this, sa_payload_t *sa_payload) +{ + u_int32_t lifetime; + u_int64_t lifebytes; + + lifetime = sa_payload->get_lifetime(sa_payload); + lifebytes = sa_payload->get_lifebytes(sa_payload); + if (this->lifetime != lifetime) + { + DBG1(DBG_IKE, "received %us lifetime, configured %us", + lifetime, this->lifetime); + this->lifetime = lifetime; + } + if (this->lifebytes != lifebytes) + { + DBG1(DBG_IKE, "received %llu lifebytes, configured %llu", + lifebytes, this->lifebytes); + this->lifebytes = lifebytes; + } +} + +/** + * Set the task ready to build notify error message + */ +static status_t send_notify(private_quick_mode_t *this, notify_type_t type) +{ + notify_payload_t *notify; + + notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1, + PROTO_ESP, type); + notify->set_spi(notify, this->spi_i); + + this->ike_sa->queue_task(this->ike_sa, + (task_t*)informational_create(this->ike_sa, notify)); + /* cancel all active/passive tasks in favour of informational */ + this->ike_sa->flush_queue(this->ike_sa, + this->initiator ? TASK_QUEUE_ACTIVE : TASK_QUEUE_PASSIVE); + return ALREADY_DONE; +} + +METHOD(task_t, build_i, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + enumerator_t *enumerator; + sa_payload_t *sa_payload; + linked_list_t *list, *tsi, *tsr; + proposal_t *proposal; + diffie_hellman_group_t group; + + this->udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY); + this->mode = this->config->get_mode(this->config); + this->child_sa = child_sa_create( + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->config, this->reqid, this->udp); + + if (this->udp && this->mode == MODE_TRANSPORT) + { + /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */ + add_nat_oa_payloads(this, message); + } + + if (this->config->use_ipcomp(this->config)) + { + if (this->udp) + { + DBG1(DBG_IKE, "IPComp is not supported if either peer is " + "natted, IPComp disabled"); + } + else + { + this->cpi_i = this->child_sa->alloc_cpi(this->child_sa); + if (!this->cpi_i) + { + DBG1(DBG_IKE, "unable to allocate a CPI from kernel, " + "IPComp disabled"); + } + } + } + + this->spi_i = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP); + if (!this->spi_i) + { + DBG1(DBG_IKE, "allocating SPI from kernel failed"); + return FAILED; + } + + list = this->config->get_proposals(this->config, FALSE); + enumerator = list->create_enumerator(list); + while (enumerator->enumerate(enumerator, &proposal)) + { + proposal->set_spi(proposal, this->spi_i); + } + enumerator->destroy(enumerator); + + get_lifetimes(this); + sa_payload = sa_payload_create_from_proposals_v1(list, + this->lifetime, this->lifebytes, AUTH_NONE, + this->mode, this->udp, this->cpi_i); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + message->add_payload(message, &sa_payload->payload_interface); + + if (!add_nonce(this, &this->nonce_i, message)) + { + return FAILED; + } + + group = this->config->get_dh_group(this->config); + if (group != MODP_NONE) + { + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + group); + if (!this->dh) + { + DBG1(DBG_IKE, "configured DH group %N not supported", + diffie_hellman_group_names, group); + return FAILED; + } + add_ke(this, message); + } + if (!this->tsi) + { + this->tsi = select_ts(this, TRUE, NULL); + } + if (!this->tsr) + { + this->tsr = select_ts(this, FALSE, NULL); + } + tsi = linked_list_create_with_items(this->tsi, NULL); + tsr = linked_list_create_with_items(this->tsr, NULL); + this->tsi = this->tsr = NULL; + charon->bus->narrow(charon->bus, this->child_sa, + NARROW_INITIATOR_PRE_AUTH, tsi, tsr); + tsi->remove_first(tsi, (void**)&this->tsi); + tsr->remove_first(tsr, (void**)&this->tsr); + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + if (!this->tsi || !this->tsr) + { + return FAILED; + } + add_ts(this, message); + return NEED_MORE; + } + case QM_NEGOTIATED: + { + return SUCCESS; + } + default: + return FAILED; + } +} + +/** + * Check for notify errors, return TRUE if error found + */ +static bool has_notify_errors(private_quick_mode_t *this, message_t *message) +{ + enumerator_t *enumerator; + payload_t *payload; + bool err = FALSE; + + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == NOTIFY_V1) + { + notify_payload_t *notify; + notify_type_t type; + + notify = (notify_payload_t*)payload; + type = notify->get_notify_type(notify); + if (type < 16384) + { + + DBG1(DBG_IKE, "received %N error notify", + notify_type_names, type); + err = TRUE; + } + else + { + DBG1(DBG_IKE, "received %N notify", notify_type_names, type); + } + } + } + enumerator->destroy(enumerator); + + return err; +} + +/** + * Check if this is a rekey for an existing CHILD_SA, reuse reqid if so + */ +static void check_for_rekeyed_child(private_quick_mode_t *this) +{ + enumerator_t *enumerator, *policies; + traffic_selector_t *local, *remote; + child_sa_t *child_sa; + + enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); + while (this->reqid == 0 && enumerator->enumerate(enumerator, &child_sa)) + { + if (child_sa->get_state(child_sa) == CHILD_INSTALLED && + streq(child_sa->get_name(child_sa), + this->config->get_name(this->config))) + { + policies = child_sa->create_policy_enumerator(child_sa); + if (policies->enumerate(policies, &local, &remote)) + { + if (local->equals(local, this->tsr) && + remote->equals(remote, this->tsi) && + this->proposal->equals(this->proposal, + child_sa->get_proposal(child_sa))) + { + this->reqid = child_sa->get_reqid(child_sa); + this->rekey = child_sa->get_spi(child_sa, TRUE); + child_sa->set_state(child_sa, CHILD_REKEYING); + DBG1(DBG_IKE, "detected rekeying of CHILD_SA %s{%u}", + child_sa->get_name(child_sa), this->reqid); + } + } + policies->destroy(policies); + } + } + enumerator->destroy(enumerator); +} + +METHOD(task_t, process_r, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *tsi, *tsr, *hostsi, *hostsr, *list = NULL; + peer_cfg_t *peer_cfg; + u_int16_t group; + bool private; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "sa payload missing"); + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + this->mode = sa_payload->get_encap_mode(sa_payload, &this->udp); + + if (!get_ts(this, message)) + { + return FAILED; + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + tsi = linked_list_create_with_items(this->tsi, NULL); + tsr = linked_list_create_with_items(this->tsr, NULL); + this->tsi = this->tsr = NULL; + hostsi = get_dynamic_hosts(this->ike_sa, FALSE); + hostsr = get_dynamic_hosts(this->ike_sa, TRUE); + this->config = peer_cfg->select_child_cfg(peer_cfg, tsr, tsi, + hostsr, hostsi); + hostsi->destroy(hostsi); + hostsr->destroy(hostsr); + if (this->config) + { + this->tsi = select_ts(this, FALSE, tsi); + this->tsr = select_ts(this, TRUE, tsr); + } + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + if (!this->config || !this->tsi || !this->tsr) + { + DBG1(DBG_IKE, "no matching CHILD_SA config found"); + return send_notify(this, INVALID_ID_INFORMATION); + } + + if (this->config->use_ipcomp(this->config)) + { + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + { + DBG1(DBG_IKE, "IPComp is not supported if either peer is " + "natted, IPComp disabled"); + } + else + { + list = sa_payload->get_ipcomp_proposals(sa_payload, + &this->cpi_i); + if (!list->get_count(list)) + { + DBG1(DBG_IKE, "expected IPComp proposal but peer did " + "not send one, IPComp disabled"); + this->cpi_i = 0; + } + } + } + if (!list || !list->get_count(list)) + { + DESTROY_IF(list); + list = sa_payload->get_proposals(sa_payload); + } + private = this->ike_sa->supports_extension(this->ike_sa, + EXT_STRONGSWAN); + this->proposal = this->config->select_proposal(this->config, + list, FALSE, private); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + + get_lifetimes(this); + apply_lifetimes(this, sa_payload); + + if (!this->proposal) + { + DBG1(DBG_IKE, "no matching proposal found, sending %N", + notify_type_names, NO_PROPOSAL_CHOSEN); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->spi_i = this->proposal->get_spi(this->proposal); + + if (!get_nonce(this, &this->nonce_i, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + + if (this->proposal->get_algorithm(this->proposal, + DIFFIE_HELLMAN_GROUP, &group, NULL)) + { + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + group); + if (!this->dh) + { + DBG1(DBG_IKE, "negotiated DH group %N not supported", + diffie_hellman_group_names, group); + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!get_ke(this, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + } + + check_for_rekeyed_child(this); + + this->child_sa = child_sa_create( + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), + this->config, this->reqid, this->udp); + + tsi = linked_list_create_with_items(this->tsi, NULL); + tsr = linked_list_create_with_items(this->tsr, NULL); + this->tsi = this->tsr = NULL; + charon->bus->narrow(charon->bus, this->child_sa, + NARROW_RESPONDER, tsr, tsi); + if (tsi->remove_first(tsi, (void**)&this->tsi) != SUCCESS || + tsr->remove_first(tsr, (void**)&this->tsr) != SUCCESS) + { + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + return send_notify(this, INVALID_ID_INFORMATION); + } + tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy)); + tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy)); + + return NEED_MORE; + } + case QM_NEGOTIATED: + { + if (message->get_exchange_type(message) == INFORMATIONAL_V1 || + has_notify_errors(this, message)) + { + return SUCCESS; + } + if (!install(this)) + { + ike_sa_t *ike_sa = this->ike_sa; + task_t *task; + + task = (task_t*)quick_delete_create(this->ike_sa, + this->proposal->get_protocol(this->proposal), + this->spi_i, TRUE, TRUE); + /* flush_queue() destroys the current task */ + ike_sa->flush_queue(ike_sa, TASK_QUEUE_PASSIVE); + ike_sa->queue_task(ike_sa, task); + return ALREADY_DONE; + } + return SUCCESS; + } + default: + return FAILED; + } +} + +METHOD(task_t, build_r, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + + this->spi_r = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP); + if (!this->spi_r) + { + DBG1(DBG_IKE, "allocating SPI from kernel failed"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->proposal->set_spi(this->proposal, this->spi_r); + + if (this->cpi_i) + { + this->cpi_r = this->child_sa->alloc_cpi(this->child_sa); + if (!this->cpi_r) + { + DBG1(DBG_IKE, "unable to allocate a CPI from " + "kernel, IPComp disabled"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + } + + if (this->udp && this->mode == MODE_TRANSPORT) + { + /* TODO-IKEv1: disable NAT-T for TRANSPORT mode by default? */ + add_nat_oa_payloads(this, message); + } + + sa_payload = sa_payload_create_from_proposal_v1(this->proposal, + this->lifetime, this->lifebytes, AUTH_NONE, + this->mode, this->udp, this->cpi_r); + message->add_payload(message, &sa_payload->payload_interface); + + if (!add_nonce(this, &this->nonce_r, message)) + { + return FAILED; + } + if (this->dh) + { + add_ke(this, message); + } + + add_ts(this, message); + + this->state = QM_NEGOTIATED; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, process_i, status_t, + private_quick_mode_t *this, message_t *message) +{ + switch (this->state) + { + case QM_INIT: + { + sa_payload_t *sa_payload; + linked_list_t *list = NULL; + bool private; + + sa_payload = (sa_payload_t*)message->get_payload(message, + SECURITY_ASSOCIATION_V1); + if (!sa_payload) + { + DBG1(DBG_IKE, "sa payload missing"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + if (this->cpi_i) + { + list = sa_payload->get_ipcomp_proposals(sa_payload, + &this->cpi_r); + if (!list->get_count(list)) + { + DBG1(DBG_IKE, "peer did not acccept our IPComp proposal, " + "IPComp disabled"); + this->cpi_i = 0; + } + } + if (!list || !list->get_count(list)) + { + DESTROY_IF(list); + list = sa_payload->get_proposals(sa_payload); + } + private = this->ike_sa->supports_extension(this->ike_sa, + EXT_STRONGSWAN); + this->proposal = this->config->select_proposal(this->config, + list, FALSE, private); + list->destroy_offset(list, offsetof(proposal_t, destroy)); + if (!this->proposal) + { + DBG1(DBG_IKE, "no matching proposal found"); + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->spi_r = this->proposal->get_spi(this->proposal); + + apply_lifetimes(this, sa_payload); + + if (!get_nonce(this, &this->nonce_r, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (this->dh && !get_ke(this, message)) + { + return send_notify(this, INVALID_KEY_INFORMATION); + } + if (!get_ts(this, message)) + { + return send_notify(this, INVALID_PAYLOAD_TYPE); + } + if (!install(this)) + { + return send_notify(this, NO_PROPOSAL_CHOSEN); + } + this->state = QM_NEGOTIATED; + return NEED_MORE; + } + default: + return FAILED; + } +} + +METHOD(task_t, get_type, task_type_t, + private_quick_mode_t *this) +{ + return TASK_QUICK_MODE; +} + +METHOD(quick_mode_t, use_reqid, void, + private_quick_mode_t *this, u_int32_t reqid) +{ + this->reqid = reqid; +} + +METHOD(quick_mode_t, rekey, void, + private_quick_mode_t *this, u_int32_t spi) +{ + this->rekey = spi; +} + +METHOD(task_t, migrate, void, + private_quick_mode_t *this, ike_sa_t *ike_sa) +{ + chunk_free(&this->nonce_i); + chunk_free(&this->nonce_r); + DESTROY_IF(this->tsi); + DESTROY_IF(this->tsr); + DESTROY_IF(this->proposal); + DESTROY_IF(this->child_sa); + DESTROY_IF(this->dh); + + this->ike_sa = ike_sa; + this->keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa); + this->state = QM_INIT; + this->tsi = NULL; + this->tsr = NULL; + this->proposal = NULL; + this->child_sa = NULL; + this->dh = NULL; + this->spi_i = 0; + this->spi_r = 0; + + if (!this->initiator) + { + DESTROY_IF(this->config); + this->config = NULL; + } +} + +METHOD(task_t, destroy, void, + private_quick_mode_t *this) +{ + chunk_free(&this->nonce_i); + chunk_free(&this->nonce_r); + DESTROY_IF(this->tsi); + DESTROY_IF(this->tsr); + DESTROY_IF(this->proposal); + DESTROY_IF(this->child_sa); + DESTROY_IF(this->config); + DESTROY_IF(this->dh); + free(this); +} + +/* + * Described in header. + */ +quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + private_quick_mode_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + .use_reqid = _use_reqid, + .rekey = _rekey, + }, + .ike_sa = ike_sa, + .initiator = config != NULL, + .config = config, + .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa), + .state = QM_INIT, + .tsi = tsi ? tsi->clone(tsi) : NULL, + .tsr = tsr ? tsr->clone(tsr) : NULL, + ); + + if (config) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.h b/src/libcharon/sa/ikev1/tasks/quick_mode.h new file mode 100644 index 000000000..0b80cb836 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup quick_mode quick_mode + * @{ @ingroup tasks_v1 + */ + +#ifndef QUICK_MODE_H_ +#define QUICK_MODE_H_ + +typedef struct quick_mode_t quick_mode_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * IKEv1 quick mode, establishes a CHILD_SA in IKEv1. + */ +struct quick_mode_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * Use a specific reqid to install this CHILD_SA. + * + * @param reqid reqid to use + */ + void (*use_reqid)(quick_mode_t *this, u_int32_t reqid); + + /** + * Set the SPI of the old SA, if rekeying. + * + * @param spi spi of SA to rekey + */ + void (*rekey)(quick_mode_t *this, u_int32_t spi); +}; + +/** + * Create a new quick_mode task. + * + * @param ike_sa IKE_SA this task works for + * @param config child_cfg if task initiator, NULL if responder + * @param tsi source of triggering packet, or NULL + * @param tsr destination of triggering packet, or NULL + * @return task to handle by the task_manager + */ +quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, + traffic_selector_t *tsi, traffic_selector_t *tsr); + +#endif /** QUICK_MODE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/tasks/xauth.c b/src/libcharon/sa/ikev1/tasks/xauth.c new file mode 100644 index 000000000..10bea5636 --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/xauth.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "xauth.h" + +#include <daemon.h> +#include <hydra.h> +#include <encoding/payloads/cp_payload.h> +#include <processing/jobs/adopt_children_job.h> + +typedef struct private_xauth_t private_xauth_t; + +/** + * Status types exchanged + */ +typedef enum { + XAUTH_FAILED = 0, + XAUTH_OK = 1, +} xauth_status_t; + +/** + * Private members of a xauth_t task. + */ +struct private_xauth_t { + + /** + * Public methods and task_t interface. + */ + xauth_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the XAUTH initiator? + */ + bool initiator; + + /** + * XAuth backend to use + */ + xauth_method_t *xauth; + + /** + * XAuth username + */ + identification_t *user; + + /** + * Generated configuration payload + */ + cp_payload_t *cp; + + /** + * received identifier + */ + u_int16_t identifier; + + /** + * status of Xauth exchange + */ + xauth_status_t status; +}; + +/** + * Load XAuth backend + */ +static xauth_method_t *load_method(private_xauth_t* this) +{ + identification_t *server, *peer; + enumerator_t *enumerator; + xauth_method_t *xauth; + xauth_role_t role; + peer_cfg_t *peer_cfg; + auth_cfg_t *auth; + char *name; + + if (this->initiator) + { + server = this->ike_sa->get_my_id(this->ike_sa); + peer = this->ike_sa->get_other_id(this->ike_sa); + role = XAUTH_SERVER; + } + else + { + peer = this->ike_sa->get_my_id(this->ike_sa); + server = this->ike_sa->get_other_id(this->ike_sa); + role = XAUTH_PEER; + } + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, !this->initiator); + if (!enumerator->enumerate(enumerator, &auth) || + (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS) != AUTH_CLASS_XAUTH) + { + if (!enumerator->enumerate(enumerator, &auth) || + (uintptr_t)auth->get(auth, AUTH_RULE_AUTH_CLASS) != AUTH_CLASS_XAUTH) + { + DBG1(DBG_CFG, "no XAuth authentication round found"); + enumerator->destroy(enumerator); + return NULL; + } + } + name = auth->get(auth, AUTH_RULE_XAUTH_BACKEND); + this->user = auth->get(auth, AUTH_RULE_XAUTH_IDENTITY); + enumerator->destroy(enumerator); + if (!this->initiator && this->user) + { /* use XAUTH username, if configured */ + peer = this->user; + } + xauth = charon->xauth->create_instance(charon->xauth, name, role, + server, peer); + if (!xauth) + { + if (name) + { + DBG1(DBG_CFG, "no XAuth method found named '%s'", name); + } + else + { + DBG1(DBG_CFG, "no XAuth method found"); + } + } + return xauth; +} + +/** + * Check if XAuth connection is allowed to succeed + */ +static bool allowed(private_xauth_t *this) +{ + if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, + this->ike_sa, FALSE)) + { + DBG1(DBG_IKE, "cancelling XAuth due to uniqueness policy"); + return FALSE; + } + if (!charon->bus->authorize(charon->bus, FALSE)) + { + DBG1(DBG_IKE, "XAuth authorization hook forbids IKE_SA, cancelling"); + return FALSE; + } + if (!charon->bus->authorize(charon->bus, TRUE)) + { + DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling"); + return FALSE; + } + return TRUE; +} + +/** + * Set IKE_SA to established state + */ +static bool establish(private_xauth_t *this) +{ + 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 TRUE; +} + +/** + * Check if we are compliant to a given peer config + */ +static bool is_compliant(private_xauth_t *this, peer_cfg_t *peer_cfg, bool log) +{ + bool complies = TRUE; + enumerator_t *e1, *e2; + auth_cfg_t *c1, *c2; + + e1 = peer_cfg->create_auth_cfg_enumerator(peer_cfg, FALSE); + e2 = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, FALSE); + while (e1->enumerate(e1, &c1)) + { + if (!e2->enumerate(e2, &c2) || !c2->complies(c2, c1, log)) + { + complies = FALSE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + + return complies; +} + +/** + * Check if we are compliant to current config, switch to another if not + */ +static bool select_compliant_config(private_xauth_t *this) +{ + peer_cfg_t *peer_cfg = NULL, *old, *current; + identification_t *my_id, *other_id; + host_t *my_host, *other_host; + enumerator_t *enumerator; + bool aggressive; + + old = this->ike_sa->get_peer_cfg(this->ike_sa); + if (is_compliant(this, old, TRUE)) + { /* current config is fine */ + return TRUE; + } + DBG1(DBG_CFG, "selected peer config '%s' inacceptable", + old->get_name(old)); + aggressive = old->use_aggressive(old); + + my_host = this->ike_sa->get_my_host(this->ike_sa); + other_host = 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); + enumerator = charon->backends->create_peer_cfg_enumerator(charon->backends, + my_host, other_host, my_id, other_id, IKEV1); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (!current->equals(current, old) && + current->use_aggressive(current) == aggressive && + is_compliant(this, current, FALSE)) + { + peer_cfg = current; + break; + } + } + if (peer_cfg) + { + DBG1(DBG_CFG, "switching to peer config '%s'", + peer_cfg->get_name(peer_cfg)); + this->ike_sa->set_peer_cfg(this->ike_sa, peer_cfg); + } + else + { + DBG1(DBG_CFG, "no alternative config found"); + } + enumerator->destroy(enumerator); + + return peer_cfg != NULL; +} + +/** + * Create auth config after successful authentication + */ +static bool add_auth_cfg(private_xauth_t *this, identification_t *id, bool local) +{ + auth_cfg_t *auth; + + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_XAUTH); + auth->add(auth, AUTH_RULE_XAUTH_IDENTITY, id->clone(id)); + auth->merge(auth, this->ike_sa->get_auth_cfg(this->ike_sa, local), FALSE); + this->ike_sa->add_auth_cfg(this->ike_sa, local, auth); + + return select_compliant_config(this); +} + +METHOD(task_t, build_i_status, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_SET); + cp->add_attribute(cp, + configuration_attribute_create_value(XAUTH_STATUS, this->status)); + + message->add_payload(message, (payload_t *)cp); + + return NEED_MORE; +} + +METHOD(task_t, build_i, status_t, + private_xauth_t *this, message_t *message) +{ + if (!this->xauth) + { + cp_payload_t *cp; + + this->xauth = load_method(this); + if (!this->xauth) + { + return FAILED; + } + if (this->xauth->initiate(this->xauth, &cp) != NEED_MORE) + { + return FAILED; + } + message->add_payload(message, (payload_t *)cp); + return NEED_MORE; + } + + if (this->cp) + { /* send previously generated payload */ + message->add_payload(message, (payload_t *)this->cp); + this->cp = NULL; + return NEED_MORE; + } + return FAILED; +} + +METHOD(task_t, build_r_ack, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = cp_payload_create_type(CONFIGURATION_V1, CFG_ACK); + cp->set_identifier(cp, this->identifier); + cp->add_attribute(cp, + configuration_attribute_create_chunk( + CONFIGURATION_ATTRIBUTE_V1, XAUTH_STATUS, chunk_empty)); + + message->add_payload(message, (payload_t *)cp); + + if (this->status == XAUTH_OK && allowed(this) && establish(this)) + { + return SUCCESS; + } + return FAILED; +} + +METHOD(task_t, process_r, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + if (!this->xauth) + { + this->xauth = load_method(this); + if (!this->xauth) + { /* send empty reply */ + return NEED_MORE; + } + } + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp) + { + DBG1(DBG_IKE, "configuration payload missing in XAuth request"); + return FAILED; + } + if (cp->get_type(cp) == CFG_REQUEST) + { + switch (this->xauth->process(this->xauth, cp, &this->cp)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + case FAILED: + default: + break; + } + this->cp = NULL; + return NEED_MORE; + } + if (cp->get_type(cp) == CFG_SET) + { + configuration_attribute_t *attribute; + enumerator_t *enumerator; + + enumerator = cp->create_attribute_enumerator(cp); + while (enumerator->enumerate(enumerator, &attribute)) + { + if (attribute->get_type(attribute) == XAUTH_STATUS) + { + this->status = attribute->get_value(attribute); + } + } + enumerator->destroy(enumerator); + if (this->status == XAUTH_OK && + add_auth_cfg(this, this->xauth->get_identity(this->xauth), TRUE)) + { + DBG1(DBG_IKE, "XAuth authentication of '%Y' (myself) successful", + this->xauth->get_identity(this->xauth)); + } + else + { + DBG1(DBG_IKE, "XAuth authentication of '%Y' (myself) failed", + this->xauth->get_identity(this->xauth)); + } + } + this->identifier = cp->get_identifier(cp); + this->public.task.build = _build_r_ack; + return NEED_MORE; +} + +METHOD(task_t, build_r, status_t, + private_xauth_t *this, message_t *message) +{ + if (!this->cp) + { /* send empty reply if building data failed */ + this->cp = cp_payload_create_type(CONFIGURATION_V1, CFG_REPLY); + } + message->add_payload(message, (payload_t *)this->cp); + this->cp = NULL; + return NEED_MORE; +} + +METHOD(task_t, process_i_status, status_t, + private_xauth_t *this, message_t *message) +{ + cp_payload_t *cp; + + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp || cp->get_type(cp) != CFG_ACK) + { + DBG1(DBG_IKE, "received invalid XAUTH status response"); + return FAILED; + } + if (this->status != XAUTH_OK) + { + DBG1(DBG_IKE, "destroying IKE_SA after failed XAuth authentication"); + return FAILED; + } + if (!establish(this)) + { + return FAILED; + } + this->ike_sa->set_condition(this->ike_sa, COND_XAUTH_AUTHENTICATED, TRUE); + lib->processor->queue_job(lib->processor, (job_t*) + adopt_children_job_create(this->ike_sa->get_id(this->ike_sa))); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_xauth_t *this, message_t *message) +{ + identification_t *id; + cp_payload_t *cp; + + cp = (cp_payload_t*)message->get_payload(message, CONFIGURATION_V1); + if (!cp) + { + DBG1(DBG_IKE, "configuration payload missing in XAuth response"); + return FAILED; + } + switch (this->xauth->process(this->xauth, cp, &this->cp)) + { + case NEED_MORE: + return NEED_MORE; + case SUCCESS: + id = this->xauth->get_identity(this->xauth); + if (this->user && !id->matches(id, this->user)) + { + DBG1(DBG_IKE, "XAuth username '%Y' does not match to " + "configured username '%Y'", id, this->user); + break; + } + DBG1(DBG_IKE, "XAuth authentication of '%Y' successful", id); + if (add_auth_cfg(this, id, FALSE) && allowed(this)) + { + this->status = XAUTH_OK; + } + break; + case FAILED: + DBG1(DBG_IKE, "XAuth authentication of '%Y' failed", + this->xauth->get_identity(this->xauth)); + break; + default: + return FAILED; + } + this->public.task.build = _build_i_status; + this->public.task.process = _process_i_status; + return NEED_MORE; +} + +METHOD(task_t, get_type, task_type_t, + private_xauth_t *this) +{ + return TASK_XAUTH; +} + +METHOD(task_t, migrate, void, + private_xauth_t *this, ike_sa_t *ike_sa) +{ + DESTROY_IF(this->xauth); + DESTROY_IF(this->cp); + + this->ike_sa = ike_sa; + this->xauth = NULL; + this->cp = NULL; + this->user = NULL; + this->status = XAUTH_FAILED; + + if (this->initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } +} + +METHOD(task_t, destroy, void, + private_xauth_t *this) +{ + DESTROY_IF(this->xauth); + DESTROY_IF(this->cp); + free(this); +} + +/* + * Described in header. + */ +xauth_t *xauth_create(ike_sa_t *ike_sa, bool initiator) +{ + private_xauth_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .destroy = _destroy, + }, + }, + .initiator = initiator, + .ike_sa = ike_sa, + .status = XAUTH_FAILED, + ); + + if (initiator) + { + this->public.task.build = _build_i; + this->public.task.process = _process_i; + } + else + { + this->public.task.build = _build_r; + this->public.task.process = _process_r; + } + return &this->public; +} diff --git a/src/libcharon/sa/ikev1/tasks/xauth.h b/src/libcharon/sa/ikev1/tasks/xauth.h new file mode 100644 index 000000000..303eb31ce --- /dev/null +++ b/src/libcharon/sa/ikev1/tasks/xauth.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup xauth_t xauth + * @{ @ingroup tasks_v1 + */ + +#ifndef XAUTH_H_ +#define XAUTH_H_ + +typedef struct xauth_t xauth_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type TASK_XAUTH, additional authentication after main/aggressive mode. + */ +struct xauth_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new xauth task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE for initiator + * @return xauth task to handle by the task_manager + */ +xauth_t *xauth_create(ike_sa_t *ike_sa, bool initiator); + +#endif /** XAUTH_H_ @}*/ diff --git a/src/libcharon/sa/authenticators/eap_authenticator.c b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c index 5c8f0b6ce..aa0644033 100644 --- a/src/libcharon/sa/authenticators/eap_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2006-2009 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -16,7 +17,8 @@ #include "eap_authenticator.h" #include <daemon.h> -#include <sa/authenticators/eap/eap_method.h> +#include <sa/ikev2/keymat_v2.h> +#include <sa/eap/eap_method.h> #include <encoding/payloads/auth_payload.h> #include <encoding/payloads/eap_payload.h> @@ -185,9 +187,9 @@ static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, if (this->method) { action = "initiating"; - type = this->method->get_type(this->method, &vendor); if (this->method->initiate(this->method, &out) == NEED_MORE) { + type = this->method->get_type(this->method, &vendor); if (vendor) { DBG1(DBG_IKE, "initiating EAP vendor type %d-%d method (id 0x%02X)", @@ -200,6 +202,8 @@ static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, } return out; } + /* type might have changed for virtual methods */ + type = this->method->get_type(this->method, &vendor); } if (vendor) { @@ -232,9 +236,10 @@ static void replace_eap_identity(private_eap_authenticator_t *this) static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, eap_payload_t *in) { - eap_type_t type, received_type; - u_int32_t vendor, received_vendor; + eap_type_t type, received_type, conf_type; + u_int32_t vendor, received_vendor, conf_vendor; eap_payload_t *out; + auth_cfg_t *auth; if (in->get_code(in) != EAP_RESPONSE) { @@ -249,15 +254,25 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, { if (received_vendor == 0 && received_type == EAP_NAK) { - DBG1(DBG_IKE, "received %N, sending %N", - eap_type_names, EAP_NAK, eap_code_names, EAP_FAILURE); + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + conf_type = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_TYPE); + conf_vendor = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_VENDOR); + if ((type == EAP_IDENTITY && !vendor) || + (type == conf_type && vendor == conf_vendor)) + { + DBG1(DBG_IKE, "received %N, sending %N", + eap_type_names, EAP_NAK, eap_code_names, EAP_FAILURE); + return eap_payload_create_code(EAP_FAILURE, + in->get_identifier(in)); + } + /* virtual methods handle NAKs in process() */ } else { DBG1(DBG_IKE, "received invalid EAP response, sending %N", eap_code_names, EAP_FAILURE); + return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in)); } - return eap_payload_create_code(EAP_FAILURE, in->get_identifier(in)); } switch (this->method->process(this->method, in, &out)) @@ -301,6 +316,8 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, return eap_payload_create_code(EAP_SUCCESS, in->get_identifier(in)); case FAILED: default: + /* type might have changed for virtual methods */ + type = this->method->get_type(this->method, &vendor); if (vendor) { DBG1(DBG_IKE, "EAP vendor specific method %d-%d failed for " @@ -323,8 +340,8 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, static eap_payload_t* client_process_eap(private_eap_authenticator_t *this, eap_payload_t *in) { - eap_type_t type; - u_int32_t vendor; + eap_type_t type, conf_type; + u_int32_t vendor, conf_vendor; auth_cfg_t *auth; eap_payload_t *out; identification_t *id; @@ -356,27 +373,49 @@ static eap_payload_t* client_process_eap(private_eap_authenticator_t *this, this->method->destroy(this->method); this->method = NULL; } + /* FIXME: sending a Nak is not correct here as EAP_IDENTITY (1) is no + * EAP method (types 3-253, 255) */ DBG1(DBG_IKE, "%N not supported, sending EAP_NAK", eap_type_names, type); - return eap_payload_create_nak(in->get_identifier(in)); + return eap_payload_create_nak(in->get_identifier(in), 0, 0, FALSE); } if (this->method == NULL) { if (vendor) { DBG1(DBG_IKE, "server requested vendor specific EAP method %d-%d ", - "(id 0x%02X)", type, vendor, in->get_identifier(in)); + "(id 0x%02X)", type, vendor, in->get_identifier(in)); } else { DBG1(DBG_IKE, "server requested %N authentication (id 0x%02X)", eap_type_names, type, in->get_identifier(in)); } + auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); + conf_type = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_TYPE); + conf_vendor = (uintptr_t)auth->get(auth, AUTH_RULE_EAP_VENDOR); + if (conf_type != EAP_NAK && + (conf_type != type || conf_vendor != vendor)) + { + if (conf_vendor) + { + DBG1(DBG_IKE, "requesting EAP method %d-%d, sending EAP_NAK", + conf_type, conf_vendor); + } + else + { + DBG1(DBG_IKE, "requesting %N authentication, sending EAP_NAK", + eap_type_names, conf_type); + } + return eap_payload_create_nak(in->get_identifier(in), conf_type, + conf_vendor, in->is_expanded(in)); + } this->method = load_method(this, type, vendor, EAP_PEER); if (!this->method) { DBG1(DBG_IKE, "EAP method not supported, sending EAP_NAK"); - return eap_payload_create_nak(in->get_identifier(in)); + return eap_payload_create_nak(in->get_identifier(in), 0, 0, + in->is_expanded(in)); } } @@ -408,7 +447,7 @@ static bool verify_auth(private_eap_authenticator_t *this, message_t *message, chunk_t auth_data, recv_auth_data; identification_t *other_id; auth_cfg_t *auth; - keymat_t *keymat; + keymat_v2_t *keymat; auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); @@ -418,9 +457,12 @@ static bool verify_auth(private_eap_authenticator_t *this, message_t *message, return FALSE; } other_id = this->ike_sa->get_other_id(this->ike_sa); - keymat = this->ike_sa->get_keymat(this->ike_sa); - auth_data = keymat->get_psk_sig(keymat, TRUE, init, nonce, - this->msk, other_id, this->reserved); + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_psk_sig(keymat, TRUE, init, nonce, + this->msk, other_id, this->reserved, &auth_data)) + { + return FALSE; + } recv_auth_data = auth_payload->get_data(auth_payload); if (!auth_data.len || !chunk_equals(auth_data, recv_auth_data)) { @@ -442,27 +484,31 @@ static bool verify_auth(private_eap_authenticator_t *this, message_t *message, /** * Build AUTH payload */ -static void build_auth(private_eap_authenticator_t *this, message_t *message, +static bool build_auth(private_eap_authenticator_t *this, message_t *message, chunk_t nonce, chunk_t init) { auth_payload_t *auth_payload; identification_t *my_id; chunk_t auth_data; - keymat_t *keymat; + keymat_v2_t *keymat; my_id = this->ike_sa->get_my_id(this->ike_sa); - keymat = this->ike_sa->get_keymat(this->ike_sa); + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N", my_id, auth_class_names, AUTH_CLASS_EAP); - auth_data = keymat->get_psk_sig(keymat, FALSE, init, nonce, - this->msk, my_id, this->reserved); + if (!keymat->get_psk_sig(keymat, FALSE, init, nonce, + this->msk, my_id, this->reserved, &auth_data)) + { + return FALSE; + } auth_payload = auth_payload_create(); auth_payload->set_auth_method(auth_payload, AUTH_PSK); auth_payload->set_data(auth_payload, auth_data); message->add_payload(message, (payload_t*)auth_payload); chunk_free(&auth_data); + return TRUE; } METHOD(authenticator_t, process_server, status_t, @@ -512,9 +558,9 @@ METHOD(authenticator_t, build_server, status_t, } return NEED_MORE; } - if (this->eap_complete && this->auth_complete) + if (this->eap_complete && this->auth_complete && + build_auth(this, message, this->received_nonce, this->sent_init)) { - build_auth(this, message, this->received_nonce, this->sent_init); return SUCCESS; } return FAILED; @@ -610,9 +656,9 @@ METHOD(authenticator_t, build_client, status_t, this->eap_payload = NULL; return NEED_MORE; } - if (this->eap_complete) + if (this->eap_complete && + build_auth(this, message, this->received_nonce, this->sent_init)) { - build_auth(this, message, this->received_nonce, this->sent_init); return NEED_MORE; } return NEED_MORE; @@ -695,4 +741,3 @@ eap_authenticator_t *eap_authenticator_create_verifier(ike_sa_t *ike_sa, return &this->public; } - diff --git a/src/libcharon/sa/authenticators/eap_authenticator.h b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.h index 726411a18..d81ebd562 100644 --- a/src/libcharon/sa/authenticators/eap_authenticator.h +++ b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.h @@ -15,7 +15,7 @@ /** * @defgroup eap_authenticator eap_authenticator - * @{ @ingroup authenticators + * @{ @ingroup authenticators_v2 */ #ifndef EAP_AUTHENTICATOR_H_ @@ -23,7 +23,7 @@ typedef struct eap_authenticator_t eap_authenticator_t; -#include <sa/authenticators/authenticator.h> +#include <sa/authenticator.h> /** * Implementation of authenticator_t using EAP authentication. diff --git a/src/libcharon/sa/authenticators/psk_authenticator.c b/src/libcharon/sa/ikev2/authenticators/psk_authenticator.c index 21fc0f9b8..997efe359 100644 --- a/src/libcharon/sa/authenticators/psk_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/psk_authenticator.c @@ -18,6 +18,7 @@ #include <daemon.h> #include <encoding/payloads/auth_payload.h> +#include <sa/ikev2/keymat_v2.h> typedef struct private_psk_authenticator_t private_psk_authenticator_t; @@ -59,9 +60,9 @@ METHOD(authenticator_t, build, status_t, auth_payload_t *auth_payload; shared_key_t *key; chunk_t auth_data; - keymat_t *keymat; + keymat_v2_t *keymat; - keymat = this->ike_sa->get_keymat(this->ike_sa); + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(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_IKE, "authentication of '%Y' (myself) with %N", @@ -72,8 +73,12 @@ METHOD(authenticator_t, build, status_t, DBG1(DBG_IKE, "no shared key found for '%Y' - '%Y'", my_id, other_id); return NOT_FOUND; } - auth_data = keymat->get_psk_sig(keymat, FALSE, this->ike_sa_init, - this->nonce, key->get_key(key), my_id, this->reserved); + if (!keymat->get_psk_sig(keymat, FALSE, this->ike_sa_init, this->nonce, + key->get_key(key), my_id, this->reserved, &auth_data)) + { + key->destroy(key); + return FAILED; + } key->destroy(key); DBG2(DBG_IKE, "successfully created shared key MAC"); auth_payload = auth_payload_create(); @@ -96,14 +101,14 @@ METHOD(authenticator_t, process, status_t, enumerator_t *enumerator; bool authenticated = FALSE; int keys_found = 0; - keymat_t *keymat; + keymat_v2_t *keymat; auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); if (!auth_payload) { return FAILED; } - keymat = this->ike_sa->get_keymat(this->ike_sa); + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); recv_auth_data = auth_payload->get_data(auth_payload); my_id = this->ike_sa->get_my_id(this->ike_sa); other_id = this->ike_sa->get_other_id(this->ike_sa); @@ -113,8 +118,11 @@ METHOD(authenticator_t, process, status_t, { keys_found++; - auth_data = keymat->get_psk_sig(keymat, TRUE, this->ike_sa_init, - this->nonce, key->get_key(key), other_id, this->reserved); + if (!keymat->get_psk_sig(keymat, TRUE, this->ike_sa_init, this->nonce, + key->get_key(key), other_id, this->reserved, &auth_data)) + { + continue; + } if (auth_data.len && chunk_equals(auth_data, recv_auth_data)) { DBG1(DBG_IKE, "authentication of '%Y' with %N successful", @@ -201,4 +209,3 @@ psk_authenticator_t *psk_authenticator_create_verifier(ike_sa_t *ike_sa, return &this->public; } - diff --git a/src/libcharon/sa/authenticators/psk_authenticator.h b/src/libcharon/sa/ikev2/authenticators/psk_authenticator.h index 8cf1a0f98..91c534145 100644 --- a/src/libcharon/sa/authenticators/psk_authenticator.h +++ b/src/libcharon/sa/ikev2/authenticators/psk_authenticator.h @@ -15,7 +15,7 @@ /** * @defgroup psk_authenticator psk_authenticator - * @{ @ingroup authenticators + * @{ @ingroup authenticators_v2 */ #ifndef PSK_AUTHENTICATOR_H_ @@ -23,7 +23,7 @@ typedef struct psk_authenticator_t psk_authenticator_t; -#include <sa/authenticators/authenticator.h> +#include <sa/authenticator.h> /** * Implementation of authenticator_t using pre-shared keys. diff --git a/src/libcharon/sa/authenticators/pubkey_authenticator.c b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c index 247891670..5ceff40ba 100644 --- a/src/libcharon/sa/authenticators/pubkey_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c @@ -19,6 +19,7 @@ #include <daemon.h> #include <encoding/payloads/auth_payload.h> +#include <sa/ikev2/keymat_v2.h> typedef struct private_pubkey_authenticator_t private_pubkey_authenticator_t; @@ -56,7 +57,7 @@ struct private_pubkey_authenticator_t { METHOD(authenticator_t, build, status_t, private_pubkey_authenticator_t *this, message_t *message) { - chunk_t octets, auth_data; + chunk_t octets = chunk_empty, auth_data; status_t status = FAILED; private_key_t *private; identification_t *id; @@ -64,7 +65,7 @@ METHOD(authenticator_t, build, status_t, auth_payload_t *auth_payload; auth_method_t auth_method; signature_scheme_t scheme; - keymat_t *keymat; + keymat_v2_t *keymat; id = this->ike_sa->get_my_id(this->ike_sa); auth = this->ike_sa->get_auth_cfg(this->ike_sa, TRUE); @@ -110,10 +111,10 @@ METHOD(authenticator_t, build, status_t, key_type_names, private->get_type(private)); return status; } - keymat = this->ike_sa->get_keymat(this->ike_sa); - octets = keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, - this->nonce, id, this->reserved); - if (private->sign(private, scheme, octets, &auth_data)) + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); + if (keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, + this->nonce, id, this->reserved, &octets) && + private->sign(private, scheme, octets, &auth_data)) { auth_payload = auth_payload_create(); auth_payload->set_auth_method(auth_payload, auth_method); @@ -144,7 +145,7 @@ METHOD(authenticator_t, process, status_t, key_type_t key_type = KEY_ECDSA; signature_scheme_t scheme; status_t status = NOT_FOUND; - keymat_t *keymat; + keymat_v2_t *keymat; auth_payload = (auth_payload_t*)message->get_payload(message, AUTHENTICATION); if (!auth_payload) @@ -174,9 +175,12 @@ METHOD(authenticator_t, process, status_t, } auth_data = auth_payload->get_data(auth_payload); id = this->ike_sa->get_other_id(this->ike_sa); - keymat = this->ike_sa->get_keymat(this->ike_sa); - octets = keymat->get_auth_octets(keymat, TRUE, this->ike_sa_init, - this->nonce, id, this->reserved); + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); + if (!keymat->get_auth_octets(keymat, TRUE, this->ike_sa_init, + this->nonce, id, this->reserved, &octets)) + { + return FAILED; + } auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); enumerator = lib->credmgr->create_public_enumerator(lib->credmgr, key_type, id, auth); diff --git a/src/libcharon/sa/authenticators/pubkey_authenticator.h b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.h index 4c3937ecc..82bfea23b 100644 --- a/src/libcharon/sa/authenticators/pubkey_authenticator.h +++ b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.h @@ -16,7 +16,7 @@ /** * @defgroup pubkey_authenticator pubkey_authenticator - * @{ @ingroup authenticators + * @{ @ingroup authenticators_v2 */ #ifndef PUBKEY_AUTHENTICATOR_H_ @@ -24,7 +24,7 @@ typedef struct pubkey_authenticator_t pubkey_authenticator_t; -#include <sa/authenticators/authenticator.h> +#include <sa/authenticator.h> /** * Implementation of authenticator_t using public key authenitcation. diff --git a/src/libcharon/sa/connect_manager.c b/src/libcharon/sa/ikev2/connect_manager.c index 7b6ca430f..5fdcea1ab 100644 --- a/src/libcharon/sa/connect_manager.c +++ b/src/libcharon/sa/ikev2/connect_manager.c @@ -839,7 +839,10 @@ static chunk_t build_signature(private_connect_manager_t *this, /* signature = SHA1( MID | ME_CONNECTID | ME_ENDPOINT | ME_CONNECTKEY ) */ sig_chunk = chunk_cat("cccc", mid_chunk, check->connect_id, check->endpoint_raw, key_chunk); - this->hasher->allocate_hash(this->hasher, sig_chunk, &sig_hash); + if (!this->hasher->allocate_hash(this->hasher, sig_chunk, &sig_hash)) + { + sig_hash = chunk_empty; + } DBG3(DBG_IKE, "sig_chunk %#B", &sig_chunk); DBG3(DBG_IKE, "sig_hash %#B", &sig_hash); @@ -919,8 +922,10 @@ static void update_checklist_state(private_connect_manager_t *this, &checklist->connect_id); callback_data_t *data = callback_data_create(this, checklist->connect_id); - job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiator_finish, data, (callback_job_cleanup_t)callback_data_destroy, NULL); - lib->scheduler->schedule_job_ms(lib->scheduler, job, ME_WAIT_TO_FINISH); + lib->scheduler->schedule_job_ms(lib->scheduler, + (job_t*)callback_job_create((callback_job_cb_t)initiator_finish, + data, (callback_job_cleanup_t)callback_data_destroy, NULL), + ME_WAIT_TO_FINISH); checklist->is_finishing = TRUE; } @@ -1007,8 +1012,12 @@ retransmit_end: */ static void queue_retransmission(private_connect_manager_t *this, check_list_t *checklist, endpoint_pair_t *pair) { - callback_data_t *data = retransmit_data_create(this, checklist->connect_id, pair->id); - job_t *job = (job_t*)callback_job_create((callback_job_cb_t)retransmit, data, (callback_job_cleanup_t)callback_data_destroy, NULL); + callback_data_t *data; + job_t *job; + + data = retransmit_data_create(this, checklist->connect_id, pair->id); + job = (job_t*)callback_job_create((callback_job_cb_t)retransmit, data, + (callback_job_cleanup_t)callback_data_destroy, NULL); u_int32_t retransmission = pair->retransmitted + 1; u_int32_t rto = ME_INTERVAL; @@ -1028,14 +1037,15 @@ static void queue_retransmission(private_connect_manager_t *this, check_list_t * static void send_check(private_connect_manager_t *this, check_list_t *checklist, check_t *check, endpoint_pair_t *pair, bool request) { - message_t *message = message_create(); + message_t *message = message_create(IKEV2_MAJOR_VERSION, IKEV2_MINOR_VERSION); message->set_message_id(message, check->mid); message->set_exchange_type(message, INFORMATIONAL); message->set_request(message, request); message->set_destination(message, check->dst->clone(check->dst)); message->set_source(message, check->src->clone(check->src)); - ike_sa_id_t *ike_sa_id = ike_sa_id_create(0, 0, request); + ike_sa_id_t *ike_sa_id = ike_sa_id_create(IKEV2_MAJOR_VERSION, 0, 0, + request); message->set_ike_sa_id(message, ike_sa_id); ike_sa_id->destroy(ike_sa_id); @@ -1154,10 +1164,12 @@ static job_requeue_t sender(callback_data_t *data) /** * Schedules checks for a checklist (time in ms) */ -static void schedule_checks(private_connect_manager_t *this, check_list_t *checklist, u_int32_t time) +static void schedule_checks(private_connect_manager_t *this, + check_list_t *checklist, u_int32_t time) { callback_data_t *data = callback_data_create(this, checklist->connect_id); - checklist->sender = (job_t*)callback_job_create((callback_job_cb_t)sender, data, (callback_job_cleanup_t)callback_data_destroy, NULL); + checklist->sender = (job_t*)callback_job_create((callback_job_cb_t)sender, + data, (callback_job_cleanup_t)callback_data_destroy, NULL); lib->scheduler->schedule_job_ms(lib->scheduler, checklist->sender, time); } @@ -1209,12 +1221,15 @@ static void finish_checks(private_connect_manager_t *this, check_list_t *checkli if (get_initiated_by_ids(this, checklist->initiator.id, checklist->responder.id, &initiated) == SUCCESS) { + callback_job_t *job; + remove_checklist(this, checklist); remove_initiated(this, initiated); initiate_data_t *data = initiate_data_create(checklist, initiated); - job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiate_mediated, data, (callback_job_cleanup_t)initiate_data_destroy, NULL); - lib->processor->queue_job(lib->processor, job); + job = callback_job_create((callback_job_cb_t)initiate_mediated, + data, (callback_job_cleanup_t)initiate_data_destroy, NULL); + lib->processor->queue_job(lib->processor, (job_t*)job); return; } else diff --git a/src/libcharon/sa/connect_manager.h b/src/libcharon/sa/ikev2/connect_manager.h index 8fa8ff697..e667e1f70 100644 --- a/src/libcharon/sa/connect_manager.h +++ b/src/libcharon/sa/ikev2/connect_manager.h @@ -15,7 +15,7 @@ /** * @defgroup connect_manager connect_manager - * @{ @ingroup sa + * @{ @ingroup ikev2 */ #ifndef CONNECT_MANAGER_H_ diff --git a/src/libcharon/sa/ikev2/keymat_v2.c b/src/libcharon/sa/ikev2/keymat_v2.c new file mode 100644 index 000000000..4d0683f0a --- /dev/null +++ b/src/libcharon/sa/ikev2/keymat_v2.c @@ -0,0 +1,687 @@ +/* + * Copyright (C) 2008 Martin Willi + * 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 "keymat_v2.h" + +#include <daemon.h> +#include <crypto/prf_plus.h> + +typedef struct private_keymat_v2_t private_keymat_v2_t; + +/** + * Private data of an keymat_t object. + */ +struct private_keymat_v2_t { + + /** + * Public keymat_v2_t interface. + */ + keymat_v2_t public; + + /** + * IKE_SA Role, initiator or responder + */ + bool initiator; + + /** + * inbound AEAD + */ + aead_t *aead_in; + + /** + * outbound AEAD + */ + aead_t *aead_out; + + /** + * General purpose PRF + */ + prf_t *prf; + + /** + * Negotiated PRF algorithm + */ + pseudo_random_function_t prf_alg; + + /** + * Key to derive key material from for CHILD_SAs, rekeying + */ + chunk_t skd; + + /** + * Key to build outging authentication data (SKp) + */ + chunk_t skp_build; + + /** + * Key to verify incoming authentication data (SKp) + */ + chunk_t skp_verify; +}; + +METHOD(keymat_t, get_version, ike_version_t, + private_keymat_v2_t *this) +{ + return IKEV2; +} + +METHOD(keymat_t, create_dh, diffie_hellman_t*, + private_keymat_v2_t *this, diffie_hellman_group_t group) +{ + return lib->crypto->create_dh(lib->crypto, group); +} + +METHOD(keymat_t, create_nonce_gen, nonce_gen_t*, + private_keymat_v2_t *this) +{ + return lib->crypto->create_nonce_gen(lib->crypto); +} + +/** + * Derive IKE keys for a combined AEAD algorithm + */ +static bool derive_ike_aead(private_keymat_v2_t *this, u_int16_t alg, + u_int16_t key_size, prf_plus_t *prf_plus) +{ + aead_t *aead_i, *aead_r; + chunk_t key = chunk_empty; + + /* SK_ei/SK_er used for encryption */ + aead_i = lib->crypto->create_aead(lib->crypto, alg, key_size / 8); + aead_r = lib->crypto->create_aead(lib->crypto, alg, key_size / 8); + if (aead_i == NULL || aead_r == NULL) + { + DBG1(DBG_IKE, "%N %N (key size %d) not supported!", + transform_type_names, ENCRYPTION_ALGORITHM, + encryption_algorithm_names, alg, key_size); + goto failure; + } + key_size = aead_i->get_key_size(aead_i); + if (key_size != aead_r->get_key_size(aead_r)) + { + goto failure; + } + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_ei secret %B", &key); + if (!aead_i->set_key(aead_i, key)) + { + goto failure; + } + chunk_clear(&key); + + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_er secret %B", &key); + if (!aead_r->set_key(aead_r, key)) + { + goto failure; + } + + if (this->initiator) + { + this->aead_in = aead_r; + this->aead_out = aead_i; + } + else + { + this->aead_in = aead_i; + this->aead_out = aead_r; + } + aead_i = aead_r = NULL; + +failure: + DESTROY_IF(aead_i); + DESTROY_IF(aead_r); + chunk_clear(&key); + return this->aead_in && this->aead_out; +} + +/** + * Derive IKE keys for traditional encryption and MAC algorithms + */ +static bool derive_ike_traditional(private_keymat_v2_t *this, u_int16_t enc_alg, + u_int16_t enc_size, u_int16_t int_alg, prf_plus_t *prf_plus) +{ + crypter_t *crypter_i = NULL, *crypter_r = NULL; + signer_t *signer_i, *signer_r; + size_t key_size; + chunk_t key = chunk_empty; + + signer_i = lib->crypto->create_signer(lib->crypto, int_alg); + signer_r = lib->crypto->create_signer(lib->crypto, int_alg); + crypter_i = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_size / 8); + crypter_r = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_size / 8); + if (signer_i == NULL || signer_r == NULL) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, INTEGRITY_ALGORITHM, + integrity_algorithm_names, int_alg); + goto failure; + } + if (crypter_i == NULL || crypter_r == NULL) + { + DBG1(DBG_IKE, "%N %N (key size %d) not supported!", + transform_type_names, ENCRYPTION_ALGORITHM, + encryption_algorithm_names, enc_alg, enc_size); + goto failure; + } + + /* SK_ai/SK_ar used for integrity protection */ + key_size = signer_i->get_key_size(signer_i); + + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_ai secret %B", &key); + if (!signer_i->set_key(signer_i, key)) + { + goto failure; + } + chunk_clear(&key); + + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_ar secret %B", &key); + if (!signer_r->set_key(signer_r, key)) + { + goto failure; + } + chunk_clear(&key); + + /* SK_ei/SK_er used for encryption */ + key_size = crypter_i->get_key_size(crypter_i); + + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_ei secret %B", &key); + if (!crypter_i->set_key(crypter_i, key)) + { + goto failure; + } + chunk_clear(&key); + + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_er secret %B", &key); + if (!crypter_r->set_key(crypter_r, key)) + { + goto failure; + } + + if (this->initiator) + { + this->aead_in = aead_create(crypter_r, signer_r); + this->aead_out = aead_create(crypter_i, signer_i); + } + else + { + this->aead_in = aead_create(crypter_i, signer_i); + this->aead_out = aead_create(crypter_r, signer_r); + } + signer_i = signer_r = NULL; + crypter_i = crypter_r = NULL; + +failure: + chunk_clear(&key); + DESTROY_IF(signer_i); + DESTROY_IF(signer_r); + DESTROY_IF(crypter_i); + DESTROY_IF(crypter_r); + return this->aead_in && this->aead_out; +} + +METHOD(keymat_v2_t, derive_ike_keys, bool, + private_keymat_v2_t *this, proposal_t *proposal, diffie_hellman_t *dh, + chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, + pseudo_random_function_t rekey_function, chunk_t rekey_skd) +{ + chunk_t skeyseed, key, secret, full_nonce, fixed_nonce, prf_plus_seed; + chunk_t spi_i, spi_r; + prf_plus_t *prf_plus = NULL; + u_int16_t alg, key_size, int_alg; + prf_t *rekey_prf = NULL; + + spi_i = chunk_alloca(sizeof(u_int64_t)); + spi_r = chunk_alloca(sizeof(u_int64_t)); + + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + + /* Create SAs general purpose PRF first, we may use it here */ + if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, PSEUDO_RANDOM_FUNCTION); + return FALSE; + } + this->prf_alg = alg; + this->prf = lib->crypto->create_prf(lib->crypto, alg); + if (this->prf == NULL) + { + DBG1(DBG_IKE, "%N %N not supported!", + transform_type_names, PSEUDO_RANDOM_FUNCTION, + pseudo_random_function_names, alg); + return FALSE; + } + DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &secret); + /* full nonce is used as seed for PRF+ ... */ + full_nonce = chunk_cat("cc", nonce_i, nonce_r); + /* but the PRF may need a fixed key which only uses the first bytes of + * the nonces. */ + switch (alg) + { + case PRF_AES128_XCBC: + /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does + * not and therefore fixed key semantics apply to XCBC for key + * derivation. */ + case PRF_CAMELLIA128_XCBC: + /* draft-kanno-ipsecme-camellia-xcbc refers to rfc 4434, we + * assume fixed key length. */ + key_size = this->prf->get_key_size(this->prf)/2; + nonce_i.len = min(nonce_i.len, key_size); + nonce_r.len = min(nonce_r.len, key_size); + break; + default: + /* all other algorithms use variable key length, full nonce */ + break; + } + fixed_nonce = chunk_cat("cc", nonce_i, nonce_r); + *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); + *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); + prf_plus_seed = chunk_cat("ccc", full_nonce, spi_i, spi_r); + + /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) + * + * if we are rekeying, SKEYSEED is built on another way + */ + if (rekey_function == PRF_UNDEFINED) /* not rekeying */ + { + /* SKEYSEED = prf(Ni | Nr, g^ir) */ + if (this->prf->set_key(this->prf, fixed_nonce) && + this->prf->allocate_bytes(this->prf, secret, &skeyseed) && + this->prf->set_key(this->prf, skeyseed)) + { + prf_plus = prf_plus_create(this->prf, TRUE, prf_plus_seed); + } + } + else + { + /* SKEYSEED = prf(SK_d (old), [g^ir (new)] | Ni | Nr) + * use OLD SAs PRF functions for both prf_plus and prf */ + rekey_prf = lib->crypto->create_prf(lib->crypto, rekey_function); + if (!rekey_prf) + { + DBG1(DBG_IKE, "PRF of old SA %N not supported!", + pseudo_random_function_names, rekey_function); + chunk_free(&full_nonce); + chunk_free(&fixed_nonce); + chunk_clear(&prf_plus_seed); + return FALSE; + } + secret = chunk_cat("mc", secret, full_nonce); + if (rekey_prf->set_key(rekey_prf, rekey_skd) && + rekey_prf->allocate_bytes(rekey_prf, secret, &skeyseed) && + rekey_prf->set_key(rekey_prf, skeyseed)) + { + prf_plus = prf_plus_create(rekey_prf, TRUE, prf_plus_seed); + } + } + DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed); + + chunk_clear(&skeyseed); + chunk_clear(&secret); + chunk_free(&full_nonce); + chunk_free(&fixed_nonce); + chunk_clear(&prf_plus_seed); + + if (!prf_plus) + { + goto failure; + } + + /* KEYMAT = SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr */ + + /* SK_d is used for generating CHILD_SA key mat => store for later use */ + key_size = this->prf->get_key_size(this->prf); + if (!prf_plus->allocate_bytes(prf_plus, key_size, &this->skd)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_d secret %B", &this->skd); + + if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, &key_size)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, ENCRYPTION_ALGORITHM); + goto failure; + } + + if (encryption_algorithm_is_aead(alg)) + { + if (!derive_ike_aead(this, alg, key_size, prf_plus)) + { + goto failure; + } + } + else + { + if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &int_alg, NULL)) + { + DBG1(DBG_IKE, "no %N selected", + transform_type_names, INTEGRITY_ALGORITHM); + goto failure; + } + if (!derive_ike_traditional(this, alg, key_size, int_alg, prf_plus)) + { + goto failure; + } + } + + /* SK_pi/SK_pr used for authentication => stored for later */ + key_size = this->prf->get_key_size(this->prf); + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_pi secret %B", &key); + if (this->initiator) + { + this->skp_build = key; + } + else + { + this->skp_verify = key; + } + if (!prf_plus->allocate_bytes(prf_plus, key_size, &key)) + { + goto failure; + } + DBG4(DBG_IKE, "Sk_pr secret %B", &key); + if (this->initiator) + { + this->skp_verify = key; + } + else + { + this->skp_build = key; + } + + /* all done, prf_plus not needed anymore */ +failure: + DESTROY_IF(prf_plus); + DESTROY_IF(rekey_prf); + + return this->skp_build.len && this->skp_verify.len; +} + +METHOD(keymat_v2_t, derive_child_keys, bool, + private_keymat_v2_t *this, proposal_t *proposal, diffie_hellman_t *dh, + chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r) +{ + u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; + chunk_t seed, secret = chunk_empty; + prf_plus_t *prf_plus; + + if (dh) + { + if (dh->get_shared_secret(dh, &secret) != SUCCESS) + { + return FALSE; + } + DBG4(DBG_CHD, "DH secret %B", &secret); + } + seed = chunk_cata("mcc", secret, nonce_i, nonce_r); + DBG4(DBG_CHD, "seed %B", &seed); + + if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, + &enc_alg, &enc_size)) + { + DBG2(DBG_CHD, " using %N for encryption", + encryption_algorithm_names, enc_alg); + + if (!enc_size) + { + enc_size = keymat_get_keylen_encr(enc_alg); + } + if (enc_alg != ENCR_NULL && !enc_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + encryption_algorithm_names, enc_alg); + return FALSE; + } + /* to bytes */ + enc_size /= 8; + + /* CCM/GCM/CTR/GMAC needs additional bytes */ + switch (enc_alg) + { + case ENCR_AES_CCM_ICV8: + case ENCR_AES_CCM_ICV12: + case ENCR_AES_CCM_ICV16: + case ENCR_CAMELLIA_CCM_ICV8: + case ENCR_CAMELLIA_CCM_ICV12: + case ENCR_CAMELLIA_CCM_ICV16: + enc_size += 3; + break; + case ENCR_AES_GCM_ICV8: + case ENCR_AES_GCM_ICV12: + case ENCR_AES_GCM_ICV16: + case ENCR_AES_CTR: + case ENCR_NULL_AUTH_AES_GMAC: + enc_size += 4; + break; + default: + break; + } + } + + if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, + &int_alg, &int_size)) + { + DBG2(DBG_CHD, " using %N for integrity", + integrity_algorithm_names, int_alg); + + if (!int_size) + { + int_size = keymat_get_keylen_integ(int_alg); + } + if (!int_size) + { + DBG1(DBG_CHD, "no keylength defined for %N", + integrity_algorithm_names, int_alg); + return FALSE; + } + /* to bytes */ + int_size /= 8; + } + + if (!this->prf->set_key(this->prf, this->skd)) + { + return FALSE; + } + prf_plus = prf_plus_create(this->prf, TRUE, seed); + if (!prf_plus) + { + return FALSE; + } + + *encr_i = *integ_i = *encr_r = *integ_r = chunk_empty; + if (!prf_plus->allocate_bytes(prf_plus, enc_size, encr_i) || + !prf_plus->allocate_bytes(prf_plus, int_size, integ_i) || + !prf_plus->allocate_bytes(prf_plus, enc_size, encr_r) || + !prf_plus->allocate_bytes(prf_plus, int_size, integ_r)) + { + chunk_free(encr_i); + chunk_free(integ_i); + chunk_free(encr_r); + chunk_free(integ_r); + prf_plus->destroy(prf_plus); + return FALSE; + } + + prf_plus->destroy(prf_plus); + + if (enc_size) + { + DBG4(DBG_CHD, "encryption initiator key %B", encr_i); + DBG4(DBG_CHD, "encryption responder key %B", encr_r); + } + if (int_size) + { + DBG4(DBG_CHD, "integrity initiator key %B", integ_i); + DBG4(DBG_CHD, "integrity responder key %B", integ_r); + } + return TRUE; +} + +METHOD(keymat_v2_t, get_skd, pseudo_random_function_t, + private_keymat_v2_t *this, chunk_t *skd) +{ + *skd = this->skd; + return this->prf_alg; +} + +METHOD(keymat_t, get_aead, aead_t*, + private_keymat_v2_t *this, bool in) +{ + return in ? this->aead_in : this->aead_out; +} + +METHOD(keymat_v2_t, get_auth_octets, bool, + private_keymat_v2_t *this, bool verify, chunk_t ike_sa_init, + chunk_t nonce, identification_t *id, char reserved[3], chunk_t *octets) +{ + chunk_t chunk, idx; + chunk_t skp; + + skp = verify ? this->skp_verify : this->skp_build; + + chunk = chunk_alloca(4); + chunk.ptr[0] = id->get_type(id); + memcpy(chunk.ptr + 1, reserved, 3); + idx = chunk_cata("cc", chunk, id->get_encoding(id)); + + DBG3(DBG_IKE, "IDx' %B", &idx); + DBG3(DBG_IKE, "SK_p %B", &skp); + if (!this->prf->set_key(this->prf, skp) || + !this->prf->allocate_bytes(this->prf, idx, &chunk)) + { + return FALSE; + } + *octets = chunk_cat("ccm", ike_sa_init, nonce, chunk); + DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') %B", octets); + return TRUE; +} + +/** + * Key pad for the AUTH method SHARED_KEY_MESSAGE_INTEGRITY_CODE. + */ +#define IKEV2_KEY_PAD "Key Pad for IKEv2" +#define IKEV2_KEY_PAD_LENGTH 17 + +METHOD(keymat_v2_t, get_psk_sig, bool, + private_keymat_v2_t *this, bool verify, chunk_t ike_sa_init, chunk_t nonce, + chunk_t secret, identification_t *id, char reserved[3], chunk_t *sig) +{ + chunk_t key_pad, key, octets; + + if (!secret.len) + { /* EAP uses SK_p if no MSK has been established */ + secret = verify ? this->skp_verify : this->skp_build; + } + if (!get_auth_octets(this, verify, ike_sa_init, nonce, id, reserved, &octets)) + { + return FALSE; + } + /* AUTH = prf(prf(Shared Secret,"Key Pad for IKEv2"), <msg octets>) */ + key_pad = chunk_create(IKEV2_KEY_PAD, IKEV2_KEY_PAD_LENGTH); + if (!this->prf->set_key(this->prf, secret) || + !this->prf->allocate_bytes(this->prf, key_pad, &key)) + { + chunk_free(&octets); + return FALSE; + } + if (!this->prf->set_key(this->prf, key) || + !this->prf->allocate_bytes(this->prf, octets, sig)) + { + chunk_free(&key); + chunk_free(&octets); + return FALSE; + } + DBG4(DBG_IKE, "secret %B", &secret); + DBG4(DBG_IKE, "prf(secret, keypad) %B", &key); + DBG3(DBG_IKE, "AUTH = prf(prf(secret, keypad), octets) %B", sig); + chunk_free(&octets); + chunk_free(&key); + + return TRUE; +} + +METHOD(keymat_t, destroy, void, + private_keymat_v2_t *this) +{ + DESTROY_IF(this->aead_in); + DESTROY_IF(this->aead_out); + DESTROY_IF(this->prf); + chunk_clear(&this->skd); + chunk_clear(&this->skp_verify); + chunk_clear(&this->skp_build); + free(this); +} + +/** + * See header + */ +keymat_v2_t *keymat_v2_create(bool initiator) +{ + private_keymat_v2_t *this; + + INIT(this, + .public = { + .keymat = { + .get_version = _get_version, + .create_dh = _create_dh, + .create_nonce_gen = _create_nonce_gen, + .get_aead = _get_aead, + .destroy = _destroy, + }, + .derive_ike_keys = _derive_ike_keys, + .derive_child_keys = _derive_child_keys, + .get_skd = _get_skd, + .get_auth_octets = _get_auth_octets, + .get_psk_sig = _get_psk_sig, + }, + .initiator = initiator, + .prf_alg = PRF_UNDEFINED, + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev2/keymat_v2.h b/src/libcharon/sa/ikev2/keymat_v2.h new file mode 100644 index 000000000..04432f05b --- /dev/null +++ b/src/libcharon/sa/ikev2/keymat_v2.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2011 Tobias Brunner + * 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. + */ + +/** + * @defgroup keymat_v2 keymat_v2 + * @{ @ingroup ikev2 + */ + +#ifndef KEYMAT_V2_H_ +#define KEYMAT_V2_H_ + +#include <sa/keymat.h> + +typedef struct keymat_v2_t keymat_v2_t; + +/** + * Derivation and management of sensitive keying material, IKEv2 variant. + */ +struct keymat_v2_t { + + /** + * Implements keymat_t. + */ + keymat_t keymat; + + /** + * Derive keys for the IKE_SA. + * + * These keys are not handed out, but are used by the associated signers, + * crypters and authentication functions. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh() + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param id IKE_SA identifier + * @param rekey_prf PRF of old SA if rekeying, PRF_UNDEFINED otherwise + * @param rekey_sdk SKd of old SA if rekeying + * @return TRUE on success + */ + bool (*derive_ike_keys)(keymat_v2_t *this, proposal_t *proposal, + diffie_hellman_t *dh, chunk_t nonce_i, + chunk_t nonce_r, ike_sa_id_t *id, + pseudo_random_function_t rekey_function, + chunk_t rekey_skd); + + /** + * Derive keys for a CHILD_SA. + * + * The keys for the CHILD_SA are allocated in the integ and encr chunks. + * An implementation might hand out encrypted keys only, which are + * decrypted in the kernel before use. + * If no PFS is used for the CHILD_SA, dh can be NULL. + * + * @param proposal selected algorithms + * @param dh diffie hellman key allocated by create_dh(), or NULL + * @param nonce_i initiators nonce value + * @param nonce_r responders nonce value + * @param encr_i chunk to write initiators encryption key to + * @param integ_i chunk to write initiators integrity key to + * @param encr_r chunk to write responders encryption key to + * @param integ_r chunk to write responders integrity key to + * @return TRUE on success + */ + bool (*derive_child_keys)(keymat_v2_t *this, + proposal_t *proposal, diffie_hellman_t *dh, + chunk_t nonce_i, chunk_t nonce_r, + chunk_t *encr_i, chunk_t *integ_i, + chunk_t *encr_r, chunk_t *integ_r); + /** + * Get SKd to pass to derive_ikey_keys() during rekeying. + * + * @param skd chunk to write SKd to (internal data) + * @return PRF function to derive keymat + */ + pseudo_random_function_t (*get_skd)(keymat_v2_t *this, chunk_t *skd); + + /** + * Generate octets to use for authentication procedure (RFC4306 2.15). + * + * This method creates the plain octets and is usually signed by a private + * key. PSK and EAP authentication include a secret into the data, use + * the get_psk_sig() method instead. + * + * @param verify TRUE to create for verfification, FALSE to sign + * @param ike_sa_init encoded ike_sa_init message + * @param nonce nonce value + * @param id identity + * @param reserved reserved bytes of id_payload + * @param octests chunk receiving allocated auth octets + * @return TRUE if octets created successfully + */ + bool (*get_auth_octets)(keymat_v2_t *this, bool verify, chunk_t ike_sa_init, + chunk_t nonce, identification_t *id, + char reserved[3], chunk_t *octets); + /** + * Build the shared secret signature used for PSK and EAP authentication. + * + * This method wraps the get_auth_octets() method and additionally + * includes the secret into the signature. If no secret is given, SK_p is + * used as secret (used for EAP methods without MSK). + * + * @param verify TRUE to create for verfification, FALSE to sign + * @param ike_sa_init encoded ike_sa_init message + * @param nonce nonce value + * @param secret optional secret to include into signature + * @param id identity + * @param reserved reserved bytes of id_payload + * @param sign chunk receiving allocated signature octets + * @return TRUE if signature created successfully + */ + bool (*get_psk_sig)(keymat_v2_t *this, bool verify, chunk_t ike_sa_init, + chunk_t nonce, chunk_t secret, + identification_t *id, char reserved[3], chunk_t *sig); +}; + +/** + * Create a keymat instance. + * + * @param initiator TRUE if we are the initiator + * @return keymat instance + */ +keymat_v2_t *keymat_v2_create(bool initiator); + +#endif /** KEYMAT_V2_H_ @}*/ diff --git a/src/libcharon/sa/mediation_manager.c b/src/libcharon/sa/ikev2/mediation_manager.c index 60eeb5d4b..60eeb5d4b 100644 --- a/src/libcharon/sa/mediation_manager.c +++ b/src/libcharon/sa/ikev2/mediation_manager.c diff --git a/src/libcharon/sa/mediation_manager.h b/src/libcharon/sa/ikev2/mediation_manager.h index 31a16f69c..5212bdb86 100644 --- a/src/libcharon/sa/mediation_manager.h +++ b/src/libcharon/sa/ikev2/mediation_manager.h @@ -15,7 +15,7 @@ /** * @defgroup mediation_manager mediation_manager - * @{ @ingroup sa + * @{ @ingroup ikev2 */ #ifndef MEDIATION_MANAGER_H_ diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c new file mode 100644 index 000000000..53051fab4 --- /dev/null +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -0,0 +1,1502 @@ +/* + * Copyright (C) 2007-2011 Tobias Brunner + * Copyright (C) 2007-2010 Martin Willi + * 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 "task_manager_v2.h" + +#include <math.h> + +#include <daemon.h> +#include <sa/ikev2/tasks/ike_init.h> +#include <sa/ikev2/tasks/ike_natd.h> +#include <sa/ikev2/tasks/ike_mobike.h> +#include <sa/ikev2/tasks/ike_auth.h> +#include <sa/ikev2/tasks/ike_auth_lifetime.h> +#include <sa/ikev2/tasks/ike_cert_pre.h> +#include <sa/ikev2/tasks/ike_cert_post.h> +#include <sa/ikev2/tasks/ike_rekey.h> +#include <sa/ikev2/tasks/ike_reauth.h> +#include <sa/ikev2/tasks/ike_delete.h> +#include <sa/ikev2/tasks/ike_config.h> +#include <sa/ikev2/tasks/ike_dpd.h> +#include <sa/ikev2/tasks/ike_vendor.h> +#include <sa/ikev2/tasks/child_create.h> +#include <sa/ikev2/tasks/child_rekey.h> +#include <sa/ikev2/tasks/child_delete.h> +#include <encoding/payloads/delete_payload.h> +#include <encoding/payloads/unknown_payload.h> +#include <processing/jobs/retransmit_job.h> +#include <processing/jobs/delete_ike_sa_job.h> + +#ifdef ME +#include <sa/ikev2/tasks/ike_me.h> +#endif + +typedef struct exchange_t exchange_t; + +/** + * An exchange in the air, used do detect and handle retransmission + */ +struct exchange_t { + + /** + * Message ID used for this transaction + */ + u_int32_t mid; + + /** + * generated packet for retransmission + */ + packet_t *packet; +}; + +typedef struct private_task_manager_t private_task_manager_t; + +/** + * private data of the task manager + */ +struct private_task_manager_t { + + /** + * public functions + */ + task_manager_v2_t public; + + /** + * associated IKE_SA we are serving + */ + ike_sa_t *ike_sa; + + /** + * Exchange we are currently handling as responder + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * packet for retransmission + */ + packet_t *packet; + + } responding; + + /** + * Exchange we are currently handling as initiator + */ + struct { + /** + * Message ID of the exchange + */ + u_int32_t mid; + + /** + * how many times we have retransmitted so far + */ + u_int retransmitted; + + /** + * packet for retransmission + */ + packet_t *packet; + + /** + * type of the initated exchange + */ + exchange_type_t type; + + } initiating; + + /** + * List of queued tasks not yet in action + */ + linked_list_t *queued_tasks; + + /** + * List of active tasks, initiated by ourselve + */ + linked_list_t *active_tasks; + + /** + * List of tasks initiated by peer + */ + linked_list_t *passive_tasks; + + /** + * the task manager has been reset + */ + bool reset; + + /** + * Number of times we retransmit messages before giving up + */ + u_int retransmit_tries; + + /** + * Retransmission timeout + */ + double retransmit_timeout; + + /** + * Base to calculate retransmission timeout + */ + double retransmit_base; +}; + +METHOD(task_manager_t, flush_queue, void, + private_task_manager_t *this, task_queue_t queue) +{ + linked_list_t *list; + task_t *task; + + switch (queue) + { + case TASK_QUEUE_ACTIVE: + list = this->active_tasks; + break; + case TASK_QUEUE_PASSIVE: + list = this->passive_tasks; + break; + case TASK_QUEUE_QUEUED: + list = this->queued_tasks; + break; + default: + return; + } + while (list->remove_last(list, (void**)&task) == SUCCESS) + { + task->destroy(task); + } +} + +/** + * flush all tasks in the task manager + */ +static void flush(private_task_manager_t *this) +{ + flush_queue(this, TASK_QUEUE_QUEUED); + flush_queue(this, TASK_QUEUE_PASSIVE); + flush_queue(this, TASK_QUEUE_ACTIVE); +} + +/** + * move a task of a specific type from the queue to the active list + */ +static bool activate_task(private_task_manager_t *this, task_type_t type) +{ + enumerator_t *enumerator; + task_t *task; + bool found = FALSE; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + if (task->get_type(task) == type) + { + DBG2(DBG_IKE, " activating %N task", task_type_names, type); + this->queued_tasks->remove_at(this->queued_tasks, enumerator); + this->active_tasks->insert_last(this->active_tasks, task); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +METHOD(task_manager_t, retransmit, status_t, + private_task_manager_t *this, u_int32_t message_id) +{ + if (this->initiating.packet && message_id == this->initiating.mid) + { + u_int32_t timeout; + job_t *job; + enumerator_t *enumerator; + packet_t *packet; + task_t *task; + ike_mobike_t *mobike = NULL; + + /* check if we are retransmitting a MOBIKE routability check */ + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + if (task->get_type(task) == TASK_IKE_MOBIKE) + { + mobike = (ike_mobike_t*)task; + if (!mobike->is_probing(mobike)) + { + mobike = NULL; + } + break; + } + } + enumerator->destroy(enumerator); + + if (mobike == NULL) + { + if (this->initiating.retransmitted <= this->retransmit_tries) + { + timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 * + pow(this->retransmit_base, this->initiating.retransmitted)); + } + else + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %d", + this->initiating.retransmitted, message_id); + } + packet = this->initiating.packet->clone(this->initiating.packet); + charon->sender->send(charon->sender, packet); + } + else + { /* for routeability checks, we use a more aggressive behavior */ + if (this->initiating.retransmitted <= ROUTEABILITY_CHECK_TRIES) + { + timeout = ROUTEABILITY_CHECK_INTERVAL; + } + else + { + DBG1(DBG_IKE, "giving up after %d path probings", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "path probing attempt %d", + this->initiating.retransmitted); + } + mobike->transmit(mobike, this->initiating.packet); + } + + this->initiating.retransmitted++; + job = (job_t*)retransmit_job_create(this->initiating.mid, + this->ike_sa->get_id(this->ike_sa)); + lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout); + } + return SUCCESS; +} + +METHOD(task_manager_t, initiate, status_t, + private_task_manager_t *this) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + status_t status; + exchange_type_t exchange = 0; + + if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED) + { + DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress", + exchange_type_names, this->initiating.type); + /* do not initiate if we already have a message in the air */ + return SUCCESS; + } + + if (this->active_tasks->get_count(this->active_tasks) == 0) + { + DBG2(DBG_IKE, "activating new tasks"); + switch (this->ike_sa->get_state(this->ike_sa)) + { + case IKE_CREATED: + activate_task(this, TASK_IKE_VENDOR); + if (activate_task(this, TASK_IKE_INIT)) + { + this->initiating.mid = 0; + exchange = IKE_SA_INIT; + activate_task(this, TASK_IKE_NATD); + activate_task(this, TASK_IKE_CERT_PRE); +#ifdef ME + /* this task has to be activated before the TASK_IKE_AUTH + * task, because that task pregenerates the packet after + * which no payloads can be added to the message anymore. + */ + activate_task(this, TASK_IKE_ME); +#endif /* ME */ + activate_task(this, TASK_IKE_AUTH); + activate_task(this, TASK_IKE_CERT_POST); + activate_task(this, TASK_IKE_CONFIG); + activate_task(this, TASK_CHILD_CREATE); + activate_task(this, TASK_IKE_AUTH_LIFETIME); + activate_task(this, TASK_IKE_MOBIKE); + } + break; + case IKE_ESTABLISHED: + if (activate_task(this, TASK_CHILD_CREATE)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, TASK_CHILD_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, TASK_CHILD_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, TASK_IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, TASK_IKE_REKEY)) + { + exchange = CREATE_CHILD_SA; + break; + } + if (activate_task(this, TASK_IKE_REAUTH)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, TASK_IKE_MOBIKE)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, TASK_IKE_DPD)) + { + exchange = INFORMATIONAL; + break; + } + if (activate_task(this, TASK_IKE_AUTH_LIFETIME)) + { + exchange = INFORMATIONAL; + break; + } +#ifdef ME + if (activate_task(this, TASK_IKE_ME)) + { + exchange = ME_CONNECT; + break; + } +#endif /* ME */ + case IKE_REKEYING: + if (activate_task(this, TASK_IKE_DELETE)) + { + exchange = INFORMATIONAL; + break; + } + case IKE_DELETING: + default: + break; + } + } + else + { + DBG2(DBG_IKE, "reinitiating already active tasks"); + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); + switch (task->get_type(task)) + { + case TASK_IKE_INIT: + exchange = IKE_SA_INIT; + break; + case TASK_IKE_AUTH: + exchange = IKE_AUTH; + break; + case TASK_CHILD_CREATE: + case TASK_CHILD_REKEY: + case TASK_IKE_REKEY: + exchange = CREATE_CHILD_SA; + break; + case TASK_IKE_MOBIKE: + exchange = INFORMATIONAL; + break; + default: + continue; + } + break; + } + enumerator->destroy(enumerator); + } + + if (exchange == 0) + { + DBG2(DBG_IKE, "nothing to initiate"); + /* nothing to do yet... */ + return SUCCESS; + } + + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + + message = message_create(IKEV2_MAJOR_VERSION, IKEV2_MINOR_VERSION); + message->set_message_id(message, this->initiating.mid); + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_exchange_type(message, exchange); + this->initiating.type = exchange; + this->initiating.retransmitted = 0; + + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + enumerator->destroy(enumerator); + message->destroy(message); + flush(this); + return DESTROY_ME; + } + } + enumerator->destroy(enumerator); + + /* update exchange type if a task changed it */ + this->initiating.type = message->get_exchange_type(message); + + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->initiating.packet); + if (status != SUCCESS) + { + /* message generation failed. There is nothing more to do than to + * close the SA */ + message->destroy(message); + flush(this); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + message->destroy(message); + + return retransmit(this, this->initiating.mid); +} + +/** + * handle an incoming response message + */ +static status_t process_response(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + task_t *task; + + if (message->get_exchange_type(message) != this->initiating.type) + { + DBG1(DBG_IKE, "received %N response, but expected %N", + exchange_type_names, message->get_exchange_type(message), + exchange_type_names, this->initiating.type); + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + /* catch if we get resetted while processing */ + this->reset = FALSE; + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->active_tasks->remove_at(this->active_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + if (this->reset) + { /* start all over again if we were reset */ + this->reset = FALSE; + enumerator->destroy(enumerator); + return initiate(this); + } + } + enumerator->destroy(enumerator); + + this->initiating.mid++; + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + this->initiating.packet->destroy(this->initiating.packet); + this->initiating.packet = NULL; + + return initiate(this); +} + +/** + * handle exchange collisions + */ +static bool handle_collisions(private_task_manager_t *this, task_t *task) +{ + enumerator_t *enumerator; + task_t *active; + task_type_t type; + + type = task->get_type(task); + + /* do we have to check */ + if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY || + type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE || + type == TASK_IKE_REAUTH) + { + /* find an exchange collision, and notify these tasks */ + enumerator = this->active_tasks->create_enumerator(this->active_tasks); + while (enumerator->enumerate(enumerator, (void**)&active)) + { + switch (active->get_type(active)) + { + case TASK_IKE_REKEY: + if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE || + type == TASK_IKE_REAUTH) + { + ike_rekey_t *rekey = (ike_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + case TASK_CHILD_REKEY: + if (type == TASK_CHILD_REKEY || type == TASK_CHILD_DELETE) + { + child_rekey_t *rekey = (child_rekey_t*)active; + rekey->collide(rekey, task); + break; + } + continue; + default: + continue; + } + enumerator->destroy(enumerator); + return TRUE; + } + enumerator->destroy(enumerator); + } + return FALSE; +} + +/** + * build a response depending on the "passive" task list + */ +static status_t build_response(private_task_manager_t *this, message_t *request) +{ + enumerator_t *enumerator; + task_t *task; + message_t *message; + host_t *me, *other; + bool delete = FALSE, hook = FALSE; + status_t status; + + me = request->get_destination(request); + other = request->get_source(request); + + message = message_create(IKEV2_MAJOR_VERSION, IKEV2_MINOR_VERSION); + message->set_exchange_type(message, request->get_exchange_type(request)); + /* send response along the path the request came in */ + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); + message->set_message_id(message, this->responding.mid); + message->set_request(message, FALSE); + + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->build(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + if (!handle_collisions(this, task)) + { + task->destroy(task); + } + break; + case NEED_MORE: + /* processed, but task needs another exchange */ + if (handle_collisions(this, task)) + { + this->passive_tasks->remove_at(this->passive_tasks, + enumerator); + } + break; + case FAILED: + default: + hook = TRUE; + /* FALL */ + case DESTROY_ME: + /* destroy IKE_SA, but SEND response first */ + delete = TRUE; + break; + } + if (delete) + { + break; + } + } + enumerator->destroy(enumerator); + + /* remove resonder SPI if IKE_SA_INIT failed */ + if (delete && request->get_exchange_type(request) == IKE_SA_INIT) + { + ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); + id->set_responder_spi(id, 0); + } + + /* message complete, send it */ + DESTROY_IF(this->responding.packet); + this->responding.packet = NULL; + status = this->ike_sa->generate_message(this->ike_sa, message, + &this->responding.packet); + message->destroy(message); + if (status != SUCCESS) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + return DESTROY_ME; + } + + charon->sender->send(charon->sender, + this->responding.packet->clone(this->responding.packet)); + if (delete) + { + if (hook) + { + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + } + return DESTROY_ME; + } + return SUCCESS; +} + +/** + * handle an incoming request message + */ +static status_t process_request(private_task_manager_t *this, + message_t *message) +{ + enumerator_t *enumerator; + task_t *task = NULL; + payload_t *payload; + notify_payload_t *notify; + delete_payload_t *delete; + + if (this->passive_tasks->get_count(this->passive_tasks) == 0) + { /* create tasks depending on request type, if not already some queued */ + switch (message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + task = (task_t*)ike_vendor_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_init_create(this->ike_sa, FALSE, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_natd_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_cert_pre_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); +#ifdef ME + task = (task_t*)ike_me_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); +#endif /* ME */ + task = (task_t*)ike_auth_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_cert_post_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_config_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)child_create_create(this->ike_sa, NULL, FALSE, + NULL, NULL); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_auth_lifetime_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + task = (task_t*)ike_mobike_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case CREATE_CHILD_SA: + { /* FIXME: we should prevent this on mediation connections */ + bool notify_found = FALSE, ts_found = FALSE; + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY: + { /* if we find a rekey notify, its CHILD_SA rekeying */ + notify = (notify_payload_t*)payload; + if (notify->get_notify_type(notify) == REKEY_SA && + (notify->get_protocol_id(notify) == PROTO_AH || + notify->get_protocol_id(notify) == PROTO_ESP)) + { + notify_found = TRUE; + } + break; + } + case TRAFFIC_SELECTOR_INITIATOR: + case TRAFFIC_SELECTOR_RESPONDER: + { /* if we don't find a TS, its IKE rekeying */ + ts_found = TRUE; + break; + } + default: + break; + } + } + enumerator->destroy(enumerator); + + if (ts_found) + { + if (notify_found) + { + task = (task_t*)child_rekey_create(this->ike_sa, + PROTO_NONE, 0); + } + else + { + task = (task_t*)child_create_create(this->ike_sa, NULL, + FALSE, NULL, NULL); + } + } + else + { + task = (task_t*)ike_rekey_create(this->ike_sa, FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } + case INFORMATIONAL: + { + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + switch (payload->get_type(payload)) + { + case NOTIFY: + { + notify = (notify_payload_t*)payload; + switch (notify->get_notify_type(notify)) + { + case ADDITIONAL_IP4_ADDRESS: + case ADDITIONAL_IP6_ADDRESS: + case NO_ADDITIONAL_ADDRESSES: + case UPDATE_SA_ADDRESSES: + case NO_NATS_ALLOWED: + case UNACCEPTABLE_ADDRESSES: + case UNEXPECTED_NAT_DETECTED: + case COOKIE2: + case NAT_DETECTION_SOURCE_IP: + case NAT_DETECTION_DESTINATION_IP: + task = (task_t*)ike_mobike_create( + this->ike_sa, FALSE); + break; + case AUTH_LIFETIME: + task = (task_t*)ike_auth_lifetime_create( + this->ike_sa, FALSE); + break; + default: + break; + } + break; + } + case DELETE: + { + delete = (delete_payload_t*)payload; + if (delete->get_protocol_id(delete) == PROTO_IKE) + { + task = (task_t*)ike_delete_create(this->ike_sa, + FALSE); + } + else + { + task = (task_t*)child_delete_create(this->ike_sa, + PROTO_NONE, 0, FALSE); + } + break; + } + default: + break; + } + if (task) + { + break; + } + } + enumerator->destroy(enumerator); + + if (task == NULL) + { + task = (task_t*)ike_dpd_create(FALSE); + } + this->passive_tasks->insert_last(this->passive_tasks, task); + break; + } +#ifdef ME + case ME_CONNECT: + { + task = (task_t*)ike_me_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } +#endif /* ME */ + default: + break; + } + } + + /* let the tasks process the message */ + enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); + while (enumerator->enumerate(enumerator, (void*)&task)) + { + switch (task->process(task, message)) + { + case SUCCESS: + /* task completed, remove it */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + task->destroy(task); + break; + case NEED_MORE: + /* processed, but task needs at least another call to build() */ + break; + case FAILED: + default: + charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); + /* FALL */ + case DESTROY_ME: + /* critical failure, destroy IKE_SA */ + this->passive_tasks->remove_at(this->passive_tasks, enumerator); + enumerator->destroy(enumerator); + task->destroy(task); + return DESTROY_ME; + } + } + enumerator->destroy(enumerator); + + return build_response(this, message); +} + +METHOD(task_manager_t, incr_mid, void, + private_task_manager_t *this, bool initiate) +{ + if (initiate) + { + this->initiating.mid++; + } + else + { + this->responding.mid++; + } +} + +/** + * Send a notify back to the sender + */ +static void send_notify_response(private_task_manager_t *this, + message_t *request, notify_type_t type, + chunk_t data) +{ + message_t *response; + packet_t *packet; + host_t *me, *other; + + response = message_create(IKEV2_MAJOR_VERSION, IKEV2_MINOR_VERSION); + response->set_exchange_type(response, request->get_exchange_type(request)); + response->set_request(response, FALSE); + response->set_message_id(response, request->get_message_id(request)); + response->add_notify(response, FALSE, type, data); + me = this->ike_sa->get_my_host(this->ike_sa); + if (me->is_anyaddr(me)) + { + me = request->get_destination(request); + this->ike_sa->set_my_host(this->ike_sa, me->clone(me)); + } + other = this->ike_sa->get_other_host(this->ike_sa); + if (other->is_anyaddr(other)) + { + other = request->get_source(request); + this->ike_sa->set_other_host(this->ike_sa, other->clone(other)); + } + response->set_source(response, me->clone(me)); + response->set_destination(response, other->clone(other)); + if (this->ike_sa->generate_message(this->ike_sa, response, + &packet) == SUCCESS) + { + charon->sender->send(charon->sender, packet); + } + response->destroy(response); +} + +/** + * Parse the given message and verify that it is valid. + */ +static status_t parse_message(private_task_manager_t *this, message_t *msg) +{ + status_t status; + u_int8_t type = 0; + + status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa)); + + if (status == SUCCESS) + { /* check for unsupported critical payloads */ + enumerator_t *enumerator; + unknown_payload_t *unknown; + payload_t *payload; + + enumerator = msg->create_payload_enumerator(msg); + while (enumerator->enumerate(enumerator, &payload)) + { + unknown = (unknown_payload_t*)payload; + type = payload->get_type(payload); + if (!payload_is_known(type) && + unknown->is_critical(unknown)) + { + DBG1(DBG_ENC, "payload type %N is not supported, " + "but its critical!", payload_type_names, type); + status = NOT_SUPPORTED; + break; + } + } + enumerator->destroy(enumerator); + } + + if (status != SUCCESS) + { + bool is_request = msg->get_request(msg); + + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "critical unknown payloads found"); + if (is_request) + { + send_notify_response(this, msg, + UNSUPPORTED_CRITICAL_PAYLOAD, + chunk_from_thing(type)); + incr_mid(this, FALSE); + } + break; + case PARSE_ERROR: + DBG1(DBG_IKE, "message parsing failed"); + if (is_request) + { + send_notify_response(this, msg, + INVALID_SYNTAX, chunk_empty); + incr_mid(this, FALSE); + } + break; + case VERIFY_ERROR: + DBG1(DBG_IKE, "message verification failed"); + if (is_request) + { + send_notify_response(this, msg, + INVALID_SYNTAX, chunk_empty); + incr_mid(this, FALSE); + } + break; + case FAILED: + DBG1(DBG_IKE, "integrity check failed"); + /* ignored */ + break; + case INVALID_STATE: + DBG1(DBG_IKE, "found encrypted message, but no keys available"); + default: + break; + } + DBG1(DBG_IKE, "%N %s with message ID %d processing failed", + exchange_type_names, msg->get_exchange_type(msg), + is_request ? "request" : "response", + msg->get_message_id(msg)); + + if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED) + { /* invalid initiation attempt, close SA */ + return DESTROY_ME; + } + } + return status; +} + + +METHOD(task_manager_t, process_message, status_t, + private_task_manager_t *this, message_t *msg) +{ + host_t *me, *other; + status_t status; + u_int32_t mid; + + charon->bus->message(charon->bus, msg, TRUE, FALSE); + status = parse_message(this, msg); + if (status != SUCCESS) + { + return status; + } + + me = msg->get_destination(msg); + other = msg->get_source(msg); + + /* if this IKE_SA is virgin, we check for a config */ + if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL) + { + ike_sa_id_t *ike_sa_id; + ike_cfg_t *ike_cfg; + job_t *job; + ike_cfg = charon->backends->get_ike_cfg(charon->backends, me, other); + if (ike_cfg == NULL) + { + /* no config found for these hosts, destroy */ + DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", + me, other, notify_type_names, NO_PROPOSAL_CHOSEN); + send_notify_response(this, msg, + NO_PROPOSAL_CHOSEN, chunk_empty); + return DESTROY_ME; + } + this->ike_sa->set_ike_cfg(this->ike_sa, ike_cfg); + ike_cfg->destroy(ike_cfg); + /* add a timeout if peer does not establish it completely */ + ike_sa_id = this->ike_sa->get_id(this->ike_sa); + job = (job_t*)delete_ike_sa_job_create(ike_sa_id, FALSE); + lib->scheduler->schedule_job(lib->scheduler, job, + lib->settings->get_int(lib->settings, + "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT, + charon->name)); + } + this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND, + time_monotonic(NULL)); + + mid = msg->get_message_id(msg); + if (msg->get_request(msg)) + { + if (mid == this->responding.mid) + { + if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || + this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING || + msg->get_exchange_type(msg) != IKE_SA_INIT) + { /* only do host updates based on verified messages */ + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) + { /* with MOBIKE, we do no implicit updates */ + this->ike_sa->update_hosts(this->ike_sa, me, other, mid == 1); + } + } + charon->bus->message(charon->bus, msg, TRUE, TRUE); + if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED) + { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */ + return SUCCESS; + } + if (process_request(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + this->responding.mid++; + } + else if ((mid == this->responding.mid - 1) && this->responding.packet) + { + packet_t *clone; + host_t *host; + + DBG1(DBG_IKE, "received retransmit of request with ID %d, " + "retransmitting response", mid); + clone = this->responding.packet->clone(this->responding.packet); + host = msg->get_destination(msg); + clone->set_source(clone, host->clone(host)); + host = msg->get_source(msg); + clone->set_destination(clone, host->clone(host)); + charon->sender->send(charon->sender, clone); + } + else + { + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", + mid, this->responding.mid); + } + } + else + { + if (mid == this->initiating.mid) + { + if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || + this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING || + msg->get_exchange_type(msg) != IKE_SA_INIT) + { /* only do host updates based on verified messages */ + if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) + { /* with MOBIKE, we do no implicit updates */ + this->ike_sa->update_hosts(this->ike_sa, me, other, FALSE); + } + } + charon->bus->message(charon->bus, msg, TRUE, TRUE); + if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED) + { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */ + return SUCCESS; + } + if (process_response(this, msg) != SUCCESS) + { + flush(this); + return DESTROY_ME; + } + } + else + { + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", + mid, this->initiating.mid); + return SUCCESS; + } + } + return SUCCESS; +} + +METHOD(task_manager_t, queue_task, void, + private_task_manager_t *this, task_t *task) +{ + if (task->get_type(task) == TASK_IKE_MOBIKE) + { /* there is no need to queue more than one mobike task */ + enumerator_t *enumerator; + task_t *current; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current->get_type(current) == TASK_IKE_MOBIKE) + { + enumerator->destroy(enumerator); + task->destroy(task); + return; + } + } + enumerator->destroy(enumerator); + } + DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); + this->queued_tasks->insert_last(this->queued_tasks, task); +} + +/** + * Check if a given task has been queued already + */ +static bool has_queued(private_task_manager_t *this, task_type_t type) +{ + enumerator_t *enumerator; + bool found = FALSE; + task_t *task; + + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + if (task->get_type(task) == type) + { + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +METHOD(task_manager_t, queue_ike, void, + private_task_manager_t *this) +{ + if (!has_queued(this, TASK_IKE_VENDOR)) + { + queue_task(this, (task_t*)ike_vendor_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_INIT)) + { + queue_task(this, (task_t*)ike_init_create(this->ike_sa, TRUE, NULL)); + } + if (!has_queued(this, TASK_IKE_NATD)) + { + queue_task(this, (task_t*)ike_natd_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_CERT_PRE)) + { + queue_task(this, (task_t*)ike_cert_pre_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_AUTH)) + { + queue_task(this, (task_t*)ike_auth_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_CERT_POST)) + { + queue_task(this, (task_t*)ike_cert_post_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_CONFIG)) + { + queue_task(this, (task_t*)ike_config_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_AUTH_LIFETIME)) + { + queue_task(this, (task_t*)ike_auth_lifetime_create(this->ike_sa, TRUE)); + } + if (!has_queued(this, TASK_IKE_MOBIKE)) + { + peer_cfg_t *peer_cfg; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg->use_mobike(peer_cfg)) + { + queue_task(this, (task_t*)ike_mobike_create(this->ike_sa, TRUE)); + } + } +#ifdef ME + if (!has_queued(this, TASK_IKE_ME)) + { + queue_task(this, (task_t*)ike_me_create(this->ike_sa, TRUE)); + } +#endif /* ME */ +} + +METHOD(task_manager_t, queue_ike_rekey, void, + private_task_manager_t *this) +{ + queue_task(this, (task_t*)ike_rekey_create(this->ike_sa, TRUE)); +} + +METHOD(task_manager_t, queue_ike_reauth, void, + private_task_manager_t *this) +{ + queue_task(this, (task_t*)ike_reauth_create(this->ike_sa)); +} + +METHOD(task_manager_t, queue_ike_delete, void, + private_task_manager_t *this) +{ + queue_task(this, (task_t*)ike_delete_create(this->ike_sa, TRUE)); +} + +METHOD(task_manager_t, queue_mobike, void, + private_task_manager_t *this, bool roam, bool address) +{ + ike_mobike_t *mobike; + + mobike = ike_mobike_create(this->ike_sa, TRUE); + if (roam) + { + mobike->roam(mobike, address); + } + else + { + mobike->addresses(mobike); + } + queue_task(this, &mobike->task); +} + +METHOD(task_manager_t, queue_child, void, + private_task_manager_t *this, child_cfg_t *cfg, u_int32_t reqid, + traffic_selector_t *tsi, traffic_selector_t *tsr) +{ + child_create_t *task; + + task = child_create_create(this->ike_sa, cfg, FALSE, tsi, tsr); + if (reqid) + { + task->use_reqid(task, reqid); + } + queue_task(this, &task->task); +} + +METHOD(task_manager_t, queue_child_rekey, void, + private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi) +{ + queue_task(this, (task_t*)child_rekey_create(this->ike_sa, protocol, spi)); +} + +METHOD(task_manager_t, queue_child_delete, void, + private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi, + bool expired) +{ + queue_task(this, (task_t*)child_delete_create(this->ike_sa, + protocol, spi, expired)); +} + +METHOD(task_manager_t, queue_dpd, void, + private_task_manager_t *this) +{ + ike_mobike_t *mobike; + + if (this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE) && + this->ike_sa->has_condition(this->ike_sa, COND_NAT_HERE)) + { + /* use mobike enabled DPD to detect NAT mapping changes */ + mobike = ike_mobike_create(this->ike_sa, TRUE); + mobike->dpd(mobike); + queue_task(this, &mobike->task); + } + else + { + queue_task(this, (task_t*)ike_dpd_create(TRUE)); + } +} + +METHOD(task_manager_t, adopt_tasks, void, + private_task_manager_t *this, task_manager_t *other_public) +{ + private_task_manager_t *other = (private_task_manager_t*)other_public; + task_t *task; + + /* move queued tasks from other to this */ + while (other->queued_tasks->remove_last(other->queued_tasks, + (void**)&task) == SUCCESS) + { + DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } +} + +METHOD(task_manager_t, busy, bool, + private_task_manager_t *this) +{ + return (this->active_tasks->get_count(this->active_tasks) > 0); +} + +METHOD(task_manager_t, reset, void, + private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) +{ + enumerator_t *enumerator; + task_t *task; + + /* reset message counters and retransmit packets */ + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + this->responding.packet = NULL; + this->initiating.packet = NULL; + if (initiate != UINT_MAX) + { + this->initiating.mid = initiate; + } + if (respond != UINT_MAX) + { + this->responding.mid = respond; + } + this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + + /* reset queued tasks */ + enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + task->migrate(task, this->ike_sa); + } + enumerator->destroy(enumerator); + + /* reset active tasks */ + while (this->active_tasks->remove_last(this->active_tasks, + (void**)&task) == SUCCESS) + { + task->migrate(task, this->ike_sa); + this->queued_tasks->insert_first(this->queued_tasks, task); + } + + this->reset = TRUE; +} + +METHOD(task_manager_t, create_task_enumerator, enumerator_t*, + private_task_manager_t *this, task_queue_t queue) +{ + switch (queue) + { + case TASK_QUEUE_ACTIVE: + return this->active_tasks->create_enumerator(this->active_tasks); + case TASK_QUEUE_PASSIVE: + return this->passive_tasks->create_enumerator(this->passive_tasks); + case TASK_QUEUE_QUEUED: + return this->queued_tasks->create_enumerator(this->queued_tasks); + default: + return enumerator_create_empty(); + } +} + +METHOD(task_manager_t, destroy, void, + private_task_manager_t *this) +{ + flush(this); + + this->active_tasks->destroy(this->active_tasks); + this->queued_tasks->destroy(this->queued_tasks); + this->passive_tasks->destroy(this->passive_tasks); + + DESTROY_IF(this->responding.packet); + DESTROY_IF(this->initiating.packet); + free(this); +} + +/* + * see header file + */ +task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa) +{ + private_task_manager_t *this; + + INIT(this, + .public = { + .task_manager = { + .process_message = _process_message, + .queue_task = _queue_task, + .queue_ike = _queue_ike, + .queue_ike_rekey = _queue_ike_rekey, + .queue_ike_reauth = _queue_ike_reauth, + .queue_ike_delete = _queue_ike_delete, + .queue_mobike = _queue_mobike, + .queue_child = _queue_child, + .queue_child_rekey = _queue_child_rekey, + .queue_child_delete = _queue_child_delete, + .queue_dpd = _queue_dpd, + .initiate = _initiate, + .retransmit = _retransmit, + .incr_mid = _incr_mid, + .reset = _reset, + .adopt_tasks = _adopt_tasks, + .busy = _busy, + .create_task_enumerator = _create_task_enumerator, + .flush_queue = _flush_queue, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .initiating.type = EXCHANGE_TYPE_UNDEFINED, + .queued_tasks = linked_list_create(), + .active_tasks = linked_list_create(), + .passive_tasks = linked_list_create(), + .retransmit_tries = lib->settings->get_int(lib->settings, + "%s.retransmit_tries", RETRANSMIT_TRIES, charon->name), + .retransmit_timeout = lib->settings->get_double(lib->settings, + "%s.retransmit_timeout", RETRANSMIT_TIMEOUT, charon->name), + .retransmit_base = lib->settings->get_double(lib->settings, + "%s.retransmit_base", RETRANSMIT_BASE, charon->name), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev2/task_manager_v2.h b/src/libcharon/sa/ikev2/task_manager_v2.h new file mode 100644 index 000000000..70444ae27 --- /dev/null +++ b/src/libcharon/sa/ikev2/task_manager_v2.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup task_manager_v2 task_manager_v2 + * @{ @ingroup ikev2 + */ + +#ifndef TASK_MANAGER_V2_H_ +#define TASK_MANAGER_V2_H_ + +typedef struct task_manager_v2_t task_manager_v2_t; + +#include <sa/task_manager.h> + +/** + * Task manager, IKEv2 variant. + */ +struct task_manager_v2_t { + + /** + * Implements task_manager_t. + */ + task_manager_t task_manager; +}; + +/** + * Create an instance of the task manager. + * + * @param ike_sa IKE_SA to manage. + */ +task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa); + +#endif /** TASK_MANAGER_V2_H_ @}*/ diff --git a/src/libcharon/sa/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 67c29d31f..46a165546 100644 --- a/src/libcharon/sa/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -18,6 +18,7 @@ #include "child_create.h" #include <daemon.h> +#include <sa/ikev2/keymat_v2.h> #include <crypto/diffie_hellman.h> #include <credentials/certificates/x509.h> #include <encoding/payloads/sa_payload.h> @@ -109,7 +110,7 @@ struct private_child_create_t { /** * IKE_SAs keymat */ - keymat_t *keymat; + keymat_v2_t *keymat; /** * mode the new CHILD_SA uses (transport/tunnel/beet) @@ -170,6 +171,11 @@ struct private_child_create_t { * whether the CHILD_SA rekeys an existing one */ bool rekey; + + /** + * whether we are retrying with another DH group + */ + bool retry; }; /** @@ -191,18 +197,24 @@ static status_t get_nonce(message_t *message, chunk_t *nonce) /** * generate a new nonce to include in a CREATE_CHILD_SA message */ -static status_t generate_nonce(chunk_t *nonce) +static status_t generate_nonce(private_child_create_t *this) { - rng_t *rng; + nonce_gen_t *nonceg; - rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng) + nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); + if (!nonceg) { - DBG1(DBG_IKE, "error generating nonce value, no RNG found"); + DBG1(DBG_IKE, "no nonce generator found to create nonce"); return FAILED; } - rng->allocate_bytes(rng, NONCE_SIZE, nonce); - rng->destroy(rng); + if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, &this->my_nonce)) + { + DBG1(DBG_IKE, "nonce allocation failed"); + nonceg->destroy(nonceg); + return FAILED; + } + nonceg->destroy(nonceg); + return SUCCESS; } @@ -265,7 +277,7 @@ static void schedule_inactivity_timeout(private_child_create_t *this) if (timeout) { close_ike = lib->settings->get_bool(lib->settings, - "charon.inactivity_close_ike", FALSE); + "%s.inactivity_close_ike", FALSE, charon->name); lib->scheduler->schedule_job(lib->scheduler, (job_t*) inactivity_job_create(this->child_sa->get_reqid(this->child_sa), timeout, close_ike), timeout); @@ -273,6 +285,62 @@ static void schedule_inactivity_timeout(private_child_create_t *this) } /** + * Check if we have a an address pool configured + */ +static bool have_pool(ike_sa_t *ike_sa) +{ + enumerator_t *enumerator; + peer_cfg_t *peer_cfg; + char *pool; + bool found = FALSE; + + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + if (peer_cfg) + { + enumerator = peer_cfg->create_pool_enumerator(peer_cfg); + if (enumerator->enumerate(enumerator, &pool)) + { + found = TRUE; + } + enumerator->destroy(enumerator); + } + return found; +} + +/** + * Get hosts to use for dynamic traffic selectors + */ +static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local) +{ + enumerator_t *enumerator; + linked_list_t *list; + host_t *host; + + list = linked_list_create(); + enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local); + while (enumerator->enumerate(enumerator, &host)) + { + list->insert_last(list, host); + } + enumerator->destroy(enumerator); + + if (list->get_count(list) == 0) + { /* no virtual IPs assigned */ + if (local) + { + host = ike_sa->get_my_host(ike_sa); + list->insert_last(list, host); + } + else if (!have_pool(ike_sa)) + { /* use host only if we don't have a pool configured */ + host = ike_sa->get_other_host(ike_sa); + list->insert_last(list, host); + } + } + return list; +} + +/** * Install a CHILD_SA for usage, return value: * - FAILED: no acceptable proposal * - INVALID_ARG: diffie hellman group inacceptable @@ -285,8 +353,8 @@ static status_t select_and_install(private_child_create_t *this, chunk_t nonce_i, nonce_r; chunk_t encr_i = chunk_empty, encr_r = chunk_empty; chunk_t integ_i = chunk_empty, integ_r = chunk_empty; - linked_list_t *my_ts, *other_ts; - host_t *me, *other, *other_vip, *my_vip; + linked_list_t *my_ts, *other_ts, *list; + host_t *me, *other; bool private; if (this->proposals == NULL) @@ -302,8 +370,6 @@ static status_t select_and_install(private_child_create_t *this, me = this->ike_sa->get_my_host(this->ike_sa); other = this->ike_sa->get_other_host(this->ike_sa); - my_vip = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); - other_vip = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN); this->proposal = this->config->select_proposal(this->config, @@ -342,15 +408,6 @@ static status_t select_and_install(private_child_create_t *this, this->dh_group = MODP_NONE; } - if (my_vip == NULL) - { - my_vip = me; - } - if (other_vip == NULL) - { - other_vip = other; - } - if (this->initiator) { nonce_i = this->my_nonce; @@ -365,10 +422,14 @@ static status_t select_and_install(private_child_create_t *this, my_ts = this->tsr; other_ts = this->tsi; } - my_ts = this->config->get_traffic_selectors(this->config, TRUE, my_ts, - my_vip); - other_ts = this->config->get_traffic_selectors(this->config, FALSE, other_ts, - other_vip); + list = get_dynamic_hosts(this->ike_sa, TRUE); + my_ts = this->config->get_traffic_selectors(this->config, + TRUE, my_ts, list); + list->destroy(list); + list = get_dynamic_hosts(this->ike_sa, FALSE); + other_ts = this->config->get_traffic_selectors(this->config, + FALSE, other_ts, list); + list->destroy(list); if (this->initiator) { @@ -491,7 +552,32 @@ static status_t select_and_install(private_child_create_t *this, return FAILED; } - status = this->child_sa->add_policies(this->child_sa, my_ts, other_ts); + if (this->initiator) + { + status = this->child_sa->add_policies(this->child_sa, my_ts, other_ts); + } + else + { + /* use a copy of the traffic selectors, as the POST hook should not + * change payloads */ + my_ts = this->tsr->clone_offset(this->tsr, + offsetof(traffic_selector_t, clone)); + other_ts = this->tsi->clone_offset(this->tsi, + offsetof(traffic_selector_t, clone)); + charon->bus->narrow(charon->bus, this->child_sa, + NARROW_RESPONDER_POST, my_ts, other_ts); + if (my_ts->get_count(my_ts) == 0 || other_ts->get_count(other_ts) == 0) + { + status = FAILED; + } + else + { + status = this->child_sa->add_policies(this->child_sa, + my_ts, other_ts); + } + my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); + other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); + } if (status != SUCCESS) { DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel"); @@ -526,18 +612,18 @@ static void build_payloads(private_child_create_t *this, message_t *message) /* add SA payload */ if (this->initiator) { - sa_payload = sa_payload_create_from_proposal_list(this->proposals); + sa_payload = sa_payload_create_from_proposals_v2(this->proposals); } else { - sa_payload = sa_payload_create_from_proposal(this->proposal); + sa_payload = sa_payload_create_from_proposal_v2(this->proposal); } message->add_payload(message, (payload_t*)sa_payload); /* add nonce payload if not in IKE_AUTH */ if (message->get_exchange_type(message) == CREATE_CHILD_SA) { - nonce_payload = nonce_payload_create(); + nonce_payload = nonce_payload_create(NONCE); nonce_payload->set_nonce(nonce_payload, this->my_nonce); message->add_payload(message, (payload_t*)nonce_payload); } @@ -545,7 +631,8 @@ static void build_payloads(private_child_create_t *this, message_t *message) /* diffie hellman exchange, if PFS enabled */ if (this->dh) { - ke_payload = ke_payload_create_from_diffie_hellman(this->dh); + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE, + this->dh); message->add_payload(message, (payload_t*)ke_payload); } @@ -680,7 +767,8 @@ static void process_payloads(private_child_create_t *this, message_t *message) if (!this->initiator) { this->dh_group = ke_payload->get_dh_group_number(ke_payload); - this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + this->dh = this->keymat->keymat.create_dh( + &this->keymat->keymat, this->dh_group); } if (this->dh) { @@ -709,20 +797,22 @@ static void process_payloads(private_child_create_t *this, message_t *message) METHOD(task_t, build_i, status_t, private_child_create_t *this, message_t *message) { - host_t *me, *other, *vip; + enumerator_t *enumerator; + host_t *vip; peer_cfg_t *peer_cfg; + linked_list_t *list; switch (message->get_exchange_type(message)) { case IKE_SA_INIT: return get_nonce(message, &this->my_nonce); case CREATE_CHILD_SA: - if (generate_nonce(&this->my_nonce) != SUCCESS) + if (generate_nonce(this) != SUCCESS) { message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); return SUCCESS; } - if (this->dh_group == MODP_NONE) + if (!this->retry) { this->dh_group = this->config->get_dh_group(this->config); } @@ -749,36 +839,38 @@ METHOD(task_t, build_i, status_t, this->config->get_name(this->config)); } - /* reuse virtual IP if we already have one */ - me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); - if (me == NULL) - { - me = this->ike_sa->get_my_host(this->ike_sa); - } - other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); - if (other == NULL) - { - other = this->ike_sa->get_other_host(this->ike_sa); - } - /* check if we want a virtual IP, but don't have one */ + list = linked_list_create(); peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); - vip = peer_cfg->get_virtual_ip(peer_cfg); - if (!this->reqid && vip) + if (!this->reqid) { - /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */ - vip = host_create_any(vip->get_family(vip)); - this->tsi = this->config->get_traffic_selectors(this->config, TRUE, - NULL, vip); - vip->destroy(vip); + enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg); + while (enumerator->enumerate(enumerator, &vip)) + { + /* propose a 0.0.0.0/0 or ::/0 subnet when we use virtual ip */ + vip = host_create_any(vip->get_family(vip)); + list->insert_last(list, vip); + } + enumerator->destroy(enumerator); } - else - { /* but narrow it for host2host / if we already have a vip */ - this->tsi = this->config->get_traffic_selectors(this->config, TRUE, - NULL, me); + if (list->get_count(list)) + { + this->tsi = this->config->get_traffic_selectors(this->config, + TRUE, NULL, list); + list->destroy_offset(list, offsetof(host_t, destroy)); } - this->tsr = this->config->get_traffic_selectors(this->config, FALSE, - NULL, other); + else + { /* no virtual IPs configured */ + list->destroy(list); + list = get_dynamic_hosts(this->ike_sa, TRUE); + this->tsi = this->config->get_traffic_selectors(this->config, + TRUE, NULL, list); + list->destroy(list); + } + list = get_dynamic_hosts(this->ike_sa, FALSE); + this->tsr = this->config->get_traffic_selectors(this->config, + FALSE, NULL, list); + list->destroy(list); if (this->packet_tsi) { @@ -812,7 +904,8 @@ METHOD(task_t, build_i, status_t, if (this->dh_group != MODP_NONE) { - this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + this->dh_group); } if (this->config->use_ipcomp(this->config)) @@ -877,7 +970,7 @@ static void handle_child_sa_failure(private_child_create_t *this, { if (message->get_exchange_type(message) == IKE_AUTH && lib->settings->get_bool(lib->settings, - "charon.close_ike_on_child_failure", FALSE)) + "%s.close_ike_on_child_failure", FALSE, charon->name)) { /* we delay the delete for 100ms, as the IKE_AUTH response must arrive * first */ @@ -905,7 +998,7 @@ METHOD(task_t, build_r, status_t, case IKE_SA_INIT: return get_nonce(message, &this->my_nonce); case CREATE_CHILD_SA: - if (generate_nonce(&this->my_nonce) != SUCCESS) + if (generate_nonce(this) != SUCCESS) { message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); @@ -931,22 +1024,16 @@ METHOD(task_t, build_r, status_t, } peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); - if (peer_cfg && this->tsi && this->tsr) + if (!this->config && peer_cfg && this->tsi && this->tsr) { - host_t *me, *other; + linked_list_t *listr, *listi; - me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); - if (me == NULL) - { - me = this->ike_sa->get_my_host(this->ike_sa); - } - other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE); - if (other == NULL) - { - other = this->ike_sa->get_other_host(this->ike_sa); - } - this->config = peer_cfg->select_child_cfg(peer_cfg, this->tsr, - this->tsi, me, other); + listr = get_dynamic_hosts(this->ike_sa, TRUE); + listi = get_dynamic_hosts(this->ike_sa, FALSE); + this->config = peer_cfg->select_child_cfg(peer_cfg, + this->tsr, this->tsi, listr, listi); + listr->destroy(listr); + listi->destroy(listi); } if (this->config == NULL) @@ -1108,6 +1195,7 @@ METHOD(task_t, process_i, status_t, DBG1(DBG_IKE, "peer didn't accept DH group %N, " "it requested %N", diffie_hellman_group_names, this->dh_group, diffie_hellman_group_names, group); + this->retry = TRUE; this->dh_group = group; this->public.task.migrate(&this->public.task, this->ike_sa); enumerator->destroy(enumerator); @@ -1192,6 +1280,13 @@ METHOD(child_create_t, get_child, child_sa_t*, return this->child_sa; } +METHOD(child_create_t, set_config, void, + private_child_create_t *this, child_cfg_t *cfg) +{ + DESTROY_IF(this->config); + this->config = cfg; +} + METHOD(child_create_t, get_lower_nonce, chunk_t, private_child_create_t *this) { @@ -1209,7 +1304,7 @@ METHOD(child_create_t, get_lower_nonce, chunk_t, METHOD(task_t, get_type, task_type_t, private_child_create_t *this) { - return CHILD_CREATE; + return TASK_CHILD_CREATE; } METHOD(task_t, migrate, void, @@ -1234,7 +1329,7 @@ METHOD(task_t, migrate, void, } this->ike_sa = ike_sa; - this->keymat = ike_sa->get_keymat(ike_sa); + this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa); this->proposal = NULL; this->proposals = NULL; this->tsi = NULL; @@ -1291,6 +1386,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, INIT(this, .public = { .get_child = _get_child, + .set_config = _set_config, .get_lower_nonce = _get_lower_nonce, .use_reqid = _use_reqid, .task = { @@ -1304,12 +1400,13 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .packet_tsi = tsi ? tsi->clone(tsi) : NULL, .packet_tsr = tsr ? tsr->clone(tsr) : NULL, .dh_group = MODP_NONE, - .keymat = ike_sa->get_keymat(ike_sa), + .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa), .mode = MODE_TUNNEL, .tfcv3 = TRUE, .ipcomp = IPCOMP_NONE, .ipcomp_received = IPCOMP_NONE, .rekey = rekey, + .retry = FALSE, ); if (config) @@ -1317,7 +1414,6 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, this->public.task.build = _build_i; this->public.task.process = _process_i; this->initiator = TRUE; - config->get_ref(config); } else { diff --git a/src/libcharon/sa/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index 5dedeb8b1..d29ba3d98 100644 --- a/src/libcharon/sa/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -15,7 +15,7 @@ /** * @defgroup child_create child_create - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef CHILD_CREATE_H_ @@ -25,11 +25,11 @@ typedef struct child_create_t child_create_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> #include <config/child_cfg.h> /** - * Task of type CHILD_CREATE, established a new CHILD_SA. + * Task of type TASK_CHILD_CREATE, established a new CHILD_SA. * * This task may be included in the IKE_AUTH message or in a separate * CREATE_CHILD_SA exchange. @@ -64,6 +64,13 @@ struct child_create_t { * @return child_sa */ child_sa_t* (*get_child) (child_create_t *this); + + /** + * Enforce a specific CHILD_SA config as responder. + * + * @param cfg configuration to enforce, reference gets owned + */ + void (*set_config)(child_create_t *this, child_cfg_t *cfg); }; /** diff --git a/src/libcharon/sa/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index dc4b30dd3..644af782c 100644 --- a/src/libcharon/sa/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -62,6 +62,11 @@ struct private_child_delete_t { bool rekeyed; /** + * CHILD_SA already expired? + */ + bool expired; + + /** * CHILD_SAs which get deleted */ linked_list_t *child_sas; @@ -87,7 +92,7 @@ static void build_payloads(private_child_delete_t *this, message_t *message) case PROTO_ESP: if (esp == NULL) { - esp = delete_payload_create(PROTO_ESP); + esp = delete_payload_create(DELETE, PROTO_ESP); message->add_payload(message, (payload_t*)esp); } esp->add_spi(esp, spi); @@ -97,7 +102,7 @@ static void build_payloads(private_child_delete_t *this, message_t *message) case PROTO_AH: if (ah == NULL) { - ah = delete_payload_create(PROTO_AH); + ah = delete_payload_create(DELETE, PROTO_AH); message->add_payload(message, (payload_t*)ah); } ah->add_spi(ah, spi); @@ -247,16 +252,29 @@ static void log_children(private_child_delete_t *this) enumerator = this->child_sas->create_enumerator(this->child_sas); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { - child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in); - child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out); - - DBG0(DBG_IKE, "closing CHILD_SA %s{%d} " - "with SPIs %.8x_i (%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R", - child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), - ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, - ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, - child_sa->get_traffic_selectors(child_sa, TRUE), - child_sa->get_traffic_selectors(child_sa, FALSE)); + if (this->expired) + { + DBG0(DBG_IKE, "closing expired CHILD_SA %s{%d} " + "with SPIs %.8x_i %.8x_o and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), + ntohl(child_sa->get_spi(child_sa, FALSE)), + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + } + else + { + child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in); + child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out); + + DBG0(DBG_IKE, "closing CHILD_SA %s{%d} with SPIs %.8x_i " + "(%llu bytes) %.8x_o (%llu bytes) and TS %#R=== %#R", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa), + ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, + ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, + child_sa->get_traffic_selectors(child_sa, TRUE), + child_sa->get_traffic_selectors(child_sa, FALSE)); + } } enumerator->destroy(enumerator); } @@ -324,7 +342,7 @@ METHOD(task_t, build_r, status_t, METHOD(task_t, get_type, task_type_t, private_child_delete_t *this) { - return CHILD_DELETE; + return TASK_CHILD_DELETE; } METHOD(child_delete_t , get_child, child_sa_t*, @@ -356,7 +374,7 @@ METHOD(task_t, destroy, void, * Described in header. */ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi) + u_int32_t spi, bool expired) { private_child_delete_t *this; @@ -373,6 +391,7 @@ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, .child_sas = linked_list_create(), .protocol = protocol, .spi = spi, + .expired = expired, ); if (protocol != PROTO_NONE) diff --git a/src/libcharon/sa/tasks/child_delete.h b/src/libcharon/sa/ikev2/tasks/child_delete.h index 365807c68..1ada0699e 100644 --- a/src/libcharon/sa/tasks/child_delete.h +++ b/src/libcharon/sa/ikev2/tasks/child_delete.h @@ -15,7 +15,7 @@ /** * @defgroup child_delete child_delete - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef CHILD_DELETE_H_ @@ -25,7 +25,7 @@ typedef struct child_delete_t child_delete_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> #include <sa/child_sa.h> /** @@ -52,9 +52,10 @@ struct child_delete_t { * @param ike_sa IKE_SA this task works for * @param protocol protocol of CHILD_SA to delete, PROTO_NONE as responder * @param spi inbound SPI of CHILD_SA to delete + * @param expired TRUE if CHILD_SA already expired * @return child_delete task to handle by the task_manager */ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi); + u_int32_t spi, bool expired); #endif /** CHILD_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 76d185590..f8c2ed141 100644 --- a/src/libcharon/sa/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -18,8 +18,8 @@ #include <daemon.h> #include <encoding/payloads/notify_payload.h> -#include <sa/tasks/child_create.h> -#include <sa/tasks/child_delete.h> +#include <sa/ikev2/tasks/child_create.h> +#include <sa/ikev2/tasks/child_delete.h> #include <processing/jobs/rekey_child_sa_job.h> #include <processing/jobs/rekey_ike_sa_job.h> @@ -153,16 +153,16 @@ METHOD(task_t, build_i, status_t, config = this->child_sa->get_config(this->child_sa); /* we just need the rekey notify ... */ - notify = notify_payload_create_from_protocol_and_type(this->protocol, - REKEY_SA); + notify = notify_payload_create_from_protocol_and_type(NOTIFY, + this->protocol, REKEY_SA); notify->set_spi(notify, this->spi); message->add_payload(message, (payload_t*)notify); /* ... our CHILD_CREATE task does the hard work for us. */ if (!this->child_create) { - this->child_create = child_create_create(this->ike_sa, config, TRUE, - NULL, NULL); + this->child_create = child_create_create(this->ike_sa, + config->get_ref(config), TRUE, NULL, NULL); } reqid = this->child_sa->get_reqid(this->child_sa); this->child_create->use_reqid(this->child_create, reqid); @@ -187,6 +187,7 @@ METHOD(task_t, process_r, status_t, METHOD(task_t, build_r, status_t, private_child_rekey_t *this, message_t *message) { + child_cfg_t *config; u_int32_t reqid; if (this->child_sa == NULL || @@ -200,6 +201,8 @@ METHOD(task_t, build_r, status_t, /* let the CHILD_CREATE task build the response */ reqid = this->child_sa->get_reqid(this->child_sa); this->child_create->use_reqid(this->child_create, reqid); + config = this->child_sa->get_config(this->child_sa); + this->child_create->set_config(this->child_create, config->get_ref(config)); this->child_create->task.build(&this->child_create->task, message); if (message->get_payload(message, SECURITY_ASSOCIATION) == NULL) @@ -224,7 +227,7 @@ static child_sa_t *handle_collision(private_child_rekey_t *this) { child_sa_t *to_delete; - if (this->collision->get_type(this->collision) == CHILD_REKEY) + if (this->collision->get_type(this->collision) == TASK_CHILD_REKEY) { chunk_t this_nonce, other_nonce; private_child_rekey_t *other = (private_child_rekey_t*)this->collision; @@ -311,7 +314,7 @@ METHOD(task_t, process_i, status_t, /* establishing new child failed, reuse old. but not when we * received a delete in the meantime */ if (!(this->collision && - this->collision->get_type(this->collision) == CHILD_DELETE)) + this->collision->get_type(this->collision) == TASK_CHILD_DELETE)) { job_t *job; u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER); @@ -352,7 +355,7 @@ METHOD(task_t, process_i, status_t, protocol = to_delete->get_protocol(to_delete); /* rekeying done, delete the obsolete CHILD_SA using a subtask */ - this->child_delete = child_delete_create(this->ike_sa, protocol, spi); + this->child_delete = child_delete_create(this->ike_sa, protocol, spi, FALSE); this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_delete; this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_delete; @@ -362,7 +365,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_child_rekey_t *this) { - return CHILD_REKEY; + return TASK_CHILD_REKEY; } METHOD(child_rekey_t, collide, void, @@ -370,7 +373,7 @@ METHOD(child_rekey_t, collide, void, { /* the task manager only detects exchange collision, but not if * the collision is for the same child. we check it here. */ - if (other->get_type(other) == CHILD_REKEY) + if (other->get_type(other) == TASK_CHILD_REKEY) { private_child_rekey_t *rekey = (private_child_rekey_t*)other; if (rekey->child_sa != this->child_sa) @@ -380,7 +383,7 @@ METHOD(child_rekey_t, collide, void, return; } } - else if (other->get_type(other) == CHILD_DELETE) + else if (other->get_type(other) == TASK_CHILD_DELETE) { child_delete_t *del = (child_delete_t*)other; if (del->get_child(del) == this->child_create->get_child(this->child_create)) @@ -403,8 +406,8 @@ METHOD(child_rekey_t, collide, void, other->destroy(other); return; } - DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, CHILD_REKEY, - task_type_names, other->get_type(other)); + DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, + TASK_CHILD_REKEY, task_type_names, other->get_type(other)); DESTROY_IF(this->collision); this->collision = other; } @@ -462,7 +465,7 @@ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, .protocol = protocol, .spi = spi, ); - + if (protocol != PROTO_NONE) { this->public.task.build = _build_i; diff --git a/src/libcharon/sa/tasks/child_rekey.h b/src/libcharon/sa/ikev2/tasks/child_rekey.h index 9b1aea5fa..23384653d 100644 --- a/src/libcharon/sa/tasks/child_rekey.h +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.h @@ -15,7 +15,7 @@ /** * @defgroup child_rekey child_rekey - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef CHILD_REKEY_H_ @@ -26,10 +26,10 @@ typedef struct child_rekey_t child_rekey_t; #include <library.h> #include <sa/ike_sa.h> #include <sa/child_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type CHILD_REKEY, rekey an established CHILD_SA. + * Task of type TASK_CHILD_REKEY, rekey an established CHILD_SA. */ struct child_rekey_t { @@ -51,7 +51,7 @@ struct child_rekey_t { }; /** - * Create a new CHILD_REKEY task. + * Create a new TASK_CHILD_REKEY task. * * @param ike_sa IKE_SA this task works for * @param protocol protocol of CHILD_SA to rekey, PROTO_NONE as responder diff --git a/src/libcharon/sa/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c index 665468fe8..cd94ccd9e 100644 --- a/src/libcharon/sa/tasks/ike_auth.c +++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c @@ -24,7 +24,7 @@ #include <encoding/payloads/auth_payload.h> #include <encoding/payloads/eap_payload.h> #include <encoding/payloads/nonce_payload.h> -#include <sa/authenticators/eap_authenticator.h> +#include <sa/ikev2/authenticators/eap_authenticator.h> typedef struct private_ike_auth_t private_ike_auth_t; @@ -120,7 +120,7 @@ struct private_ike_auth_t { static bool multiple_auth_enabled() { return lib->settings->get_bool(lib->settings, - "charon.multiple_authentication", TRUE); + "%s.multiple_authentication", TRUE, charon->name); } /** @@ -270,8 +270,10 @@ static bool load_cfg_candidates(private_ike_auth_t *this) 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); + me, other, my_id, other_id, IKEV2); while (enumerator->enumerate(enumerator, &peer_cfg)) { peer_cfg->get_ref(peer_cfg); @@ -406,7 +408,8 @@ METHOD(task_t, build_i, status_t, if (cfg) { idr = cfg->get(cfg, AUTH_RULE_IDENTITY); - if (idr && !idr->contains_wildcards(idr)) + if (!cfg->get(cfg, AUTH_RULE_IDENTITY_LOOSE) && idr && + !idr->contains_wildcards(idr)) { this->ike_sa->set_other_id(this->ike_sa, idr->clone(idr)); id_payload = id_payload_create_from_identification( @@ -433,7 +436,8 @@ METHOD(task_t, build_i, status_t, 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) + this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NO && + this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NEVER) { host_t *host; @@ -1033,7 +1037,7 @@ peer_auth_failed: METHOD(task_t, get_type, task_type_t, private_ike_auth_t *this) { - return IKE_AUTHENTICATE; + return TASK_IKE_AUTH; } METHOD(task_t, migrate, void, @@ -1104,4 +1108,3 @@ ike_auth_t *ike_auth_create(ike_sa_t *ike_sa, bool initiator) } return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_auth.h b/src/libcharon/sa/ikev2/tasks/ike_auth.h index 132907941..ca864a710 100644 --- a/src/libcharon/sa/tasks/ike_auth.h +++ b/src/libcharon/sa/ikev2/tasks/ike_auth.h @@ -15,7 +15,7 @@ /** * @defgroup ike_auth ike_auth - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_AUTH_H_ @@ -25,7 +25,7 @@ typedef struct ike_auth_t ike_auth_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_auth, authenticates an IKE_SA using authenticators. @@ -46,7 +46,7 @@ struct ike_auth_t { }; /** - * Create a new task of type IKE_AUTHENTICATE. + * Create a new task of type TASK_IKE_AUTH. * * @param ike_sa IKE_SA this task works for * @param initiator TRUE if task is the initiator of an exchange diff --git a/src/libcharon/sa/tasks/ike_auth_lifetime.c b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c index a57cfd075..a7d162e68 100644 --- a/src/libcharon/sa/tasks/ike_auth_lifetime.c +++ b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c @@ -124,7 +124,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_auth_lifetime_t *this) { - return IKE_AUTH_LIFETIME; + return TASK_IKE_AUTH_LIFETIME; } METHOD(task_t, migrate, void, @@ -170,4 +170,3 @@ ike_auth_lifetime_t *ike_auth_lifetime_create(ike_sa_t *ike_sa, bool initiator) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_auth_lifetime.h b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.h index 3b129b9e3..4d5087ff5 100644 --- a/src/libcharon/sa/tasks/ike_auth_lifetime.h +++ b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.h @@ -15,7 +15,7 @@ /** * @defgroup ike_auth_lifetime ike_auth_lifetime - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_AUTH_LIFETIME_H_ @@ -25,10 +25,10 @@ typedef struct ike_auth_lifetime_t ike_auth_lifetime_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type IKE_AUTH_LIFETIME, implements RFC4478. + * Task of type TASK_IKE_AUTH_LIFETIME, implements RFC4478. * * This task exchanges lifetimes for IKE_AUTH to force a client to * reauthenticate before the responders lifetime reaches the limit. @@ -42,7 +42,7 @@ struct ike_auth_lifetime_t { }; /** - * Create a new IKE_AUTH_LIFETIME task. + * Create a new TASK_IKE_AUTH_LIFETIME task. * * @param ike_sa IKE_SA this task works for * @param initiator TRUE if taks is initiated by us @@ -50,4 +50,4 @@ struct ike_auth_lifetime_t { */ ike_auth_lifetime_t *ike_auth_lifetime_create(ike_sa_t *ike_sa, bool initiator); -#endif /** IKE_MOBIKE_H_ @}*/ +#endif /** IKE_AUTH_LIFETIME_H_ @}*/ diff --git a/src/libcharon/sa/tasks/ike_cert_post.c b/src/libcharon/sa/ikev2/tasks/ike_cert_post.c index 94af50eae..a93e5137e 100644 --- a/src/libcharon/sa/tasks/ike_cert_post.c +++ b/src/libcharon/sa/ikev2/tasks/ike_cert_post.c @@ -62,14 +62,14 @@ static cert_payload_t *build_cert_payload(private_ike_cert_post_t *this, if (!this->ike_sa->supports_extension(this->ike_sa, EXT_HASH_AND_URL)) { - return cert_payload_create_from_cert(cert); + return cert_payload_create_from_cert(CERTIFICATE, cert); } hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); if (!hasher) { DBG1(DBG_IKE, "unable to use hash-and-url: sha1 not supported"); - return cert_payload_create_from_cert(cert); + return cert_payload_create_from_cert(CERTIFICATE, cert); } if (!cert->get_encoding(cert, CERT_ASN1_DER, &encoded)) @@ -78,7 +78,12 @@ static cert_payload_t *build_cert_payload(private_ike_cert_post_t *this, hasher->destroy(hasher); return NULL; } - hasher->allocate_hash(hasher, encoded, &hash); + if (!hasher->allocate_hash(hasher, encoded, &hash)) + { + hasher->destroy(hasher); + chunk_free(&encoded); + return cert_payload_create_from_cert(CERTIFICATE, cert); + } chunk_free(&encoded); hasher->destroy(hasher); id = identification_create_from_encoding(ID_KEY_ID, hash); @@ -91,7 +96,7 @@ static cert_payload_t *build_cert_payload(private_ike_cert_post_t *this, } else { - payload = cert_payload_create_from_cert(cert); + payload = cert_payload_create_from_cert(CERTIFICATE, cert); } enumerator->destroy(enumerator); chunk_free(&hash); @@ -154,7 +159,7 @@ static void build_certs(private_ike_cert_post_t *this, message_t *message) { if (type == AUTH_RULE_IM_CERT) { - payload = cert_payload_create_from_cert(cert); + payload = cert_payload_create_from_cert(CERTIFICATE, cert); if (payload) { DBG1(DBG_IKE, "sending issuer cert \"%Y\"", @@ -207,7 +212,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_cert_post_t *this) { - return IKE_CERT_POST; + return TASK_IKE_CERT_POST; } METHOD(task_t, migrate, void, @@ -254,4 +259,3 @@ ike_cert_post_t *ike_cert_post_create(ike_sa_t *ike_sa, bool initiator) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_cert_post.h b/src/libcharon/sa/ikev2/tasks/ike_cert_post.h index b3881a01a..34606b1e8 100644 --- a/src/libcharon/sa/tasks/ike_cert_post.h +++ b/src/libcharon/sa/ikev2/tasks/ike_cert_post.h @@ -15,7 +15,7 @@ /** * @defgroup ike_cert_post ike_cert_post - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_CERT_POST_H_ @@ -25,7 +25,7 @@ typedef struct ike_cert_post_t ike_cert_post_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_cert_post, certificate processing after authentication. diff --git a/src/libcharon/sa/tasks/ike_cert_pre.c b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c index b33aebe46..60e878777 100644 --- a/src/libcharon/sa/tasks/ike_cert_pre.c +++ b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c @@ -398,7 +398,8 @@ static void build_certreqs(private_ike_cert_pre_t *this, message_t *message) { message->add_payload(message, (payload_t*)req); - if (lib->settings->get_bool(lib->settings, "charon.hash_and_url", FALSE)) + if (lib->settings->get_bool(lib->settings, + "%s.hash_and_url", FALSE, charon->name)) { message->add_notify(message, FALSE, HTTP_CERT_LOOKUP_SUPPORTED, chunk_empty); @@ -479,7 +480,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_cert_pre_t *this) { - return IKE_CERT_PRE; + return TASK_IKE_CERT_PRE; } METHOD(task_t, migrate, void, diff --git a/src/libcharon/sa/tasks/ike_cert_pre.h b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.h index 4b2d0d470..ac1a85c29 100644 --- a/src/libcharon/sa/tasks/ike_cert_pre.h +++ b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.h @@ -15,7 +15,7 @@ /** * @defgroup ike_cert_pre ike_cert_pre - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_CERT_PRE_H_ @@ -25,7 +25,7 @@ typedef struct ike_cert_pre_t ike_cert_pre_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_cert_post, certificate processing before authentication. diff --git a/src/libcharon/sa/tasks/ike_config.c b/src/libcharon/sa/ikev2/tasks/ike_config.c index 4ef9c56a5..c44f0452c 100644 --- a/src/libcharon/sa/tasks/ike_config.c +++ b/src/libcharon/sa/ikev2/tasks/ike_config.c @@ -43,9 +43,9 @@ struct private_ike_config_t { bool initiator; /** - * virtual ip + * Received list of virtual IPs, host_t* */ - host_t *virtual_ip; + linked_list_t *vips; /** * list of attributes requested and its handler, entry_t @@ -98,7 +98,8 @@ static configuration_attribute_t *build_vip(host_t *vip) chunk = chunk_cata("cc", chunk, prefix); } } - return configuration_attribute_create_value(type, chunk); + return configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE, + type, chunk); } /** @@ -128,11 +129,11 @@ static void handle_attribute(private_ike_config_t *this, /* and pass it to the handle function */ handler = hydra->attributes->handle(hydra->attributes, this->ike_sa->get_other_id(this->ike_sa), handler, - ca->get_type(ca), ca->get_value(ca)); + ca->get_type(ca), ca->get_chunk(ca)); if (handler) { this->ike_sa->add_configuration_attribute(this->ike_sa, - handler, ca->get_type(ca), ca->get_value(ca)); + handler, ca->get_type(ca), ca->get_chunk(ca)); } } @@ -153,7 +154,7 @@ static void process_attribute(private_ike_config_t *this, /* fall */ case INTERNAL_IP6_ADDRESS: { - addr = ca->get_value(ca); + addr = ca->get_chunk(ca); if (addr.len == 0) { ip = host_create_any(family); @@ -169,8 +170,7 @@ static void process_attribute(private_ike_config_t *this, } if (ip) { - DESTROY_IF(this->virtual_ip); - this->virtual_ip = ip; + this->vips->insert_last(this->vips, ip); } break; } @@ -241,23 +241,45 @@ METHOD(task_t, build_i, status_t, peer_cfg_t *config; configuration_attribute_type_t type; chunk_t data; - host_t *vip; + linked_list_t *vips; + host_t *host; + + vips = linked_list_create(); /* reuse virtual IP if we already have one */ - vip = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE); - if (!vip) + enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, + TRUE); + while (enumerator->enumerate(enumerator, &host)) + { + vips->insert_last(vips, host); + } + enumerator->destroy(enumerator); + + if (vips->get_count(vips) == 0) { config = this->ike_sa->get_peer_cfg(this->ike_sa); - vip = config->get_virtual_ip(config); + enumerator = config->create_virtual_ip_enumerator(config); + while (enumerator->enumerate(enumerator, &host)) + { + vips->insert_last(vips, host); + } + enumerator->destroy(enumerator); } - if (vip) + + if (vips->get_count(vips)) { - cp = cp_payload_create_type(CFG_REQUEST); - cp->add_attribute(cp, build_vip(vip)); + cp = cp_payload_create_type(CONFIGURATION, CFG_REQUEST); + enumerator = vips->create_enumerator(vips); + while (enumerator->enumerate(enumerator, &host)) + { + cp->add_attribute(cp, build_vip(host)); + } + enumerator->destroy(enumerator); } - enumerator = hydra->attributes->create_initiator_enumerator(hydra->attributes, - this->ike_sa->get_other_id(this->ike_sa), vip); + enumerator = hydra->attributes->create_initiator_enumerator( + hydra->attributes, + this->ike_sa->get_other_id(this->ike_sa), vips); while (enumerator->enumerate(enumerator, &handler, &type, &data)) { configuration_attribute_t *ca; @@ -266,10 +288,11 @@ METHOD(task_t, build_i, status_t, /* create configuration attribute */ DBG2(DBG_IKE, "building %N attribute", configuration_attribute_type_names, type); - ca = configuration_attribute_create_value(type, data); + ca = configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE, + type, data); if (!cp) { - cp = cp_payload_create_type(CFG_REQUEST); + cp = cp_payload_create_type(CONFIGURATION, CFG_REQUEST); } cp->add_attribute(cp, ca); @@ -282,6 +305,8 @@ METHOD(task_t, build_i, status_t, } enumerator->destroy(enumerator); + vips->destroy(vips); + if (cp) { message->add_payload(message, (payload_t*)cp); @@ -308,58 +333,92 @@ METHOD(task_t, build_r, status_t, enumerator_t *enumerator; configuration_attribute_type_t type; chunk_t value; - host_t *vip = NULL; cp_payload_t *cp = NULL; peer_cfg_t *config; identification_t *id; + linked_list_t *vips, *pools; + host_t *requested; id = this->ike_sa->get_other_eap_id(this->ike_sa); - config = this->ike_sa->get_peer_cfg(this->ike_sa); - if (this->virtual_ip) + vips = linked_list_create(); + pools = linked_list_create_from_enumerator( + config->create_pool_enumerator(config)); + + this->ike_sa->clear_virtual_ips(this->ike_sa, FALSE); + + enumerator = this->vips->create_enumerator(this->vips); + while (enumerator->enumerate(enumerator, &requested)) { - DBG1(DBG_IKE, "peer requested virtual IP %H", this->virtual_ip); - if (config->get_pool(config)) + host_t *found = NULL; + + /* query all pools until we get an address */ + DBG1(DBG_IKE, "peer requested virtual IP %H", requested); + + found = hydra->attributes->acquire_address(hydra->attributes, + pools, id, requested); + if (found) { - vip = hydra->attributes->acquire_address(hydra->attributes, - config->get_pool(config), id, this->virtual_ip); + DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", found, id); + this->ike_sa->add_virtual_ip(this->ike_sa, FALSE, found); + if (!cp) + { + cp = cp_payload_create_type(CONFIGURATION, CFG_REPLY); + } + cp->add_attribute(cp, build_vip(found)); + vips->insert_last(vips, found); } - if (vip == NULL) + else { - DBG1(DBG_IKE, "no virtual IP found, sending %N", - notify_type_names, INTERNAL_ADDRESS_FAILURE); - message->add_notify(message, FALSE, INTERNAL_ADDRESS_FAILURE, - chunk_empty); - return SUCCESS; + DBG1(DBG_IKE, "no virtual IP found for %H requested by '%Y'", + requested, id); } - DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", vip, id); - this->ike_sa->set_virtual_ip(this->ike_sa, FALSE, vip); + } + enumerator->destroy(enumerator); - cp = cp_payload_create_type(CFG_REPLY); - cp->add_attribute(cp, build_vip(vip)); + if (this->vips->get_count(this->vips) && !vips->get_count(vips)) + { + DBG1(DBG_IKE, "no virtual IP found, sending %N", + notify_type_names, INTERNAL_ADDRESS_FAILURE); + message->add_notify(message, FALSE, INTERNAL_ADDRESS_FAILURE, + chunk_empty); + vips->destroy_offset(vips, offsetof(host_t, destroy)); + pools->destroy(pools); + return SUCCESS; + } + if (pools->get_count(pools) && !this->vips->get_count(this->vips)) + { + DBG1(DBG_IKE, "expected a virtual IP request, sending %N", + notify_type_names, FAILED_CP_REQUIRED); + message->add_notify(message, FALSE, FAILED_CP_REQUIRED, chunk_empty); + vips->destroy_offset(vips, offsetof(host_t, destroy)); + pools->destroy(pools); + return SUCCESS; } /* query registered providers for additional attributes to include */ enumerator = hydra->attributes->create_responder_enumerator( - hydra->attributes, config->get_pool(config), id, vip); + hydra->attributes, pools, id, vips); while (enumerator->enumerate(enumerator, &type, &value)) { if (!cp) { - cp = cp_payload_create_type(CFG_REPLY); + cp = cp_payload_create_type(CONFIGURATION, CFG_REPLY); } DBG2(DBG_IKE, "building %N attribute", configuration_attribute_type_names, type); cp->add_attribute(cp, - configuration_attribute_create_value(type, value)); + configuration_attribute_create_chunk(CONFIGURATION_ATTRIBUTE, + type, value)); } enumerator->destroy(enumerator); + vips->destroy_offset(vips, offsetof(host_t, destroy)); + pools->destroy(pools); if (cp) { message->add_payload(message, (payload_t*)cp); } - DESTROY_IF(vip); return SUCCESS; } return NEED_MORE; @@ -370,13 +429,22 @@ METHOD(task_t, process_i, status_t, { if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED) { /* in last IKE_AUTH exchange */ + enumerator_t *enumerator; + host_t *host; process_payloads(this, message); - if (this->virtual_ip) + this->ike_sa->clear_virtual_ips(this->ike_sa, TRUE); + + enumerator = this->vips->create_enumerator(this->vips); + while (enumerator->enumerate(enumerator, &host)) { - this->ike_sa->set_virtual_ip(this->ike_sa, TRUE, this->virtual_ip); + if (!host->is_anyaddr(host)) + { + this->ike_sa->add_virtual_ip(this->ike_sa, TRUE, host); + } } + enumerator->destroy(enumerator); return SUCCESS; } return NEED_MORE; @@ -385,16 +453,15 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_config_t *this) { - return IKE_CONFIG; + return TASK_IKE_CONFIG; } METHOD(task_t, migrate, void, private_ike_config_t *this, ike_sa_t *ike_sa) { - DESTROY_IF(this->virtual_ip); - this->ike_sa = ike_sa; - this->virtual_ip = NULL; + this->vips->destroy_offset(this->vips, offsetof(host_t, destroy)); + this->vips = linked_list_create(); this->requested->destroy_function(this->requested, free); this->requested = linked_list_create(); } @@ -402,7 +469,7 @@ METHOD(task_t, migrate, void, METHOD(task_t, destroy, void, private_ike_config_t *this) { - DESTROY_IF(this->virtual_ip); + this->vips->destroy_offset(this->vips, offsetof(host_t, destroy)); this->requested->destroy_function(this->requested, free); free(this); } @@ -424,6 +491,7 @@ ike_config_t *ike_config_create(ike_sa_t *ike_sa, bool initiator) }, .initiator = initiator, .ike_sa = ike_sa, + .vips = linked_list_create(), .requested = linked_list_create(), ); @@ -440,4 +508,3 @@ ike_config_t *ike_config_create(ike_sa_t *ike_sa, bool initiator) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_config.h b/src/libcharon/sa/ikev2/tasks/ike_config.h index 8cef08697..e35457645 100644 --- a/src/libcharon/sa/tasks/ike_config.h +++ b/src/libcharon/sa/ikev2/tasks/ike_config.h @@ -15,7 +15,7 @@ /** * @defgroup ike_config ike_config - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_CONFIG_H_ @@ -25,10 +25,10 @@ typedef struct ike_config_t ike_config_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type IKE_CONFIG, sets up a virtual IP and other + * Task of type TASK_IKE_CONFIG, sets up a virtual IP and other * configurations for an IKE_SA. */ struct ike_config_t { diff --git a/src/libcharon/sa/tasks/ike_delete.c b/src/libcharon/sa/ikev2/tasks/ike_delete.c index d79674fe4..f127b0c15 100644 --- a/src/libcharon/sa/tasks/ike_delete.c +++ b/src/libcharon/sa/ikev2/tasks/ike_delete.c @@ -65,7 +65,7 @@ METHOD(task_t, build_i, status_t, this->ike_sa->get_other_host(this->ike_sa), this->ike_sa->get_other_id(this->ike_sa)); - delete_payload = delete_payload_create(PROTO_IKE); + delete_payload = delete_payload_create(DELETE, PROTO_IKE); message->add_payload(message, (payload_t*)delete_payload); if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) @@ -149,7 +149,7 @@ METHOD(task_t, build_r, status_t, METHOD(task_t, get_type, task_type_t, private_ike_delete_t *this) { - return IKE_DELETE; + return TASK_IKE_DELETE; } METHOD(task_t, migrate, void, diff --git a/src/libcharon/sa/tasks/ike_delete.h b/src/libcharon/sa/ikev2/tasks/ike_delete.h index 82782f393..2d5d7cb3a 100644 --- a/src/libcharon/sa/tasks/ike_delete.h +++ b/src/libcharon/sa/ikev2/tasks/ike_delete.h @@ -15,7 +15,7 @@ /** * @defgroup ike_delete ike_delete - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_DELETE_H_ @@ -25,7 +25,7 @@ typedef struct ike_delete_t ike_delete_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_delete, delete an IKE_SA. diff --git a/src/libcharon/sa/tasks/ike_dpd.c b/src/libcharon/sa/ikev2/tasks/ike_dpd.c index 106eff87c..28ccc2efe 100644 --- a/src/libcharon/sa/tasks/ike_dpd.c +++ b/src/libcharon/sa/ikev2/tasks/ike_dpd.c @@ -46,7 +46,7 @@ METHOD(task_t, return_success, status_t, METHOD(task_t, get_type, task_type_t, private_ike_dpd_t *this) { - return IKE_DPD; + return TASK_IKE_DPD; } diff --git a/src/libcharon/sa/tasks/ike_dpd.h b/src/libcharon/sa/ikev2/tasks/ike_dpd.h index a9f68c31c..026871610 100644 --- a/src/libcharon/sa/tasks/ike_dpd.h +++ b/src/libcharon/sa/ikev2/tasks/ike_dpd.h @@ -15,7 +15,7 @@ /** * @defgroup ike_dpd ike_dpd - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_DPD_H_ @@ -25,7 +25,7 @@ typedef struct ike_dpd_t ike_dpd_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_dpd, detects dead peers. diff --git a/src/libcharon/sa/tasks/ike_init.c b/src/libcharon/sa/ikev2/tasks/ike_init.c index dd8a4b086..f2a06735e 100644 --- a/src/libcharon/sa/tasks/ike_init.c +++ b/src/libcharon/sa/ikev2/tasks/ike_init.c @@ -20,6 +20,7 @@ #include <string.h> #include <daemon.h> +#include <sa/ikev2/keymat_v2.h> #include <crypto/diffie_hellman.h> #include <encoding/payloads/sa_payload.h> #include <encoding/payloads/ke_payload.h> @@ -68,7 +69,7 @@ struct private_ike_init_t { /** * Keymat derivation (from IKE_SA) */ - keymat_t *keymat; + keymat_v2_t *keymat; /** * nonce chosen by us @@ -132,7 +133,7 @@ static void build_payloads(private_ike_init_t *this, message_t *message) enumerator->destroy(enumerator); } - sa_payload = sa_payload_create_from_proposal_list(proposal_list); + sa_payload = sa_payload_create_from_proposals_v2(proposal_list); proposal_list->destroy_offset(proposal_list, offsetof(proposal_t, destroy)); } else @@ -142,13 +143,13 @@ static void build_payloads(private_ike_init_t *this, message_t *message) /* include SPI of new IKE_SA when we are rekeying */ this->proposal->set_spi(this->proposal, id->get_responder_spi(id)); } - sa_payload = sa_payload_create_from_proposal(this->proposal); + sa_payload = sa_payload_create_from_proposal_v2(this->proposal); } message->add_payload(message, (payload_t*)sa_payload); - nonce_payload = nonce_payload_create(); + nonce_payload = nonce_payload_create(NONCE); nonce_payload->set_nonce(nonce_payload, this->my_nonce); - ke_payload = ke_payload_create_from_diffie_hellman(this->dh); + ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE, this->dh); if (this->old_sa) { /* payload order differs if we are rekeying */ @@ -197,8 +198,8 @@ static void process_payloads(private_ike_init_t *this, message_t *message) this->dh_group = ke_payload->get_dh_group_number(ke_payload); if (!this->initiator) { - this->dh = this->keymat->create_dh(this->keymat, - this->dh_group); + this->dh = this->keymat->keymat.create_dh( + &this->keymat->keymat, this->dh_group); } if (this->dh) { @@ -224,8 +225,6 @@ static void process_payloads(private_ike_init_t *this, message_t *message) METHOD(task_t, build_i, status_t, private_ike_init_t *this, message_t *message) { - rng_t *rng; - this->config = this->ike_sa->get_ike_cfg(this->ike_sa); DBG0(DBG_IKE, "initiating IKE_SA %s[%d] to %H", this->ike_sa->get_name(this->ike_sa), @@ -243,7 +242,8 @@ METHOD(task_t, build_i, status_t, if (!this->dh) { this->dh_group = this->config->get_dh_group(this->config); - this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + this->dh_group); if (!this->dh) { DBG1(DBG_IKE, "configured DH group %N not supported", @@ -255,14 +255,21 @@ METHOD(task_t, build_i, status_t, /* generate nonce only when we are trying the first time */ if (this->my_nonce.ptr == NULL) { - rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng) + nonce_gen_t *nonceg; + + nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); + if (!nonceg) + { + DBG1(DBG_IKE, "no nonce generator found to create nonce"); + return FAILED; + } + if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, &this->my_nonce)) { - DBG1(DBG_IKE, "error generating nonce"); + DBG1(DBG_IKE, "nonce allocation failed"); + nonceg->destroy(nonceg); return FAILED; } - rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce); - rng->destroy(rng); + nonceg->destroy(nonceg); } if (this->cookie.ptr) @@ -288,20 +295,25 @@ METHOD(task_t, build_i, status_t, METHOD(task_t, process_r, status_t, private_ike_init_t *this, message_t *message) { - rng_t *rng; + nonce_gen_t *nonceg; this->config = this->ike_sa->get_ike_cfg(this->ike_sa); DBG0(DBG_IKE, "%H is initiating an IKE_SA", message->get_source(message)); this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); - rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng) + nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); + if (!nonceg) + { + DBG1(DBG_IKE, "no nonce generator found to create nonce"); + return FAILED; + } + if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, &this->my_nonce)) { - DBG1(DBG_IKE, "error generating nonce"); + DBG1(DBG_IKE, "nonce allocation failed"); + nonceg->destroy(nonceg); return FAILED; } - rng->allocate_bytes(rng, NONCE_SIZE, &this->my_nonce); - rng->destroy(rng); + nonceg->destroy(nonceg); #ifdef ME { @@ -327,7 +339,7 @@ METHOD(task_t, process_r, status_t, static bool derive_keys(private_ike_init_t *this, chunk_t nonce_i, chunk_t nonce_r) { - keymat_t *old_keymat; + keymat_v2_t *old_keymat; pseudo_random_function_t prf_alg = PRF_UNDEFINED; chunk_t skd = chunk_empty; ike_sa_id_t *id; @@ -336,7 +348,7 @@ static bool derive_keys(private_ike_init_t *this, if (this->old_sa) { /* rekeying: Include old SKd, use old PRF, apply SPI */ - old_keymat = this->old_sa->get_keymat(this->old_sa); + old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa); prf_alg = old_keymat->get_skd(old_keymat, &skd); if (this->initiator) { @@ -352,8 +364,8 @@ static bool derive_keys(private_ike_init_t *this, { return FALSE; } - charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, - nonce_i, nonce_r, this->old_sa); + charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, chunk_empty, + nonce_i, nonce_r, this->old_sa, NULL); return TRUE; } @@ -505,7 +517,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_init_t *this) { - return IKE_INIT; + return TASK_IKE_INIT; } METHOD(task_t, migrate, void, @@ -515,12 +527,13 @@ METHOD(task_t, migrate, void, chunk_free(&this->other_nonce); this->ike_sa = ike_sa; - this->keymat = ike_sa->get_keymat(ike_sa); + this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa); this->proposal = NULL; if (this->dh && this->dh->get_dh_group(this->dh) != this->dh_group) { /* reset DH value only if group changed (INVALID_KE_PAYLOAD) */ this->dh->destroy(this->dh); - this->dh = this->keymat->create_dh(this->keymat, this->dh_group); + this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat, + this->dh_group); } } @@ -568,7 +581,7 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa) .ike_sa = ike_sa, .initiator = initiator, .dh_group = MODP_NONE, - .keymat = ike_sa->get_keymat(ike_sa), + .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa), .old_sa = old_sa, ); diff --git a/src/libcharon/sa/tasks/ike_init.h b/src/libcharon/sa/ikev2/tasks/ike_init.h index 4b7f60416..ab169954d 100644 --- a/src/libcharon/sa/tasks/ike_init.h +++ b/src/libcharon/sa/ikev2/tasks/ike_init.h @@ -15,7 +15,7 @@ /** * @defgroup ike_init ike_init - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_INIT_H_ @@ -25,10 +25,10 @@ typedef struct ike_init_t ike_init_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type IKE_INIT, creates an IKE_SA without authentication. + * Task of type TASK_IKE_INIT, creates an IKE_SA without authentication. * * The authentication of is handle in the ike_auth task. */ @@ -48,7 +48,7 @@ struct ike_init_t { }; /** - * Create a new IKE_INIT task. + * Create a new TASK_IKE_INIT task. * * @param ike_sa IKE_SA this task works for (new one when rekeying) * @param initiator TRUE if task is the original initiator diff --git a/src/libcharon/sa/tasks/ike_me.c b/src/libcharon/sa/ikev2/tasks/ike_me.c index 8f90efcc3..135c06d19 100644 --- a/src/libcharon/sa/tasks/ike_me.c +++ b/src/libcharon/sa/ikev2/tasks/ike_me.c @@ -136,7 +136,7 @@ static void gather_and_add_endpoints(private_ike_me_t *this, message_t *message) port = host->get_port(host); enumerator = hydra->kernel_interface->create_address_enumerator( - hydra->kernel_interface, FALSE, FALSE); + hydra->kernel_interface, ADDR_TYPE_REGULAR); while (enumerator->enumerate(enumerator, (void**)&addr)) { host = addr->clone(addr); @@ -291,9 +291,21 @@ METHOD(task_t, build_i, status_t, { /* only the initiator creates a connect ID. the responder * returns the connect ID that it received from the initiator */ - rng->allocate_bytes(rng, ME_CONNECTID_LEN, &this->connect_id); + if (!rng->allocate_bytes(rng, ME_CONNECTID_LEN, + &this->connect_id)) + { + DBG1(DBG_IKE, "unable to generate ID for ME_CONNECT"); + rng->destroy(rng); + return FAILED; + } + } + if (!rng->allocate_bytes(rng, ME_CONNECTKEY_LEN, + &this->connect_key)) + { + DBG1(DBG_IKE, "unable to generate connect key for ME_CONNECT"); + rng->destroy(rng); + return FAILED; } - rng->allocate_bytes(rng, ME_CONNECTKEY_LEN, &this->connect_key); rng->destroy(rng); message->add_notify(message, FALSE, ME_CONNECTID, this->connect_id); @@ -750,7 +762,7 @@ METHOD(ike_me_t, relay, void, METHOD(task_t, get_type, task_type_t, private_ike_me_t *this) { - return IKE_ME; + return TASK_IKE_ME; } METHOD(task_t, migrate, void, diff --git a/src/libcharon/sa/tasks/ike_me.h b/src/libcharon/sa/ikev2/tasks/ike_me.h index 31285a426..44a4ce69c 100644 --- a/src/libcharon/sa/tasks/ike_me.h +++ b/src/libcharon/sa/ikev2/tasks/ike_me.h @@ -15,7 +15,7 @@ /** * @defgroup ike_me ike_me - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_ME_H_ @@ -25,10 +25,10 @@ typedef struct ike_me_t ike_me_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type IKE_ME, detects and handles IKE-ME extensions. + * Task of type TASK_IKE_ME, detects and handles IKE-ME extensions. * * This tasks handles the ME_MEDIATION Notify exchange to setup a mediation * connection, allows to initiate mediated connections using ME_CONNECT diff --git a/src/libcharon/sa/tasks/ike_mobike.c b/src/libcharon/sa/ikev2/tasks/ike_mobike.c index fb1100028..ae3526f42 100644 --- a/src/libcharon/sa/tasks/ike_mobike.c +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.c @@ -20,7 +20,7 @@ #include <hydra.h> #include <daemon.h> -#include <sa/tasks/ike_natd.h> +#include <sa/ikev2/tasks/ike_natd.h> #include <encoding/payloads/notify_payload.h> #define COOKIE2_SIZE 16 @@ -54,7 +54,7 @@ struct private_ike_mobike_t { chunk_t cookie2; /** - * NAT discovery reusing the IKE_NATD task + * NAT discovery reusing the TASK_IKE_NATD task */ ike_natd_t *natd; @@ -192,7 +192,7 @@ static void build_address_list(private_ike_mobike_t *this, message_t *message) me = this->ike_sa->get_my_host(this->ike_sa); enumerator = hydra->kernel_interface->create_address_enumerator( - hydra->kernel_interface, FALSE, FALSE); + hydra->kernel_interface, ADDR_TYPE_REGULAR); while (enumerator->enumerate(enumerator, (void**)&host)) { if (me->ip_equals(me, host)) @@ -227,18 +227,20 @@ static void build_address_list(private_ike_mobike_t *this, message_t *message) /** * build a cookie and add it to the message */ -static void build_cookie(private_ike_mobike_t *this, message_t *message) +static bool build_cookie(private_ike_mobike_t *this, message_t *message) { rng_t *rng; chunk_free(&this->cookie2); rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); - if (rng) + if (!rng || !rng->allocate_bytes(rng, COOKIE2_SIZE, &this->cookie2)) { - rng->allocate_bytes(rng, COOKIE2_SIZE, &this->cookie2); - rng->destroy(rng); - message->add_notify(message, FALSE, COOKIE2, this->cookie2); + DESTROY_IF(rng); + return FALSE; } + message->add_notify(message, FALSE, COOKIE2, this->cookie2); + rng->destroy(rng); + return TRUE; } /** @@ -248,15 +250,26 @@ static void update_children(private_ike_mobike_t *this) { enumerator_t *enumerator; child_sa_t *child_sa; + linked_list_t *vips; + host_t *host; + + vips = linked_list_create(); + + enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, TRUE); + while (enumerator->enumerate(enumerator, &host)) + { + vips->insert_last(vips, host); + } + enumerator->destroy(enumerator); enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { if (child_sa->update(child_sa, this->ike_sa->get_my_host(this->ike_sa), - this->ike_sa->get_other_host(this->ike_sa), - this->ike_sa->get_virtual_ip(this->ike_sa, TRUE), - this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) == NOT_SUPPORTED) + this->ike_sa->get_other_host(this->ike_sa), vips, + this->ike_sa->has_condition(this->ike_sa, + COND_NAT_ANY)) == NOT_SUPPORTED) { this->ike_sa->rekey_child_sa(this->ike_sa, child_sa->get_protocol(child_sa), @@ -264,18 +277,24 @@ static void update_children(private_ike_mobike_t *this) } } enumerator->destroy(enumerator); + + vips->destroy(vips); } /** * Apply the port of the old host, if its ip equals the new, use port otherwise. */ -static void apply_port(host_t *host, host_t *old, u_int16_t port) +static void apply_port(host_t *host, host_t *old, u_int16_t port, bool local) { if (host->ip_equals(host, old)) { port = old->get_port(old); } - else if (port == IKEV2_UDP_PORT) + else if (local && port == charon->socket->get_port(charon->socket, FALSE)) + { + port = charon->socket->get_port(charon->socket, TRUE); + } + else if (!local && port == IKEV2_UDP_PORT) { port = IKEV2_NATT_PORT; } @@ -312,9 +331,9 @@ METHOD(ike_mobike_t, transmit, void, continue; } /* reuse port for an active address, 4500 otherwise */ - apply_port(me, me_old, ike_cfg->get_my_port(ike_cfg)); + apply_port(me, me_old, ike_cfg->get_my_port(ike_cfg), TRUE); other = other->clone(other); - apply_port(other, other_old, ike_cfg->get_other_port(ike_cfg)); + apply_port(other, other_old, ike_cfg->get_other_port(ike_cfg), FALSE); DBG1(DBG_IKE, "checking path %#H - %#H", me, other); copy = packet->clone(packet); copy->set_source(copy, me); @@ -358,7 +377,10 @@ METHOD(task_t, build_i, status_t, { message->add_notify(message, FALSE, UPDATE_SA_ADDRESSES, chunk_empty); - build_cookie(this, message); + if (!build_cookie(this, message)) + { + return FAILED; + } update_children(this); } if (this->address && !this->check) @@ -584,7 +606,7 @@ METHOD(ike_mobike_t, is_probing, bool, METHOD(task_t, get_type, task_type_t, private_ike_mobike_t *this) { - return IKE_MOBIKE; + return TASK_IKE_MOBIKE; } METHOD(task_t, migrate, void, @@ -646,4 +668,3 @@ ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_mobike.h b/src/libcharon/sa/ikev2/tasks/ike_mobike.h index 16611939e..3b447af51 100644 --- a/src/libcharon/sa/tasks/ike_mobike.h +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.h @@ -15,7 +15,7 @@ /** * @defgroup ike_mobike ike_mobike - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_MOBIKE_H_ @@ -25,8 +25,8 @@ typedef struct ike_mobike_t ike_mobike_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> -#include <network/packet.h> +#include <sa/task.h> +#include <utils/packet.h> /** * Task of type ike_mobike, detects and handles MOBIKE extension. diff --git a/src/libcharon/sa/tasks/ike_natd.c b/src/libcharon/sa/ikev2/tasks/ike_natd.c index f06a518fa..0a93db9ed 100644 --- a/src/libcharon/sa/tasks/ike_natd.c +++ b/src/libcharon/sa/ikev2/tasks/ike_natd.c @@ -104,7 +104,10 @@ static chunk_t generate_natd_hash(private_ike_natd_t *this, /* natd_hash = SHA1( spi_i | spi_r | address | port ) */ natd_chunk = chunk_cat("cccc", spi_i_chunk, spi_r_chunk, addr_chunk, port_chunk); - this->hasher->allocate_hash(this->hasher, natd_chunk, &natd_hash); + if (!this->hasher->allocate_hash(this->hasher, natd_chunk, &natd_hash)) + { + natd_hash = chunk_empty; + } DBG3(DBG_IKE, "natd_chunk %B", &natd_chunk); DBG3(DBG_IKE, "natd_hash %B", &natd_hash); @@ -121,12 +124,12 @@ static chunk_t generate_natd_hash_faked(private_ike_natd_t *this) chunk_t chunk; rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); - if (!rng) + if (!rng || !rng->allocate_bytes(rng, HASH_SIZE_SHA1, &chunk)) { DBG1(DBG_IKE, "unable to get random bytes for NATD fake"); + DESTROY_IF(rng); return chunk_empty; } - rng->allocate_bytes(rng, HASH_SIZE_SHA1, &chunk); rng->destroy(rng); return chunk; } @@ -152,7 +155,11 @@ static notify_payload_t *build_natd_payload(private_ike_natd_t *this, { hash = generate_natd_hash(this, ike_sa_id, host); } - notify = notify_payload_create(); + if (!hash.len) + { + return NULL; + } + notify = notify_payload_create(NOTIFY); notify->set_notify_type(notify, type); notify->set_notification_data(notify, hash); chunk_free(&hash); @@ -298,7 +305,10 @@ METHOD(task_t, build_i, status_t, /* destination is always set */ host = message->get_destination(message); notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, host); - message->add_payload(message, (payload_t*)notify); + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } /* source may be any, we have 3 possibilities to get our source address: * 1. It is defined in the config => use the one of the IKE_SA @@ -306,10 +316,13 @@ METHOD(task_t, build_i, status_t, * 3. Include all possbile addresses */ host = message->get_source(message); - if (!host->is_anyaddr(host)) - { /* 1. */ + if (!host->is_anyaddr(host) || ike_cfg->force_encap(ike_cfg)) + { /* 1. or if we force UDP encap, as it doesn't matter if it's %any */ notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); - message->add_payload(message, (payload_t*)notify); + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } } else { @@ -319,13 +332,16 @@ METHOD(task_t, build_i, status_t, { /* 2. */ host->set_port(host, ike_cfg->get_my_port(ike_cfg)); notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); - message->add_payload(message, (payload_t*)notify); + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } host->destroy(host); } else { /* 3. */ enumerator = hydra->kernel_interface->create_address_enumerator( - hydra->kernel_interface, FALSE, FALSE); + hydra->kernel_interface, ADDR_TYPE_REGULAR); while (enumerator->enumerate(enumerator, (void**)&host)) { /* apply port 500 to host, but work on a copy */ @@ -333,7 +349,10 @@ METHOD(task_t, build_i, status_t, host->set_port(host, ike_cfg->get_my_port(ike_cfg)); notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); host->destroy(host); - message->add_payload(message, (payload_t*)notify); + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } } enumerator->destroy(enumerator); } @@ -365,11 +384,16 @@ METHOD(task_t, build_r, status_t, /* initiator seems to support NAT detection, add response */ me = message->get_source(message); notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, me); - message->add_payload(message, (payload_t*)notify); - + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } other = message->get_destination(message); notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, other); - message->add_payload(message, (payload_t*)notify); + if (notify) + { + message->add_payload(message, (payload_t*)notify); + } } return SUCCESS; } @@ -385,7 +409,7 @@ METHOD(task_t, process_r, status_t, METHOD(task_t, get_type, task_type_t, private_ike_natd_t *this) { - return IKE_NATD; + return TASK_IKE_NATD; } METHOD(task_t, migrate, void, diff --git a/src/libcharon/sa/tasks/ike_natd.h b/src/libcharon/sa/ikev2/tasks/ike_natd.h index 68114af42..9c571b8e6 100644 --- a/src/libcharon/sa/tasks/ike_natd.h +++ b/src/libcharon/sa/ikev2/tasks/ike_natd.h @@ -15,7 +15,7 @@ /** * @defgroup ike_natd ike_natd - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_NATD_H_ @@ -25,7 +25,7 @@ typedef struct ike_natd_t ike_natd_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_natd, detects NAT situation in IKE_SA_INIT exchange. @@ -42,7 +42,7 @@ struct ike_natd_t { * * MOBIKE uses NAT payloads in DPD to detect changes in the NAT mappings. * - * @return TRUE if mappings have changed + * @return TRUE if mappings have changed */ bool (*has_mapping_changed)(ike_natd_t *this); }; diff --git a/src/libcharon/sa/tasks/ike_reauth.c b/src/libcharon/sa/ikev2/tasks/ike_reauth.c index 48002d81c..6f90339ea 100644 --- a/src/libcharon/sa/tasks/ike_reauth.c +++ b/src/libcharon/sa/ikev2/tasks/ike_reauth.c @@ -16,7 +16,7 @@ #include "ike_reauth.h" #include <daemon.h> -#include <sa/tasks/ike_delete.h> +#include <sa/ikev2/tasks/ike_delete.h> typedef struct private_ike_reauth_t private_ike_reauth_t; @@ -68,7 +68,7 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_reauth_t *this) { - return IKE_REAUTH; + return TASK_IKE_REAUTH; } METHOD(task_t, migrate, void, @@ -108,4 +108,3 @@ ike_reauth_t *ike_reauth_create(ike_sa_t *ike_sa) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_reauth.h b/src/libcharon/sa/ikev2/tasks/ike_reauth.h index 5e97b719c..781b463a7 100644 --- a/src/libcharon/sa/tasks/ike_reauth.h +++ b/src/libcharon/sa/ikev2/tasks/ike_reauth.h @@ -15,7 +15,7 @@ /** * @defgroup ike_reauth ike_reauth - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_REAUTH_H_ @@ -25,7 +25,7 @@ typedef struct ike_reauth_t ike_reauth_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Task of type ike_reauth, reestablishes an IKE_SA. diff --git a/src/libcharon/sa/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c index 826d6e192..c3c6cf00e 100644 --- a/src/libcharon/sa/tasks/ike_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c @@ -18,8 +18,8 @@ #include <daemon.h> #include <encoding/payloads/notify_payload.h> -#include <sa/tasks/ike_init.h> -#include <sa/tasks/ike_delete.h> +#include <sa/ikev2/tasks/ike_init.h> +#include <sa/ikev2/tasks/ike_delete.h> #include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/rekey_ike_sa_job.h> @@ -52,7 +52,7 @@ struct private_ike_rekey_t { bool initiator; /** - * the IKE_INIT task which is reused to simplify rekeying + * the TASK_IKE_INIT task which is reused to simplify rekeying */ ike_init_t *ike_init; @@ -123,15 +123,20 @@ METHOD(task_t, process_i_delete, status_t, METHOD(task_t, build_i, status_t, private_ike_rekey_t *this, message_t *message) { + ike_version_t version; peer_cfg_t *peer_cfg; host_t *other_host; /* create new SA only on first try */ if (this->new_sa == NULL) { - this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, - TRUE); - + version = this->ike_sa->get_version(this->ike_sa); + this->new_sa = charon->ike_sa_manager->checkout_new( + charon->ike_sa_manager, version, TRUE); + if (!this->new_sa) + { /* shouldn't happen */ + return FAILED; + } peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); other_host = this->ike_sa->get_other_host(this->ike_sa); this->new_sa->set_peer_cfg(this->new_sa, peer_cfg); @@ -176,7 +181,11 @@ METHOD(task_t, process_r, status_t, enumerator->destroy(enumerator); this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, - FALSE); + this->ike_sa->get_version(this->ike_sa), FALSE); + if (!this->new_sa) + { /* shouldn't happen */ + return FAILED; + } peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); this->new_sa->set_peer_cfg(this->new_sa, peer_cfg); @@ -230,8 +239,8 @@ METHOD(task_t, process_i, status_t, case FAILED: /* rekeying failed, fallback to old SA */ if (!(this->collision && ( - this->collision->get_type(this->collision) == IKE_DELETE || - this->collision->get_type(this->collision) == IKE_REAUTH))) + this->collision->get_type(this->collision) == TASK_IKE_DELETE || + this->collision->get_type(this->collision) == TASK_IKE_REAUTH))) { job_t *job; u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER); @@ -253,7 +262,7 @@ METHOD(task_t, process_i, status_t, /* check for collisions */ if (this->collision && - this->collision->get_type(this->collision) == IKE_REKEY) + this->collision->get_type(this->collision) == TASK_IKE_REKEY) { private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision; @@ -323,14 +332,14 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, get_type, task_type_t, private_ike_rekey_t *this) { - return IKE_REKEY; + return TASK_IKE_REKEY; } METHOD(ike_rekey_t, collide, void, private_ike_rekey_t* this, task_t *other) { - DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, IKE_REKEY, - task_type_names, other->get_type(other)); + DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, + TASK_IKE_REKEY, task_type_names, other->get_type(other)); DESTROY_IF(this->collision); this->collision = other; } diff --git a/src/libcharon/sa/tasks/ike_rekey.h b/src/libcharon/sa/ikev2/tasks/ike_rekey.h index 1c9550768..6a12e9034 100644 --- a/src/libcharon/sa/tasks/ike_rekey.h +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.h @@ -15,7 +15,7 @@ /** * @defgroup ike_rekey ike_rekey - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_REKEY_H_ @@ -25,10 +25,10 @@ typedef struct ike_rekey_t ike_rekey_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** - * Task of type IKE_REKEY, rekey an established IKE_SA. + * Task of type TASK_IKE_REKEY, rekey an established IKE_SA. */ struct ike_rekey_t { @@ -50,11 +50,11 @@ struct ike_rekey_t { }; /** - * Create a new IKE_REKEY task. + * Create a new TASK_IKE_REKEY task. * * @param ike_sa IKE_SA this task works for * @param initiator TRUE for initiator, FALSE for responder - * @return IKE_REKEY task to handle by the task_manager + * @return TASK_IKE_REKEY task to handle by the task_manager */ ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator); diff --git a/src/libcharon/sa/tasks/ike_vendor.c b/src/libcharon/sa/ikev2/tasks/ike_vendor.c index 1c14ee06b..2730f5876 100644 --- a/src/libcharon/sa/tasks/ike_vendor.c +++ b/src/libcharon/sa/ikev2/tasks/ike_vendor.c @@ -53,11 +53,12 @@ METHOD(task_t, build, status_t, private_ike_vendor_t *this, message_t *message) { if (lib->settings->get_bool(lib->settings, - "charon.send_vendor_id", FALSE)) + "%s.send_vendor_id", FALSE, charon->name)) { vendor_id_payload_t *vid; - vid = vendor_id_payload_create_data(chunk_clone(strongswan_vid)); + vid = vendor_id_payload_create_data(VENDOR_ID, + chunk_clone(strongswan_vid)); message->add_payload(message, &vid->payload_interface); } @@ -83,12 +84,12 @@ METHOD(task_t, process, status_t, if (chunk_equals(data, strongswan_vid)) { - DBG1(DBG_IKE, "received strongSwan vendor id"); + DBG1(DBG_IKE, "received strongSwan vendor ID"); this->ike_sa->enable_extension(this->ike_sa, EXT_STRONGSWAN); } else { - DBG1(DBG_ENC, "received unknown vendor id: %#B", &data); + DBG1(DBG_ENC, "received unknown vendor ID: %#B", &data); } } } @@ -106,7 +107,7 @@ METHOD(task_t, migrate, void, METHOD(task_t, get_type, task_type_t, private_ike_vendor_t *this) { - return IKE_VENDOR; + return TASK_IKE_VENDOR; } METHOD(task_t, destroy, void, @@ -138,4 +139,3 @@ ike_vendor_t *ike_vendor_create(ike_sa_t *ike_sa, bool initiator) return &this->public; } - diff --git a/src/libcharon/sa/tasks/ike_vendor.h b/src/libcharon/sa/ikev2/tasks/ike_vendor.h index 6c353c447..86c711636 100644 --- a/src/libcharon/sa/tasks/ike_vendor.h +++ b/src/libcharon/sa/ikev2/tasks/ike_vendor.h @@ -15,7 +15,7 @@ /** * @defgroup ike_vendor ike_vendor - * @{ @ingroup tasks + * @{ @ingroup tasks_v2 */ #ifndef IKE_VENDOR_H_ @@ -25,7 +25,7 @@ typedef struct ike_vendor_t ike_vendor_t; #include <library.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * Vendor ID processing task. diff --git a/src/libcharon/sa/keymat.c b/src/libcharon/sa/keymat.c index d762fa34e..26c305f77 100644 --- a/src/libcharon/sa/keymat.c +++ b/src/libcharon/sa/keymat.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Martin Willi + * Copyright (C) 2011 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -15,621 +15,112 @@ #include "keymat.h" -#include <daemon.h> -#include <crypto/prf_plus.h> +#include <sa/ikev1/keymat_v1.h> +#include <sa/ikev2/keymat_v2.h> -typedef struct private_keymat_t private_keymat_t; +static keymat_constructor_t keymat_v1_ctor = NULL, keymat_v2_ctor = NULL; /** - * Private data of an keymat_t object. + * See header */ -struct private_keymat_t { - - /** - * Public keymat_t interface. - */ - keymat_t public; - - /** - * IKE_SA Role, initiator or responder - */ - bool initiator; - - /** - * inbound AEAD - */ - aead_t *aead_in; - - /** - * outbound AEAD - */ - aead_t *aead_out; - - /** - * General purpose PRF - */ - prf_t *prf; - - /** - * Negotiated PRF algorithm - */ - pseudo_random_function_t prf_alg; - - /** - * Key to derive key material from for CHILD_SAs, rekeying - */ - chunk_t skd; - - /** - * Key to build outging authentication data (SKp) - */ - chunk_t skp_build; - - /** - * Key to verify incoming authentication data (SKp) - */ - chunk_t skp_verify; -}; +keymat_t *keymat_create(ike_version_t version, bool initiator) +{ + keymat_t *keymat = NULL; -typedef struct keylen_entry_t keylen_entry_t; + switch (version) + { + case IKEV1: +#ifdef USE_IKEV1 + keymat = keymat_v1_ctor ? keymat_v1_ctor(initiator) + : &keymat_v1_create(initiator)->keymat; +#endif + break; + case IKEV2: +#ifdef USE_IKEV2 + keymat = keymat_v2_ctor ? keymat_v2_ctor(initiator) + : &keymat_v2_create(initiator)->keymat; +#endif + break; + default: + break; + } + return keymat; +} /** * Implicit key length for an algorithm */ -struct keylen_entry_t { +typedef struct { /** IKEv2 algorithm identifier */ - int algo; + int alg; /** key length in bits */ int len; -}; - -#define END_OF_LIST -1 - -/** - * Keylen for encryption algos - */ -keylen_entry_t keylen_enc[] = { - {ENCR_DES, 64}, - {ENCR_3DES, 192}, - {END_OF_LIST, 0} -}; +} keylen_entry_t; /** - * Keylen for integrity algos + * See header. */ -keylen_entry_t keylen_int[] = { - {AUTH_HMAC_MD5_96, 128}, - {AUTH_HMAC_MD5_128, 128}, - {AUTH_HMAC_SHA1_96, 160}, - {AUTH_HMAC_SHA1_160, 160}, - {AUTH_HMAC_SHA2_256_96, 256}, - {AUTH_HMAC_SHA2_256_128, 256}, - {AUTH_HMAC_SHA2_384_192, 384}, - {AUTH_HMAC_SHA2_512_256, 512}, - {AUTH_AES_XCBC_96, 128}, - {END_OF_LIST, 0} -}; - -/** - * Lookup key length of an algorithm - */ -static int lookup_keylen(keylen_entry_t *list, int algo) +int keymat_get_keylen_encr(encryption_algorithm_t alg) { - while (list->algo != END_OF_LIST) + keylen_entry_t map[] = { + {ENCR_DES, 64}, + {ENCR_3DES, 192}, + }; + int i; + + for (i = 0; i < countof(map); i++) { - if (algo == list->algo) + if (map[i].alg == alg) { - return list->len; + return map[i].len; } - list++; } return 0; } -METHOD(keymat_t, create_dh, diffie_hellman_t*, - private_keymat_t *this, diffie_hellman_group_t group) -{ - return lib->crypto->create_dh(lib->crypto, group);; -} - /** - * Derive IKE keys for a combined AEAD algorithm + * See header. */ -static bool derive_ike_aead(private_keymat_t *this, u_int16_t alg, - u_int16_t key_size, prf_plus_t *prf_plus) +int keymat_get_keylen_integ(integrity_algorithm_t alg) { - aead_t *aead_i, *aead_r; - chunk_t key; - - /* SK_ei/SK_er used for encryption */ - aead_i = lib->crypto->create_aead(lib->crypto, alg, key_size / 8); - aead_r = lib->crypto->create_aead(lib->crypto, alg, key_size / 8); - if (aead_i == NULL || aead_r == NULL) - { - DBG1(DBG_IKE, "%N %N (key size %d) not supported!", - transform_type_names, ENCRYPTION_ALGORITHM, - encryption_algorithm_names, alg, key_size); - return FALSE; - } - key_size = aead_i->get_key_size(aead_i); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_ei secret %B", &key); - aead_i->set_key(aead_i, key); - chunk_clear(&key); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_er secret %B", &key); - aead_r->set_key(aead_r, key); - chunk_clear(&key); - - if (this->initiator) - { - this->aead_in = aead_r; - this->aead_out = aead_i; - } - else - { - this->aead_in = aead_i; - this->aead_out = aead_r; + keylen_entry_t map[] = { + {AUTH_HMAC_MD5_96, 128}, + {AUTH_HMAC_MD5_128, 128}, + {AUTH_HMAC_SHA1_96, 160}, + {AUTH_HMAC_SHA1_160, 160}, + {AUTH_HMAC_SHA2_256_96, 256}, + {AUTH_HMAC_SHA2_256_128, 256}, + {AUTH_HMAC_SHA2_384_192, 384}, + {AUTH_HMAC_SHA2_512_256, 512}, + {AUTH_AES_XCBC_96, 128}, + }; + int i; + + for (i = 0; i < countof(map); i++) + { + if (map[i].alg == alg) + { + return map[i].len; + } } - return TRUE; + return 0; } /** - * Derive IKE keys for traditional encryption and MAC algorithms + * See header. */ -static bool derive_ike_traditional(private_keymat_t *this, u_int16_t enc_alg, - u_int16_t enc_size, u_int16_t int_alg, prf_plus_t *prf_plus) +void keymat_register_constructor(ike_version_t version, + keymat_constructor_t create) { - crypter_t *crypter_i, *crypter_r; - signer_t *signer_i, *signer_r; - size_t key_size; - chunk_t key; - - /* SK_ai/SK_ar used for integrity protection */ - signer_i = lib->crypto->create_signer(lib->crypto, int_alg); - signer_r = lib->crypto->create_signer(lib->crypto, int_alg); - if (signer_i == NULL || signer_r == NULL) + switch (version) { - DBG1(DBG_IKE, "%N %N not supported!", - transform_type_names, INTEGRITY_ALGORITHM, - integrity_algorithm_names, int_alg); - return FALSE; - } - key_size = signer_i->get_key_size(signer_i); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_ai secret %B", &key); - signer_i->set_key(signer_i, key); - chunk_clear(&key); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_ar secret %B", &key); - signer_r->set_key(signer_r, key); - chunk_clear(&key); - - /* SK_ei/SK_er used for encryption */ - crypter_i = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_size / 8); - crypter_r = lib->crypto->create_crypter(lib->crypto, enc_alg, enc_size / 8); - if (crypter_i == NULL || crypter_r == NULL) - { - DBG1(DBG_IKE, "%N %N (key size %d) not supported!", - transform_type_names, ENCRYPTION_ALGORITHM, - encryption_algorithm_names, enc_alg, enc_size); - signer_i->destroy(signer_i); - signer_r->destroy(signer_r); - return FALSE; - } - key_size = crypter_i->get_key_size(crypter_i); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_ei secret %B", &key); - crypter_i->set_key(crypter_i, key); - chunk_clear(&key); - - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_er secret %B", &key); - crypter_r->set_key(crypter_r, key); - chunk_clear(&key); - - if (this->initiator) - { - this->aead_in = aead_create(crypter_r, signer_r); - this->aead_out = aead_create(crypter_i, signer_i); - } - else - { - this->aead_in = aead_create(crypter_i, signer_i); - this->aead_out = aead_create(crypter_r, signer_r); - } - return TRUE; -} - -METHOD(keymat_t, derive_ike_keys, bool, - private_keymat_t *this, proposal_t *proposal, diffie_hellman_t *dh, - chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id, - pseudo_random_function_t rekey_function, chunk_t rekey_skd) -{ - chunk_t skeyseed, key, secret, full_nonce, fixed_nonce, prf_plus_seed; - chunk_t spi_i, spi_r; - prf_plus_t *prf_plus; - u_int16_t alg, key_size, int_alg; - prf_t *rekey_prf = NULL; - - spi_i = chunk_alloca(sizeof(u_int64_t)); - spi_r = chunk_alloca(sizeof(u_int64_t)); - - if (dh->get_shared_secret(dh, &secret) != SUCCESS) - { - return FALSE; - } - - /* Create SAs general purpose PRF first, we may use it here */ - if (!proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) - { - DBG1(DBG_IKE, "no %N selected", - transform_type_names, PSEUDO_RANDOM_FUNCTION); - return FALSE; - } - this->prf_alg = alg; - this->prf = lib->crypto->create_prf(lib->crypto, alg); - if (this->prf == NULL) - { - DBG1(DBG_IKE, "%N %N not supported!", - transform_type_names, PSEUDO_RANDOM_FUNCTION, - pseudo_random_function_names, alg); - return FALSE; - } - DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &secret); - /* full nonce is used as seed for PRF+ ... */ - full_nonce = chunk_cat("cc", nonce_i, nonce_r); - /* but the PRF may need a fixed key which only uses the first bytes of - * the nonces. */ - switch (alg) - { - case PRF_AES128_XCBC: - /* while rfc4434 defines variable keys for AES-XCBC, rfc3664 does - * not and therefore fixed key semantics apply to XCBC for key - * derivation. */ - case PRF_CAMELLIA128_XCBC: - /* draft-kanno-ipsecme-camellia-xcbc refers to rfc 4434, we - * assume fixed key length. */ - key_size = this->prf->get_key_size(this->prf)/2; - nonce_i.len = min(nonce_i.len, key_size); - nonce_r.len = min(nonce_r.len, key_size); + case IKEV1: + keymat_v1_ctor = create; + break; + case IKEV2: + keymat_v2_ctor = create; break; default: - /* all other algorithms use variable key length, full nonce */ break; } - fixed_nonce = chunk_cat("cc", nonce_i, nonce_r); - *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); - *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); - prf_plus_seed = chunk_cat("ccc", full_nonce, spi_i, spi_r); - - /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) - * - * if we are rekeying, SKEYSEED is built on another way - */ - if (rekey_function == PRF_UNDEFINED) /* not rekeying */ - { - /* SKEYSEED = prf(Ni | Nr, g^ir) */ - this->prf->set_key(this->prf, fixed_nonce); - this->prf->allocate_bytes(this->prf, secret, &skeyseed); - this->prf->set_key(this->prf, skeyseed); - prf_plus = prf_plus_create(this->prf, prf_plus_seed); - } - else - { - /* SKEYSEED = prf(SK_d (old), [g^ir (new)] | Ni | Nr) - * use OLD SAs PRF functions for both prf_plus and prf */ - rekey_prf = lib->crypto->create_prf(lib->crypto, rekey_function); - if (!rekey_prf) - { - DBG1(DBG_IKE, "PRF of old SA %N not supported!", - pseudo_random_function_names, rekey_function); - chunk_free(&full_nonce); - chunk_free(&fixed_nonce); - chunk_clear(&prf_plus_seed); - return FALSE; - } - secret = chunk_cat("mc", secret, full_nonce); - rekey_prf->set_key(rekey_prf, rekey_skd); - rekey_prf->allocate_bytes(rekey_prf, secret, &skeyseed); - rekey_prf->set_key(rekey_prf, skeyseed); - prf_plus = prf_plus_create(rekey_prf, prf_plus_seed); - } - DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed); - - chunk_clear(&skeyseed); - chunk_clear(&secret); - chunk_free(&full_nonce); - chunk_free(&fixed_nonce); - chunk_clear(&prf_plus_seed); - - /* KEYMAT = SK_d | SK_ai | SK_ar | SK_ei | SK_er | SK_pi | SK_pr */ - - /* SK_d is used for generating CHILD_SA key mat => store for later use */ - key_size = this->prf->get_key_size(this->prf); - prf_plus->allocate_bytes(prf_plus, key_size, &this->skd); - DBG4(DBG_IKE, "Sk_d secret %B", &this->skd); - - if (!proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, &key_size)) - { - DBG1(DBG_IKE, "no %N selected", - transform_type_names, ENCRYPTION_ALGORITHM); - prf_plus->destroy(prf_plus); - DESTROY_IF(rekey_prf); - return FALSE; - } - - if (encryption_algorithm_is_aead(alg)) - { - if (!derive_ike_aead(this, alg, key_size, prf_plus)) - { - prf_plus->destroy(prf_plus); - DESTROY_IF(rekey_prf); - return FALSE; - } - } - else - { - if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, - &int_alg, NULL)) - { - DBG1(DBG_IKE, "no %N selected", - transform_type_names, INTEGRITY_ALGORITHM); - prf_plus->destroy(prf_plus); - DESTROY_IF(rekey_prf); - return FALSE; - } - if (!derive_ike_traditional(this, alg, key_size, int_alg, prf_plus)) - { - prf_plus->destroy(prf_plus); - DESTROY_IF(rekey_prf); - return FALSE; - } - } - - /* SK_pi/SK_pr used for authentication => stored for later */ - key_size = this->prf->get_key_size(this->prf); - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_pi secret %B", &key); - if (this->initiator) - { - this->skp_build = key; - } - else - { - this->skp_verify = key; - } - prf_plus->allocate_bytes(prf_plus, key_size, &key); - DBG4(DBG_IKE, "Sk_pr secret %B", &key); - if (this->initiator) - { - this->skp_verify = key; - } - else - { - this->skp_build = key; - } - - /* all done, prf_plus not needed anymore */ - prf_plus->destroy(prf_plus); - DESTROY_IF(rekey_prf); - - return TRUE; -} - -METHOD(keymat_t, derive_child_keys, bool, - private_keymat_t *this, proposal_t *proposal, diffie_hellman_t *dh, - chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i, - chunk_t *encr_r, chunk_t *integ_r) -{ - u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; - chunk_t seed, secret = chunk_empty; - prf_plus_t *prf_plus; - - if (dh) - { - if (dh->get_shared_secret(dh, &secret) != SUCCESS) - { - return FALSE; - } - DBG4(DBG_CHD, "DH secret %B", &secret); - } - seed = chunk_cata("mcc", secret, nonce_i, nonce_r); - DBG4(DBG_CHD, "seed %B", &seed); - - if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, - &enc_alg, &enc_size)) - { - DBG2(DBG_CHD, " using %N for encryption", - encryption_algorithm_names, enc_alg); - - if (!enc_size) - { - enc_size = lookup_keylen(keylen_enc, enc_alg); - } - if (enc_alg != ENCR_NULL && !enc_size) - { - DBG1(DBG_CHD, "no keylength defined for %N", - encryption_algorithm_names, enc_alg); - return FALSE; - } - /* to bytes */ - enc_size /= 8; - - /* CCM/GCM/CTR/GMAC needs additional bytes */ - switch (enc_alg) - { - case ENCR_AES_CCM_ICV8: - case ENCR_AES_CCM_ICV12: - case ENCR_AES_CCM_ICV16: - case ENCR_CAMELLIA_CCM_ICV8: - case ENCR_CAMELLIA_CCM_ICV12: - case ENCR_CAMELLIA_CCM_ICV16: - enc_size += 3; - break; - case ENCR_AES_GCM_ICV8: - case ENCR_AES_GCM_ICV12: - case ENCR_AES_GCM_ICV16: - case ENCR_AES_CTR: - case ENCR_NULL_AUTH_AES_GMAC: - enc_size += 4; - break; - default: - break; - } - } - - if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, - &int_alg, &int_size)) - { - DBG2(DBG_CHD, " using %N for integrity", - integrity_algorithm_names, int_alg); - - if (!int_size) - { - int_size = lookup_keylen(keylen_int, int_alg); - } - if (!int_size) - { - DBG1(DBG_CHD, "no keylength defined for %N", - integrity_algorithm_names, int_alg); - return FALSE; - } - /* to bytes */ - int_size /= 8; - } - - this->prf->set_key(this->prf, this->skd); - prf_plus = prf_plus_create(this->prf, seed); - - prf_plus->allocate_bytes(prf_plus, enc_size, encr_i); - prf_plus->allocate_bytes(prf_plus, int_size, integ_i); - prf_plus->allocate_bytes(prf_plus, enc_size, encr_r); - prf_plus->allocate_bytes(prf_plus, int_size, integ_r); - - prf_plus->destroy(prf_plus); - - if (enc_size) - { - DBG4(DBG_CHD, "encryption initiator key %B", encr_i); - DBG4(DBG_CHD, "encryption responder key %B", encr_r); - } - if (int_size) - { - DBG4(DBG_CHD, "integrity initiator key %B", integ_i); - DBG4(DBG_CHD, "integrity responder key %B", integ_r); - } - return TRUE; -} - -METHOD(keymat_t, get_skd, pseudo_random_function_t, - private_keymat_t *this, chunk_t *skd) -{ - *skd = this->skd; - return this->prf_alg; -} - -METHOD(keymat_t, get_aead, aead_t*, - private_keymat_t *this, bool in) -{ - return in ? this->aead_in : this->aead_out; -} - -METHOD(keymat_t, get_auth_octets, chunk_t, - private_keymat_t *this, bool verify, chunk_t ike_sa_init, - chunk_t nonce, identification_t *id, char reserved[3]) -{ - chunk_t chunk, idx, octets; - chunk_t skp; - - skp = verify ? this->skp_verify : this->skp_build; - - chunk = chunk_alloca(4); - chunk.ptr[0] = id->get_type(id); - memcpy(chunk.ptr + 1, reserved, 3); - idx = chunk_cata("cc", chunk, id->get_encoding(id)); - - DBG3(DBG_IKE, "IDx' %B", &idx); - DBG3(DBG_IKE, "SK_p %B", &skp); - this->prf->set_key(this->prf, skp); - this->prf->allocate_bytes(this->prf, idx, &chunk); - - octets = chunk_cat("ccm", ike_sa_init, nonce, chunk); - DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') %B", &octets); - return octets; } - -/** - * Key pad for the AUTH method SHARED_KEY_MESSAGE_INTEGRITY_CODE. - */ -#define IKEV2_KEY_PAD "Key Pad for IKEv2" -#define IKEV2_KEY_PAD_LENGTH 17 - -METHOD(keymat_t, get_psk_sig, chunk_t, - private_keymat_t *this, bool verify, chunk_t ike_sa_init, - chunk_t nonce, chunk_t secret, identification_t *id, char reserved[3]) -{ - chunk_t key_pad, key, sig, octets; - - if (!secret.len) - { /* EAP uses SK_p if no MSK has been established */ - secret = verify ? this->skp_verify : this->skp_build; - } - octets = get_auth_octets(this, verify, ike_sa_init, nonce, id, reserved); - /* AUTH = prf(prf(Shared Secret,"Key Pad for IKEv2"), <msg octets>) */ - key_pad = chunk_create(IKEV2_KEY_PAD, IKEV2_KEY_PAD_LENGTH); - this->prf->set_key(this->prf, secret); - this->prf->allocate_bytes(this->prf, key_pad, &key); - this->prf->set_key(this->prf, key); - this->prf->allocate_bytes(this->prf, octets, &sig); - DBG4(DBG_IKE, "secret %B", &secret); - DBG4(DBG_IKE, "prf(secret, keypad) %B", &key); - DBG3(DBG_IKE, "AUTH = prf(prf(secret, keypad), octets) %B", &sig); - chunk_free(&octets); - chunk_free(&key); - - return sig; -} - -METHOD(keymat_t, destroy, void, - private_keymat_t *this) -{ - DESTROY_IF(this->aead_in); - DESTROY_IF(this->aead_out); - DESTROY_IF(this->prf); - chunk_clear(&this->skd); - chunk_clear(&this->skp_verify); - chunk_clear(&this->skp_build); - free(this); -} - -/** - * See header - */ -keymat_t *keymat_create(bool initiator) -{ - private_keymat_t *this; - - INIT(this, - .public = { - .create_dh = _create_dh, - .derive_ike_keys = _derive_ike_keys, - .derive_child_keys = _derive_child_keys, - .get_skd = _get_skd, - .get_aead = _get_aead, - .get_auth_octets = _get_auth_octets, - .get_psk_sig = _get_psk_sig, - .destroy = _destroy, - }, - .initiator = initiator, - .prf_alg = PRF_UNDEFINED, - ); - - return &this->public; -} - diff --git a/src/libcharon/sa/keymat.h b/src/libcharon/sa/keymat.h index 6c2b5d4b5..02db5ca58 100644 --- a/src/libcharon/sa/keymat.h +++ b/src/libcharon/sa/keymat.h @@ -21,14 +21,23 @@ #ifndef KEYMAT_H_ #define KEYMAT_H_ +typedef struct keymat_t keymat_t; + #include <library.h> #include <utils/identification.h> #include <crypto/prfs/prf.h> #include <crypto/aead.h> #include <config/proposal.h> +#include <config/peer_cfg.h> #include <sa/ike_sa_id.h> -typedef struct keymat_t keymat_t; +/** + * Constructor function for custom keymat implementations + * + * @param initiator TRUE if the keymat is used as initiator + * @return keymat_t implementation + */ +typedef keymat_t* (*keymat_constructor_t)(bool initiator); /** * Derivation an management of sensitive keying material. @@ -36,6 +45,13 @@ typedef struct keymat_t keymat_t; struct keymat_t { /** + * Get IKE version of this keymat. + * + * @return IKEV1 for keymat_v1_t, IKEV2 for keymat_v2_t + */ + ike_version_t (*get_version)(keymat_t *this); + + /** * Create a diffie hellman object for key agreement. * * The diffie hellman is either for IKE negotiation/rekeying or @@ -50,58 +66,18 @@ struct keymat_t { * @param group diffie hellman group * @return DH object, NULL if group not supported */ - diffie_hellman_t* (*create_dh)(keymat_t *this, diffie_hellman_group_t group); + diffie_hellman_t* (*create_dh)(keymat_t *this, + diffie_hellman_group_t group); /** - * Derive keys for the IKE_SA. + * Create a nonce generator object. * - * These keys are not handed out, but are used by the associated signers, - * crypters and authentication functions. + * The nonce generator can be used to create nonces needed during IKE/CHILD + * SA establishment or rekeying. * - * @param proposal selected algorithms - * @param dh diffie hellman key allocated by create_dh() - * @param nonce_i initiators nonce value - * @param nonce_r responders nonce value - * @param id IKE_SA identifier - * @param rekey_prf PRF of old SA if rekeying, PRF_UNDEFINED otherwise - * @param rekey_sdk SKd of old SA if rekeying - * @return TRUE on success + * @return nonce generator object */ - bool (*derive_ike_keys)(keymat_t *this, proposal_t *proposal, - diffie_hellman_t *dh, chunk_t nonce_i, - chunk_t nonce_r, ike_sa_id_t *id, - pseudo_random_function_t rekey_function, - chunk_t rekey_skd); - /** - * Derive keys for a CHILD_SA. - * - * The keys for the CHILD_SA are allocated in the integ and encr chunks. - * An implementation might hand out encrypted keys only, which are - * decrypted in the kernel before use. - * If no PFS is used for the CHILD_SA, dh can be NULL. - * - * @param proposal selected algorithms - * @param dh diffie hellman key allocated by create_dh(), or NULL - * @param nonce_i initiators nonce value - * @param nonce_r responders nonce value - * @param encr_i chunk to write initiators encryption key to - * @param integ_i chunk to write initiators integrity key to - * @param encr_r chunk to write responders encryption key to - * @param integ_r chunk to write responders integrity key to - * @return TRUE on success - */ - bool (*derive_child_keys)(keymat_t *this, - proposal_t *proposal, diffie_hellman_t *dh, - chunk_t nonce_i, chunk_t nonce_r, - chunk_t *encr_i, chunk_t *integ_i, - chunk_t *encr_r, chunk_t *integ_r); - /** - * Get SKd to pass to derive_ikey_keys() during rekeying. - * - * @param skd chunk to write SKd to (internal data) - * @return PRF function to derive keymat - */ - pseudo_random_function_t (*get_skd)(keymat_t *this, chunk_t *skd); + nonce_gen_t* (*create_nonce_gen)(keymat_t *this); /* * Get a AEAD transform to en-/decrypt and sign/verify IKE messages. @@ -112,52 +88,43 @@ struct keymat_t { aead_t* (*get_aead)(keymat_t *this, bool in); /** - * Generate octets to use for authentication procedure (RFC4306 2.15). - * - * This method creates the plain octets and is usually signed by a private - * key. PSK and EAP authentication include a secret into the data, use - * the get_psk_sig() method instead. - * - * @param verify TRUE to create for verfification, FALSE to sign - * @param ike_sa_init encoded ike_sa_init message - * @param nonce nonce value - * @param id identity - * @param reserved reserved bytes of id_payload - * @return authentication octets - */ - chunk_t (*get_auth_octets)(keymat_t *this, bool verify, chunk_t ike_sa_init, - chunk_t nonce, identification_t *id, - char reserved[3]); - /** - * Build the shared secret signature used for PSK and EAP authentication. - * - * This method wraps the get_auth_octets() method and additionally - * includes the secret into the signature. If no secret is given, SK_p is - * used as secret (used for EAP methods without MSK). - * - * @param verify TRUE to create for verfification, FALSE to sign - * @param ike_sa_init encoded ike_sa_init message - * @param nonce nonce value - * @param secret optional secret to include into signature - * @param id identity - * @param reserved reserved bytes of id_payload - * @return signature octets - */ - chunk_t (*get_psk_sig)(keymat_t *this, bool verify, chunk_t ike_sa_init, - chunk_t nonce, chunk_t secret, - identification_t *id, char reserved[3]); - /** * Destroy a keymat_t. */ void (*destroy)(keymat_t *this); }; /** - * Create a keymat instance. + * Create the appropriate keymat_t implementation based on the IKE version. + * + * @param version requested IKE version + * @param initiator TRUE if we are initiator + * @return keymat_t implmenetation + */ +keymat_t *keymat_create(ike_version_t version, bool initiator); + +/** + * Look up the key length of an encryption algorithm. + * + * @param alg algorithm to get key length for + * @return key length in bits + */ +int keymat_get_keylen_encr(encryption_algorithm_t alg); + +/** + * Look up the key length of an integrity algorithm. + * + * @param alg algorithm to get key length for + * @return key length in bits + */ +int keymat_get_keylen_integ(integrity_algorithm_t alg); + +/** + * Register keymat_t constructor for given IKE version. * - * @param initiator TRUE if we are the initiator - * @return keymat instance + * @param version IKE version of given keymat constructor + * @param create keymat constructor function, NULL to unregister */ -keymat_t *keymat_create(bool initiator); +void keymat_register_constructor(ike_version_t version, + keymat_constructor_t create); #endif /** KEYMAT_H_ @}*/ diff --git a/src/libcharon/sa/shunt_manager.c b/src/libcharon/sa/shunt_manager.c index 52b2ecd62..5af43fb91 100644 --- a/src/libcharon/sa/shunt_manager.c +++ b/src/libcharon/sa/shunt_manager.c @@ -206,6 +206,7 @@ METHOD(shunt_manager_t, uninstall, bool, return FALSE; } uninstall_shunt_policy(child); + child->destroy(child); return TRUE; } diff --git a/src/libcharon/sa/tasks/task.c b/src/libcharon/sa/task.c index 0d7383141..4336b23ff 100644 --- a/src/libcharon/sa/tasks/task.c +++ b/src/libcharon/sa/task.c @@ -16,12 +16,11 @@ #include "task.h" -#ifdef ME -ENUM(task_type_names, IKE_INIT, CHILD_REKEY, +ENUM(task_type_names, TASK_IKE_INIT, TASK_ISAKMP_CERT_POST, "IKE_INIT", "IKE_NATD", "IKE_MOBIKE", - "IKE_AUTHENTICATE", + "IKE_AUTH", "IKE_AUTH_LIFETIME", "IKE_CERT_PRE", "IKE_CERT_POST", @@ -31,28 +30,23 @@ ENUM(task_type_names, IKE_INIT, CHILD_REKEY, "IKE_DELETE", "IKE_DPD", "IKE_VENDOR", +#ifdef ME "IKE_ME", +#endif /* ME */ "CHILD_CREATE", "CHILD_DELETE", "CHILD_REKEY", + "MAIN_MODE", + "AGGRESSIVE_MODE", + "INFORMATIONAL", + "ISAKMP_DELETE", + "XAUTH", + "MODE_CONFIG", + "QUICK_MODE", + "QUICK_DELETE", + "ISAKMP_VENDOR", + "ISAKMP_NATD", + "ISAKMP_DPD", + "ISAKMP_CERT_PRE", + "ISAKMP_CERT_POST", ); -#else -ENUM(task_type_names, IKE_INIT, CHILD_REKEY, - "IKE_INIT", - "IKE_NATD", - "IKE_MOBIKE", - "IKE_AUTHENTICATE", - "IKE_AUTH_LIFETIME", - "IKE_CERT_PRE", - "IKE_CERT_POST", - "IKE_CONFIG", - "IKE_REKEY", - "IKE_REAUTH", - "IKE_DELETE", - "IKE_DPD", - "IKE_VENDOR", - "CHILD_CREATE", - "CHILD_DELETE", - "CHILD_REKEY", -); -#endif /* ME */ diff --git a/src/libcharon/sa/tasks/task.h b/src/libcharon/sa/task.h index d57085954..c37221a77 100644 --- a/src/libcharon/sa/tasks/task.h +++ b/src/libcharon/sa/task.h @@ -16,7 +16,7 @@ /** * @defgroup task task - * @{ @ingroup tasks + * @{ @ingroup sa */ #ifndef TASK_H_ @@ -34,41 +34,67 @@ typedef struct task_t task_t; */ enum task_type_t { /** establish an unauthenticated IKE_SA */ - IKE_INIT, + TASK_IKE_INIT, /** detect NAT situation */ - IKE_NATD, + TASK_IKE_NATD, /** handle MOBIKE stuff */ - IKE_MOBIKE, + TASK_IKE_MOBIKE, /** authenticate the initiated IKE_SA */ - IKE_AUTHENTICATE, + TASK_IKE_AUTH, /** AUTH_LIFETIME negotiation, RFC4478 */ - IKE_AUTH_LIFETIME, + TASK_IKE_AUTH_LIFETIME, /** certificate processing before authentication (certreqs, cert parsing) */ - IKE_CERT_PRE, + TASK_IKE_CERT_PRE, /** certificate processing after authentication (certs payload generation) */ - IKE_CERT_POST, + TASK_IKE_CERT_POST, /** Configuration payloads, virtual IP and such */ - IKE_CONFIG, + TASK_IKE_CONFIG, /** rekey an IKE_SA */ - IKE_REKEY, + TASK_IKE_REKEY, /** reestablish a complete IKE_SA */ - IKE_REAUTH, + TASK_IKE_REAUTH, /** delete an IKE_SA */ - IKE_DELETE, + TASK_IKE_DELETE, /** liveness check */ - IKE_DPD, + TASK_IKE_DPD, /** Vendor ID processing */ - IKE_VENDOR, + TASK_IKE_VENDOR, #ifdef ME /** handle ME stuff */ - IKE_ME, + TASK_IKE_ME, #endif /* ME */ /** establish a CHILD_SA within an IKE_SA */ - CHILD_CREATE, + TASK_CHILD_CREATE, /** delete an established CHILD_SA */ - CHILD_DELETE, + TASK_CHILD_DELETE, /** rekey an CHILD_SA */ - CHILD_REKEY, + TASK_CHILD_REKEY, + /** IKEv1 main mode */ + TASK_MAIN_MODE, + /** IKEv1 aggressive mode */ + TASK_AGGRESSIVE_MODE, + /** IKEv1 informational exchange */ + TASK_INFORMATIONAL, + /** IKEv1 delete using an informational */ + TASK_ISAKMP_DELETE, + /** IKEv1 XAUTH authentication */ + TASK_XAUTH, + /** IKEv1 Mode Config */ + TASK_MODE_CONFIG, + /** IKEv1 quick mode */ + TASK_QUICK_MODE, + /** IKEv1 delete of a quick mode SA */ + TASK_QUICK_DELETE, + /** IKEv1 vendor ID payload handling */ + TASK_ISAKMP_VENDOR, + /** IKEv1 NAT detection */ + TASK_ISAKMP_NATD, + /** IKEv1 DPD */ + TASK_ISAKMP_DPD, + /** IKEv1 pre-authentication certificate handling */ + TASK_ISAKMP_CERT_PRE, + /** IKEv1 post-authentication certificate handling */ + TASK_ISAKMP_CERT_POST, }; /** @@ -105,6 +131,7 @@ struct task_t { * - FAILED if a critical error occurred * - DESTROY_ME if IKE_SA has been properly deleted * - NEED_MORE if another call to build/process needed + * - ALREADY_DONE to cancel task processing * - SUCCESS if task completed */ status_t (*build) (task_t *this, message_t *message); @@ -114,9 +141,10 @@ struct task_t { * * @param message message to read payloads from * @return - * - FAILED if a critical error occurred + * - FAILED if a critical error occurred * - DESTROY_ME if IKE_SA has been properly deleted * - NEED_MORE if another call to build/process needed + * - ALREADY_DONE to cancel task processing * - SUCCESS if task completed */ status_t (*process) (task_t *this, message_t *message); diff --git a/src/libcharon/sa/task_manager.c b/src/libcharon/sa/task_manager.c index 022a5e3d6..c42008ba9 100644 --- a/src/libcharon/sa/task_manager.c +++ b/src/libcharon/sa/task_manager.c @@ -1,6 +1,5 @@ /* - * Copyright (C) 2007 Tobias Brunner - * Copyright (C) 2007-2010 Martin Willi + * Copyright (C) 2011 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -16,1135 +15,29 @@ #include "task_manager.h" -#include <math.h> - -#include <daemon.h> -#include <sa/tasks/ike_init.h> -#include <sa/tasks/ike_natd.h> -#include <sa/tasks/ike_mobike.h> -#include <sa/tasks/ike_auth.h> -#include <sa/tasks/ike_auth_lifetime.h> -#include <sa/tasks/ike_cert_pre.h> -#include <sa/tasks/ike_cert_post.h> -#include <sa/tasks/ike_rekey.h> -#include <sa/tasks/ike_delete.h> -#include <sa/tasks/ike_config.h> -#include <sa/tasks/ike_dpd.h> -#include <sa/tasks/ike_vendor.h> -#include <sa/tasks/child_create.h> -#include <sa/tasks/child_rekey.h> -#include <sa/tasks/child_delete.h> -#include <encoding/payloads/delete_payload.h> -#include <processing/jobs/retransmit_job.h> - -#ifdef ME -#include <sa/tasks/ike_me.h> -#endif - -typedef struct exchange_t exchange_t; +#include <sa/ikev1/task_manager_v1.h> +#include <sa/ikev2/task_manager_v2.h> /** - * An exchange in the air, used do detect and handle retransmission + * See header */ -struct exchange_t { - - /** - * Message ID used for this transaction - */ - u_int32_t mid; - - /** - * generated packet for retransmission - */ - packet_t *packet; -}; - -typedef struct private_task_manager_t private_task_manager_t; - -/** - * private data of the task manager - */ -struct private_task_manager_t { - - /** - * public functions - */ - task_manager_t public; - - /** - * associated IKE_SA we are serving - */ - ike_sa_t *ike_sa; - - /** - * Exchange we are currently handling as responder - */ - struct { - /** - * Message ID of the exchange - */ - u_int32_t mid; - - /** - * packet for retransmission - */ - packet_t *packet; - - } responding; - - /** - * Exchange we are currently handling as initiator - */ - struct { - /** - * Message ID of the exchange - */ - u_int32_t mid; - - /** - * how many times we have retransmitted so far - */ - u_int retransmitted; - - /** - * packet for retransmission - */ - packet_t *packet; - - /** - * type of the initated exchange - */ - exchange_type_t type; - - } initiating; - - /** - * List of queued tasks not yet in action - */ - linked_list_t *queued_tasks; - - /** - * List of active tasks, initiated by ourselve - */ - linked_list_t *active_tasks; - - /** - * List of tasks initiated by peer - */ - linked_list_t *passive_tasks; - - /** - * the task manager has been reset - */ - bool reset; - - /** - * Number of times we retransmit messages before giving up - */ - u_int retransmit_tries; - - /** - * Retransmission timeout - */ - double retransmit_timeout; - - /** - * Base to calculate retransmission timeout - */ - double retransmit_base; -}; - -/** - * flush all tasks in the task manager - */ -static void flush(private_task_manager_t *this) -{ - this->passive_tasks->destroy_offset(this->passive_tasks, - offsetof(task_t, destroy)); - this->passive_tasks = linked_list_create(); - this->active_tasks->destroy_offset(this->active_tasks, - offsetof(task_t, destroy)); - this->active_tasks = linked_list_create(); - this->queued_tasks->destroy_offset(this->queued_tasks, - offsetof(task_t, destroy)); - this->queued_tasks = linked_list_create(); -} - -/** - * move a task of a specific type from the queue to the active list - */ -static bool activate_task(private_task_manager_t *this, task_type_t type) -{ - enumerator_t *enumerator; - task_t *task; - bool found = FALSE; - - enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, (void**)&task)) - { - if (task->get_type(task) == type) - { - DBG2(DBG_IKE, " activating %N task", task_type_names, type); - this->queued_tasks->remove_at(this->queued_tasks, enumerator); - this->active_tasks->insert_last(this->active_tasks, task); - found = TRUE; - break; - } - } - enumerator->destroy(enumerator); - return found; -} - -METHOD(task_manager_t, retransmit, status_t, - private_task_manager_t *this, u_int32_t message_id) -{ - if (message_id == this->initiating.mid) - { - u_int32_t timeout; - job_t *job; - enumerator_t *enumerator; - packet_t *packet; - task_t *task; - ike_mobike_t *mobike = NULL; - - /* check if we are retransmitting a MOBIKE routability check */ - enumerator = this->active_tasks->create_enumerator(this->active_tasks); - while (enumerator->enumerate(enumerator, (void*)&task)) - { - if (task->get_type(task) == IKE_MOBIKE) - { - mobike = (ike_mobike_t*)task; - if (!mobike->is_probing(mobike)) - { - mobike = NULL; - } - break; - } - } - enumerator->destroy(enumerator); - - if (mobike == NULL) - { - if (this->initiating.retransmitted <= this->retransmit_tries) - { - timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 * - pow(this->retransmit_base, this->initiating.retransmitted)); - } - else - { - DBG1(DBG_IKE, "giving up after %d retransmits", - this->initiating.retransmitted - 1); - if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) - { - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - } - return DESTROY_ME; - } - - if (this->initiating.retransmitted) - { - DBG1(DBG_IKE, "retransmit %d of request with message ID %d", - this->initiating.retransmitted, message_id); - } - packet = this->initiating.packet->clone(this->initiating.packet); - charon->sender->send(charon->sender, packet); - } - else - { /* for routeability checks, we use a more aggressive behavior */ - if (this->initiating.retransmitted <= ROUTEABILITY_CHECK_TRIES) - { - timeout = ROUTEABILITY_CHECK_INTERVAL; - } - else - { - DBG1(DBG_IKE, "giving up after %d path probings", - this->initiating.retransmitted - 1); - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - return DESTROY_ME; - } - - if (this->initiating.retransmitted) - { - DBG1(DBG_IKE, "path probing attempt %d", - this->initiating.retransmitted); - } - mobike->transmit(mobike, this->initiating.packet); - } - - this->initiating.retransmitted++; - job = (job_t*)retransmit_job_create(this->initiating.mid, - this->ike_sa->get_id(this->ike_sa)); - lib->scheduler->schedule_job_ms(lib->scheduler, job, timeout); - } - return SUCCESS; -} - -METHOD(task_manager_t, initiate, status_t, - private_task_manager_t *this) +task_manager_t *task_manager_create(ike_sa_t *ike_sa) { - enumerator_t *enumerator; - task_t *task; - message_t *message; - host_t *me, *other; - status_t status; - exchange_type_t exchange = 0; - - if (this->initiating.type != EXCHANGE_TYPE_UNDEFINED) - { - DBG2(DBG_IKE, "delaying task initiation, %N exchange in progress", - exchange_type_names, this->initiating.type); - /* do not initiate if we already have a message in the air */ - return SUCCESS; - } - - if (this->active_tasks->get_count(this->active_tasks) == 0) - { - DBG2(DBG_IKE, "activating new tasks"); - switch (this->ike_sa->get_state(this->ike_sa)) - { - case IKE_CREATED: - activate_task(this, IKE_VENDOR); - if (activate_task(this, IKE_INIT)) - { - this->initiating.mid = 0; - exchange = IKE_SA_INIT; - activate_task(this, IKE_NATD); - activate_task(this, IKE_CERT_PRE); -#ifdef ME - /* this task has to be activated before the IKE_AUTHENTICATE - * task, because that task pregenerates the packet after - * which no payloads can be added to the message anymore. - */ - activate_task(this, IKE_ME); -#endif /* ME */ - activate_task(this, IKE_AUTHENTICATE); - activate_task(this, IKE_CERT_POST); - activate_task(this, IKE_CONFIG); - activate_task(this, CHILD_CREATE); - activate_task(this, IKE_AUTH_LIFETIME); - activate_task(this, IKE_MOBIKE); - } - break; - case IKE_ESTABLISHED: - if (activate_task(this, CHILD_CREATE)) - { - exchange = CREATE_CHILD_SA; - break; - } - if (activate_task(this, CHILD_DELETE)) - { - exchange = INFORMATIONAL; - break; - } - if (activate_task(this, CHILD_REKEY)) - { - exchange = CREATE_CHILD_SA; - break; - } - if (activate_task(this, IKE_DELETE)) - { - exchange = INFORMATIONAL; - break; - } - if (activate_task(this, IKE_REKEY)) - { - exchange = CREATE_CHILD_SA; - break; - } - if (activate_task(this, IKE_REAUTH)) - { - exchange = INFORMATIONAL; - break; - } - if (activate_task(this, IKE_MOBIKE)) - { - exchange = INFORMATIONAL; - break; - } - if (activate_task(this, IKE_DPD)) - { - exchange = INFORMATIONAL; - break; - } - if (activate_task(this, IKE_AUTH_LIFETIME)) - { - exchange = INFORMATIONAL; - break; - } -#ifdef ME - if (activate_task(this, IKE_ME)) - { - exchange = ME_CONNECT; - break; - } -#endif /* ME */ - case IKE_REKEYING: - if (activate_task(this, IKE_DELETE)) - { - exchange = INFORMATIONAL; - break; - } - case IKE_DELETING: - default: - break; - } - } - else + switch (ike_sa->get_version(ike_sa)) { - DBG2(DBG_IKE, "reinitiating already active tasks"); - enumerator = this->active_tasks->create_enumerator(this->active_tasks); - while (enumerator->enumerate(enumerator, (void**)&task)) - { - DBG2(DBG_IKE, " %N task", task_type_names, task->get_type(task)); - switch (task->get_type(task)) - { - case IKE_INIT: - exchange = IKE_SA_INIT; - break; - case IKE_AUTHENTICATE: - exchange = IKE_AUTH; - break; - case CHILD_CREATE: - case CHILD_REKEY: - case IKE_REKEY: - exchange = CREATE_CHILD_SA; - break; - case IKE_MOBIKE: - exchange = INFORMATIONAL; - break; - default: - continue; - } + case IKEV1: +#ifdef USE_IKEV1 + return &task_manager_v1_create(ike_sa)->task_manager; +#endif break; - } - enumerator->destroy(enumerator); - } - - if (exchange == 0) - { - DBG2(DBG_IKE, "nothing to initiate"); - /* nothing to do yet... */ - return SUCCESS; - } - - me = this->ike_sa->get_my_host(this->ike_sa); - other = this->ike_sa->get_other_host(this->ike_sa); - - message = message_create(); - message->set_message_id(message, this->initiating.mid); - message->set_source(message, me->clone(me)); - message->set_destination(message, other->clone(other)); - message->set_exchange_type(message, exchange); - this->initiating.type = exchange; - this->initiating.retransmitted = 0; - - enumerator = this->active_tasks->create_enumerator(this->active_tasks); - while (enumerator->enumerate(enumerator, (void*)&task)) - { - switch (task->build(task, message)) - { - case SUCCESS: - /* task completed, remove it */ - this->active_tasks->remove_at(this->active_tasks, enumerator); - task->destroy(task); - break; - case NEED_MORE: - /* processed, but task needs another exchange */ - break; - case FAILED: - default: - if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) - { - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - } - /* FALL */ - case DESTROY_ME: - /* critical failure, destroy IKE_SA */ - enumerator->destroy(enumerator); - message->destroy(message); - flush(this); - return DESTROY_ME; - } - } - enumerator->destroy(enumerator); - - /* update exchange type if a task changed it */ - this->initiating.type = message->get_exchange_type(message); - - status = this->ike_sa->generate_message(this->ike_sa, message, - &this->initiating.packet); - if (status != SUCCESS) - { - /* message generation failed. There is nothing more to do than to - * close the SA */ - message->destroy(message); - flush(this); - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - return DESTROY_ME; - } - message->destroy(message); - - return retransmit(this, this->initiating.mid); -} - -/** - * handle an incoming response message - */ -static status_t process_response(private_task_manager_t *this, - message_t *message) -{ - enumerator_t *enumerator; - task_t *task; - - if (message->get_exchange_type(message) != this->initiating.type) - { - DBG1(DBG_IKE, "received %N response, but expected %N", - exchange_type_names, message->get_exchange_type(message), - exchange_type_names, this->initiating.type); - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - return DESTROY_ME; - } - - /* catch if we get resetted while processing */ - this->reset = FALSE; - enumerator = this->active_tasks->create_enumerator(this->active_tasks); - while (enumerator->enumerate(enumerator, (void*)&task)) - { - switch (task->process(task, message)) - { - case SUCCESS: - /* task completed, remove it */ - this->active_tasks->remove_at(this->active_tasks, enumerator); - task->destroy(task); - break; - case NEED_MORE: - /* processed, but task needs another exchange */ - break; - case FAILED: - default: - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - /* FALL */ - case DESTROY_ME: - /* critical failure, destroy IKE_SA */ - this->active_tasks->remove_at(this->active_tasks, enumerator); - enumerator->destroy(enumerator); - task->destroy(task); - return DESTROY_ME; - } - if (this->reset) - { /* start all over again if we were reset */ - this->reset = FALSE; - enumerator->destroy(enumerator); - return initiate(this); - } - } - enumerator->destroy(enumerator); - - this->initiating.mid++; - this->initiating.type = EXCHANGE_TYPE_UNDEFINED; - this->initiating.packet->destroy(this->initiating.packet); - this->initiating.packet = NULL; - - return initiate(this); -} - -/** - * handle exchange collisions - */ -static bool handle_collisions(private_task_manager_t *this, task_t *task) -{ - enumerator_t *enumerator; - task_t *active; - task_type_t type; - - type = task->get_type(task); - - /* do we have to check */ - if (type == IKE_REKEY || type == CHILD_REKEY || - type == CHILD_DELETE || type == IKE_DELETE || type == IKE_REAUTH) - { - /* find an exchange collision, and notify these tasks */ - enumerator = this->active_tasks->create_enumerator(this->active_tasks); - while (enumerator->enumerate(enumerator, (void**)&active)) - { - switch (active->get_type(active)) - { - case IKE_REKEY: - if (type == IKE_REKEY || type == IKE_DELETE || - type == IKE_REAUTH) - { - ike_rekey_t *rekey = (ike_rekey_t*)active; - rekey->collide(rekey, task); - break; - } - continue; - case CHILD_REKEY: - if (type == CHILD_REKEY || type == CHILD_DELETE) - { - child_rekey_t *rekey = (child_rekey_t*)active; - rekey->collide(rekey, task); - break; - } - continue; - default: - continue; - } - enumerator->destroy(enumerator); - return TRUE; - } - enumerator->destroy(enumerator); - } - return FALSE; -} - -/** - * build a response depending on the "passive" task list - */ -static status_t build_response(private_task_manager_t *this, message_t *request) -{ - enumerator_t *enumerator; - task_t *task; - message_t *message; - host_t *me, *other; - bool delete = FALSE, hook = FALSE; - status_t status; - - me = request->get_destination(request); - other = request->get_source(request); - - message = message_create(); - message->set_exchange_type(message, request->get_exchange_type(request)); - /* send response along the path the request came in */ - message->set_source(message, me->clone(me)); - message->set_destination(message, other->clone(other)); - message->set_message_id(message, this->responding.mid); - message->set_request(message, FALSE); - - enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); - while (enumerator->enumerate(enumerator, (void*)&task)) - { - switch (task->build(task, message)) - { - case SUCCESS: - /* task completed, remove it */ - this->passive_tasks->remove_at(this->passive_tasks, enumerator); - if (!handle_collisions(this, task)) - { - task->destroy(task); - } - break; - case NEED_MORE: - /* processed, but task needs another exchange */ - if (handle_collisions(this, task)) - { - this->passive_tasks->remove_at(this->passive_tasks, - enumerator); - } - break; - case FAILED: - default: - hook = TRUE; - /* FALL */ - case DESTROY_ME: - /* destroy IKE_SA, but SEND response first */ - delete = TRUE; - break; - } - if (delete) - { + case IKEV2: +#ifdef USE_IKEV2 + return &task_manager_v2_create(ike_sa)->task_manager; +#endif break; - } - } - enumerator->destroy(enumerator); - - /* remove resonder SPI if IKE_SA_INIT failed */ - if (delete && request->get_exchange_type(request) == IKE_SA_INIT) - { - ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); - id->set_responder_spi(id, 0); - } - - /* message complete, send it */ - DESTROY_IF(this->responding.packet); - this->responding.packet = NULL; - status = this->ike_sa->generate_message(this->ike_sa, message, - &this->responding.packet); - message->destroy(message); - if (status != SUCCESS) - { - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - return DESTROY_ME; - } - - charon->sender->send(charon->sender, - this->responding.packet->clone(this->responding.packet)); - if (delete) - { - if (hook) - { - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - } - return DESTROY_ME; - } - return SUCCESS; -} - -/** - * handle an incoming request message - */ -static status_t process_request(private_task_manager_t *this, - message_t *message) -{ - enumerator_t *enumerator; - task_t *task = NULL; - payload_t *payload; - notify_payload_t *notify; - delete_payload_t *delete; - - if (this->passive_tasks->get_count(this->passive_tasks) == 0) - { /* create tasks depending on request type, if not already some queued */ - switch (message->get_exchange_type(message)) - { - case IKE_SA_INIT: - { - task = (task_t*)ike_vendor_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_init_create(this->ike_sa, FALSE, NULL); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_natd_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_cert_pre_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); -#ifdef ME - task = (task_t*)ike_me_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); -#endif /* ME */ - task = (task_t*)ike_auth_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_cert_post_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_config_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)child_create_create(this->ike_sa, NULL, FALSE, - NULL, NULL); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_auth_lifetime_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - task = (task_t*)ike_mobike_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - break; - } - case CREATE_CHILD_SA: - { /* FIXME: we should prevent this on mediation connections */ - bool notify_found = FALSE, ts_found = FALSE; - enumerator = message->create_payload_enumerator(message); - while (enumerator->enumerate(enumerator, &payload)) - { - switch (payload->get_type(payload)) - { - case NOTIFY: - { /* if we find a rekey notify, its CHILD_SA rekeying */ - notify = (notify_payload_t*)payload; - if (notify->get_notify_type(notify) == REKEY_SA && - (notify->get_protocol_id(notify) == PROTO_AH || - notify->get_protocol_id(notify) == PROTO_ESP)) - { - notify_found = TRUE; - } - break; - } - case TRAFFIC_SELECTOR_INITIATOR: - case TRAFFIC_SELECTOR_RESPONDER: - { /* if we don't find a TS, its IKE rekeying */ - ts_found = TRUE; - break; - } - default: - break; - } - } - enumerator->destroy(enumerator); - - if (ts_found) - { - if (notify_found) - { - task = (task_t*)child_rekey_create(this->ike_sa, - PROTO_NONE, 0); - } - else - { - task = (task_t*)child_create_create(this->ike_sa, NULL, - FALSE, NULL, NULL); - } - } - else - { - task = (task_t*)ike_rekey_create(this->ike_sa, FALSE); - } - this->passive_tasks->insert_last(this->passive_tasks, task); - break; - } - case INFORMATIONAL: - { - enumerator = message->create_payload_enumerator(message); - while (enumerator->enumerate(enumerator, &payload)) - { - switch (payload->get_type(payload)) - { - case NOTIFY: - { - notify = (notify_payload_t*)payload; - switch (notify->get_notify_type(notify)) - { - case ADDITIONAL_IP4_ADDRESS: - case ADDITIONAL_IP6_ADDRESS: - case NO_ADDITIONAL_ADDRESSES: - case UPDATE_SA_ADDRESSES: - case NO_NATS_ALLOWED: - case UNACCEPTABLE_ADDRESSES: - case UNEXPECTED_NAT_DETECTED: - case COOKIE2: - case NAT_DETECTION_SOURCE_IP: - case NAT_DETECTION_DESTINATION_IP: - task = (task_t*)ike_mobike_create( - this->ike_sa, FALSE); - break; - case AUTH_LIFETIME: - task = (task_t*)ike_auth_lifetime_create( - this->ike_sa, FALSE); - break; - default: - break; - } - break; - } - case DELETE: - { - delete = (delete_payload_t*)payload; - if (delete->get_protocol_id(delete) == PROTO_IKE) - { - task = (task_t*)ike_delete_create(this->ike_sa, - FALSE); - } - else - { - task = (task_t*)child_delete_create(this->ike_sa, - PROTO_NONE, 0); - } - break; - } - default: - break; - } - if (task) - { - break; - } - } - enumerator->destroy(enumerator); - - if (task == NULL) - { - task = (task_t*)ike_dpd_create(FALSE); - } - this->passive_tasks->insert_last(this->passive_tasks, task); - break; - } -#ifdef ME - case ME_CONNECT: - { - task = (task_t*)ike_me_create(this->ike_sa, FALSE); - this->passive_tasks->insert_last(this->passive_tasks, task); - } -#endif /* ME */ - default: - break; - } - } - - /* let the tasks process the message */ - enumerator = this->passive_tasks->create_enumerator(this->passive_tasks); - while (enumerator->enumerate(enumerator, (void*)&task)) - { - switch (task->process(task, message)) - { - case SUCCESS: - /* task completed, remove it */ - this->passive_tasks->remove_at(this->passive_tasks, enumerator); - task->destroy(task); - break; - case NEED_MORE: - /* processed, but task needs at least another call to build() */ - break; - case FAILED: - default: - charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); - /* FALL */ - case DESTROY_ME: - /* critical failure, destroy IKE_SA */ - this->passive_tasks->remove_at(this->passive_tasks, enumerator); - enumerator->destroy(enumerator); - task->destroy(task); - return DESTROY_ME; - } - } - enumerator->destroy(enumerator); - - return build_response(this, message); -} - -METHOD(task_manager_t, process_message, status_t, - private_task_manager_t *this, message_t *msg) -{ - host_t *me, *other; - u_int32_t mid; - - mid = msg->get_message_id(msg); - me = msg->get_destination(msg); - other = msg->get_source(msg); - - if (msg->get_request(msg)) - { - if (mid == this->responding.mid) - { - if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || - this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING || - msg->get_exchange_type(msg) != IKE_SA_INIT) - { /* only do host updates based on verified messages */ - if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) - { /* with MOBIKE, we do no implicit updates */ - this->ike_sa->update_hosts(this->ike_sa, me, other, mid == 1); - } - } - charon->bus->message(charon->bus, msg, TRUE); - if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED) - { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */ - return SUCCESS; - } - if (process_request(this, msg) != SUCCESS) - { - flush(this); - return DESTROY_ME; - } - this->responding.mid++; - } - else if ((mid == this->responding.mid - 1) && this->responding.packet) - { - packet_t *clone; - host_t *host; - - DBG1(DBG_IKE, "received retransmit of request with ID %d, " - "retransmitting response", mid); - clone = this->responding.packet->clone(this->responding.packet); - host = msg->get_destination(msg); - clone->set_source(clone, host->clone(host)); - host = msg->get_source(msg); - clone->set_destination(clone, host->clone(host)); - charon->sender->send(charon->sender, clone); - } - else - { - DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", - mid, this->responding.mid); - } - } - else - { - if (mid == this->initiating.mid) - { - if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || - this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING || - msg->get_exchange_type(msg) != IKE_SA_INIT) - { /* only do host updates based on verified messages */ - if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) - { /* with MOBIKE, we do no implicit updates */ - this->ike_sa->update_hosts(this->ike_sa, me, other, FALSE); - } - } - charon->bus->message(charon->bus, msg, TRUE); - if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED) - { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */ - return SUCCESS; - } - if (process_response(this, msg) != SUCCESS) - { - flush(this); - return DESTROY_ME; - } - } - else - { - DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", - mid, this->initiating.mid); - return SUCCESS; - } - } - return SUCCESS; -} - -METHOD(task_manager_t, queue_task, void, - private_task_manager_t *this, task_t *task) -{ - if (task->get_type(task) == IKE_MOBIKE) - { /* there is no need to queue more than one mobike task */ - enumerator_t *enumerator; - task_t *current; - - enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, (void**)¤t)) - { - if (current->get_type(current) == IKE_MOBIKE) - { - enumerator->destroy(enumerator); - task->destroy(task); - return; - } - } - enumerator->destroy(enumerator); - } - DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); - this->queued_tasks->insert_last(this->queued_tasks, task); -} - -METHOD(task_manager_t, adopt_tasks, void, - private_task_manager_t *this, task_manager_t *other_public) -{ - private_task_manager_t *other = (private_task_manager_t*)other_public; - task_t *task; - - /* move queued tasks from other to this */ - while (other->queued_tasks->remove_last(other->queued_tasks, - (void**)&task) == SUCCESS) - { - DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); - task->migrate(task, this->ike_sa); - this->queued_tasks->insert_first(this->queued_tasks, task); - } -} - -METHOD(task_manager_t, busy, bool, - private_task_manager_t *this) -{ - return (this->active_tasks->get_count(this->active_tasks) > 0); -} - -METHOD(task_manager_t, incr_mid, void, - private_task_manager_t *this, bool initiate) -{ - if (initiate) - { - this->initiating.mid++; - } - else - { - this->responding.mid++; - } -} - -METHOD(task_manager_t, reset, void, - private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) -{ - enumerator_t *enumerator; - task_t *task; - - /* reset message counters and retransmit packets */ - DESTROY_IF(this->responding.packet); - DESTROY_IF(this->initiating.packet); - this->responding.packet = NULL; - this->initiating.packet = NULL; - if (initiate != UINT_MAX) - { - this->initiating.mid = initiate; - } - if (respond != UINT_MAX) - { - this->responding.mid = respond; - } - this->initiating.type = EXCHANGE_TYPE_UNDEFINED; - - /* reset queued tasks */ - enumerator = this->queued_tasks->create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, &task)) - { - task->migrate(task, this->ike_sa); - } - enumerator->destroy(enumerator); - - /* reset active tasks */ - while (this->active_tasks->remove_last(this->active_tasks, - (void**)&task) == SUCCESS) - { - task->migrate(task, this->ike_sa); - this->queued_tasks->insert_first(this->queued_tasks, task); - } - - this->reset = TRUE; -} - -METHOD(task_manager_t, create_task_enumerator, enumerator_t*, - private_task_manager_t *this, task_queue_t queue) -{ - switch (queue) - { - case TASK_QUEUE_ACTIVE: - return this->active_tasks->create_enumerator(this->active_tasks); - case TASK_QUEUE_PASSIVE: - return this->passive_tasks->create_enumerator(this->passive_tasks); - case TASK_QUEUE_QUEUED: - return this->queued_tasks->create_enumerator(this->queued_tasks); default: - return enumerator_create_empty(); + break; } -} - -METHOD(task_manager_t, destroy, void, - private_task_manager_t *this) -{ - flush(this); - - this->active_tasks->destroy(this->active_tasks); - this->queued_tasks->destroy(this->queued_tasks); - this->passive_tasks->destroy(this->passive_tasks); - - DESTROY_IF(this->responding.packet); - DESTROY_IF(this->initiating.packet); - free(this); -} - -/* - * see header file - */ -task_manager_t *task_manager_create(ike_sa_t *ike_sa) -{ - private_task_manager_t *this; - - INIT(this, - .public = { - .process_message = _process_message, - .queue_task = _queue_task, - .initiate = _initiate, - .retransmit = _retransmit, - .incr_mid = _incr_mid, - .reset = _reset, - .adopt_tasks = _adopt_tasks, - .busy = _busy, - .create_task_enumerator = _create_task_enumerator, - .destroy = _destroy, - }, - .ike_sa = ike_sa, - .initiating.type = EXCHANGE_TYPE_UNDEFINED, - .queued_tasks = linked_list_create(), - .active_tasks = linked_list_create(), - .passive_tasks = linked_list_create(), - .retransmit_tries = lib->settings->get_int(lib->settings, - "charon.retransmit_tries", RETRANSMIT_TRIES), - .retransmit_timeout = lib->settings->get_double(lib->settings, - "charon.retransmit_timeout", RETRANSMIT_TIMEOUT), - .retransmit_base = lib->settings->get_double(lib->settings, - "charon.retransmit_base", RETRANSMIT_BASE), - ); - - return &this->public; + return NULL; } diff --git a/src/libcharon/sa/task_manager.h b/src/libcharon/sa/task_manager.h index 5bc6c80c4..c649cf78e 100644 --- a/src/libcharon/sa/task_manager.h +++ b/src/libcharon/sa/task_manager.h @@ -29,7 +29,7 @@ typedef enum task_queue_t task_queue_t; #include <library.h> #include <encoding/message.h> #include <sa/ike_sa.h> -#include <sa/tasks/task.h> +#include <sa/task.h> /** * First retransmit timeout in seconds. @@ -125,6 +125,69 @@ struct task_manager_t { void (*queue_task) (task_manager_t *this, task_t *task); /** + * Queue IKE_SA establishing tasks. + */ + void (*queue_ike)(task_manager_t *this); + + /** + * Queue IKE_SA rekey tasks. + */ + void (*queue_ike_rekey)(task_manager_t *this); + + /** + * Queue IKE_SA reauth tasks. + */ + void (*queue_ike_reauth)(task_manager_t *this); + + /** + * Queue MOBIKE task + * + * @param roam TRUE to switch to new address + * @param address TRUE to include address list update + */ + void (*queue_mobike)(task_manager_t *this, bool roam, bool address); + + /** + * Queue IKE_SA delete tasks. + */ + void (*queue_ike_delete)(task_manager_t *this); + + /** + * Queue CHILD_SA establishing tasks. + * + * @param cfg CHILD_SA config to establish + * @param reqid reqid to use for CHILD_SA + * @param tsi initiator traffic selector, if packet-triggered + * @param tsr responder traffic selector, if packet-triggered + */ + void (*queue_child)(task_manager_t *this, child_cfg_t *cfg, u_int32_t reqid, + traffic_selector_t *tsi, traffic_selector_t *tsr); + + /** + * Queue CHILD_SA rekeying tasks. + * + * @param protocol CHILD_SA protocol, AH|ESP + * @param spi CHILD_SA SPI to rekey + */ + void (*queue_child_rekey)(task_manager_t *this, protocol_id_t protocol, + u_int32_t spi); + + /** + * Queue CHILD_SA delete tasks. + * + * @param protocol CHILD_SA protocol, AH|ESP + * @param spi CHILD_SA SPI to rekey + * @param expired TRUE if SA already expired + */ + void (*queue_child_delete)(task_manager_t *this, protocol_id_t protocol, + u_int32_t spi, bool expired); + + /** + * Queue liveness checking tasks. + */ + void (*queue_dpd)(task_manager_t *this); + + /** * Retransmit a request if it hasn't been acknowledged yet. * * A return value of INVALID_STATE means that the message was already @@ -166,9 +229,11 @@ struct task_manager_t { * resets the message IDs and resets all active tasks using the migrate() * method. * Use a value of UINT_MAX to keep the current message ID. + * For IKEv1, the arguments do not set the message ID, but the DPD sequence + * number counters. * - * @param initiate message ID to initiate exchanges (send) - * @param respond message ID to respond to exchanges (expect) + * @param initiate message ID / DPD seq to initiate exchanges (send) + * @param respond message ID / DPD seq to respond to exchanges (expect) */ void (*reset) (task_manager_t *this, u_int32_t initiate, u_int32_t respond); @@ -189,15 +254,23 @@ struct task_manager_t { task_queue_t queue); /** + * Flush a queue, cancelling all tasks. + * + * @param queue queue to flush + */ + void (*flush_queue)(task_manager_t *this, task_queue_t queue); + + /** * Destroy the task_manager_t. */ void (*destroy) (task_manager_t *this); }; /** - * Create an instance of the task manager. + * Create a task manager instance for the correct IKE version. * - * @param ike_sa IKE_SA to manage. + * @param ike_sa IKE_SA to create a task manager for + * @return task manager implementation for IKE version */ task_manager_t *task_manager_create(ike_sa_t *ike_sa); diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 86d9f4c22..fdcfa0a20 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -98,7 +98,7 @@ METHOD(trap_manager_t, install, u_int32_t, ike_cfg_t *ike_cfg; child_sa_t *child_sa; host_t *me, *other; - linked_list_t *my_ts, *other_ts; + linked_list_t *my_ts, *other_ts, *list; enumerator_t *enumerator; bool found = FALSE; status_t status; @@ -127,14 +127,14 @@ METHOD(trap_manager_t, install, u_int32_t, /* try to resolve addresses */ ike_cfg = peer->get_ike_cfg(peer); - other = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg), + other = host_create_from_dns(ike_cfg->get_other_addr(ike_cfg, NULL), 0, ike_cfg->get_other_port(ike_cfg)); if (!other || other->is_anyaddr(other)) { DBG1(DBG_CFG, "installing trap failed, remote address unknown"); return 0; } - me = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg), + me = host_create_from_dns(ike_cfg->get_my_addr(ike_cfg, NULL), other->get_family(other), ike_cfg->get_my_port(ike_cfg)); if (!me || me->is_anyaddr(me)) { @@ -152,10 +152,14 @@ METHOD(trap_manager_t, install, u_int32_t, /* create and route CHILD_SA */ child_sa = child_sa_create(me, other, child, 0, FALSE); - my_ts = child->get_traffic_selectors(child, TRUE, NULL, me); - other_ts = child->get_traffic_selectors(child, FALSE, NULL, other); - me->destroy(me); - other->destroy(other); + + list = linked_list_create_with_items(me, NULL); + my_ts = child->get_traffic_selectors(child, TRUE, NULL, list); + list->destroy_offset(list, offsetof(host_t, destroy)); + + list = linked_list_create_with_items(other, NULL); + other_ts = child->get_traffic_selectors(child, FALSE, NULL, list); + list->destroy_offset(list, offsetof(host_t, destroy)); /* while we don't know the finally negotiated protocol (ESP|AH), we * could iterate all proposals for a best guess (TODO). But as we @@ -284,26 +288,34 @@ METHOD(trap_manager_t, acquire, void, ike_sa = charon->ike_sa_manager->checkout_by_config( charon->ike_sa_manager, peer); - if (ike_sa->get_peer_cfg(ike_sa) == NULL) - { - ike_sa->set_peer_cfg(ike_sa, peer); - } - if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME) + if (ike_sa) { - /* make sure the entry is still there */ - this->lock->read_lock(this->lock); - if (this->traps->find_first(this->traps, NULL, - (void**)&found) == SUCCESS) + if (ike_sa->get_peer_cfg(ike_sa) == NULL) { - found->ike_sa = ike_sa; + ike_sa->set_peer_cfg(ike_sa, peer); + } + if (ike_sa->get_version(ike_sa) == IKEV1) + { /* in IKEv1, don't prepend the acquiring packet TS, as we only + * have a single TS that we can establish in a Quick Mode. */ + src = dst = NULL; + } + if (ike_sa->initiate(ike_sa, child, reqid, src, dst) != DESTROY_ME) + { + /* make sure the entry is still there */ + this->lock->read_lock(this->lock); + if (this->traps->find_first(this->traps, NULL, + (void**)&found) == SUCCESS) + { + found->ike_sa = ike_sa; + } + this->lock->unlock(this->lock); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + } + else + { + charon->ike_sa_manager->checkin_and_destroy( + charon->ike_sa_manager, ike_sa); } - this->lock->unlock(this->lock); - charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); - } - else - { - charon->ike_sa_manager->checkin_and_destroy( - charon->ike_sa_manager, ike_sa); } peer->destroy(peer); } diff --git a/src/libcharon/sa/xauth/xauth_manager.c b/src/libcharon/sa/xauth/xauth_manager.c new file mode 100644 index 000000000..432c9c0ab --- /dev/null +++ b/src/libcharon/sa/xauth/xauth_manager.c @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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 "xauth_manager.h" + +#include <utils/linked_list.h> +#include <threading/rwlock.h> + +typedef struct private_xauth_manager_t private_xauth_manager_t; +typedef struct xauth_entry_t xauth_entry_t; + +/** + * XAuth constructor entry + */ +struct xauth_entry_t { + + /** + * Xauth backend name + */ + char *name; + + /** + * Role of the method, XAUTH_SERVER or XAUTH_PEER + */ + xauth_role_t role; + + /** + * constructor function to create instance + */ + xauth_constructor_t constructor; +}; + +/** + * private data of xauth_manager + */ +struct private_xauth_manager_t { + + /** + * public functions + */ + xauth_manager_t public; + + /** + * list of eap_entry_t's + */ + linked_list_t *methods; + + /** + * rwlock to lock methods + */ + rwlock_t *lock; +}; + +METHOD(xauth_manager_t, add_method, void, + private_xauth_manager_t *this, char *name, xauth_role_t role, + xauth_constructor_t constructor) +{ + xauth_entry_t *entry; + + INIT(entry, + .name = name, + .role = role, + .constructor = constructor, + ); + + this->lock->write_lock(this->lock); + this->methods->insert_last(this->methods, entry); + this->lock->unlock(this->lock); +} + +METHOD(xauth_manager_t, remove_method, void, + private_xauth_manager_t *this, xauth_constructor_t constructor) +{ + enumerator_t *enumerator; + xauth_entry_t *entry; + + this->lock->write_lock(this->lock); + enumerator = this->methods->create_enumerator(this->methods); + while (enumerator->enumerate(enumerator, &entry)) + { + if (constructor == entry->constructor) + { + this->methods->remove_at(this->methods, enumerator); + free(entry); + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); +} + +METHOD(xauth_manager_t, create_instance, xauth_method_t*, + private_xauth_manager_t *this, char *name, xauth_role_t role, + identification_t *server, identification_t *peer) +{ + enumerator_t *enumerator; + xauth_entry_t *entry; + xauth_method_t *method = NULL; + + this->lock->read_lock(this->lock); + enumerator = this->methods->create_enumerator(this->methods); + while (enumerator->enumerate(enumerator, &entry)) + { + if (role == entry->role && + (!name || streq(name, entry->name))) + { + method = entry->constructor(server, peer); + if (method) + { + break; + } + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return method; +} + +METHOD(xauth_manager_t, destroy, void, + private_xauth_manager_t *this) +{ + this->methods->destroy_function(this->methods, free); + this->lock->destroy(this->lock); + free(this); +} + +/* + * See header + */ +xauth_manager_t *xauth_manager_create() +{ + private_xauth_manager_t *this; + + INIT(this, + .public = { + .add_method = _add_method, + .remove_method = _remove_method, + .create_instance = _create_instance, + .destroy = _destroy, + }, + .methods = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/xauth/xauth_manager.h b/src/libcharon/sa/xauth/xauth_manager.h new file mode 100644 index 000000000..929d5de8f --- /dev/null +++ b/src/libcharon/sa/xauth/xauth_manager.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2011 Martin Willi + * Copyright (C) 2011 revosec AG + * + * 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. + */ + +/** + * @defgroup xauth_manager xauth_manager + * @{ @ingroup xauth + */ + +#ifndef XAUTH_MANAGER_H_ +#define XAUTH_MANAGER_H_ + +#include <sa/xauth/xauth_method.h> + +typedef struct xauth_manager_t xauth_manager_t; + +/** + * The XAuth manager manages all XAuth implementations and creates instances. + * + * A plugin registers it's implemented XAuth method at the manager by + * providing type and a contructor function. The manager then instanciates + * xauth_method_t instances through the provided constructor to handle + * XAuth authentication. + */ +struct xauth_manager_t { + + /** + * Register a XAuth method implementation. + * + * @param name backend name to register + * @param role XAUTH_SERVER or XAUTH_PEER + * @param constructor constructor function, returns an xauth_method_t + */ + void (*add_method)(xauth_manager_t *this, char *name, + xauth_role_t role, xauth_constructor_t constructor); + + /** + * Unregister a XAuth method implementation using it's constructor. + * + * @param constructor constructor function, as added in add_method + */ + void (*remove_method)(xauth_manager_t *this, xauth_constructor_t constructor); + + /** + * Create a new XAuth method instance. + * + * @param name backend name, as it was registered with + * @param role XAUTH_SERVER or XAUTH_PEER + * @param server identity of the server + * @param peer identity of the peer (client) + * @return XAUTH method instance, NULL if no constructor found + */ + xauth_method_t* (*create_instance)(xauth_manager_t *this, + char *name, xauth_role_t role, + identification_t *server, identification_t *peer); + + /** + * Destroy a eap_manager instance. + */ + void (*destroy)(xauth_manager_t *this); +}; + +/** + * Create a eap_manager instance. + */ +xauth_manager_t *xauth_manager_create(); + +#endif /** XAUTH_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/xauth/xauth_method.c b/src/libcharon/sa/xauth/xauth_method.c new file mode 100644 index 000000000..838822d1e --- /dev/null +++ b/src/libcharon/sa/xauth/xauth_method.c @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2006 Martin Willi + * 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 "xauth_method.h" + +#include <daemon.h> + +ENUM(xauth_role_names, XAUTH_SERVER, XAUTH_PEER, + "XAUTH_SERVER", + "XAUTH_PEER", +); + +/** + * See header + */ +bool xauth_method_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + if (reg) + { + charon->xauth->add_method(charon->xauth, feature->arg.xauth, + feature->type == FEATURE_XAUTH_SERVER ? XAUTH_SERVER : XAUTH_PEER, + (xauth_constructor_t)data); + } + else + { + charon->xauth->remove_method(charon->xauth, (xauth_constructor_t)data); + } + return TRUE; +} diff --git a/src/libcharon/sa/xauth/xauth_method.h b/src/libcharon/sa/xauth/xauth_method.h new file mode 100644 index 000000000..9f6067dbf --- /dev/null +++ b/src/libcharon/sa/xauth/xauth_method.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2006 Martin Willi + * 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. + */ + +/** + * @defgroup xauth_method xauth_method + * @{ @ingroup xauth + */ + +#ifndef XAUTH_METHOD_H_ +#define XAUTH_METHOD_H_ + +typedef struct xauth_method_t xauth_method_t; +typedef enum xauth_role_t xauth_role_t; + +#include <library.h> +#include <plugins/plugin.h> +#include <utils/identification.h> +#include <encoding/payloads/cp_payload.h> + +/** + * Role of an xauth_method, SERVER or PEER (client) + */ +enum xauth_role_t { + XAUTH_SERVER, + XAUTH_PEER, +}; + +/** + * enum names for xauth_role_t. + */ +extern enum_name_t *xauth_role_names; + +/** + * Interface of an XAuth method for server and client side. + * + * An XAuth method initiates an XAuth exchange and processes requests and + * responses. An XAuth method may need multiple exchanges before succeeding. + * Sending of XAUTH(STATUS) message is done by the framework, not a method. + */ +struct xauth_method_t { + + /** + * Initiate the XAuth exchange. + * + * initiate() is only useable for server implementations, as clients only + * reply to server requests. + * A cp_payload is created in "out" if result is NEED_MORE. + * + * @param out cp_payload to send to the client + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if unable to create XAuth request payload + */ + status_t (*initiate) (xauth_method_t *this, cp_payload_t **out); + + /** + * Process a received XAuth message. + * + * A cp_payload is created in "out" if result is NEED_MORE. + * + * @param in cp_payload response received + * @param out created cp_payload to send + * @return + * - NEED_MORE, if an other exchange is required + * - FAILED, if XAuth method failed + * - SUCCESS, if XAuth method succeeded + */ + status_t (*process) (xauth_method_t *this, cp_payload_t *in, + cp_payload_t **out); + + /** + * Get the XAuth username received as XAuth initiator. + * + * @return used XAuth username, pointer to internal data + */ + identification_t* (*get_identity)(xauth_method_t *this); + + /** + * Destroys a eap_method_t object. + */ + void (*destroy) (xauth_method_t *this); +}; + +/** + * Constructor definition for a pluggable XAuth method. + * + * Each XAuth module must define a constructor function which will return + * an initialized object with the methods defined in xauth_method_t. + * Constructors for server and peers are identical, to support both roles + * of a XAuth method, a plugin needs register two constructors in the + * xauth_manager_t. + * + * @param server ID of the server to use for credential lookup + * @param peer ID of the peer to use for credential lookup + * @return implementation of the eap_method_t interface + */ +typedef xauth_method_t *(*xauth_constructor_t)(identification_t *server, + identification_t *peer); + +/** + * Helper function to (un-)register XAuth methods from plugin features. + * + * This function is a plugin_feature_callback_t and can be used with the + * PLUGIN_CALLBACK macro to register a XAuth method constructor. + * + * @param plugin plugin registering the XAuth method constructor + * @param feature associated plugin feature + * @param reg TRUE to register, FALSE to unregister. + * @param data data passed to callback, an xauth_constructor_t + */ +bool xauth_method_register(plugin_t *plugin, plugin_feature_t *feature, + bool reg, void *data); + +#endif /** XAUTH_METHOD_H_ @}*/ |