diff options
Diffstat (limited to 'src/libcharon/sa')
42 files changed, 1940 insertions, 299 deletions
diff --git a/src/libcharon/sa/authenticator.c b/src/libcharon/sa/authenticator.c index 8571274ac..6c3681a2d 100644 --- a/src/libcharon/sa/authenticator.c +++ b/src/libcharon/sa/authenticator.c @@ -31,12 +31,14 @@ ENUM_BEGIN(auth_method_names, AUTH_RSA, AUTH_DSS, "RSA signature", "pre-shared key", "DSS signature"); -ENUM_NEXT(auth_method_names, AUTH_ECDSA_256, AUTH_GSPM, AUTH_DSS, +ENUM_NEXT(auth_method_names, AUTH_ECDSA_256, AUTH_DS, AUTH_DSS, "ECDSA-256 signature", "ECDSA-384 signature", "ECDSA-521 signature", - "secure password method"); -ENUM_NEXT(auth_method_names, AUTH_XAUTH_INIT_PSK, AUTH_HYBRID_RESP_RSA, AUTH_GSPM, + "secure password method", + "NULL authentication", + "digital signature"); +ENUM_NEXT(auth_method_names, AUTH_XAUTH_INIT_PSK, AUTH_HYBRID_RESP_RSA, AUTH_DS, "XAuthInitPSK", "XAuthRespPSK", "XAuthInitRSA", @@ -99,6 +101,7 @@ authenticator_t *authenticator_create_verifier( case AUTH_ECDSA_256: case AUTH_ECDSA_384: case AUTH_ECDSA_521: + case AUTH_DS: return (authenticator_t*)pubkey_authenticator_create_verifier(ike_sa, sent_nonce, received_init, reserved); case AUTH_PSK: diff --git a/src/libcharon/sa/authenticator.h b/src/libcharon/sa/authenticator.h index 914f42d9d..97c042e71 100644 --- a/src/libcharon/sa/authenticator.h +++ b/src/libcharon/sa/authenticator.h @@ -80,6 +80,16 @@ enum auth_method_t { AUTH_GSPM = 12, /** + * NULL Authentication Method as specified in draft-ietf-ipsecme-ikev2-null-auth + */ + AUTH_NULL = 13, + + /** + * Digital Signature as specified in RFC 7427 + */ + AUTH_DS = 14, + + /** * IKEv1 initiator XAUTH with PSK, outside of IANA range */ AUTH_XAUTH_INIT_PSK = 256, diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index a96ab4e90..e0db2e655 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -34,6 +34,8 @@ ENUM(child_sa_state_names, CHILD_CREATED, CHILD_DESTROYING, "INSTALLED", "UPDATING", "REKEYING", + "REKEYED", + "RETRYING", "DELETING", "DESTROYING", ); @@ -100,6 +102,16 @@ struct private_child_sa_t { u_int32_t reqid; /** + * Did we allocate/confirm and must release the reqid? + */ + bool reqid_allocated; + + /* + * Unique CHILD_SA identifier + */ + u_int32_t unique_id; + + /** * inbound mark used for this child_sa */ mark_t mark_in; @@ -228,6 +240,12 @@ METHOD(child_sa_t, get_reqid, u_int32_t, return this->reqid; } +METHOD(child_sa_t, get_unique_id, u_int32_t, + private_child_sa_t *this) +{ + return this->unique_id; +} + METHOD(child_sa_t, get_config, child_cfg_t*, private_child_sa_t *this) { @@ -602,7 +620,7 @@ METHOD(child_sa_t, alloc_spi, u_int32_t, { if (hydra->kernel_interface->get_spi(hydra->kernel_interface, this->other_addr, this->my_addr, - proto_ike2ip(protocol), this->reqid, + proto_ike2ip(protocol), &this->my_spi) == SUCCESS) { /* if we allocate a SPI, but then are unable to establish the SA, we @@ -618,7 +636,7 @@ METHOD(child_sa_t, alloc_cpi, u_int16_t, { if (hydra->kernel_interface->get_cpi(hydra->kernel_interface, this->other_addr, this->my_addr, - this->reqid, &this->my_cpi) == SUCCESS) + &this->my_cpi) == SUCCESS) { return this->my_cpi; } @@ -632,7 +650,7 @@ METHOD(child_sa_t, install, status_t, { u_int16_t enc_alg = ENCR_UNDEFINED, int_alg = AUTH_UNDEFINED, size; u_int16_t esn = NO_EXT_SEQ_NUMBERS; - traffic_selector_t *src_ts = NULL, *dst_ts = NULL; + linked_list_t *src_ts = NULL, *dst_ts = NULL; time_t now; lifetime_cfg_t *lifetime; u_int32_t tfc = 0; @@ -680,6 +698,18 @@ METHOD(child_sa_t, install, status_t, this->proposal->get_algorithm(this->proposal, EXTENDED_SEQUENCE_NUMBERS, &esn, NULL); + if (!this->reqid_allocated) + { + status = hydra->kernel_interface->alloc_reqid(hydra->kernel_interface, + my_ts, other_ts, this->mark_in, this->mark_out, + &this->reqid); + if (status != SUCCESS) + { + return status; + } + this->reqid_allocated = TRUE; + } + lifetime = this->config->get_lifetime(this->config); now = time_monotonic(NULL); @@ -704,18 +734,16 @@ METHOD(child_sa_t, install, status_t, lifetime->time.rekey = 0; } - /* BEET requires the bound address from the traffic selectors. - * TODO: We add just the first traffic selector for now, as the - * kernel accepts a single TS per SA only */ + /* BEET requires the bound address from the traffic selectors */ if (inbound) { - my_ts->get_first(my_ts, (void**)&dst_ts); - other_ts->get_first(other_ts, (void**)&src_ts); + dst_ts = my_ts; + src_ts = other_ts; } else { - my_ts->get_first(my_ts, (void**)&src_ts); - other_ts->get_first(other_ts, (void**)&dst_ts); + src_ts = my_ts; + dst_ts = other_ts; } status = hydra->kernel_interface->add_sa(hydra->kernel_interface, @@ -723,7 +751,7 @@ METHOD(child_sa_t, install, status_t, inbound ? this->mark_in : this->mark_out, tfc, lifetime, enc_alg, encr, int_alg, integ, this->mode, this->ipcomp, cpi, this->config->get_replay_window(this->config), - initiator, this->encap, esn, update, src_ts, dst_ts); + initiator, this->encap, esn, inbound, update, src_ts, dst_ts); free(lifetime); @@ -798,6 +826,19 @@ METHOD(child_sa_t, add_policies, status_t, traffic_selector_t *my_ts, *other_ts; status_t status = SUCCESS; + if (!this->reqid_allocated) + { + /* trap policy, get or confirm reqid */ + status = hydra->kernel_interface->alloc_reqid( + hydra->kernel_interface, my_ts_list, other_ts_list, + this->mark_in, this->mark_out, &this->reqid); + if (status != SUCCESS) + { + return status; + } + this->reqid_allocated = TRUE; + } + /* apply traffic selectors */ enumerator = my_ts_list->create_enumerator(my_ts_list); while (enumerator->enumerate(enumerator, &my_ts)) @@ -805,12 +846,15 @@ METHOD(child_sa_t, add_policies, status_t, array_insert(this->my_ts, ARRAY_TAIL, my_ts->clone(my_ts)); } enumerator->destroy(enumerator); + array_sort(this->my_ts, (void*)traffic_selector_cmp, NULL); + enumerator = other_ts_list->create_enumerator(other_ts_list); while (enumerator->enumerate(enumerator, &other_ts)) { array_insert(this->other_ts, ARRAY_TAIL, other_ts->clone(other_ts)); } enumerator->destroy(enumerator); + array_sort(this->other_ts, (void*)traffic_selector_cmp, NULL); if (this->config->install_policy(this->config)) { @@ -1071,6 +1115,22 @@ METHOD(child_sa_t, destroy, void, set_state(this, CHILD_DESTROYING); + if (this->config->install_policy(this->config)) + { + /* delete all policies in the kernel */ + enumerator = create_policy_enumerator(this); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + { + del_policies_internal(this, my_ts, other_ts, priority); + if (priority == POLICY_PRIORITY_DEFAULT && require_policy_update()) + { + del_policies_internal(this, my_ts, other_ts, + POLICY_PRIORITY_FALLBACK); + } + } + enumerator->destroy(enumerator); + } + /* delete SAs in the kernel, if they are set up */ if (this->my_spi) { @@ -1087,20 +1147,13 @@ METHOD(child_sa_t, destroy, void, this->mark_out); } - if (this->config->install_policy(this->config)) + if (this->reqid_allocated) { - /* delete all policies in the kernel */ - enumerator = create_policy_enumerator(this); - while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + if (hydra->kernel_interface->release_reqid(hydra->kernel_interface, + this->reqid, this->mark_in, this->mark_out) != SUCCESS) { - del_policies_internal(this, my_ts, other_ts, priority); - if (priority == POLICY_PRIORITY_DEFAULT && require_policy_update()) - { - del_policies_internal(this, my_ts, other_ts, - POLICY_PRIORITY_FALLBACK); - } + DBG1(DBG_CHD, "releasing reqid %u failed", this->reqid); } - enumerator->destroy(enumerator); } array_destroy_offset(this->my_ts, offsetof(traffic_selector_t, destroy)); @@ -1151,15 +1204,17 @@ static host_t* get_proxy_addr(child_cfg_t *config, host_t *ike, bool local) * Described in header. */ child_sa_t * child_sa_create(host_t *me, host_t* other, - child_cfg_t *config, u_int32_t rekey, bool encap) + child_cfg_t *config, u_int32_t rekey, bool encap, + u_int mark_in, u_int mark_out) { - static refcount_t reqid = 0; private_child_sa_t *this; + static refcount_t unique_id = 0, unique_mark = 0, mark; INIT(this, .public = { .get_name = _get_name, .get_reqid = _get_reqid, + .get_unique_id = _get_unique_id, .get_config = _get_config, .get_state = _get_state, .set_state = _set_state, @@ -1201,6 +1256,7 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, .close_action = config->get_close_action(config), .dpd_action = config->get_dpd_action(config), .reqid = config->get_reqid(config), + .unique_id = ref_get(&unique_id), .mark_in = config->get_mark(config, TRUE), .mark_out = config->get_mark(config, FALSE), .install_time = time_monotonic(NULL), @@ -1209,9 +1265,37 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, this->config = config; config->get_ref(config); + if (mark_in) + { + this->mark_in.value = mark_in; + } + if (mark_out) + { + this->mark_out.value = mark_out; + } + if (this->mark_in.value == MARK_UNIQUE || + this->mark_out.value == MARK_UNIQUE) + { + mark = ref_get(&unique_mark); + if (this->mark_in.value == MARK_UNIQUE) + { + this->mark_in.value = mark; + } + if (this->mark_out.value == MARK_UNIQUE) + { + this->mark_out.value = mark; + } + } + if (!this->reqid) { - /* reuse old reqid if we are rekeying an existing CHILD_SA */ + /* reuse old reqid if we are rekeying an existing CHILD_SA. While the + * reqid cache would find the same reqid for our selectors, this does + * not work in a special case: If an SA is triggered by a trap policy, + * but the negotiated SA gets narrowed, we still must reuse the same + * reqid to successfully "trigger" the SA on the kernel level. Rekeying + * such an SA requires an explicit reqid, as the cache currently knows + * the original selectors only for that reqid. */ if (rekey) { this->reqid = rekey; @@ -1219,22 +1303,9 @@ child_sa_t * child_sa_create(host_t *me, host_t* other, else { this->reqid = charon->traps->find_reqid(charon->traps, config); - if (!this->reqid) - { - this->reqid = ref_get(&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)) diff --git a/src/libcharon/sa/child_sa.h b/src/libcharon/sa/child_sa.h index a0c6c357f..debe8eb2c 100644 --- a/src/libcharon/sa/child_sa.h +++ b/src/libcharon/sa/child_sa.h @@ -68,6 +68,16 @@ enum child_sa_state_t { CHILD_REKEYING, /** + * CHILD_SA that was rekeyed, but stays installed + */ + CHILD_REKEYED, + + /** + * CHILD_SA negotiation failed, but gets retried + */ + CHILD_RETRYING, + + /** * CHILD_SA in progress of delete */ CHILD_DELETING, @@ -121,6 +131,16 @@ struct child_sa_t { u_int32_t (*get_reqid)(child_sa_t *this); /** + * Get the unique numerical identifier for this CHILD_SA. + * + * While the same reqid might be shared between multiple SAs, the unique_id + * is truly unique for all CHILD_SA instances. + * + * @return unique CHILD_SA identifier + */ + u_int32_t (*get_unique_id)(child_sa_t *this); + + /** * Get the config used to set up this child sa. * * @return child_cfg @@ -379,9 +399,12 @@ struct child_sa_t { * @param config config to use for this CHILD_SA * @param reqid reqid of old CHILD_SA when rekeying, 0 otherwise * @param encap TRUE to enable UDP encapsulation (NAT traversal) + * @param mark_in explicit inbound mark value to use, 0 for config + * @param mark_out explicit outbound mark value to use, 0 for config * @return child_sa_t object */ child_sa_t * child_sa_create(host_t *me, host_t *other, child_cfg_t *config, - u_int32_t reqid, bool encap); + u_int32_t reqid, bool encap, + u_int mark_in, u_int mark_out); #endif /** CHILD_SA_H_ @}*/ diff --git a/src/libcharon/sa/child_sa_manager.c b/src/libcharon/sa/child_sa_manager.c new file mode 100644 index 000000000..071a119da --- /dev/null +++ b/src/libcharon/sa/child_sa_manager.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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 "child_sa_manager.h" + +#include <daemon.h> +#include <threading/mutex.h> +#include <collections/hashtable.h> + +typedef struct private_child_sa_manager_t private_child_sa_manager_t; + +/** + * Private data of an child_sa_manager_t object. + */ +struct private_child_sa_manager_t { + + /** + * Public child_sa_manager_t interface. + */ + child_sa_manager_t public; + + /** + * CHILD_SAs by inbound SPI/dst, child_entry_t => child_entry_t + */ + hashtable_t *in; + + /** + * CHILD_SAs by outbound SPI/dst, child_entry_t => child_entry_t + */ + hashtable_t *out; + + /** + * CHILD_SAs by unique ID, child_entry_t => child_entry_t + */ + hashtable_t *ids; + + /** + * Mutex to access any hashtable + */ + mutex_t *mutex; +}; + +/** + * Hashtable entry for a known CHILD_SA + */ +typedef struct { + /** the associated IKE_SA */ + ike_sa_id_t *ike_id; + /** unique CHILD_SA identifier */ + u_int32_t unique_id; + /** inbound SPI */ + u_int32_t spi_in; + /** outbound SPI */ + u_int32_t spi_out; + /** inbound host address */ + host_t *host_in; + /** outbound host address and port */ + host_t *host_out; + /** IPsec protocol, AH|ESP */ + protocol_id_t proto; +} child_entry_t; + +/** + * Destroy a CHILD_SA entry + */ +static void child_entry_destroy(child_entry_t *entry) +{ + entry->ike_id->destroy(entry->ike_id); + entry->host_in->destroy(entry->host_in); + entry->host_out->destroy(entry->host_out); + free(entry); +} + +/** + * Hashtable hash function for inbound SAs + */ +static u_int hash_in(child_entry_t *entry) +{ + return chunk_hash_inc(chunk_from_thing(entry->spi_in), + chunk_hash_inc(entry->host_in->get_address(entry->host_in), + chunk_hash(chunk_from_thing(entry->proto)))); +} + +/** + * Hashtable equals function for inbound SAs + */ +static bool equals_in(child_entry_t *a, child_entry_t *b) +{ + return a->spi_in == b->spi_in && + a->proto == b->proto && + a->host_in->ip_equals(a->host_in, b->host_in); +} + +/** + * Hashtable hash function for outbound SAs + */ +static u_int hash_out(child_entry_t *entry) +{ + return chunk_hash_inc(chunk_from_thing(entry->spi_out), + chunk_hash_inc(entry->host_out->get_address(entry->host_out), + chunk_hash(chunk_from_thing(entry->proto)))); +} + +/** + * Hashtable equals function for outbound SAs + */ +static bool equals_out(child_entry_t *a, child_entry_t *b) +{ + return a->spi_out == b->spi_out && + a->proto == b->proto && + a->host_out->ip_equals(a->host_out, b->host_out); +} + +/** + * Hashtable hash function for SAs by unique ID + */ +static u_int hash_id(child_entry_t *entry) +{ + return chunk_hash(chunk_from_thing(entry->unique_id)); +} + +/** + * Hashtable equals function for SAs by unique ID + */ +static bool equals_id(child_entry_t *a, child_entry_t *b) +{ + return a->unique_id == b->unique_id; +} + +METHOD(child_sa_manager_t, add, void, + private_child_sa_manager_t *this, child_sa_t *child_sa, ike_sa_t *ike_sa) +{ + child_entry_t *entry; + host_t *in, *out; + ike_sa_id_t *id; + + id = ike_sa->get_id(ike_sa); + in = ike_sa->get_my_host(ike_sa); + out = ike_sa->get_other_host(ike_sa); + + INIT(entry, + .ike_id = id->clone(id), + .unique_id = child_sa->get_unique_id(child_sa), + .proto = child_sa->get_protocol(child_sa), + .spi_in = child_sa->get_spi(child_sa, TRUE), + .spi_out = child_sa->get_spi(child_sa, FALSE), + .host_in = in->clone(in), + .host_out = out->clone(out), + ); + + this->mutex->lock(this->mutex); + if (!this->in->get(this->in, entry) && + !this->out->get(this->out, entry)) + { + this->in->put(this->in, entry, entry); + this->out->put(this->out, entry, entry); + entry = this->ids->put(this->ids, entry, entry); + } + this->mutex->unlock(this->mutex); + + if (entry) + { + child_entry_destroy(entry); + } +} + +METHOD(child_sa_manager_t, remove_, void, + private_child_sa_manager_t *this, child_sa_t *child_sa) +{ + child_entry_t *entry, key = { + .unique_id = child_sa->get_unique_id(child_sa), + }; + + this->mutex->lock(this->mutex); + entry = this->ids->remove(this->ids, &key); + if (entry) + { + this->in->remove(this->in, entry); + this->out->remove(this->out, entry); + } + this->mutex->unlock(this->mutex); + + if (entry) + { + child_entry_destroy(entry); + } +} + +/** + * Check out an IKE_SA for a given CHILD_SA + */ +static ike_sa_t *checkout_ikesa(private_child_sa_manager_t *this, + ike_sa_id_t *id, u_int32_t unique_id, child_sa_t **child_sa) +{ + enumerator_t *enumerator; + child_sa_t *current; + ike_sa_t *ike_sa; + bool found = FALSE; + + ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id); + id->destroy(id); + if (ike_sa) + { + enumerator = ike_sa->create_child_sa_enumerator(ike_sa); + while (enumerator->enumerate(enumerator, ¤t)) + { + found = current->get_unique_id(current) == unique_id; + if (found) + { + if (child_sa) + { + *child_sa = current; + } + break; + } + } + enumerator->destroy(enumerator); + + if (found) + { + return ike_sa; + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + } + return NULL; +} + +METHOD(child_sa_manager_t, checkout_by_id, ike_sa_t*, + private_child_sa_manager_t *this, u_int32_t unique_id, + child_sa_t **child_sa) +{ + ike_sa_id_t *id; + child_entry_t *entry, key = { + .unique_id = unique_id, + }; + + this->mutex->lock(this->mutex); + entry = this->ids->get(this->ids, &key); + if (entry) + { + id = entry->ike_id->clone(entry->ike_id); + } + this->mutex->unlock(this->mutex); + + if (entry) + { + return checkout_ikesa(this, id, unique_id, child_sa); + } + return NULL; +} + +METHOD(child_sa_manager_t, checkout, ike_sa_t*, + private_child_sa_manager_t *this, protocol_id_t protocol, u_int32_t spi, + host_t *dst, child_sa_t **child_sa) +{ + ike_sa_id_t *id; + u_int32_t unique_id; + child_entry_t *entry, key = { + .spi_in = spi, + .spi_out = spi, + .host_in = dst, + .host_out = dst, + .proto = protocol, + }; + + this->mutex->lock(this->mutex); + entry = this->in->get(this->in, &key); + if (!entry) + { + entry = this->out->get(this->out, &key); + } + if (entry) + { + unique_id = entry->unique_id; + id = entry->ike_id->clone(entry->ike_id); + } + this->mutex->unlock(this->mutex); + + if (entry) + { + return checkout_ikesa(this, id, unique_id, child_sa); + } + return NULL; +} + +METHOD(child_sa_manager_t, destroy, void, + private_child_sa_manager_t *this) +{ + this->in->destroy(this->in); + this->out->destroy(this->out); + this->ids->destroy(this->ids); + this->mutex->destroy(this->mutex); + free(this); +} + +/** + * See header + */ +child_sa_manager_t *child_sa_manager_create() +{ + private_child_sa_manager_t *this; + + INIT(this, + .public = { + .add = _add, + .remove = _remove_, + .checkout = _checkout, + .checkout_by_id = _checkout_by_id, + .destroy = _destroy, + }, + .in = hashtable_create((hashtable_hash_t)hash_in, + (hashtable_equals_t)equals_in, 8), + .out = hashtable_create((hashtable_hash_t)hash_out, + (hashtable_equals_t)equals_out, 8), + .ids = hashtable_create((hashtable_hash_t)hash_id, + (hashtable_equals_t)equals_id, 8), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/child_sa_manager.h b/src/libcharon/sa/child_sa_manager.h new file mode 100644 index 000000000..4d57528e8 --- /dev/null +++ b/src/libcharon/sa/child_sa_manager.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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 child_sa_manager child_sa_manager + * @{ @ingroup sa + */ + +#ifndef CHILD_SA_MANAGER_H_ +#define CHILD_SA_MANAGER_H_ + +#include <sa/ike_sa.h> +#include <sa/child_sa.h> + +typedef struct child_sa_manager_t child_sa_manager_t; + +/** + * Handle CHILD_SA to IKE_SA relations + */ +struct child_sa_manager_t { + + /** + * Register a CHILD_SA/IKE_SA relation. + * + * @param child_sa CHILD_SA to register + * @param ike_sa IKE_SA owning the CHILD_SA + */ + void (*add)(child_sa_manager_t *this, child_sa_t *child_sa, ike_sa_t *ike_sa); + + /** + * Unregister a CHILD_SA/IKE_SA relation. + * + * @param child_sa CHILD_SA to unregister + */ + void (*remove)(child_sa_manager_t *this, child_sa_t *child_sa); + + /** + * Find a CHILD_SA and check out the associated IKE_SA by SPI. + * + * On success, the returned IKE_SA must be checked in after use to + * the IKE_SA manager. + * + * @param protocol IPsec protocol, AH|ESP + * @param spi SPI of CHILD_SA to check out + * @param dst SA destination host related to SPI + * @param child_sa returns CHILD_SA managed by IKE_SA + * @return IKE_SA, NULL if not found + */ + ike_sa_t *(*checkout)(child_sa_manager_t *this, + protocol_id_t protocol, u_int32_t spi, host_t *dst, + child_sa_t **child_sa); + + /** + * Find a CHILD_SA and check out the associated IKE_SA by unique_id. + * + * On success, the returned IKE_SA must be checked in after use to + * the IKE_SA manager. + * + * @param unique_id unique ID of CHILD_SA to check out + * @param child_sa returns CHILD_SA managed by IKE_SA + * @return IKE_SA, NULL if not found + */ + ike_sa_t *(*checkout_by_id)(child_sa_manager_t *this, u_int32_t unique_id, + child_sa_t **child_sa); + + /** + * Destroy a child_sa_manager_t. + */ + void (*destroy)(child_sa_manager_t *this); +}; + +/** + * Create a child_sa_manager instance. + */ +child_sa_manager_t *child_sa_manager_create(); + +#endif /** CHILD_SA_MANAGER_H_ @}*/ diff --git a/src/libcharon/sa/eap/eap_method.h b/src/libcharon/sa/eap/eap_method.h index 6242a5a6e..689c0f990 100644 --- a/src/libcharon/sa/eap/eap_method.h +++ b/src/libcharon/sa/eap/eap_method.h @@ -137,6 +137,18 @@ struct eap_method_t { void (*set_identifier) (eap_method_t *this, u_int8_t identifier); /** + * Get authentication details performed by this EAP method. + * + * After EAP completion, the auth data contains additional information + * of the authentication process, used certificates etc. + * This method is optional to implement, but if it is, it must return + * a valid auth_cfg. + * + * @return auth method, internal data + */ + auth_cfg_t* (*get_auth)(eap_method_t *this); + + /** * Destroys a eap_method_t object. */ void (*destroy) (eap_method_t *this); diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index d92b9df8e..3aafa4c13 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -932,6 +932,7 @@ METHOD(ike_sa_t, update_hosts, void, /* update our address in any case */ if (force && !me->equals(me, this->my_host)) { + charon->bus->ike_update(charon->bus, &this->public, TRUE, me); set_my_host(this, me->clone(me)); update = TRUE; } @@ -945,6 +946,7 @@ METHOD(ike_sa_t, update_hosts, void, (!has_condition(this, COND_NAT_HERE) || !has_condition(this, COND_ORIGINAL_INITIATOR))) { + charon->bus->ike_update(charon->bus, &this->public, FALSE, other); set_other_host(this, other->clone(other)); update = TRUE; } @@ -964,6 +966,10 @@ METHOD(ike_sa_t, update_hosts, void, enumerator = array_create_enumerator(this->child_sas); while (enumerator->enumerate(enumerator, &child_sa)) { + charon->child_sa_manager->remove(charon->child_sa_manager, child_sa); + charon->child_sa_manager->add(charon->child_sa_manager, + child_sa, &this->public); + if (child_sa->update(child_sa, this->my_host, this->other_host, vips, has_condition(this, COND_NAT_ANY)) == NOT_SUPPORTED) { @@ -971,6 +977,7 @@ METHOD(ike_sa_t, update_hosts, void, child_sa->get_protocol(child_sa), child_sa->get_spi(child_sa, TRUE)); } + } enumerator->destroy(enumerator); @@ -1444,6 +1451,8 @@ METHOD(ike_sa_t, add_child_sa, void, private_ike_sa_t *this, child_sa_t *child_sa) { array_insert_create(&this->child_sas, ARRAY_TAIL, child_sa); + charon->child_sa_manager->add(charon->child_sa_manager, + child_sa, &this->public); } METHOD(ike_sa_t, get_child_sa, child_sa_t*, @@ -1471,16 +1480,58 @@ METHOD(ike_sa_t, get_child_count, int, return array_count(this->child_sas); } +/** + * Private data of a create_child_sa_enumerator() + */ +typedef struct { + /** implements enumerator */ + enumerator_t public; + /** inner array enumerator */ + enumerator_t *inner; + /** current item */ + child_sa_t *current; +} child_enumerator_t; + +METHOD(enumerator_t, child_enumerate, bool, + child_enumerator_t *this, child_sa_t **child_sa) +{ + if (this->inner->enumerate(this->inner, &this->current)) + { + *child_sa = this->current; + return TRUE; + } + return FALSE; +} + +METHOD(enumerator_t, child_enumerator_destroy, void, + child_enumerator_t *this) +{ + this->inner->destroy(this->inner); + free(this); +} + METHOD(ike_sa_t, create_child_sa_enumerator, enumerator_t*, private_ike_sa_t *this) { - return array_create_enumerator(this->child_sas); + child_enumerator_t *enumerator; + + INIT(enumerator, + .public = { + .enumerate = (void*)_child_enumerate, + .destroy = _child_enumerator_destroy, + }, + .inner = array_create_enumerator(this->child_sas), + ); + return &enumerator->public; } METHOD(ike_sa_t, remove_child_sa, void, private_ike_sa_t *this, enumerator_t *enumerator) { - array_remove_at(this->child_sas, enumerator); + child_enumerator_t *ce = (child_enumerator_t*)enumerator; + + charon->child_sa_manager->remove(charon->child_sa_manager, ce->current); + array_remove_at(this->child_sas, ce->inner); } METHOD(ike_sa_t, rekey_child_sa, status_t, @@ -1513,13 +1564,13 @@ METHOD(ike_sa_t, destroy_child_sa, status_t, child_sa_t *child_sa; status_t status = NOT_FOUND; - enumerator = array_create_enumerator(this->child_sas); + enumerator = create_child_sa_enumerator(this); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { if (child_sa->get_protocol(child_sa) == protocol && child_sa->get_spi(child_sa, TRUE) == spi) { - array_remove_at(this->child_sas, enumerator); + remove_child_sa(this, enumerator); child_sa->destroy(child_sa); status = SUCCESS; break; @@ -1771,7 +1822,7 @@ METHOD(ike_sa_t, reestablish, status_t, #endif /* ME */ { /* handle existing CHILD_SAs */ - enumerator = array_create_enumerator(this->child_sas); + enumerator = create_child_sa_enumerator(this); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { if (has_condition(this, COND_REAUTHENTICATING)) @@ -1780,7 +1831,7 @@ METHOD(ike_sa_t, reestablish, status_t, { case CHILD_ROUTED: { /* move routed child directly */ - array_remove_at(this->child_sas, enumerator); + remove_child_sa(this, enumerator); new->add_child_sa(new, child_sa); action = ACTION_NONE; break; @@ -2209,6 +2260,12 @@ METHOD(ike_sa_t, inherit_post, void, array_insert_create(&this->other_vips, ARRAY_TAIL, vip); } + /* MOBIKE additional addresses */ + while (array_remove(other->peer_addresses, ARRAY_HEAD, &vip)) + { + array_insert_create(&this->peer_addresses, ARRAY_TAIL, vip); + } + /* authentication information */ enumerator = array_create_enumerator(other->my_auths); while (enumerator->enumerate(enumerator, &cfg)) @@ -2251,7 +2308,8 @@ METHOD(ike_sa_t, inherit_post, void, /* adopt all children */ while (array_remove(other->child_sas, ARRAY_HEAD, &child_sa)) { - array_insert_create(&this->child_sas, ARRAY_TAIL, child_sa); + charon->child_sa_manager->remove(charon->child_sa_manager, child_sa); + add_child_sa(this, child_sa); } /* move pending tasks to the new IKE_SA */ @@ -2296,8 +2354,8 @@ METHOD(ike_sa_t, destroy, void, { if (entry.handler) { - hydra->attributes->release(hydra->attributes, entry.handler, - this->other_id, entry.type, entry.data); + charon->attributes->release(charon->attributes, entry.handler, + &this->public, entry.type, entry.data); } free(entry.data.ptr); } @@ -2305,6 +2363,7 @@ METHOD(ike_sa_t, destroy, void, * routes that the CHILD_SA tries to uninstall. */ while (array_remove(this->child_sas, ARRAY_TAIL, &child_sa)) { + charon->child_sa_manager->remove(charon->child_sa_manager, child_sa); child_sa->destroy(child_sa); } while (array_remove(this->my_vips, ARRAY_TAIL, &vip)) @@ -2321,12 +2380,11 @@ METHOD(ike_sa_t, destroy, void, if (this->peer_cfg) { 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); + charon->attributes->release_address(charon->attributes, + pools, vip, &this->public); pools->destroy(pools); } vip->destroy(vip); diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index c72d87367..9dbc805c9 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -131,6 +131,11 @@ enum ike_extension_t { * peer supports proprietary IKEv1 or standardized IKEv2 fragmentation */ EXT_IKE_FRAGMENTATION = (1<<11), + + /** + * Signature Authentication, RFC 7427 + */ + EXT_SIGNATURE_AUTH = (1<<12), }; /** @@ -936,8 +941,9 @@ struct ike_sa_t { /** * Reauthenticate the IKE_SA. * - * Create a completely new IKE_SA with authentication, recreates all children - * within the IKE_SA, closes this IKE_SA. + * Triggers a new IKE_SA that replaces this one. IKEv1 implicitly inherits + * all Quick Modes, while IKEv2 recreates all active and queued CHILD_SAs + * in the new IKE_SA. * * @return DESTROY_ME to destroy the IKE_SA */ diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c index bdabc59b5..13fc74ff7 100644 --- a/src/libcharon/sa/ike_sa_manager.c +++ b/src/libcharon/sa/ike_sa_manager.c @@ -1184,7 +1184,8 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*, DBG2(DBG_MGR, "checkout IKE_SA by message"); - if (id->get_responder_spi(id) == 0) + if (id->get_responder_spi(id) == 0 && + message->get_message_id(message) == 0) { if (message->get_major_version(message) == IKEV2_MAJOR_VERSION) { @@ -1383,54 +1384,35 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*, } METHOD(ike_sa_manager_t, checkout_by_id, ike_sa_t*, - private_ike_sa_manager_t *this, u_int32_t id, bool child) + private_ike_sa_manager_t *this, u_int32_t id) { - enumerator_t *enumerator, *children; + enumerator_t *enumerator; entry_t *entry; ike_sa_t *ike_sa = NULL; - child_sa_t *child_sa; u_int segment; - DBG2(DBG_MGR, "checkout IKE_SA by ID"); + DBG2(DBG_MGR, "checkout IKE_SA by ID %u", id); enumerator = create_table_enumerator(this); while (enumerator->enumerate(enumerator, &entry, &segment)) { if (wait_for_entry(this, entry, segment)) { - /* look for a child with such a reqid ... */ - if (child) - { - children = entry->ike_sa->create_child_sa_enumerator(entry->ike_sa); - while (children->enumerate(children, (void**)&child_sa)) - { - if (child_sa->get_reqid(child_sa) == id) - { - ike_sa = entry->ike_sa; - break; - } - } - children->destroy(children); - } - else /* ... or for a IKE_SA with such a unique id */ - { - if (entry->ike_sa->get_unique_id(entry->ike_sa) == id) - { - ike_sa = entry->ike_sa; - } - } - /* got one, return */ - if (ike_sa) + if (entry->ike_sa->get_unique_id(entry->ike_sa) == id) { + ike_sa = entry->ike_sa; entry->checked_out = TRUE; - DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out", - ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa)); break; } } } enumerator->destroy(enumerator); + if (ike_sa) + { + DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out", + ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa)); + } charon->bus->set_sa(charon->bus, ike_sa); return ike_sa; } @@ -1746,29 +1728,45 @@ METHOD(ike_sa_manager_t, create_id_enumerator, enumerator_t*, } /** - * Move all CHILD_SAs from old to new + * Move all CHILD_SAs and virtual IPs from old to new */ -static void adopt_children(ike_sa_t *old, ike_sa_t *new) +static void adopt_children_and_vips(ike_sa_t *old, ike_sa_t *new) { enumerator_t *enumerator; child_sa_t *child_sa; + host_t *vip; + int chcount = 0, vipcount = 0; + enumerator = old->create_child_sa_enumerator(old); while (enumerator->enumerate(enumerator, &child_sa)) { old->remove_child_sa(old, enumerator); new->add_child_sa(new, child_sa); + chcount++; } enumerator->destroy(enumerator); -} -/** - * Check if the replaced IKE_SA might get reauthenticated from host - */ -static bool is_ikev1_reauth(ike_sa_t *duplicate, host_t *host) -{ - return duplicate->get_version(duplicate) == IKEV1 && - host->equals(host, duplicate->get_other_host(duplicate)); + enumerator = old->create_virtual_ip_enumerator(old, FALSE); + while (enumerator->enumerate(enumerator, &vip)) + { + new->add_virtual_ip(new, FALSE, vip); + vipcount++; + } + enumerator->destroy(enumerator); + /* this does not release the addresses, which is good, but it does trigger + * an assign_vips(FALSE) event... */ + old->clear_virtual_ips(old, FALSE); + /* ...trigger the analogous event on the new SA */ + charon->bus->set_sa(charon->bus, new); + charon->bus->assign_vips(charon->bus, new, TRUE); + charon->bus->set_sa(charon->bus, old); + + if (chcount || vipcount) + { + DBG1(DBG_IKE, "detected reauth of existing IKE_SA, adopting %d " + "children and %d virtual IPs", chcount, vipcount); + } } /** @@ -1780,13 +1778,20 @@ static status_t enforce_replace(private_ike_sa_manager_t *this, { charon->bus->alert(charon->bus, ALERT_UNIQUE_REPLACE); - if (is_ikev1_reauth(duplicate, host)) + if (host->equals(host, duplicate->get_other_host(duplicate))) { /* looks like a reauthentication attempt */ - adopt_children(duplicate, new); + if (!new->has_condition(new, COND_INIT_CONTACT_SEEN) && + new->get_version(new) == IKEV1) + { + /* IKEv1 implicitly takes over children, IKEv2 recreates them + * explicitly. */ + adopt_children_and_vips(duplicate, new); + } /* For IKEv1 we have to delay the delete for the old IKE_SA. Some * peers need to complete the new SA first, otherwise the quick modes - * might get lost. */ + * might get lost. For IKEv2 we do the same, as we want overlapping + * CHILD_SAs to keep connectivity up. */ lib->scheduler->schedule_job(lib->scheduler, (job_t*) delete_ike_sa_job_create(duplicate->get_id(duplicate), TRUE), 10); return SUCCESS; @@ -1851,7 +1856,9 @@ METHOD(ike_sa_manager_t, check_uniqueness, bool, other, other_host); break; case UNIQUE_KEEP: - if (!is_ikev1_reauth(duplicate, other_host)) + /* potential reauthentication? */ + if (!other_host->equals(other_host, + duplicate->get_other_host(duplicate))) { cancel = TRUE; /* we keep the first IKE_SA and delete all diff --git a/src/libcharon/sa/ike_sa_manager.h b/src/libcharon/sa/ike_sa_manager.h index a68ae7763..f259d8e56 100644 --- a/src/libcharon/sa/ike_sa_manager.h +++ b/src/libcharon/sa/ike_sa_manager.h @@ -129,19 +129,15 @@ struct ike_sa_manager_t { /** * Check out an IKE_SA a unique ID. * - * Every IKE_SA and every CHILD_SA is uniquely identified by an ID. - * These checkout function uses, depending - * on the child parameter, the unique ID of the IKE_SA or the reqid - * of one of a IKE_SAs CHILD_SA. + * Every IKE_SA is uniquely identified by a numerical ID. This checkout + * function uses the unique ID of the IKE_SA to check it out. * * @param id unique ID of the object - * @param child TRUE to use CHILD, FALSE to use IKE_SA * @return * - checked out IKE_SA, if found * - NULL, if not found */ - ike_sa_t* (*checkout_by_id) (ike_sa_manager_t* this, u_int32_t id, - bool child); + ike_sa_t* (*checkout_by_id) (ike_sa_manager_t* this, u_int32_t id); /** * Check out an IKE_SA by the policy/connection name. diff --git a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c index aa966cd5f..bb187f07c 100644 --- a/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c +++ b/src/libcharon/sa/ikev1/authenticators/psk_v1_authenticator.c @@ -74,7 +74,10 @@ METHOD(authenticator_t, build, status_t, keymat_v1_t *keymat; chunk_t hash, dh; - this->dh->get_my_public_value(this->dh, &dh); + if (!this->dh->get_my_public_value(this->dh, &dh)) + { + return FAILED; + } 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, @@ -108,7 +111,10 @@ METHOD(authenticator_t, process, status_t, return FAILED; } - this->dh->get_my_public_value(this->dh, &dh); + if (!this->dh->get_my_public_value(this->dh, &dh)) + { + return FAILED; + } 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, diff --git a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c index bfe5ff449..52228ef2e 100644 --- a/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c +++ b/src/libcharon/sa/ikev1/authenticators/pubkey_v1_authenticator.c @@ -94,7 +94,11 @@ METHOD(authenticator_t, build, status_t, return NOT_FOUND; } - this->dh->get_my_public_value(this->dh, &dh); + if (!this->dh->get_my_public_value(this->dh, &dh)) + { + private->destroy(private); + return FAILED; + } 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, @@ -152,7 +156,10 @@ METHOD(authenticator_t, process, status_t, } id = this->ike_sa->get_other_id(this->ike_sa); - this->dh->get_my_public_value(this->dh, &dh); + if (!this->dh->get_my_public_value(this->dh, &dh)) + { + return FAILED; + } 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, diff --git a/src/libcharon/sa/ikev1/keymat_v1.c b/src/libcharon/sa/ikev1/keymat_v1.c index 619d197bd..f5a91dbeb 100644 --- a/src/libcharon/sa/ikev1/keymat_v1.c +++ b/src/libcharon/sa/ikev1/keymat_v1.c @@ -425,7 +425,7 @@ METHOD(keymat_v1_t, derive_ike_keys, bool, return FALSE; } - if (dh->get_shared_secret(dh, &g_xy) != SUCCESS) + if (!dh->get_shared_secret(dh, &g_xy)) { return FALSE; } @@ -560,7 +560,10 @@ METHOD(keymat_v1_t, derive_ike_keys, bool, return FALSE; } - dh->get_my_public_value(dh, &dh_me); + if (!dh->get_my_public_value(dh, &dh_me)) + { + return FALSE; + } g_xi = this->initiator ? dh_me : dh_other; g_xr = this->initiator ? dh_other : dh_me; @@ -661,7 +664,7 @@ METHOD(keymat_v1_t, derive_child_keys, bool, protocol = proposal->get_protocol(proposal); if (dh) { - if (dh->get_shared_secret(dh, &secret) != SUCCESS) + if (!dh->get_shared_secret(dh, &secret)) { return FALSE; } diff --git a/src/libcharon/sa/ikev1/phase1.c b/src/libcharon/sa/ikev1/phase1.c index d01a831f8..c968b2a9c 100644 --- a/src/libcharon/sa/ikev1/phase1.c +++ b/src/libcharon/sa/ikev1/phase1.c @@ -694,7 +694,13 @@ METHOD(phase1_t, add_nonce_ke, bool, nonce_gen_t *nonceg; chunk_t nonce; - ke_payload = ke_payload_create_from_diffie_hellman(PLV1_KEY_EXCHANGE, this->dh); + ke_payload = ke_payload_create_from_diffie_hellman(PLV1_KEY_EXCHANGE, + this->dh); + if (!ke_payload) + { + DBG1(DBG_IKE, "creating KE payload failed"); + return FALSE; + } message->add_payload(message, &ke_payload->payload_interface); nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat); @@ -739,7 +745,11 @@ METHOD(phase1_t, get_nonce_ke, bool, 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); + if (!this->dh->set_other_public_value(this->dh, this->dh_value)) + { + DBG1(DBG_IKE, "unable to apply received KE value"); + return FALSE; + } nonce_payload = (nonce_payload_t*)message->get_payload(message, PLV1_NONCE); if (!nonce_payload) diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 0f8e8bc6d..cb22bf606 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -1596,7 +1596,8 @@ static bool is_redundant(private_task_manager_t *this, child_sa_t *child_sa) 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)); + child_sa->get_name(child_sa), + child_sa->get_unique_id(child_sa)); redundant = TRUE; break; } @@ -1647,6 +1648,8 @@ METHOD(task_manager_t, queue_child_rekey, void, 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->use_marks(task, child_sa->get_mark(child_sa, TRUE).value, + child_sa->get_mark(child_sa, FALSE).value); task->rekey(task, child_sa->get_spi(child_sa, TRUE)); queue_task(this, &task->task); diff --git a/src/libcharon/sa/ikev1/tasks/isakmp_delete.c b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c index bea0428c4..a56805afb 100644 --- a/src/libcharon/sa/ikev1/tasks/isakmp_delete.c +++ b/src/libcharon/sa/ikev1/tasks/isakmp_delete.c @@ -1,4 +1,7 @@ /* + * Copyright (C) 2015 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2011 Martin Willi * Copyright (C) 2011 revosec AG * @@ -74,6 +77,42 @@ METHOD(task_t, process_i, status_t, METHOD(task_t, process_r, status_t, private_isakmp_delete_t *this, message_t *message) { + enumerator_t *payloads; + payload_t *payload; + delete_payload_t *delete_payload; + ike_sa_id_t *id; + u_int64_t spi_i, spi_r; + bool found = FALSE; + + /* some peers send DELETE payloads for other IKE_SAs, e.g. those for expired + * ones after a rekeyeing, make sure the SPIs match */ + id = this->ike_sa->get_id(this->ike_sa); + payloads = message->create_payload_enumerator(message); + while (payloads->enumerate(payloads, &payload)) + { + if (payload->get_type(payload) == PLV1_DELETE) + { + delete_payload = (delete_payload_t*)payload; + if (!delete_payload->get_ike_spi(delete_payload, &spi_i, &spi_r)) + { + continue; + } + if (id->get_initiator_spi(id) == spi_i && + id->get_responder_spi(id) == spi_r) + { + found = TRUE; + break; + } + } + } + payloads->destroy(payloads); + + if (!found) + { + DBG1(DBG_IKE, "received DELETE for different IKE_SA, ignored"); + return SUCCESS; + } + 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)); diff --git a/src/libcharon/sa/ikev1/tasks/main_mode.c b/src/libcharon/sa/ikev1/tasks/main_mode.c index 2fb4c6935..3ea4a2a85 100644 --- a/src/libcharon/sa/ikev1/tasks/main_mode.c +++ b/src/libcharon/sa/ikev1/tasks/main_mode.c @@ -205,6 +205,43 @@ static status_t send_delete(private_main_mode_t *this) return ALREADY_DONE; } +/** + * Add an INITIAL_CONTACT notify if first contact with peer + */ +static void add_initial_contact(private_main_mode_t *this, message_t *message, + identification_t *idi) +{ + identification_t *idr; + host_t *host; + notify_payload_t *notify; + ike_sa_id_t *ike_sa_id; + u_int64_t spi_i, spi_r; + chunk_t spi; + + idr = this->ph1->get_id(this->ph1, this->peer_cfg, FALSE); + if (idr && !idr->contains_wildcards(idr)) + { + if (this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NO && + this->peer_cfg->get_unique_policy(this->peer_cfg) != UNIQUE_NEVER) + { + host = this->ike_sa->get_other_host(this->ike_sa); + if (!charon->ike_sa_manager->has_contact(charon->ike_sa_manager, + idi, idr, host->get_family(host))) + { + notify = notify_payload_create_from_protocol_and_type( + PLV1_NOTIFY, PROTO_IKE, INITIAL_CONTACT_IKEV1); + 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); + message->add_payload(message, (payload_t*)notify); + } + } + } +} + METHOD(task_t, build_i, status_t, private_main_mode_t *this, message_t *message) { @@ -311,6 +348,8 @@ METHOD(task_t, build_i, status_t, return send_notify(this, AUTHENTICATION_FAILED); } + add_initial_contact(this, message, id); + this->state = MM_AUTH; return NEED_MORE; } diff --git a/src/libcharon/sa/ikev1/tasks/mode_config.c b/src/libcharon/sa/ikev1/tasks/mode_config.c index 94026b9af..d0994a961 100644 --- a/src/libcharon/sa/ikev1/tasks/mode_config.c +++ b/src/libcharon/sa/ikev1/tasks/mode_config.c @@ -16,7 +16,6 @@ #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; @@ -136,9 +135,8 @@ static void handle_attribute(private_mode_config_t *this, 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)); + handler = charon->attributes->handle(charon->attributes, + this->ike_sa, handler, ca->get_type(ca), ca->get_chunk(ca)); this->ike_sa->add_configuration_attribute(this->ike_sa, handler, ca->get_type(ca), ca->get_chunk(ca)); } @@ -326,9 +324,8 @@ static status_t build_request(private_mode_config_t *this, message_t *message) enumerator->destroy(enumerator); } - enumerator = hydra->attributes->create_initiator_enumerator( - hydra->attributes, - this->ike_sa->get_other_id(this->ike_sa), vips); + enumerator = charon->attributes->create_initiator_enumerator( + charon->attributes, this->ike_sa, vips); while (enumerator->enumerate(enumerator, &handler, &type, &data)) { add_attribute(this, cp, type, data, handler); @@ -353,7 +350,7 @@ static status_t build_set(private_mode_config_t *this, message_t *message) cp_payload_t *cp; peer_cfg_t *config; identification_t *id; - linked_list_t *pools; + linked_list_t *pools, *migrated, *vips; host_t *any4, *any6, *found; char *name; @@ -361,45 +358,62 @@ static status_t build_set(private_mode_config_t *this, message_t *message) id = this->ike_sa->get_other_eap_id(this->ike_sa); config = this->ike_sa->get_peer_cfg(this->ike_sa); - any4 = host_create_any(AF_INET); - any6 = host_create_any(AF_INET6); + /* if we migrated virtual IPs during reauthentication, reassign them */ + migrated = linked_list_create_from_enumerator( + this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, + FALSE)); + vips = migrated->clone_offset(migrated, offsetof(host_t, clone)); + migrated->destroy(migrated); this->ike_sa->clear_virtual_ips(this->ike_sa, FALSE); /* in push mode, we ask each configured pool for an address */ - enumerator = config->create_pool_enumerator(config); - while (enumerator->enumerate(enumerator, &name)) + if (!vips->get_count(vips)) { - pools = linked_list_create_with_items(name, NULL); - /* try IPv4, then IPv6 */ - found = hydra->attributes->acquire_address(hydra->attributes, - pools, id, any4); - if (!found) - { - found = hydra->attributes->acquire_address(hydra->attributes, - pools, id, any6); - } - pools->destroy(pools); - if (found) + any4 = host_create_any(AF_INET); + any6 = host_create_any(AF_INET6); + enumerator = config->create_pool_enumerator(config); + while (enumerator->enumerate(enumerator, &name)) { - 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)); - this->vips->insert_last(this->vips, found); + pools = linked_list_create_with_items(name, NULL); + /* try IPv4, then IPv6 */ + found = charon->attributes->acquire_address(charon->attributes, + pools, this->ike_sa, any4); + if (!found) + { + found = charon->attributes->acquire_address(charon->attributes, + pools, this->ike_sa, any6); + } + pools->destroy(pools); + if (found) + { + vips->insert_last(vips, found); + } } + enumerator->destroy(enumerator); + any4->destroy(any4); + any6->destroy(any6); } - enumerator->destroy(enumerator); - any4->destroy(any4); - any6->destroy(any6); + enumerator = vips->create_enumerator(vips); + while (enumerator->enumerate(enumerator, &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)); + this->vips->insert_last(this->vips, found); + vips->remove_at(vips, enumerator); + } + enumerator->destroy(enumerator); + vips->destroy(vips); charon->bus->assign_vips(charon->bus, this->ike_sa, TRUE); /* query registered providers for additional attributes to include */ pools = linked_list_create_from_enumerator( config->create_pool_enumerator(config)); - enumerator = hydra->attributes->create_responder_enumerator( - hydra->attributes, pools, id, this->vips); + enumerator = charon->attributes->create_responder_enumerator( + charon->attributes, pools, this->ike_sa, this->vips); while (enumerator->enumerate(enumerator, &type, &value)) { add_attribute(this, cp, type, value, NULL); @@ -458,6 +472,28 @@ METHOD(task_t, process_r, status_t, } /** + * Assign a migrated virtual IP + */ +static host_t *assign_migrated_vip(linked_list_t *migrated, host_t *requested) +{ + enumerator_t *enumerator; + host_t *found = NULL, *vip; + + enumerator = migrated->create_enumerator(migrated); + while (enumerator->enumerate(enumerator, &vip)) + { + if (vip->ip_equals(vip, requested)) + { + migrated->remove_at(migrated, enumerator); + found = vip; + break; + } + } + enumerator->destroy(enumerator); + return found; +} + +/** * Build CFG_REPLY message after receiving CFG_REQUEST */ static status_t build_reply(private_mode_config_t *this, message_t *message) @@ -468,29 +504,35 @@ static status_t build_reply(private_mode_config_t *this, message_t *message) cp_payload_t *cp; peer_cfg_t *config; identification_t *id; - linked_list_t *vips, *pools; - host_t *requested; + linked_list_t *vips, *pools, *migrated; + host_t *requested, *found; cp = cp_payload_create_type(PLV1_CONFIGURATION, 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)); - + /* if we migrated virtual IPs during reauthentication, reassign them */ + vips = linked_list_create_from_enumerator( + this->ike_sa->create_virtual_ip_enumerator(this->ike_sa, + FALSE)); + migrated = vips->clone_offset(vips, offsetof(host_t, clone)); + vips->destroy(vips); this->ike_sa->clear_virtual_ips(this->ike_sa, FALSE); + vips = linked_list_create(); 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); + found = assign_migrated_vip(migrated, requested); + if (!found) + { + found = charon->attributes->acquire_address(charon->attributes, + pools, this->ike_sa, requested); + } if (found) { DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", found, id); @@ -509,8 +551,8 @@ static status_t build_reply(private_mode_config_t *this, message_t *message) charon->bus->assign_vips(charon->bus, this->ike_sa, TRUE); /* query registered providers for additional attributes to include */ - enumerator = hydra->attributes->create_responder_enumerator( - hydra->attributes, pools, id, vips); + enumerator = charon->attributes->create_responder_enumerator( + charon->attributes, pools, this->ike_sa, vips); while (enumerator->enumerate(enumerator, &type, &value)) { cp->add_attribute(cp, @@ -518,6 +560,15 @@ static status_t build_reply(private_mode_config_t *this, message_t *message) type, value)); } enumerator->destroy(enumerator); + /* if a client did not re-request all adresses, release them */ + enumerator = migrated->create_enumerator(migrated); + while (enumerator->enumerate(enumerator, &found)) + { + charon->attributes->release_address(charon->attributes, + pools, found, this->ike_sa); + } + enumerator->destroy(enumerator); + migrated->destroy_offset(migrated, offsetof(host_t, destroy)); vips->destroy_offset(vips, offsetof(host_t, destroy)); pools->destroy(pools); diff --git a/src/libcharon/sa/ikev1/tasks/quick_delete.c b/src/libcharon/sa/ikev1/tasks/quick_delete.c index 499081caa..1b95a8b11 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_delete.c +++ b/src/libcharon/sa/ikev1/tasks/quick_delete.c @@ -105,7 +105,7 @@ static bool delete_child(private_quick_delete_t *this, protocol_id_t protocol, this->spi = spi = child_sa->get_spi(child_sa, TRUE); } - rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYING; + rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYED; child_sa->set_state(child_sa, CHILD_DELETING); my_ts = linked_list_create_from_enumerator( @@ -116,7 +116,7 @@ static bool delete_child(private_quick_delete_t *this, protocol_id_t protocol, { 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), + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), ntohl(child_sa->get_spi(child_sa, TRUE)), ntohl(child_sa->get_spi(child_sa, FALSE)), my_ts, other_ts); } @@ -127,7 +127,7 @@ static bool delete_child(private_quick_delete_t *this, protocol_id_t protocol, 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), + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, my_ts, other_ts); diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index 1133aab65..96edfd8d8 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -156,6 +156,16 @@ struct private_quick_mode_t { u_int32_t reqid; /** + * Explicit inbound mark value to use, if any + */ + u_int mark_in; + + /** + * Explicit inbound mark value to use, if any + */ + u_int mark_out; + + /** * SPI of SA we rekey */ u_int32_t rekey; @@ -196,8 +206,8 @@ static void schedule_inactivity_timeout(private_quick_mode_t *this) close_ike = lib->settings->get_bool(lib->settings, "%s.inactivity_close_ike", FALSE, lib->ns); lib->scheduler->schedule_job(lib->scheduler, (job_t*) - inactivity_job_create(this->child_sa->get_reqid(this->child_sa), - timeout, close_ike), timeout); + inactivity_job_create(this->child_sa->get_unique_id(this->child_sa), + timeout, close_ike), timeout); } } @@ -375,7 +385,7 @@ static bool install(private_quick_mode_t *this) 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), + this->child_sa->get_unique_id(this->child_sa), ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), my_ts, other_ts); @@ -391,15 +401,14 @@ static bool install(private_quick_mode_t *this) if (old) { charon->bus->child_rekey(charon->bus, old, this->child_sa); + /* rekeyed CHILD_SAs stay installed until they expire */ + old->set_state(old, CHILD_REKEYED); } else { charon->bus->child_updown(charon->bus, this->child_sa, TRUE); } - if (!this->rekey) - { - schedule_inactivity_timeout(this); - } + schedule_inactivity_timeout(this); this->child_sa = NULL; return TRUE; } @@ -456,12 +465,19 @@ static bool get_nonce(private_quick_mode_t *this, chunk_t *nonce, /** * Add KE payload to message */ -static void add_ke(private_quick_mode_t *this, message_t *message) +static bool add_ke(private_quick_mode_t *this, message_t *message) { ke_payload_t *ke_payload; - ke_payload = ke_payload_create_from_diffie_hellman(PLV1_KEY_EXCHANGE, this->dh); + ke_payload = ke_payload_create_from_diffie_hellman(PLV1_KEY_EXCHANGE, + this->dh); + if (!ke_payload) + { + DBG1(DBG_IKE, "creating KE payload failed"); + return FALSE; + } message->add_payload(message, &ke_payload->payload_interface); + return TRUE; } /** @@ -477,8 +493,12 @@ static bool get_ke(private_quick_mode_t *this, message_t *message) DBG1(DBG_IKE, "KE payload missing"); return FALSE; } - this->dh->set_other_public_value(this->dh, - ke_payload->get_key_exchange_data(ke_payload)); + if (!this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload))) + { + DBG1(DBG_IKE, "unable to apply received KE value"); + return FALSE; + } return TRUE; } @@ -788,7 +808,8 @@ METHOD(task_t, build_i, status_t, 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); + this->config, this->reqid, this->udp, + this->mark_in, this->mark_out); if (this->udp && this->mode == MODE_TRANSPORT) { @@ -870,7 +891,10 @@ METHOD(task_t, build_i, status_t, } if (group != MODP_NONE) { - add_ke(this, message); + if (!add_ke(this, message)) + { + return FAILED; + } } if (!this->tsi) { @@ -964,6 +988,7 @@ static void check_for_rekeyed_child(private_quick_mode_t *this) { case CHILD_INSTALLED: case CHILD_REKEYING: + case CHILD_REKEYED: policies = child_sa->create_policy_enumerator(child_sa); if (policies->enumerate(policies, &local, &remote) && local->equals(local, this->tsr) && @@ -972,9 +997,14 @@ static void check_for_rekeyed_child(private_quick_mode_t *this) { this->reqid = child_sa->get_reqid(child_sa); this->rekey = child_sa->get_spi(child_sa, TRUE); + this->mark_in = child_sa->get_mark(child_sa, + TRUE).value; + this->mark_out = child_sa->get_mark(child_sa, + FALSE).value; 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); + child_sa->get_name(child_sa), + child_sa->get_unique_id(child_sa)); } policies->destroy(policies); break; @@ -1097,7 +1127,8 @@ METHOD(task_t, process_r, status_t, 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); + this->config, this->reqid, this->udp, + this->mark_in, this->mark_out); tsi = linked_list_create_with_items(this->tsi, NULL); tsr = linked_list_create_with_items(this->tsr, NULL); @@ -1202,7 +1233,10 @@ METHOD(task_t, build_r, status_t, } if (this->dh) { - add_ke(this, message); + if (!add_ke(this, message)) + { + return FAILED; + } } add_ts(this, message); @@ -1307,6 +1341,13 @@ METHOD(quick_mode_t, use_reqid, void, this->reqid = reqid; } +METHOD(quick_mode_t, use_marks, void, + private_quick_mode_t *this, u_int in, u_int out) +{ + this->mark_in = in; + this->mark_out = out; +} + METHOD(quick_mode_t, rekey, void, private_quick_mode_t *this, u_int32_t spi) { @@ -1334,6 +1375,8 @@ METHOD(task_t, migrate, void, this->dh = NULL; this->spi_i = 0; this->spi_r = 0; + this->mark_in = 0; + this->mark_out = 0; if (!this->initiator) { @@ -1372,6 +1415,7 @@ quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config, .destroy = _destroy, }, .use_reqid = _use_reqid, + .use_marks = _use_marks, .rekey = _rekey, }, .ike_sa = ike_sa, diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.h b/src/libcharon/sa/ikev1/tasks/quick_mode.h index 0b80cb836..ee9b64d13 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.h +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.h @@ -45,6 +45,14 @@ struct quick_mode_t { void (*use_reqid)(quick_mode_t *this, u_int32_t reqid); /** + * Use specific mark values, overriding configuration. + * + * @param in inbound mark value + * @param out outbound mark value + */ + void (*use_marks)(quick_mode_t *this, u_int in, u_int out); + + /** * Set the SPI of the old SA, if rekeying. * * @param spi spi of SA to rekey diff --git a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c index eed6d1996..ebef31930 100644 --- a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c @@ -522,6 +522,13 @@ METHOD(authenticator_t, process_server, status_t, { return FAILED; } + if (this->method->get_auth) + { + auth_cfg_t *auth; + + auth = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE); + auth->merge(auth, this->method->get_auth(this->method), FALSE); + } return NEED_MORE; } diff --git a/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c index 6fb14bc06..151b49718 100644 --- a/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2008-2015 Tobias Brunner * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -20,6 +20,9 @@ #include <daemon.h> #include <encoding/payloads/auth_payload.h> #include <sa/ikev2/keymat_v2.h> +#include <asn1/asn1.h> +#include <asn1/oid.h> +#include <collections/array.h> typedef struct private_pubkey_authenticator_t private_pubkey_authenticator_t; @@ -52,83 +55,303 @@ struct private_pubkey_authenticator_t { * Reserved bytes of ID payload */ char reserved[3]; + + /** + * Whether to store signature schemes on remote auth configs. + */ + bool store_signature_scheme; }; -METHOD(authenticator_t, build, status_t, - private_pubkey_authenticator_t *this, message_t *message) +/** + * Parse authentication data used for Signature Authentication as per RFC 7427 + */ +static bool parse_signature_auth_data(chunk_t *auth_data, key_type_t *key_type, + signature_scheme_t *scheme) { - chunk_t octets = chunk_empty, auth_data; - status_t status = FAILED; - private_key_t *private; - identification_t *id; - auth_cfg_t *auth; - auth_payload_t *auth_payload; - auth_method_t auth_method; + u_int8_t len; + int oid; + + if (!auth_data->len) + { + return FALSE; + } + len = auth_data->ptr[0]; + *auth_data = chunk_skip(*auth_data, 1); + /* we currently don't support schemes that require parameters */ + oid = asn1_parse_algorithmIdentifier(*auth_data, 1, NULL); + *scheme = signature_scheme_from_oid(oid); + if (*scheme == SIGN_UNKNOWN) + { + return FALSE; + } + *key_type = key_type_from_signature_scheme(*scheme); + *auth_data = chunk_skip(*auth_data, len); + return TRUE; +} + +/** + * Build authentication data used for Signature Authentication as per RFC 7427 + */ +static bool build_signature_auth_data(chunk_t *auth_data, + signature_scheme_t scheme) +{ + chunk_t data; + u_int8_t len; + int oid; + + oid = signature_scheme_to_oid(scheme); + if (oid == OID_UNKNOWN) + { + return FALSE; + } + data = asn1_algorithmIdentifier(oid); + len = data.len; + *auth_data = chunk_cat("cmm", chunk_from_thing(len), data, *auth_data); + return TRUE; +} + +/** + * Selects possible signature schemes based on our configuration, the other + * peer's capabilities and the private key + */ +static array_t *select_signature_schemes(keymat_v2_t *keymat, + auth_cfg_t *auth, private_key_t *private) +{ + enumerator_t *enumerator; signature_scheme_t scheme; + uintptr_t config; + auth_rule_t rule; + key_type_t key_type; + bool have_config = FALSE; + array_t *selected; + + selected = array_create(sizeof(signature_scheme_t), 0); + key_type = private->get_type(private); + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &rule, &config)) + { + if (rule != AUTH_RULE_SIGNATURE_SCHEME) + { + continue; + } + have_config = TRUE; + if (key_type == key_type_from_signature_scheme(config) && + keymat->hash_algorithm_supported(keymat, + hasher_from_signature_scheme(config))) + { + scheme = config; + array_insert(selected, ARRAY_TAIL, &scheme); + } + } + enumerator->destroy(enumerator); + + if (!have_config) + { + /* if no specific configuration, find schemes appropriate for the key + * and supported by the other peer */ + enumerator = signature_schemes_for_key(key_type, + private->get_keysize(private)); + while (enumerator->enumerate(enumerator, &scheme)) + { + if (keymat->hash_algorithm_supported(keymat, + hasher_from_signature_scheme(scheme))) + { + array_insert(selected, ARRAY_TAIL, &scheme); + } + } + enumerator->destroy(enumerator); + + /* for RSA we tried at least SHA-512, also try other schemes down to + * what we'd use with classic authentication */ + if (key_type == KEY_RSA) + { + signature_scheme_t schemes[] = { + SIGN_RSA_EMSA_PKCS1_SHA384, + SIGN_RSA_EMSA_PKCS1_SHA256, + SIGN_RSA_EMSA_PKCS1_SHA1, + }, contained; + bool found; + int i, j; + + for (i = 0; i < countof(schemes); i++) + { + scheme = schemes[i]; + found = FALSE; + for (j = 0; j < array_count(selected); j++) + { + array_get(selected, j, &contained); + if (scheme == contained) + { + found = TRUE; + break; + } + } + if (!found && keymat->hash_algorithm_supported(keymat, + hasher_from_signature_scheme(scheme))) + { + array_insert(selected, ARRAY_TAIL, &scheme); + } + } + } + } + return selected; +} + +/** + * Create a signature using RFC 7427 signature authentication + */ +static status_t sign_signature_auth(private_pubkey_authenticator_t *this, + auth_cfg_t *auth, private_key_t *private, + identification_t *id, chunk_t *auth_data) +{ + enumerator_t *enumerator; keymat_v2_t *keymat; + signature_scheme_t scheme = SIGN_UNKNOWN, *schemep; + array_t *schemes; + chunk_t octets = chunk_empty; + status_t status = FAILED; - 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, KEY_ANY, id, auth); - if (private == NULL) + keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); + schemes = select_signature_schemes(keymat, auth, private); + if (!array_count(schemes)) { - DBG1(DBG_IKE, "no private key found for '%Y'", id); - return NOT_FOUND; + DBG1(DBG_IKE, "no common hash algorithm found to create signature " + "with %N key", key_type_names, private->get_type(private)); + array_destroy(schemes); + return FAILED; } + if (keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, + this->nonce, id, this->reserved, &octets)) + { + enumerator = array_create_enumerator(schemes); + while (enumerator->enumerate(enumerator, &schemep)) + { + scheme = *schemep; + if (private->sign(private, scheme, octets, auth_data) && + build_signature_auth_data(auth_data, scheme)) + { + status = SUCCESS; + break; + } + else + { + DBG2(DBG_IKE, "unable to create %N signature for %N key", + signature_scheme_names, scheme, key_type_names, + private->get_type(private)); + } + } + enumerator->destroy(enumerator); + } + DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N %s", id, + signature_scheme_names, scheme, + status == SUCCESS ? "successful" : "failed"); + array_destroy(schemes); + chunk_free(&octets); + return status; +} + +/** + * Create a classic IKEv2 signature + */ +static status_t sign_classic(private_pubkey_authenticator_t *this, + auth_cfg_t *auth, private_key_t *private, + identification_t *id, auth_method_t *auth_method, + chunk_t *auth_data) +{ + signature_scheme_t scheme; + keymat_v2_t *keymat; + chunk_t octets = chunk_empty; + status_t status = FAILED; + switch (private->get_type(private)) { case KEY_RSA: - /* we currently use always SHA1 for signatures, - * TODO: support other hashes depending on configuration/auth */ scheme = SIGN_RSA_EMSA_PKCS1_SHA1; - auth_method = AUTH_RSA; + *auth_method = AUTH_RSA; break; case KEY_ECDSA: - /* we try to deduct the signature scheme from the keysize */ + /* deduct the signature scheme from the keysize */ switch (private->get_keysize(private)) { case 256: scheme = SIGN_ECDSA_256; - auth_method = AUTH_ECDSA_256; + *auth_method = AUTH_ECDSA_256; break; case 384: scheme = SIGN_ECDSA_384; - auth_method = AUTH_ECDSA_384; + *auth_method = AUTH_ECDSA_384; break; case 521: scheme = SIGN_ECDSA_521; - auth_method = AUTH_ECDSA_521; + *auth_method = AUTH_ECDSA_521; break; default: DBG1(DBG_IKE, "%d bit ECDSA private key size not supported", - private->get_keysize(private)); - return status; + private->get_keysize(private)); + return FAILED; } break; default: DBG1(DBG_IKE, "private key of type %N not supported", - key_type_names, private->get_type(private)); - return status; + key_type_names, private->get_type(private)); + return FAILED; } + 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)) + private->sign(private, scheme, octets, auth_data)) { - auth_payload = auth_payload_create(); - auth_payload->set_auth_method(auth_payload, auth_method); - auth_payload->set_data(auth_payload, auth_data); - chunk_free(&auth_data); - message->add_payload(message, (payload_t*)auth_payload); status = SUCCESS; } DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N %s", id, - auth_method_names, auth_method, - (status == SUCCESS)? "successful":"failed"); + auth_method_names, *auth_method, + status == SUCCESS ? "successful" : "failed"); chunk_free(&octets); + return status; +} + +METHOD(authenticator_t, build, status_t, + private_pubkey_authenticator_t *this, message_t *message) +{ + private_key_t *private; + identification_t *id; + auth_cfg_t *auth; + chunk_t auth_data; + status_t status; + auth_payload_t *auth_payload; + auth_method_t auth_method; + + 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, KEY_ANY, id, auth); + if (!private) + { + DBG1(DBG_IKE, "no private key found for '%Y'", id); + return NOT_FOUND; + } + + if (this->ike_sa->supports_extension(this->ike_sa, EXT_SIGNATURE_AUTH)) + { + auth_method = AUTH_DS; + status = sign_signature_auth(this, auth, private, id, &auth_data); + } + else + { + status = sign_classic(this, auth, private, id, &auth_method, + &auth_data); + } private->destroy(private); + if (status == SUCCESS) + { + auth_payload = auth_payload_create(); + auth_payload->set_auth_method(auth_payload, auth_method); + auth_payload->set_data(auth_payload, auth_data); + chunk_free(&auth_data); + message->add_payload(message, (payload_t*)auth_payload); + } return status; } @@ -153,11 +376,10 @@ METHOD(authenticator_t, process, status_t, return FAILED; } auth_method = auth_payload->get_auth_method(auth_payload); + auth_data = auth_payload->get_data(auth_payload); switch (auth_method) { case AUTH_RSA: - /* We currently accept SHA1 signatures only - * TODO: allow other hash algorithms and note it in "auth" */ key_type = KEY_RSA; scheme = SIGN_RSA_EMSA_PKCS1_SHA1; break; @@ -170,10 +392,15 @@ METHOD(authenticator_t, process, status_t, case AUTH_ECDSA_521: scheme = SIGN_ECDSA_521; break; + case AUTH_DS: + if (parse_signature_auth_data(&auth_data, &key_type, &scheme)) + { + break; + } + /* fall-through */ default: return INVALID_ARG; } - auth_data = auth_payload->get_data(auth_payload); id = this->ike_sa->get_other_id(this->ike_sa); keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa); if (!keymat->get_auth_octets(keymat, TRUE, this->ike_sa_init, @@ -188,11 +415,16 @@ METHOD(authenticator_t, process, status_t, { if (public->verify(public, scheme, octets, auth_data)) { - DBG1(DBG_IKE, "authentication of '%Y' with %N successful", - id, auth_method_names, auth_method); + DBG1(DBG_IKE, "authentication of '%Y' with %N successful", id, + auth_method == AUTH_DS ? signature_scheme_names : auth_method_names, + auth_method == AUTH_DS ? scheme : auth_method); status = SUCCESS; auth->merge(auth, current_auth, FALSE); auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + if (this->store_signature_scheme) + { + auth->add(auth, AUTH_RULE_SIGNATURE_SCHEME, (uintptr_t)scheme); + } break; } else @@ -265,6 +497,8 @@ pubkey_authenticator_t *pubkey_authenticator_create_verifier(ike_sa_t *ike_sa, .ike_sa = ike_sa, .ike_sa_init = received_init, .nonce = sent_nonce, + .store_signature_scheme = lib->settings->get_bool(lib->settings, + "%s.signature_authentication_constraints", TRUE, lib->ns), ); memcpy(this->reserved, reserved, sizeof(this->reserved)); diff --git a/src/libcharon/sa/ikev2/keymat_v2.c b/src/libcharon/sa/ikev2/keymat_v2.c index 88ad14faf..f70f5cfed 100644 --- a/src/libcharon/sa/ikev2/keymat_v2.c +++ b/src/libcharon/sa/ikev2/keymat_v2.c @@ -1,4 +1,5 @@ /* + * Copyright (C) 2015 Tobias Brunner * Copyright (C) 2008 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -17,6 +18,7 @@ #include <daemon.h> #include <crypto/prf_plus.h> +#include <crypto/hashers/hash_algorithm_set.h> typedef struct private_keymat_v2_t private_keymat_v2_t; @@ -69,6 +71,11 @@ struct private_keymat_v2_t { * Key to verify incoming authentication data (SKp) */ chunk_t skp_verify; + + /** + * Set of hash algorithms supported by peer for signature authentication + */ + hash_algorithm_set_t *hash_algorithms; }; METHOD(keymat_t, get_version, ike_version_t, @@ -293,7 +300,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool, spi_i = chunk_alloca(sizeof(u_int64_t)); spi_r = chunk_alloca(sizeof(u_int64_t)); - if (dh->get_shared_secret(dh, &secret) != SUCCESS) + if (!dh->get_shared_secret(dh, &secret)) { return FALSE; } @@ -547,7 +554,7 @@ METHOD(keymat_v2_t, derive_child_keys, bool, if (dh) { - if (dh->get_shared_secret(dh, &secret) != SUCCESS) + if (!dh->get_shared_secret(dh, &secret)) { return FALSE; } @@ -676,6 +683,26 @@ METHOD(keymat_v2_t, get_psk_sig, bool, return TRUE; } +METHOD(keymat_v2_t, hash_algorithm_supported, bool, + private_keymat_v2_t *this, hash_algorithm_t hash) +{ + if (!this->hash_algorithms) + { + return FALSE; + } + return this->hash_algorithms->contains(this->hash_algorithms, hash); +} + +METHOD(keymat_v2_t, add_hash_algorithm, void, + private_keymat_v2_t *this, hash_algorithm_t hash) +{ + if (!this->hash_algorithms) + { + this->hash_algorithms = hash_algorithm_set_create(); + } + this->hash_algorithms->add(this->hash_algorithms, hash); +} + METHOD(keymat_t, destroy, void, private_keymat_v2_t *this) { @@ -685,6 +712,7 @@ METHOD(keymat_t, destroy, void, chunk_clear(&this->skd); chunk_clear(&this->skp_verify); chunk_clear(&this->skp_build); + DESTROY_IF(this->hash_algorithms); free(this); } @@ -709,6 +737,9 @@ keymat_v2_t *keymat_v2_create(bool initiator) .get_skd = _get_skd, .get_auth_octets = _get_auth_octets, .get_psk_sig = _get_psk_sig, + .add_hash_algorithm = _add_hash_algorithm, + .hash_algorithm_supported = _hash_algorithm_supported, + }, .initiator = initiator, .prf_alg = PRF_UNDEFINED, diff --git a/src/libcharon/sa/ikev2/keymat_v2.h b/src/libcharon/sa/ikev2/keymat_v2.h index 04432f05b..927b62b03 100644 --- a/src/libcharon/sa/ikev2/keymat_v2.h +++ b/src/libcharon/sa/ikev2/keymat_v2.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 Tobias Brunner + * Copyright (C) 2011-2015 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -124,6 +124,22 @@ struct keymat_v2_t { 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); + + /** + * Add a hash algorithm supported by the peer for signature authentication. + * + * @param hash hash algorithm + */ + void (*add_hash_algorithm)(keymat_v2_t *this, hash_algorithm_t hash); + + /** + * Check if a given hash algorithm is supported by the peer for signature + * authentication. + * + * @param hash hash algorithm + * @return TRUE if supported, FALSE otherwise + */ + bool (*hash_algorithm_supported)(keymat_v2_t *this, hash_algorithm_t hash); }; /** diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index eb7df3516..298167703 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -29,6 +29,7 @@ #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_reauth_complete.h> #include <sa/ikev2/tasks/ike_delete.h> #include <sa/ikev2/tasks/ike_config.h> #include <sa/ikev2/tasks/ike_dpd.h> @@ -171,6 +172,11 @@ struct private_task_manager_t { * Base to calculate retransmission timeout */ double retransmit_base; + + /** + * Use make-before-break instead of break-before-make reauth? + */ + bool make_before_break; }; /** @@ -510,6 +516,11 @@ METHOD(task_manager_t, initiate, status_t, break; } #endif /* ME */ + if (activate_task(this, TASK_IKE_REAUTH_COMPLETE)) + { + exchange = INFORMATIONAL; + break; + } case IKE_REKEYING: if (activate_task(this, TASK_IKE_DELETE)) { @@ -604,6 +615,11 @@ METHOD(task_manager_t, initiate, status_t, /* update exchange type if a task changed it */ this->initiating.type = message->get_exchange_type(message); + if (this->initiating.type == EXCHANGE_TYPE_UNDEFINED) + { + message->destroy(message); + return SUCCESS; + } if (!generate_message(this, message, &this->initiating.packets)) { @@ -1170,7 +1186,7 @@ static status_t parse_message(private_task_manager_t *this, message_t *msg) { unknown = (unknown_payload_t*)payload; type = payload->get_type(payload); - if (!payload_is_known(type) && + if (!payload_is_known(type, msg->get_major_version(msg)) && unknown->is_critical(unknown)) { DBG1(DBG_ENC, "payload type %N is not supported, " @@ -1288,17 +1304,16 @@ METHOD(task_manager_t, process_message, status_t, { if (mid == this->responding.mid) { - /* reject initial messages once established */ - if (msg->get_exchange_type(msg) == IKE_SA_INIT || - msg->get_exchange_type(msg) == IKE_AUTH) + /* reject initial messages if not received in specific states */ + if ((msg->get_exchange_type(msg) == IKE_SA_INIT && + this->ike_sa->get_state(this->ike_sa) != IKE_CREATED) || + (msg->get_exchange_type(msg) == IKE_AUTH && + this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)) { - if (this->ike_sa->get_state(this->ike_sa) != IKE_CREATED && - this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) - { - DBG1(DBG_IKE, "ignoring %N in established IKE_SA state", - exchange_type_names, msg->get_exchange_type(msg)); - return FAILED; - } + DBG1(DBG_IKE, "ignoring %N in IKE_SA state %N", + exchange_type_names, msg->get_exchange_type(msg), + ike_sa_state_names, this->ike_sa->get_state(this->ike_sa)); + return FAILED; } if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) { /* with MOBIKE, we do no implicit updates */ @@ -1339,10 +1354,6 @@ METHOD(task_manager_t, process_message, status_t, { DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", mid, this->responding.mid); - if (msg->get_exchange_type(msg) == IKE_SA_INIT) - { /* clean up IKE_SA state if IKE_SA_INIT has invalid msg ID */ - return DESTROY_ME; - } } } else @@ -1505,9 +1516,79 @@ METHOD(task_manager_t, queue_ike_rekey, void, queue_task(this, (task_t*)ike_rekey_create(this->ike_sa, TRUE)); } +/** + * Start reauthentication using make-before-break + */ +static void trigger_mbb_reauth(private_task_manager_t *this) +{ + enumerator_t *enumerator; + child_sa_t *child_sa; + child_cfg_t *cfg; + ike_sa_t *new; + host_t *host; + task_t *task; + + 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)) + { + cfg = child_sa->get_config(child_sa); + new->queue_task(new, &child_create_create(new, cfg->get_ref(cfg), + FALSE, NULL, NULL)->task); + } + enumerator->destroy(enumerator); + + enumerator = array_create_enumerator(this->queued_tasks); + while (enumerator->enumerate(enumerator, &task)) + { + if (task->get_type(task) == TASK_CHILD_CREATE) + { + task->migrate(task, new); + new->queue_task(new, task); + array_remove_at(this->queued_tasks, enumerator); + } + } + enumerator->destroy(enumerator); + + if (new->initiate(new, NULL, 0, NULL, NULL) != DESTROY_ME) + { + new->queue_task(new, (task_t*)ike_reauth_complete_create(new, + this->ike_sa->get_id(this->ike_sa))); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); + } + 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_reauth, void, private_task_manager_t *this) { + if (this->make_before_break) + { + return trigger_mbb_reauth(this); + } queue_task(this, (task_t*)ike_reauth_create(this->ike_sa)); } @@ -1773,6 +1854,8 @@ task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa) "%s.retransmit_timeout", RETRANSMIT_TIMEOUT, lib->ns), .retransmit_base = lib->settings->get_double(lib->settings, "%s.retransmit_base", RETRANSMIT_BASE, lib->ns), + .make_before_break = lib->settings->get_bool(lib->settings, + "%s.make_before_break", FALSE, lib->ns), ); return &this->public; diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index a1f01c276..6d9132a68 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -105,6 +105,11 @@ struct private_child_create_t { diffie_hellman_t *dh; /** + * Applying DH public value failed? + */ + bool dh_failed; + + /** * group used for DH exchange */ diffie_hellman_group_t dh_group; @@ -160,6 +165,16 @@ struct private_child_create_t { u_int32_t reqid; /** + * Explicit inbound mark value + */ + u_int mark_in; + + /** + * Explicit outbound mark value + */ + u_int mark_out; + + /** * CHILD_SA which gets established */ child_sa_t *child_sa; @@ -286,7 +301,7 @@ static bool allocate_spi(private_child_create_t *this) */ static void schedule_inactivity_timeout(private_child_create_t *this) { - u_int32_t timeout; + u_int32_t timeout, id; bool close_ike; timeout = this->config->get_inactivity(this->config); @@ -294,9 +309,9 @@ static void schedule_inactivity_timeout(private_child_create_t *this) { close_ike = lib->settings->get_bool(lib->settings, "%s.inactivity_close_ike", FALSE, lib->ns); + id = this->child_sa->get_unique_id(this->child_sa); lib->scheduler->schedule_job(lib->scheduler, (job_t*) - inactivity_job_create(this->child_sa->get_reqid(this->child_sa), - timeout, close_ike), timeout); + inactivity_job_create(id, timeout, close_ike), timeout); } } @@ -683,10 +698,7 @@ static status_t select_and_install(private_child_create_t *this, this->ike_sa->add_child_sa(this->ike_sa, this->child_sa); this->established = TRUE; - if (!this->rekey) - { /* a rekeyed SA uses the same reqid, no need for a new job */ - schedule_inactivity_timeout(this); - } + schedule_inactivity_timeout(this); my_ts = linked_list_create_from_enumerator( this->child_sa->create_ts_enumerator(this->child_sa, TRUE)); @@ -696,7 +708,7 @@ static status_t select_and_install(private_child_create_t *this, 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), + this->child_sa->get_unique_id(this->child_sa), ntohl(this->child_sa->get_spi(this->child_sa, TRUE)), ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), my_ts, other_ts); @@ -709,7 +721,7 @@ static status_t select_and_install(private_child_create_t *this, /** * build the payloads for the message */ -static void build_payloads(private_child_create_t *this, message_t *message) +static bool build_payloads(private_child_create_t *this, message_t *message) { sa_payload_t *sa_payload; nonce_payload_t *nonce_payload; @@ -741,6 +753,11 @@ static void build_payloads(private_child_create_t *this, message_t *message) { ke_payload = ke_payload_create_from_diffie_hellman(PLV2_KEY_EXCHANGE, this->dh); + if (!ke_payload) + { + DBG1(DBG_IKE, "creating KE payload failed"); + return FALSE; + } message->add_payload(message, (payload_t*)ke_payload); } @@ -769,6 +786,7 @@ static void build_payloads(private_child_create_t *this, message_t *message) message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED, chunk_empty); } + return TRUE; } /** @@ -880,7 +898,7 @@ static void process_payloads(private_child_create_t *this, message_t *message) } if (this->dh) { - this->dh->set_other_public_value(this->dh, + this->dh_failed = !this->dh->set_other_public_value(this->dh, ke_payload->get_key_exchange_data(ke_payload)); } break; @@ -996,7 +1014,8 @@ METHOD(task_t, build_i, status_t, 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->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)); + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY), + this->mark_in, this->mark_out); if (!allocate_spi(this)) { @@ -1027,7 +1046,10 @@ METHOD(task_t, build_i, status_t, NARROW_INITIATOR_PRE_AUTH, this->tsi, this->tsr); } - build_payloads(this, message); + if (!build_payloads(this, message)) + { + return FAILED; + } this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy)); this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy)); @@ -1168,12 +1190,19 @@ 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) != SUCCESS) + if (generate_nonce(this) != SUCCESS ) { message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); return SUCCESS; } + if (this->dh_failed) + { + DBG1(DBG_IKE, "applying DH public value failed"); + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, + chunk_empty); + return SUCCESS; + } no_dh = FALSE; break; case IKE_AUTH: @@ -1241,7 +1270,8 @@ METHOD(task_t, build_r, status_t, 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->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)); + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY), + this->mark_in, this->mark_out); if (this->ipcomp_received != IPCOMP_NONE) { @@ -1279,7 +1309,12 @@ METHOD(task_t, build_r, status_t, return SUCCESS; } - build_payloads(this, message); + if (!build_payloads(this, message)) + { + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); + handle_child_sa_failure(this, message); + return SUCCESS; + } if (!this->rekey) { /* invoke the child_up() hook if we are not rekeying */ @@ -1408,6 +1443,7 @@ METHOD(task_t, process_i, status_t, this->dh_group, diffie_hellman_group_names, group); this->retry = TRUE; this->dh_group = group; + this->child_sa->set_state(this->child_sa, CHILD_RETRYING); this->public.task.migrate(&this->public.task, this->ike_sa); enumerator->destroy(enumerator); return NEED_MORE; @@ -1456,6 +1492,13 @@ METHOD(task_t, process_i, status_t, return delete_failed_sa(this); } + if (this->dh_failed) + { + DBG1(DBG_IKE, "applying DH public value failed"); + handle_child_sa_failure(this, message); + return delete_failed_sa(this); + } + if (select_and_install(this, no_dh, ike_auth) == SUCCESS) { if (!this->rekey) @@ -1477,6 +1520,13 @@ METHOD(child_create_t, use_reqid, void, this->reqid = reqid; } +METHOD(child_create_t, use_marks, void, + private_child_create_t *this, u_int in, u_int out) +{ + this->mark_in = in; + this->mark_out = out; +} + METHOD(child_create_t, get_child, child_sa_t*, private_child_create_t *this) { @@ -1526,6 +1576,7 @@ METHOD(task_t, migrate, void, DESTROY_IF(this->child_sa); DESTROY_IF(this->proposal); DESTROY_IF(this->dh); + this->dh_failed = FALSE; if (this->proposals) { this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy)); @@ -1544,6 +1595,8 @@ METHOD(task_t, migrate, void, this->ipcomp_received = IPCOMP_NONE; this->other_cpi = 0; this->reqid = 0; + this->mark_in = 0; + this->mark_out = 0; this->established = FALSE; } @@ -1592,6 +1645,7 @@ child_create_t *child_create_create(ike_sa_t *ike_sa, .set_config = _set_config, .get_lower_nonce = _get_lower_nonce, .use_reqid = _use_reqid, + .use_marks = _use_marks, .task = { .get_type = _get_type, .migrate = _migrate, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index d29ba3d98..46d9403ee 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -52,6 +52,14 @@ struct child_create_t { void (*use_reqid) (child_create_t *this, u_int32_t reqid); /** + * Use specific mark values to override configuration. + * + * @param in inbound mark value + * @param out outbound mark value + */ + void (*use_marks)(child_create_t *this, u_int in, u_int out); + + /** * Get the lower of the two nonces, used for rekey collisions. * * @return lower nonce diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index 2b1697423..f0b11e291 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -267,7 +267,7 @@ static void log_children(private_child_delete_t *this) { 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), + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), ntohl(child_sa->get_spi(child_sa, TRUE)), ntohl(child_sa->get_spi(child_sa, FALSE)), my_ts, other_ts); } @@ -278,7 +278,7 @@ static void log_children(private_child_delete_t *this) 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), + child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa), ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in, ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out, my_ts, other_ts); diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index db872827d..c806e19ca 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -96,9 +96,9 @@ static void schedule_delayed_rekey(private_child_rekey_t *this) retry = RETRY_INTERVAL - (random() % RETRY_JITTER); job = (job_t*)rekey_child_sa_job_create( - this->child_sa->get_reqid(this->child_sa), this->child_sa->get_protocol(this->child_sa), - this->child_sa->get_spi(this->child_sa, TRUE)); + this->child_sa->get_spi(this->child_sa, TRUE), + this->ike_sa->get_my_host(this->ike_sa)); DBG1(DBG_IKE, "CHILD_SA rekeying failed, trying again in %d seconds", retry); this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); lib->scheduler->schedule_job(lib->scheduler, job, retry); @@ -184,6 +184,9 @@ METHOD(task_t, build_i, status_t, } reqid = this->child_sa->get_reqid(this->child_sa); this->child_create->use_reqid(this->child_create, reqid); + this->child_create->use_marks(this->child_create, + this->child_sa->get_mark(this->child_sa, TRUE).value, + this->child_sa->get_mark(this->child_sa, FALSE).value); if (this->child_create->task.build(&this->child_create->task, message) != NEED_MORE) @@ -224,6 +227,9 @@ 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); + this->child_create->use_marks(this->child_create, + this->child_sa->get_mark(this->child_sa, TRUE).value, + this->child_sa->get_mark(this->child_sa, FALSE).value); 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); diff --git a/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c index 0dac975e7..ca17494de 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c +++ b/src/libcharon/sa/ikev2/tasks/ike_cert_pre.c @@ -229,12 +229,12 @@ static void process_x509(cert_payload_t *payload, auth_cfg_t *auth, return; } url = strdup(url); - if (first) + if (*first) { /* first URL is for an end entity certificate */ DBG1(DBG_IKE, "received hash-and-url for end entity cert \"%s\"", url); auth->add(auth, AUTH_HELPER_SUBJECT_HASH_URL, url); - first = FALSE; + *first = FALSE; } else { diff --git a/src/libcharon/sa/ikev2/tasks/ike_config.c b/src/libcharon/sa/ikev2/tasks/ike_config.c index da06e2a36..646f20c61 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_config.c +++ b/src/libcharon/sa/ikev2/tasks/ike_config.c @@ -17,7 +17,6 @@ #include "ike_config.h" #include <daemon.h> -#include <hydra.h> #include <encoding/payloads/cp_payload.h> typedef struct private_ike_config_t private_ike_config_t; @@ -127,9 +126,8 @@ static void handle_attribute(private_ike_config_t *this, 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)); + handler = charon->attributes->handle(charon->attributes, + this->ike_sa, handler, ca->get_type(ca), ca->get_chunk(ca)); this->ike_sa->add_configuration_attribute(this->ike_sa, handler, ca->get_type(ca), ca->get_chunk(ca)); } @@ -274,9 +272,8 @@ METHOD(task_t, build_i, status_t, enumerator->destroy(enumerator); } - enumerator = hydra->attributes->create_initiator_enumerator( - hydra->attributes, - this->ike_sa->get_other_id(this->ike_sa), vips); + enumerator = charon->attributes->create_initiator_enumerator( + charon->attributes, this->ike_sa, vips); while (enumerator->enumerate(enumerator, &handler, &type, &data)) { configuration_attribute_t *ca; @@ -352,8 +349,8 @@ METHOD(task_t, build_r, status_t, /* 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); + found = charon->attributes->acquire_address(charon->attributes, + pools, this->ike_sa, requested); if (found) { DBG1(DBG_IKE, "assigning virtual IP %H to peer '%Y'", found, id); @@ -398,8 +395,8 @@ METHOD(task_t, build_r, status_t, } /* query registered providers for additional attributes to include */ - enumerator = hydra->attributes->create_responder_enumerator( - hydra->attributes, pools, id, vips); + enumerator = charon->attributes->create_responder_enumerator( + charon->attributes, pools, this->ike_sa, vips); while (enumerator->enumerate(enumerator, &type, &value)) { if (!cp) diff --git a/src/libcharon/sa/ikev2/tasks/ike_init.c b/src/libcharon/sa/ikev2/tasks/ike_init.c index 71c5f22fa..0d5700ef2 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_init.c +++ b/src/libcharon/sa/ikev2/tasks/ike_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 Tobias Brunner + * Copyright (C) 2008-2015 Tobias Brunner * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -20,8 +20,11 @@ #include <string.h> #include <daemon.h> +#include <bio/bio_reader.h> +#include <bio/bio_writer.h> #include <sa/ikev2/keymat_v2.h> #include <crypto/diffie_hellman.h> +#include <crypto/hashers/hash_algorithm_set.h> #include <encoding/payloads/sa_payload.h> #include <encoding/payloads/ke_payload.h> #include <encoding/payloads/nonce_payload.h> @@ -67,6 +70,11 @@ struct private_ike_init_t { diffie_hellman_t *dh; /** + * Applying DH public value failed? + */ + bool dh_failed; + + /** * Keymat derivation (from IKE_SA) */ keymat_v2_t *keymat; @@ -100,12 +108,114 @@ struct private_ike_init_t { * retries done so far after failure (cookie or bad dh group) */ u_int retry; + + /** + * Whether to use Signature Authentication as per RFC 7427 + */ + bool signature_authentication; }; /** + * Notify the peer about the hash algorithms we support or expect, + * as per RFC 7427 + */ +static void send_supported_hash_algorithms(private_ike_init_t *this, + message_t *message) +{ + hash_algorithm_set_t *algos; + enumerator_t *enumerator, *rounds; + bio_writer_t *writer; + hash_algorithm_t hash; + peer_cfg_t *peer; + auth_cfg_t *auth; + auth_rule_t rule; + uintptr_t config; + char *plugin_name; + + algos = hash_algorithm_set_create(); + peer = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer) + { + rounds = peer->create_auth_cfg_enumerator(peer, FALSE); + while (rounds->enumerate(rounds, &auth)) + { + enumerator = auth->create_enumerator(auth); + while (enumerator->enumerate(enumerator, &rule, &config)) + { + if (rule == AUTH_RULE_SIGNATURE_SCHEME) + { + hash = hasher_from_signature_scheme(config); + if (hasher_algorithm_for_ikev2(hash)) + { + algos->add(algos, hash); + } + } + } + enumerator->destroy(enumerator); + } + rounds->destroy(rounds); + } + + if (!algos->count(algos)) + { + enumerator = lib->crypto->create_hasher_enumerator(lib->crypto); + while (enumerator->enumerate(enumerator, &hash, &plugin_name)) + { + if (hasher_algorithm_for_ikev2(hash)) + { + algos->add(algos, hash); + } + } + enumerator->destroy(enumerator); + } + + if (algos->count(algos)) + { + writer = bio_writer_create(0); + enumerator = algos->create_enumerator(algos); + while (enumerator->enumerate(enumerator, &hash)) + { + writer->write_uint16(writer, hash); + } + enumerator->destroy(enumerator); + message->add_notify(message, FALSE, SIGNATURE_HASH_ALGORITHMS, + writer->get_buf(writer)); + writer->destroy(writer); + } + algos->destroy(algos); +} + +/** + * Store algorithms supported by other peer + */ +static void handle_supported_hash_algorithms(private_ike_init_t *this, + notify_payload_t *notify) +{ + bio_reader_t *reader; + u_int16_t algo; + bool added = FALSE; + + reader = bio_reader_create(notify->get_notification_data(notify)); + while (reader->remaining(reader) >= 2 && reader->read_uint16(reader, &algo)) + { + if (hasher_algorithm_for_ikev2(algo)) + { + this->keymat->add_hash_algorithm(this->keymat, algo); + added = TRUE; + } + } + reader->destroy(reader); + + if (added) + { + this->ike_sa->enable_extension(this->ike_sa, EXT_SIGNATURE_AUTH); + } +} + +/** * build the payloads for the message */ -static void build_payloads(private_ike_init_t *this, message_t *message) +static bool build_payloads(private_ike_init_t *this, message_t *message) { sa_payload_t *sa_payload; ke_payload_t *ke_payload; @@ -149,7 +259,13 @@ static void build_payloads(private_ike_init_t *this, message_t *message) nonce_payload = nonce_payload_create(PLV2_NONCE); nonce_payload->set_nonce(nonce_payload, this->my_nonce); - ke_payload = ke_payload_create_from_diffie_hellman(PLV2_KEY_EXCHANGE, this->dh); + ke_payload = ke_payload_create_from_diffie_hellman(PLV2_KEY_EXCHANGE, + this->dh); + if (!ke_payload) + { + DBG1(DBG_IKE, "creating KE payload failed"); + return FALSE; + } if (this->old_sa) { /* payload order differs if we are rekeying */ @@ -174,6 +290,17 @@ static void build_payloads(private_ike_init_t *this, message_t *message) chunk_empty); } } + /* submit supported hash algorithms for signature authentication */ + if (!this->old_sa && this->signature_authentication) + { + if (this->initiator || + this->ike_sa->supports_extension(this->ike_sa, + EXT_SIGNATURE_AUTH)) + { + send_supported_hash_algorithms(this, message); + } + } + return TRUE; } /** @@ -183,6 +310,7 @@ static void process_payloads(private_ike_init_t *this, message_t *message) { enumerator_t *enumerator; payload_t *payload; + ke_payload_t *ke_payload = NULL; enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) @@ -211,19 +339,9 @@ static void process_payloads(private_ike_init_t *this, message_t *message) } case PLV2_KEY_EXCHANGE: { - ke_payload_t *ke_payload = (ke_payload_t*)payload; + ke_payload = (ke_payload_t*)payload; this->dh_group = ke_payload->get_dh_group_number(ke_payload); - if (!this->initiator) - { - this->dh = this->keymat->keymat.create_dh( - &this->keymat->keymat, this->dh_group); - } - if (this->dh) - { - this->dh->set_other_public_value(this->dh, - ke_payload->get_key_exchange_data(ke_payload)); - } break; } case PLV2_NONCE: @@ -237,17 +355,44 @@ static void process_payloads(private_ike_init_t *this, message_t *message) { notify_payload_t *notify = (notify_payload_t*)payload; - if (notify->get_notify_type(notify) == FRAGMENTATION_SUPPORTED) + switch (notify->get_notify_type(notify)) { - this->ike_sa->enable_extension(this->ike_sa, - EXT_IKE_FRAGMENTATION); + case FRAGMENTATION_SUPPORTED: + this->ike_sa->enable_extension(this->ike_sa, + EXT_IKE_FRAGMENTATION); + break; + case SIGNATURE_HASH_ALGORITHMS: + if (this->signature_authentication) + { + handle_supported_hash_algorithms(this, notify); + } + break; + default: + /* other notifies are handled elsewhere */ + break; } + } default: break; } } enumerator->destroy(enumerator); + + if (ke_payload && this->proposal && + this->proposal->has_dh_group(this->proposal, this->dh_group)) + { + if (!this->initiator) + { + this->dh = this->keymat->keymat.create_dh( + &this->keymat->keymat, this->dh_group); + } + if (this->dh) + { + this->dh_failed = !this->dh->set_other_public_value(this->dh, + ke_payload->get_key_exchange_data(ke_payload)); + } + } } METHOD(task_t, build_i, status_t, @@ -305,7 +450,10 @@ METHOD(task_t, build_i, status_t, message->add_notify(message, FALSE, COOKIE, this->cookie); } - build_payloads(this, message); + if (!build_payloads(this, message)) + { + return FAILED; + } #ifdef ME { @@ -433,13 +581,24 @@ METHOD(task_t, build_r, status_t, return FAILED; } + if (this->dh_failed) + { + DBG1(DBG_IKE, "applying DH public value failed"); + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } + if (!derive_keys(this, this->other_nonce, this->my_nonce)) { DBG1(DBG_IKE, "key derivation failed"); message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); return FAILED; } - build_payloads(this, message); + if (!build_payloads(this, message)) + { + message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } return SUCCESS; } @@ -554,6 +713,12 @@ METHOD(task_t, process_i, status_t, return FAILED; } + if (this->dh_failed) + { + DBG1(DBG_IKE, "applying DH public value failed"); + return FAILED; + } + if (!derive_keys(this, this->my_nonce, this->other_nonce)) { DBG1(DBG_IKE, "key derivation failed"); @@ -577,6 +742,7 @@ METHOD(task_t, migrate, void, this->ike_sa = ike_sa; this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa); this->proposal = NULL; + this->dh_failed = FALSE; 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); @@ -631,6 +797,8 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa) .dh_group = MODP_NONE, .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa), .old_sa = old_sa, + .signature_authentication = lib->settings->get_bool(lib->settings, + "%s.signature_authentication", TRUE, lib->ns), ); if (initiator) diff --git a/src/libcharon/sa/ikev2/tasks/ike_mobike.c b/src/libcharon/sa/ikev2/tasks/ike_mobike.c index d91fa5862..6295d7960 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_mobike.c +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.c @@ -256,6 +256,7 @@ static void update_children(private_ike_mobike_t *this) enumerator_t *enumerator; child_sa_t *child_sa; linked_list_t *vips; + status_t status; host_t *host; vips = linked_list_create(); @@ -270,15 +271,25 @@ static void update_children(private_ike_mobike_t *this) 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), vips, - this->ike_sa->has_condition(this->ike_sa, - COND_NAT_ANY)) == NOT_SUPPORTED) + status = child_sa->update(child_sa, + this->ike_sa->get_my_host(this->ike_sa), + this->ike_sa->get_other_host(this->ike_sa), vips, + this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)); + switch (status) { - this->ike_sa->rekey_child_sa(this->ike_sa, - child_sa->get_protocol(child_sa), - child_sa->get_spi(child_sa, TRUE)); + case NOT_SUPPORTED: + this->ike_sa->rekey_child_sa(this->ike_sa, + child_sa->get_protocol(child_sa), + child_sa->get_spi(child_sa, TRUE)); + break; + case SUCCESS: + charon->child_sa_manager->remove(charon->child_sa_manager, + child_sa); + charon->child_sa_manager->add(charon->child_sa_manager, + child_sa, this->ike_sa); + break; + default: + break; } } enumerator->destroy(enumerator); diff --git a/src/libcharon/sa/ikev2/tasks/ike_reauth.h b/src/libcharon/sa/ikev2/tasks/ike_reauth.h index 781b463a7..e2e48f0d4 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_reauth.h +++ b/src/libcharon/sa/ikev2/tasks/ike_reauth.h @@ -29,6 +29,8 @@ typedef struct ike_reauth_t ike_reauth_t; /** * Task of type ike_reauth, reestablishes an IKE_SA. + * + * This task implements break-before-make reauthentication. */ struct ike_reauth_t { diff --git a/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.c b/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.c new file mode 100644 index 000000000..a01489c03 --- /dev/null +++ b/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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 "ike_reauth_complete.h" + +#include <daemon.h> +#include <processing/jobs/delete_ike_sa_job.h> + + +typedef struct private_ike_reauth_complete_t private_ike_reauth_complete_t; + +/** + * Private members of a ike_reauth_complete_t task. + */ +struct private_ike_reauth_complete_t { + + /** + * Public methods and task_t interface. + */ + ike_reauth_complete_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Reauthenticated IKE_SA identifier + */ + ike_sa_id_t *id; +}; + +METHOD(task_t, build_i, status_t, + private_ike_reauth_complete_t *this, message_t *message) +{ + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); + lib->processor->queue_job(lib->processor, + (job_t*)delete_ike_sa_job_create(this->id, TRUE)); + return SUCCESS; +} + +METHOD(task_t, process_i, status_t, + private_ike_reauth_complete_t *this, message_t *message) +{ + return DESTROY_ME; +} + +METHOD(task_t, get_type, task_type_t, + private_ike_reauth_complete_t *this) +{ + return TASK_IKE_REAUTH_COMPLETE; +} + +METHOD(task_t, migrate, void, + private_ike_reauth_complete_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +METHOD(task_t, destroy, void, + private_ike_reauth_complete_t *this) +{ + this->id->destroy(this->id); + free(this); +} + +/* + * Described in header. + */ +ike_reauth_complete_t *ike_reauth_complete_create(ike_sa_t *ike_sa, + ike_sa_id_t *id) +{ + private_ike_reauth_complete_t *this; + + INIT(this, + .public = { + .task = { + .get_type = _get_type, + .migrate = _migrate, + .build = _build_i, + .process = _process_i, + .destroy = _destroy, + }, + }, + .ike_sa = ike_sa, + .id = id->clone(id), + ); + + return &this->public; +} diff --git a/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.h b/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.h new file mode 100644 index 000000000..cc3d3b713 --- /dev/null +++ b/src/libcharon/sa/ikev2/tasks/ike_reauth_complete.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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 ike_reauth_complete ike_reauth_complete + * @{ @ingroup tasks_v2 + */ + +#ifndef IKE_REAUTH_COMPLETE_H_ +#define IKE_REAUTH_COMPLETE_H_ + +typedef struct ike_reauth_complete_t ike_reauth_complete_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/task.h> + +/** + * Task of type IKE_REAUTH_COMPLETE, removes reauthenticated SA after reauth. + * + * This task completes make-before-break reauthentication by deleting the + * old, reauthenticated IKE_SA after the new one established. + */ +struct ike_reauth_complete_t { + + /** + * Implements the task_t interface + */ + task_t task; +}; + +/** + * Create a new ike_reauth_complete task. + * + * This task is initiator only. + * + * @param ike_sa IKE_SA this task works for + * @param id old, reauthenticated IKE_SA + * @return ike_reauth_complete task to handle by the task_manager + */ +ike_reauth_complete_t *ike_reauth_complete_create(ike_sa_t *ike_sa, + ike_sa_id_t *id); + +#endif /** IKE_REAUTH_COMPLETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c index 444ac6ade..1855517ce 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c @@ -22,6 +22,7 @@ #include <sa/ikev2/tasks/ike_delete.h> #include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/rekey_ike_sa_job.h> +#include <processing/jobs/initiate_tasks_job.h> typedef struct private_ike_rekey_t private_ike_rekey_t; @@ -68,12 +69,33 @@ struct private_ike_rekey_t { }; /** + * Check if an IKE_SA has any queued tasks, return initiation job + */ +static job_t* check_queued_tasks(ike_sa_t *ike_sa) +{ + enumerator_t *enumerator; + task_t *task; + job_t *job = NULL; + + enumerator = ike_sa->create_task_enumerator(ike_sa, TASK_QUEUE_QUEUED); + if (enumerator->enumerate(enumerator, &task)) + { + job = (job_t*)initiate_tasks_job_create(ike_sa->get_id(ike_sa)); + } + enumerator->destroy(enumerator); + + return job; +} + +/** * Establish the new replacement IKE_SA */ static void establish_new(private_ike_rekey_t *this) { if (this->new_sa) { + job_t *job; + this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED); DBG0(DBG_IKE, "IKE_SA %s[%d] rekeyed between %H[%Y]...%H[%Y]", this->new_sa->get_name(this->new_sa), @@ -85,7 +107,14 @@ static void establish_new(private_ike_rekey_t *this) this->new_sa->inherit_post(this->new_sa, this->ike_sa); charon->bus->ike_rekey(charon->bus, this->ike_sa, this->new_sa); + job = check_queued_tasks(this->new_sa); + /* don't queue job before checkin(), as the IKE_SA is not yet + * registered at the manager */ charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); + if (job) + { + lib->processor->queue_job(lib->processor, job); + } this->new_sa = NULL; /* set threads active IKE_SA after checkin */ charon->bus->set_sa(charon->bus, this->ike_sa); @@ -163,6 +192,7 @@ METHOD(task_t, process_r, status_t, { case CHILD_CREATED: case CHILD_REKEYING: + case CHILD_RETRYING: case CHILD_DELETING: /* we do not allow rekeying while we have children in-progress */ DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open"); @@ -209,6 +239,12 @@ METHOD(task_t, build_r, status_t, this->public.task.build = _build_r_delete; this->public.task.process = _process_r_delete; + /* the peer does have to delete the IKE_SA. If it does not, we get a + * unusable IKE_SA in REKEYING state without a replacement. We consider + * this a timeout condition by the peer, and trigger a delete actively. */ + lib->scheduler->schedule_job(lib->scheduler, (job_t*) + delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), 90); + return NEED_MORE; } diff --git a/src/libcharon/sa/task.c b/src/libcharon/sa/task.c index 4336b23ff..b35b58185 100644 --- a/src/libcharon/sa/task.c +++ b/src/libcharon/sa/task.c @@ -27,6 +27,7 @@ ENUM(task_type_names, TASK_IKE_INIT, TASK_ISAKMP_CERT_POST, "IKE_CONFIG", "IKE_REKEY", "IKE_REAUTH", + "IKE_REAUTH_COMPLETE", "IKE_DELETE", "IKE_DPD", "IKE_VENDOR", diff --git a/src/libcharon/sa/task.h b/src/libcharon/sa/task.h index f2c4299cc..7bd3da1fe 100644 --- a/src/libcharon/sa/task.h +++ b/src/libcharon/sa/task.h @@ -22,6 +22,8 @@ #ifndef TASK_H_ #define TASK_H_ +#include <utils/utils.h> + typedef enum task_type_t task_type_t; typedef struct task_t task_t; @@ -51,8 +53,10 @@ enum task_type_t { TASK_IKE_CONFIG, /** rekey an IKE_SA */ TASK_IKE_REKEY, - /** reestablish a complete IKE_SA */ + /** reestablish a complete IKE_SA, break-before-make */ TASK_IKE_REAUTH, + /** completion task for make-before-break IKE_SA re-authentication */ + TASK_IKE_REAUTH_COMPLETE, /** delete an IKE_SA */ TASK_IKE_DELETE, /** liveness check */ diff --git a/src/libcharon/sa/trap_manager.c b/src/libcharon/sa/trap_manager.c index 7e55d6b0f..d6ff3c8c5 100644 --- a/src/libcharon/sa/trap_manager.c +++ b/src/libcharon/sa/trap_manager.c @@ -65,6 +65,11 @@ struct private_trap_manager_t { * listener to track acquiring IKE_SAs */ trap_listener_t listener; + + /** + * Whether to ignore traffic selectors from acquires + */ + bool ignore_acquire_ts; }; /** @@ -171,7 +176,7 @@ METHOD(trap_manager_t, install, u_int32_t, this->lock->unlock(this->lock); /* create and route CHILD_SA */ - child_sa = child_sa_create(me, other, child, reqid, FALSE); + child_sa = child_sa_create(me, other, child, reqid, FALSE, 0, 0); list = linked_list_create_with_items(me, NULL); my_ts = child->get_traffic_selectors(child, TRUE, NULL, list); @@ -353,7 +358,7 @@ METHOD(trap_manager_t, acquire, void, { ike_sa->set_peer_cfg(ike_sa, peer); } - if (ike_sa->get_version(ike_sa) == IKEV1) + if (this->ignore_acquire_ts || 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; @@ -484,6 +489,8 @@ trap_manager_t *trap_manager_create(void) }, .traps = linked_list_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .ignore_acquire_ts = lib->settings->get_bool(lib->settings, + "%s.ignore_acquire_ts", FALSE, lib->ns), ); charon->bus->add_listener(charon->bus, &this->listener.listener); |